diff --git a/README.md b/README.md index db028ea..76e9e9a 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ I built SocratiCode because I regularly work on existing, large, and complex cod - **Live file watching** — Optionally watch for file changes and keep the index updated in real time (debounced 2s). Watcher also invalidates the code graph cache. - **Parallel processing** — Files are scanned and chunked in parallel batches (50 at a time) for fast I/O, while embedding generation and upserts are batched separately for optimal throughput. - **Multi-project** — Index multiple projects simultaneously. Each gets its own isolated collection with full project path tracking. -- **Respects ignore rules** — Honors all `.gitignore` files (root + nested), plus an optional `.socraticodeignore` for additional exclusions. Includes sensible built-in defaults. `.gitignore` processing can be disabled via `RESPECT_GITIGNORE=false`. +- **Respects ignore rules** — Honors all `.gitignore` files (root + nested), plus an optional `.socraticodeignore` for additional exclusions. Includes sensible built-in defaults. `.gitignore` processing can be disabled via `RESPECT_GITIGNORE=false`. Dot-directories (e.g. `.agent`) can be included via `INCLUDE_DOT_FILES=true`. - **Custom file extensions** — Projects with non-standard extensions (e.g. `.tpl`, `.blade`) can be included via `EXTRA_EXTENSIONS` env var or `extraExtensions` tool parameter. Works for both indexing and code graph. - **Configurable infrastructure** — All ports, hosts, and API keys are configurable via environment variables. Qdrant API key support for enterprise deployments. - **Auto-setup** — On first use, automatically checks Docker, pulls images, starts containers, and pulls the embedding model. Only prerequisite: Docker. @@ -725,6 +725,7 @@ Artifacts are chunked and embedded into Qdrant using the same hybrid dense + BM2 | Variable | Default | Description | |----------|---------|-------------| | `RESPECT_GITIGNORE` | `true` | Set to `false` to skip `.gitignore` processing. Built-in defaults and `.socraticodeignore` still apply. | +| `INCLUDE_DOT_FILES` | `false` | Set to `true` to include dot-directories (e.g. `.agent`, `.config`) in indexing. By default, directories and files starting with `.` are excluded. Useful for projects where important code lives in dot-directories. | | `EXTRA_EXTENSIONS` | *(none)* | Comma-separated list of additional file extensions to scan (e.g. `.tpl,.blade,.hbs`). Applies to both indexing and code graph. Files with extra extensions are indexed as plaintext and appear as leaf nodes in the code graph. Can also be passed per-operation via the `extraExtensions` tool parameter. | | `MAX_FILE_SIZE_MB` | `5` | Maximum file size in MB. Files larger than this are skipped during indexing. Increase for repos with large generated or data files you want indexed. | | `SEARCH_DEFAULT_LIMIT` | `10` | Default number of results returned by `codebase_search` (1-50). Each result is a ranked code chunk with file path, line range, and content. Higher values give broader coverage but produce more output. Can still be overridden per-query via the `limit` tool parameter. | diff --git a/src/services/indexer.ts b/src/services/indexer.ts index 2b2e932..f27da0b 100644 --- a/src/services/indexer.ts +++ b/src/services/indexer.ts @@ -615,7 +615,7 @@ export async function getIndexableFiles( const allFiles = await glob("**/*", { cwd: projectPath, nodir: true, - dot: false, + dot: (process.env.INCLUDE_DOT_FILES ?? "false").toLowerCase() === "true", absolute: false, }); diff --git a/tests/unit/indexer.test.ts b/tests/unit/indexer.test.ts index 55c9623..49b5c57 100644 --- a/tests/unit/indexer.test.ts +++ b/tests/unit/indexer.test.ts @@ -1,9 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-only // Copyright (C) 2026 Giancarlo Erra - Altaire Limited -import { beforeAll, describe, expect, it } from "vitest"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { CHUNK_SIZE, MAX_CHUNK_CHARS } from "../../src/constants.js"; import { ensureDynamicLanguages } from "../../src/services/code-graph.js"; -import { chunkFileContent, chunkId, hashContent, isIndexableFile } from "../../src/services/indexer.js"; +import { chunkFileContent, chunkId, getIndexableFiles, hashContent, isIndexableFile } from "../../src/services/indexer.js"; // Register dynamic language grammars once for AST-aware chunking tests beforeAll(() => { @@ -286,4 +289,69 @@ describe("indexer utilities", () => { } }); }); + + // ── getIndexableFiles (INCLUDE_DOT_FILES) ───────────────────────────── + + describe("getIndexableFiles", () => { + let tmpDir: string; + + beforeAll(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "socraticode-dotfiles-test-")); + + // Regular file + fs.mkdirSync(path.join(tmpDir, "src"), { recursive: true }); + fs.writeFileSync(path.join(tmpDir, "src", "index.ts"), "export const x = 1;\n"); + + // Dot-directory with an indexable file + fs.mkdirSync(path.join(tmpDir, ".agent", "rules"), { recursive: true }); + fs.writeFileSync(path.join(tmpDir, ".agent", "rules", "config.ts"), "export const rule = true;\n"); + + // Dot-file at root (e.g. .gitignore) + fs.writeFileSync(path.join(tmpDir, ".gitignore"), "node_modules/\n"); + }); + + afterAll(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + }); + + it("excludes dot-directory files by default", async () => { + const files = await getIndexableFiles(tmpDir); + const hasDotDir = files.some((f) => f.includes(".agent")); + expect(hasDotDir).toBe(false); + }); + + it("includes dot-directory files when INCLUDE_DOT_FILES=true", async () => { + vi.stubEnv("INCLUDE_DOT_FILES", "true"); + try { + const files = await getIndexableFiles(tmpDir); + const dotFiles = files.filter((f) => f.includes(".agent")); + expect(dotFiles.length).toBeGreaterThan(0); + expect(dotFiles.some((f) => f.includes("config.ts"))).toBe(true); + } finally { + vi.unstubAllEnvs(); + } + }); + + it("treats INCLUDE_DOT_FILES case-insensitively", async () => { + vi.stubEnv("INCLUDE_DOT_FILES", "True"); + try { + const files = await getIndexableFiles(tmpDir); + const hasDotDir = files.some((f) => f.includes(".agent")); + expect(hasDotDir).toBe(true); + } finally { + vi.unstubAllEnvs(); + } + }); + + it("excludes dot-directory files for non-true values", async () => { + vi.stubEnv("INCLUDE_DOT_FILES", "yes"); + try { + const files = await getIndexableFiles(tmpDir); + const hasDotDir = files.some((f) => f.includes(".agent")); + expect(hasDotDir).toBe(false); + } finally { + vi.unstubAllEnvs(); + } + }); + }); });