You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(ingestion): exclude venv/node_modules/cache dirs from analyze + all retrieval APIs (#255)
## Problem
`codehub analyze` could ingest virtualenv, vendored-dependency, and
tool-cache content into the index. The single source of truth for
unconditional directory exclusion — `HARDCODED_IGNORES` in
`packages/ingestion/src/pipeline/gitignore.ts` — listed `.venv` but
**not** the bare `venv`, nor common Python/JS/build/cache directories
(`.tox`, `.mypy_cache`, `.pytest_cache`, `.ruff_cache`,
`bower_components`, `.pnpm-store`, `.yarn`, `.gradle`, `.parcel-cache`,
`.cache`, `.idea`, `.vscode`).
Because **every retrieval surface is store-backed** (`query`, `context`,
`impact`, `sql`, `pack` all read `store.sqlite`, which is built at scan
time), whatever scan ingested leaked into all of them. The fix belongs
at scan time, at the one list — no per-tool guards needed.
## What changed
- **`HARDCODED_IGNORES` extended.** The scan walker matches each path
segment's exact name (`hardcoded.has(name)` in `phases/scan.ts`), so a
bare name excludes that directory **at any depth**, not just the repo
root. Added: `venv`, `bower_components`, `.pnpm-store`, `.yarn`, `.tox`,
`.mypy_cache`, `.pytest_cache`, `.ruff_cache`, `.gradle`,
`.parcel-cache`, `.cache`, `.idea`, `.vscode`. The list is now grouped
and commented by category.
- **Deliberately NOT hardcoded:** `vendor`, `env`, `out`, `bin`, `obj`.
These commonly hold first-party source — this repo itself keeps source
under `packages/ingestion/src/pipeline/phases/vendor/graphty-leiden.ts`.
A hardcoded ignore can't be re-included via `.gitignore` `!`-negation,
so hardcoding `vendor` would make OpenCodeHub stop indexing its own
code. These are left to the repo's own `.gitignore`, where real vendored
deps are already excluded.
- **`.gitignore` honoring on analyze** was already correct
(`loadGitignoreChain` + layered `shouldIgnore` wired into the scan
walk). This PR adds end-to-end regression coverage for it.
## Why retrieval needs no separate change
Audited all 28 MCP tools. Every path that returns file paths/content is
safe-by-construction once scan exclusion is correct:
- Store-backed tools (`query`, `context`, `impact`, `sql`,
`detect_changes`, `pack` default engine) only emit nodes the scan kept.
- `query` snippet reads touch the filesystem only for nodes already in
the store.
- Group tools (`group_contracts`, `group_cross_repo_links`) and
`list_findings_delta` read persisted `.codehub` registry/SARIF JSON, not
arbitrary repo files.
- The MCP `scan` tool spawns external scanners that receive
`HARDCODED_IGNORES` via the wrappers (same source of truth).
- The only FS-walking retrieval path is the **opt-in legacy `repomix`
pack engine**, which relies on repomix's own `node_modules`/`.gitignore`
defaults (out of scope; slated for removal in M7).
## Tests
- `scan.test.ts`: asserts every `HARDCODED_IGNORES` dir is skipped at
root **and** nested; that `venv`/`.venv`/`node_modules` never appear in
scan output; and that a user-`.gitignore`'d directory is excluded
end-to-end through the scan phase.
- `gitignore.test.ts`: unit guard pinning the required names, asserting
`vendor` is **absent**, and that entries are bare segments (no
globs/slashes/dupes).
- Full suite green: ingestion 633, scanners 94, CLI 343 — 0 failures.
Whole-repo biome lint clean (686 files). `banned-strings` PASS.
## Docs
- README "Design choices worth knowing" gains a **First-party source
only** row documenting the exclusion contract and the deliberate
ambiguous-name exclusions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: README.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -77,6 +77,7 @@ flowchart LR
77
77
|**Apache-2.0, end to end**| Every runtime dep is OSI-approved permissive. No PolyForm, BSL, Commons Clause, Elastic v2, GPL, or AGPL. You can fork, embed, and ship commercial products on top without a license-review detour. |
78
78
|**Local-first, offline-capable**|`codehub analyze --offline` opens zero sockets. Your code never leaves your machine. No telemetry. |
79
79
|**Deterministic indexing**| Identical inputs produce a byte-identical graph hash. Reproducible. Auditable. Cacheable in CI. |
80
+
|**First-party source only**|`analyze` honors the repo's `.gitignore` (nested files included) and always skips dependency installs, virtualenvs, build output, and tool caches — `node_modules`, `.venv`/`venv`, `__pycache__`, `dist`/`build`/`target`, `.next`/`.nuxt`/`.turbo`, `.mypy_cache`/`.pytest_cache`/`.ruff_cache`, `coverage`, and similar. Exclusion is decided once at scan time (`HARDCODED_IGNORES` in `packages/ingestion/src/pipeline/gitignore.ts`), so every retrieval surface — `query`, `context`, `impact`, `sql`, `pack` — inherits it. Ambiguous names that are often real source (`vendor`, `env`, `out`, `bin`) are left to your `.gitignore`, which supports `!`-negation a hardcoded rule can't. |
80
81
|**MCP-native**| Works out-of-the-box with Claude Code, Cursor, Codex, Windsurf, OpenCode. The MCP server is the primary interface; CLI exists for scripts and CI. |
81
82
|**Single-file embedded storage**| One `store.sqlite` file holds everything — symbols, edges, embeddings, BM25 (FTS5) + HNSW traversal, and the temporal views (cochanges, summaries) — via Node's built-in `node:sqlite`. No daemon, no database to operate, and **zero native storage bindings** (ADR 0019 removed both `@ladybugdb/core` and `@duckdb/node-api`). |
82
83
|**15 languages at GA**| TypeScript, JavaScript, Python, Go, Rust, Java, C#, C, C++, Ruby, Kotlin, Swift, PHP, Dart, COBOL — tree-sitter for the first 14 plus a regex provider for fixed-format COBOL. |
0 commit comments