homegraph 0.9.10 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +243 -193
- package/dist/bin/fatal-handler.d.ts +20 -0
- package/dist/bin/fatal-handler.d.ts.map +1 -0
- package/dist/bin/fatal-handler.js +118 -0
- package/dist/bin/fatal-handler.js.map +1 -0
- package/dist/bin/homegraph.d.ts +6 -6
- package/dist/bin/homegraph.js +532 -109
- package/dist/bin/homegraph.js.map +1 -1
- package/dist/bin/node-version-check.d.ts +4 -4
- package/dist/bin/node-version-check.js +12 -12
- package/dist/bin/uninstall.d.ts +1 -1
- package/dist/bin/uninstall.js +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +8 -1
- package/dist/context/index.js.map +1 -1
- package/dist/db/index.d.ts +22 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +48 -2
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +26 -17
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/queries.d.ts +21 -1
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +180 -137
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +152 -151
- package/dist/db/sqlite-adapter.d.ts +1 -1
- package/dist/db/sqlite-adapter.js +3 -3
- package/dist/directory.d.ts +87 -6
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +319 -57
- package/dist/directory.js.map +1 -1
- package/dist/errors.d.ts +15 -15
- package/dist/errors.js +20 -20
- package/dist/extraction/astro-extractor.d.ts +79 -0
- package/dist/extraction/astro-extractor.d.ts.map +1 -0
- package/dist/extraction/astro-extractor.js +320 -0
- package/dist/extraction/astro-extractor.js.map +1 -0
- package/dist/extraction/context.d.ts +1 -1
- package/dist/extraction/context.js +1 -1
- package/dist/extraction/extraction-version.d.ts +2 -2
- package/dist/extraction/extraction-version.d.ts.map +1 -1
- package/dist/extraction/extraction-version.js +2 -2
- package/dist/extraction/extraction-version.js.map +1 -1
- package/dist/extraction/function-ref.d.ts +118 -0
- package/dist/extraction/function-ref.d.ts.map +1 -0
- package/dist/extraction/function-ref.js +727 -0
- package/dist/extraction/function-ref.js.map +1 -0
- package/dist/extraction/generated-detection.d.ts.map +1 -1
- package/dist/extraction/generated-detection.js +3 -0
- package/dist/extraction/generated-detection.js.map +1 -1
- package/dist/extraction/grammars.d.ts +15 -5
- package/dist/extraction/grammars.d.ts.map +1 -1
- package/dist/extraction/grammars.js +44 -10
- package/dist/extraction/grammars.js.map +1 -1
- package/dist/extraction/index.d.ts +37 -0
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +562 -70
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/languages/arkts-viewtree.d.ts +22 -0
- package/dist/extraction/languages/arkts-viewtree.d.ts.map +1 -0
- package/dist/extraction/languages/arkts-viewtree.js +133 -0
- package/dist/extraction/languages/arkts-viewtree.js.map +1 -0
- package/dist/extraction/languages/arkts.d.ts.map +1 -1
- package/dist/extraction/languages/arkts.js +211 -20
- package/dist/extraction/languages/arkts.js.map +1 -1
- package/dist/extraction/languages/c-cpp.d.ts +8 -0
- package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/dist/extraction/languages/c-cpp.js +104 -2
- package/dist/extraction/languages/c-cpp.js.map +1 -1
- package/dist/extraction/languages/csharp.d.ts.map +1 -1
- package/dist/extraction/languages/csharp.js +58 -3
- package/dist/extraction/languages/csharp.js.map +1 -1
- package/dist/extraction/languages/dart.d.ts.map +1 -1
- package/dist/extraction/languages/dart.js +183 -1
- package/dist/extraction/languages/dart.js.map +1 -1
- package/dist/extraction/languages/go.d.ts.map +1 -1
- package/dist/extraction/languages/go.js +37 -0
- package/dist/extraction/languages/go.js.map +1 -1
- package/dist/extraction/languages/index.d.ts.map +1 -1
- package/dist/extraction/languages/index.js +2 -0
- package/dist/extraction/languages/index.js.map +1 -1
- package/dist/extraction/languages/java.d.ts.map +1 -1
- package/dist/extraction/languages/java.js +241 -0
- package/dist/extraction/languages/java.js.map +1 -1
- package/dist/extraction/languages/javascript.d.ts.map +1 -1
- package/dist/extraction/languages/javascript.js +16 -0
- package/dist/extraction/languages/javascript.js.map +1 -1
- package/dist/extraction/languages/kotlin.d.ts.map +1 -1
- package/dist/extraction/languages/kotlin.js +94 -0
- package/dist/extraction/languages/kotlin.js.map +1 -1
- package/dist/extraction/languages/objc.d.ts.map +1 -1
- package/dist/extraction/languages/objc.js +42 -0
- package/dist/extraction/languages/objc.js.map +1 -1
- package/dist/extraction/languages/pascal.d.ts.map +1 -1
- package/dist/extraction/languages/pascal.js +11 -0
- package/dist/extraction/languages/pascal.js.map +1 -1
- package/dist/extraction/languages/php.d.ts.map +1 -1
- package/dist/extraction/languages/php.js +77 -1
- package/dist/extraction/languages/php.js.map +1 -1
- package/dist/extraction/languages/r.d.ts +3 -0
- package/dist/extraction/languages/r.d.ts.map +1 -0
- package/dist/extraction/languages/r.js +314 -0
- package/dist/extraction/languages/r.js.map +1 -0
- package/dist/extraction/languages/rust.d.ts.map +1 -1
- package/dist/extraction/languages/rust.js +29 -0
- package/dist/extraction/languages/rust.js.map +1 -1
- package/dist/extraction/languages/scala.d.ts.map +1 -1
- package/dist/extraction/languages/scala.js +44 -9
- package/dist/extraction/languages/scala.js.map +1 -1
- package/dist/extraction/languages/swift.d.ts.map +1 -1
- package/dist/extraction/languages/swift.js +61 -0
- package/dist/extraction/languages/swift.js.map +1 -1
- package/dist/extraction/languages/typescript.d.ts +13 -0
- package/dist/extraction/languages/typescript.d.ts.map +1 -1
- package/dist/extraction/languages/typescript.js +38 -0
- package/dist/extraction/languages/typescript.js.map +1 -1
- package/dist/extraction/liquid-extractor.d.ts.map +1 -1
- package/dist/extraction/liquid-extractor.js +4 -1
- package/dist/extraction/liquid-extractor.js.map +1 -1
- package/dist/extraction/parse-worker.js +4 -1
- package/dist/extraction/parse-worker.js.map +1 -1
- package/dist/extraction/svelte-extractor.d.ts.map +1 -1
- package/dist/extraction/svelte-extractor.js +6 -3
- package/dist/extraction/svelte-extractor.js.map +1 -1
- package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
- package/dist/extraction/tree-sitter-helpers.js +59 -10
- package/dist/extraction/tree-sitter-helpers.js.map +1 -1
- package/dist/extraction/tree-sitter-types.d.ts +29 -0
- package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.d.ts +192 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +1553 -45
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/extraction/vue-extractor.d.ts.map +1 -1
- package/dist/extraction/vue-extractor.js +6 -3
- package/dist/extraction/vue-extractor.js.map +1 -1
- package/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-r.wasm +0 -0
- package/dist/graph/traversal.d.ts.map +1 -1
- package/dist/graph/traversal.js +11 -2
- package/dist/graph/traversal.js.map +1 -1
- package/dist/index.d.ts +64 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +137 -41
- package/dist/index.js.map +1 -1
- package/dist/installer/config-writer.d.ts +1 -1
- package/dist/installer/config-writer.js +1 -1
- package/dist/installer/index.d.ts +4 -4
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +100 -73
- package/dist/installer/index.js.map +1 -1
- package/dist/installer/instructions-template.d.ts +34 -11
- package/dist/installer/instructions-template.d.ts.map +1 -1
- package/dist/installer/instructions-template.js +44 -12
- package/dist/installer/instructions-template.js.map +1 -1
- package/dist/installer/targets/antigravity.d.ts +6 -6
- package/dist/installer/targets/antigravity.js +14 -14
- package/dist/installer/targets/claude.d.ts +19 -13
- package/dist/installer/targets/claude.d.ts.map +1 -1
- package/dist/installer/targets/claude.js +97 -29
- package/dist/installer/targets/claude.js.map +1 -1
- package/dist/installer/targets/codebuddy.d.ts +17 -0
- package/dist/installer/targets/codebuddy.d.ts.map +1 -0
- package/dist/installer/targets/codebuddy.js +150 -0
- package/dist/installer/targets/codebuddy.js.map +1 -0
- package/dist/installer/targets/codex.js +5 -7
- package/dist/installer/targets/codex.js.map +1 -1
- package/dist/installer/targets/cursor.d.ts +1 -1
- package/dist/installer/targets/cursor.js +7 -7
- package/dist/installer/targets/deveco.d.ts +23 -0
- package/dist/installer/targets/deveco.d.ts.map +1 -0
- package/dist/installer/targets/deveco.js +217 -0
- package/dist/installer/targets/deveco.js.map +1 -0
- package/dist/installer/targets/gemini.js +5 -7
- package/dist/installer/targets/gemini.js.map +1 -1
- package/dist/installer/targets/hermes.js +15 -15
- package/dist/installer/targets/kiro.d.ts +1 -1
- package/dist/installer/targets/kiro.js +4 -4
- package/dist/installer/targets/opencode.d.ts +9 -1
- package/dist/installer/targets/opencode.d.ts.map +1 -1
- package/dist/installer/targets/opencode.js +92 -41
- package/dist/installer/targets/opencode.js.map +1 -1
- package/dist/installer/targets/registry.d.ts.map +1 -1
- package/dist/installer/targets/registry.js +4 -0
- package/dist/installer/targets/registry.js.map +1 -1
- package/dist/installer/targets/shared.d.ts +28 -4
- package/dist/installer/targets/shared.d.ts.map +1 -1
- package/dist/installer/targets/shared.js +32 -15
- package/dist/installer/targets/shared.js.map +1 -1
- package/dist/installer/targets/types.d.ts +9 -2
- package/dist/installer/targets/types.d.ts.map +1 -1
- package/dist/mcp/daemon-manager.d.ts +42 -0
- package/dist/mcp/daemon-manager.d.ts.map +1 -0
- package/dist/mcp/daemon-manager.js +129 -0
- package/dist/mcp/daemon-manager.js.map +1 -0
- package/dist/mcp/daemon-paths.d.ts +1 -1
- package/dist/mcp/daemon-paths.js +3 -3
- package/dist/mcp/daemon-registry.d.ts +47 -0
- package/dist/mcp/daemon-registry.d.ts.map +1 -0
- package/dist/mcp/daemon-registry.js +229 -0
- package/dist/mcp/daemon-registry.js.map +1 -0
- package/dist/mcp/daemon.d.ts.map +1 -1
- package/dist/mcp/daemon.js +27 -6
- package/dist/mcp/daemon.js.map +1 -1
- package/dist/mcp/dynamic-boundaries.d.ts +41 -0
- package/dist/mcp/dynamic-boundaries.d.ts.map +1 -0
- package/dist/mcp/dynamic-boundaries.js +359 -0
- package/dist/mcp/dynamic-boundaries.js.map +1 -0
- package/dist/mcp/engine.d.ts +4 -4
- package/dist/mcp/engine.d.ts.map +1 -1
- package/dist/mcp/engine.js +32 -24
- package/dist/mcp/engine.js.map +1 -1
- package/dist/mcp/index.d.ts +8 -7
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +39 -18
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/liveness-watchdog.d.ts +18 -0
- package/dist/mcp/liveness-watchdog.d.ts.map +1 -0
- package/dist/mcp/liveness-watchdog.js +207 -0
- package/dist/mcp/liveness-watchdog.js.map +1 -0
- package/dist/mcp/proxy.d.ts +6 -0
- package/dist/mcp/proxy.d.ts.map +1 -1
- package/dist/mcp/proxy.js +75 -18
- package/dist/mcp/proxy.js.map +1 -1
- package/dist/mcp/server-instructions.d.ts +22 -7
- package/dist/mcp/server-instructions.d.ts.map +1 -1
- package/dist/mcp/server-instructions.js +91 -59
- package/dist/mcp/server-instructions.js.map +1 -1
- package/dist/mcp/session.d.ts +3 -1
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +43 -7
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/stdin-teardown.d.ts +27 -0
- package/dist/mcp/stdin-teardown.d.ts.map +1 -0
- package/dist/mcp/stdin-teardown.js +49 -0
- package/dist/mcp/stdin-teardown.js.map +1 -0
- package/dist/mcp/tools.d.ts +137 -16
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +1271 -219
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/transport.d.ts +1 -1
- package/dist/mcp/transport.d.ts.map +1 -1
- package/dist/mcp/transport.js +20 -4
- package/dist/mcp/transport.js.map +1 -1
- package/dist/mcp/version.d.ts +1 -1
- package/dist/mcp/version.js +2 -2
- package/dist/project-config.d.ts +36 -0
- package/dist/project-config.d.ts.map +1 -0
- package/dist/project-config.js +235 -0
- package/dist/project-config.js.map +1 -0
- package/dist/reasoning/config.d.ts +45 -0
- package/dist/reasoning/config.d.ts.map +1 -0
- package/dist/reasoning/config.js +171 -0
- package/dist/reasoning/config.js.map +1 -0
- package/dist/reasoning/credentials.d.ts +5 -0
- package/dist/reasoning/credentials.d.ts.map +1 -0
- package/dist/reasoning/credentials.js +83 -0
- package/dist/reasoning/credentials.js.map +1 -0
- package/dist/reasoning/login.d.ts +21 -0
- package/dist/reasoning/login.d.ts.map +1 -0
- package/dist/reasoning/login.js +85 -0
- package/dist/reasoning/login.js.map +1 -0
- package/dist/reasoning/reasoner.d.ts +43 -0
- package/dist/reasoning/reasoner.d.ts.map +1 -0
- package/dist/reasoning/reasoner.js +308 -0
- package/dist/reasoning/reasoner.js.map +1 -0
- package/dist/resolution/c-fnptr-synthesizer.d.ts +33 -0
- package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -0
- package/dist/resolution/c-fnptr-synthesizer.js +352 -0
- package/dist/resolution/c-fnptr-synthesizer.js.map +1 -0
- package/dist/resolution/callback-synthesizer.d.ts +6 -1
- package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/dist/resolution/callback-synthesizer.js +1197 -1
- package/dist/resolution/callback-synthesizer.js.map +1 -1
- package/dist/resolution/frameworks/arkts-entry.d.ts +12 -0
- package/dist/resolution/frameworks/arkts-entry.d.ts.map +1 -0
- package/dist/resolution/frameworks/arkts-entry.js +214 -0
- package/dist/resolution/frameworks/arkts-entry.js.map +1 -0
- package/dist/resolution/frameworks/arkts-napi.d.ts +3 -0
- package/dist/resolution/frameworks/arkts-napi.d.ts.map +1 -0
- package/dist/resolution/frameworks/arkts-napi.js +166 -0
- package/dist/resolution/frameworks/arkts-napi.js.map +1 -0
- package/dist/resolution/frameworks/astro.d.ts +9 -0
- package/dist/resolution/frameworks/astro.d.ts.map +1 -0
- package/dist/resolution/frameworks/astro.js +169 -0
- package/dist/resolution/frameworks/astro.js.map +1 -0
- package/dist/resolution/frameworks/goframe.d.ts +41 -0
- package/dist/resolution/frameworks/goframe.d.ts.map +1 -0
- package/dist/resolution/frameworks/goframe.js +112 -0
- package/dist/resolution/frameworks/goframe.js.map +1 -0
- package/dist/resolution/frameworks/index.d.ts +3 -0
- package/dist/resolution/frameworks/index.d.ts.map +1 -1
- package/dist/resolution/frameworks/index.js +17 -1
- package/dist/resolution/frameworks/index.js.map +1 -1
- package/dist/resolution/frameworks/java.js +6 -1
- package/dist/resolution/frameworks/java.js.map +1 -1
- package/dist/resolution/frameworks/react.d.ts.map +1 -1
- package/dist/resolution/frameworks/react.js +32 -63
- package/dist/resolution/frameworks/react.js.map +1 -1
- package/dist/resolution/frameworks/svelte.js +6 -2
- package/dist/resolution/frameworks/svelte.js.map +1 -1
- package/dist/resolution/frameworks/vue.js +24 -27
- package/dist/resolution/frameworks/vue.js.map +1 -1
- package/dist/resolution/goframe-synthesizer.d.ts +28 -0
- package/dist/resolution/goframe-synthesizer.d.ts.map +1 -0
- package/dist/resolution/goframe-synthesizer.js +158 -0
- package/dist/resolution/goframe-synthesizer.js.map +1 -0
- package/dist/resolution/import-resolver.d.ts +10 -0
- package/dist/resolution/import-resolver.d.ts.map +1 -1
- package/dist/resolution/import-resolver.js +125 -2
- package/dist/resolution/import-resolver.js.map +1 -1
- package/dist/resolution/index.d.ts +37 -0
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +306 -3
- package/dist/resolution/index.js.map +1 -1
- package/dist/resolution/name-matcher.d.ts +41 -0
- package/dist/resolution/name-matcher.d.ts.map +1 -1
- package/dist/resolution/name-matcher.js +519 -14
- package/dist/resolution/name-matcher.js.map +1 -1
- package/dist/resolution/strip-comments.d.ts +1 -1
- package/dist/resolution/strip-comments.d.ts.map +1 -1
- package/dist/resolution/strip-comments.js +2 -0
- package/dist/resolution/strip-comments.js.map +1 -1
- package/dist/resolution/types.d.ts +20 -3
- package/dist/resolution/types.d.ts.map +1 -1
- package/dist/search/query-utils.d.ts +17 -1
- package/dist/search/query-utils.d.ts.map +1 -1
- package/dist/search/query-utils.js +79 -10
- package/dist/search/query-utils.js.map +1 -1
- package/dist/sync/git-hooks.d.ts +4 -4
- package/dist/sync/git-hooks.js +5 -5
- package/dist/sync/watcher.d.ts +69 -2
- package/dist/sync/watcher.d.ts.map +1 -1
- package/dist/sync/watcher.js +226 -21
- package/dist/sync/watcher.js.map +1 -1
- package/dist/sync/worktree.d.ts +3 -3
- package/dist/sync/worktree.js +5 -5
- package/dist/telemetry/index.d.ts +143 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +541 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/types.d.ts +18 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -1
- package/dist/types.js.map +1 -1
- package/dist/upgrade/index.d.ts.map +1 -1
- package/dist/upgrade/index.js +46 -10
- package/dist/upgrade/index.js.map +1 -1
- package/dist/utils.d.ts +46 -27
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +87 -53
- package/dist/utils.js.map +1 -1
- package/package.json +57 -57
- package/scripts/add-lang/bench.sh +60 -60
- package/scripts/add-lang/check-grammar.mjs +75 -75
- package/scripts/add-lang/dump-ast.mjs +103 -103
- package/scripts/add-lang/verify-extraction.mjs +70 -70
- package/scripts/agent-eval/ab-adoption.sh +91 -0
- package/scripts/agent-eval/ab-hook.sh +86 -0
- package/scripts/agent-eval/ab-impl.sh +78 -0
- package/scripts/agent-eval/ab-new-vs-baseline.sh +102 -0
- package/scripts/agent-eval/ab-sufficiency.sh +78 -0
- package/scripts/agent-eval/arms-F.sh +21 -21
- package/scripts/agent-eval/arms-matrix.sh +37 -37
- package/scripts/agent-eval/audit.sh +68 -68
- package/scripts/agent-eval/bench-readme.sh +28 -28
- package/scripts/agent-eval/bench-why-repo.sh +22 -22
- package/scripts/agent-eval/block-read-hook.sh +19 -19
- package/scripts/agent-eval/hook-settings.json +15 -15
- package/scripts/agent-eval/itrun.sh +120 -120
- package/scripts/agent-eval/offload-eval-3arm.sh +72 -0
- package/scripts/agent-eval/offload-eval-cost.mjs +133 -0
- package/scripts/agent-eval/offload-eval-effort.mjs +108 -0
- package/scripts/agent-eval/offload-eval-frontload-matrix.sh +25 -0
- package/scripts/agent-eval/offload-eval-frontload.sh +47 -0
- package/scripts/agent-eval/offload-eval-ground-truth.json +18 -0
- package/scripts/agent-eval/offload-eval-hook.mjs +84 -0
- package/scripts/agent-eval/offload-eval-judge.mjs +103 -0
- package/scripts/agent-eval/offload-eval-matrix.sh +20 -0
- package/scripts/agent-eval/offload-eval-metrics.mjs +94 -0
- package/scripts/agent-eval/offload-eval-refs1.sh +50 -0
- package/scripts/agent-eval/offload-eval-setup.sh +24 -0
- package/scripts/agent-eval/offload-eval-styles.sh +72 -0
- package/scripts/agent-eval/offload-eval-summarize.mjs +68 -0
- package/scripts/agent-eval/offload-eval.md +76 -0
- package/scripts/agent-eval/parse-arms.mjs +116 -116
- package/scripts/agent-eval/parse-bench-readme.mjs +84 -84
- package/scripts/agent-eval/parse-run.mjs +45 -45
- package/scripts/agent-eval/parse-session.mjs +93 -93
- package/scripts/agent-eval/probe-context.mjs +21 -21
- package/scripts/agent-eval/probe-explore.mjs +40 -40
- package/scripts/agent-eval/probe-node.mjs +20 -20
- package/scripts/agent-eval/probe-sweep.mjs +119 -119
- package/scripts/agent-eval/probe-trace.mjs +20 -20
- package/scripts/agent-eval/redirect-read-hook.sh +38 -0
- package/scripts/agent-eval/run-agent.sh +34 -34
- package/scripts/agent-eval/run-all.sh +69 -67
- package/scripts/agent-eval/run-arms.sh +56 -56
- package/scripts/agent-eval/seq-matrix.mjs +137 -137
- package/scripts/build-bundle.sh +118 -118
- package/scripts/extract-release-notes.mjs +130 -130
- package/scripts/local-install.sh +41 -41
- package/scripts/npm-sdk.js +75 -75
- package/scripts/npm-shim.js +246 -246
- package/scripts/pack-npm.sh +119 -119
- package/scripts/prepare-release.mjs +270 -270
- package/scripts/qa_eval/README.md +400 -0
- package/scripts/qa_eval/agent_runner.py +526 -0
- package/scripts/qa_eval/data/.gitignore +4 -0
- package/scripts/qa_eval/data/test-set.jsonl +22 -0
- package/scripts/qa_eval/eval_metrics.py +233 -0
- package/scripts/qa_eval/external_agent.py +577 -0
- package/scripts/qa_eval/llm_config.py +92 -0
- package/scripts/qa_eval/memory_monitor.py +132 -0
- package/scripts/qa_eval/my_answer_accuracy.py +187 -0
- package/scripts/qa_eval/requirements.txt +2 -0
- package/scripts/qa_eval/run_pipeline.py +702 -0
- package/scripts/qa_eval/stats_efficiency.py +279 -0
- package/scripts/qa_eval/stats_scores.py +207 -0
- package/dist/bin/codegraph.d.ts +0 -26
- package/dist/bin/codegraph.d.ts.map +0 -1
- package/dist/bin/codegraph.js +0 -1572
- package/dist/bin/codegraph.js.map +0 -1
- package/dist/extraction/arkanalyzer-extractor.d.ts +0 -6
- package/dist/extraction/arkanalyzer-extractor.d.ts.map +0 -1
- package/dist/extraction/arkanalyzer-extractor.js +0 -11
- package/dist/extraction/arkanalyzer-extractor.js.map +0 -1
- package/dist/extraction/arkanalyzer-project.d.ts +0 -20
- package/dist/extraction/arkanalyzer-project.d.ts.map +0 -1
- package/dist/extraction/arkanalyzer-project.js +0 -596
- package/dist/extraction/arkanalyzer-project.js.map +0 -1
package/dist/extraction/index.js
CHANGED
|
@@ -41,9 +41,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
41
41
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
42
|
};
|
|
43
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
-
exports.loadAllGrammars = exports.loadGrammarsForLanguages = exports.initGrammars = exports.getSupportedLanguages = exports.isGrammarLoaded = exports.isLanguageSupported = exports.isSourceFile = exports.detectLanguage = exports.extractFromSource = exports.ExtractionOrchestrator = void 0;
|
|
44
|
+
exports.loadAllGrammars = exports.loadGrammarsForLanguages = exports.initGrammars = exports.getSupportedLanguages = exports.isGrammarLoaded = exports.isLanguageSupported = exports.isSourceFile = exports.detectLanguage = exports.extractFromSource = exports.ExtractionOrchestrator = exports.ScopeIgnore = void 0;
|
|
45
45
|
exports.hashContent = hashContent;
|
|
46
46
|
exports.buildDefaultIgnore = buildDefaultIgnore;
|
|
47
|
+
exports.buildScopeIgnore = buildScopeIgnore;
|
|
48
|
+
exports.discoverEmbeddedRepoRoots = discoverEmbeddedRepoRoots;
|
|
47
49
|
exports.scanDirectory = scanDirectory;
|
|
48
50
|
exports.scanDirectoryAsync = scanDirectoryAsync;
|
|
49
51
|
const fs = __importStar(require("fs"));
|
|
@@ -53,6 +55,8 @@ const crypto = __importStar(require("crypto"));
|
|
|
53
55
|
const child_process_1 = require("child_process");
|
|
54
56
|
const tree_sitter_1 = require("./tree-sitter");
|
|
55
57
|
const grammars_1 = require("./grammars");
|
|
58
|
+
const project_config_1 = require("../project-config");
|
|
59
|
+
const directory_1 = require("../directory");
|
|
56
60
|
const errors_1 = require("../errors");
|
|
57
61
|
const utils_1 = require("../utils");
|
|
58
62
|
const ignore_1 = __importDefault(require("ignore"));
|
|
@@ -62,6 +66,17 @@ const frameworks_1 = require("../resolution/frameworks");
|
|
|
62
66
|
* File reads are I/O-bound; batching overlaps I/O wait with CPU parse work.
|
|
63
67
|
*/
|
|
64
68
|
const FILE_IO_BATCH_SIZE = 10;
|
|
69
|
+
/**
|
|
70
|
+
* How many files the `sync()` reconcile processes between cooperative yields to
|
|
71
|
+
* the event loop. The reconcile runs two O(files) loops of synchronous `fs`
|
|
72
|
+
* calls (existsSync for removals, statSync for adds/mods); on a very large repo
|
|
73
|
+
* (~100k files) an un-yielded run wedges the main thread for minutes, which both
|
|
74
|
+
* trips the liveness watchdog (it SIGKILLs a process whose loop stops turning)
|
|
75
|
+
* and blocks the first MCP tool call behind the catch-up gate (issue #905).
|
|
76
|
+
* Yielding every N files keeps the socket, the watchdog heartbeat, and any
|
|
77
|
+
* concurrent read query responsive while the reconcile runs.
|
|
78
|
+
*/
|
|
79
|
+
const SYNC_RECONCILE_YIELD_INTERVAL = 1000;
|
|
65
80
|
// PARSER_RESET_INTERVAL moved to parse-worker.ts (runs in worker thread)
|
|
66
81
|
/**
|
|
67
82
|
* Maximum time (ms) to wait for a single file to parse in the worker thread.
|
|
@@ -91,7 +106,7 @@ function hashContent(content) {
|
|
|
91
106
|
const MAX_FILE_SIZE = 1024 * 1024;
|
|
92
107
|
/**
|
|
93
108
|
* Directory names that are dependency, build, cache, or tooling output across the
|
|
94
|
-
* languages/frameworks
|
|
109
|
+
* languages/frameworks HomeGraph supports — curated from the canonical
|
|
95
110
|
* github/gitignore templates. Excluded by default so the graph reflects your code,
|
|
96
111
|
* not third-party noise, without requiring a `.gitignore` (issue #407). The
|
|
97
112
|
* exclusion applies uniformly (git or not, tracked or not); the only opt-in is an
|
|
@@ -100,7 +115,7 @@ const MAX_FILE_SIZE = 1024 * 1024;
|
|
|
100
115
|
* `Library`) are deliberately NOT listed, to avoid ever hiding real source.
|
|
101
116
|
*
|
|
102
117
|
* Only dirs that actually contain *indexable source* (or are enormous) earn a slot
|
|
103
|
-
* — IDE/state dirs like `.idea`/`.vs` are omitted because
|
|
118
|
+
* — IDE/state dirs like `.idea`/`.vs` are omitted because HomeGraph indexes only
|
|
104
119
|
* recognized source extensions, so they produce no symbols regardless.
|
|
105
120
|
*/
|
|
106
121
|
const DEFAULT_IGNORE_DIRS = new Set([
|
|
@@ -147,6 +162,74 @@ const DEFAULT_IGNORE_PATTERNS = [
|
|
|
147
162
|
'cmake-build-*/', // CLion / CMake build trees
|
|
148
163
|
'bazel-*/', // Bazel output symlink trees
|
|
149
164
|
];
|
|
165
|
+
/** True if `buf` decodes as strict UTF-8 (no invalid byte sequences). */
|
|
166
|
+
function isValidUtf8(buf) {
|
|
167
|
+
try {
|
|
168
|
+
new TextDecoder('utf-8', { fatal: true }).decode(buf);
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Read a `.gitignore` and return patterns safe to hand to the `ignore` matcher —
|
|
177
|
+
* never throwing, even when the file isn't real gitignore text. Two failure
|
|
178
|
+
* modes, both seen in the wild (issue #682):
|
|
179
|
+
*
|
|
180
|
+
* - The file isn't valid UTF-8 — e.g. transparently encrypted in place by
|
|
181
|
+
* corporate DLP / endpoint-security software, leaving a UTF-16 header plus
|
|
182
|
+
* ciphertext. None of it is meaningful patterns, so the whole file is skipped.
|
|
183
|
+
* - The file is text but a single line can't be compiled to a regex by the
|
|
184
|
+
* `ignore` library — `\\[` and friends throw "Unterminated character class".
|
|
185
|
+
* Crucially the throw is LAZY (at match time, not `.add()`), so it would
|
|
186
|
+
* otherwise escape mid-scan. That one pattern is dropped; the rest are kept.
|
|
187
|
+
*
|
|
188
|
+
* Either way a warning that NAMES the file is logged (the reporter couldn't tell
|
|
189
|
+
* which `.gitignore` was at fault) and indexing continues instead of aborting.
|
|
190
|
+
* Returns '' when there's nothing usable.
|
|
191
|
+
*/
|
|
192
|
+
function readGitignorePatterns(giPath) {
|
|
193
|
+
let buf;
|
|
194
|
+
try {
|
|
195
|
+
buf = fs.readFileSync(giPath);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return ''; // unreadable (permissions / race) — treat as absent
|
|
199
|
+
}
|
|
200
|
+
// A NUL byte never appears in real gitignore text, and a fatal UTF-8 decode
|
|
201
|
+
// catches the rest. Such a file isn't ignore patterns at all.
|
|
202
|
+
if (buf.includes(0) || !isValidUtf8(buf)) {
|
|
203
|
+
(0, errors_1.logWarn)('Ignoring a .gitignore that is not valid UTF-8 text — it may have been encrypted ' +
|
|
204
|
+
'in place by endpoint-security software. Indexing continues without it.', { file: giPath });
|
|
205
|
+
return '';
|
|
206
|
+
}
|
|
207
|
+
const content = buf.toString('utf-8');
|
|
208
|
+
// Fast path: one `.ignores()` call forces the library to compile EVERY rule,
|
|
209
|
+
// so if it doesn't throw, the whole file is safe to use verbatim.
|
|
210
|
+
try {
|
|
211
|
+
(0, ignore_1.default)().add(content).ignores('.homegraph-probe');
|
|
212
|
+
return content;
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
// Fall through: a line is uncompilable — keep the good ones, drop the bad.
|
|
216
|
+
}
|
|
217
|
+
const kept = [];
|
|
218
|
+
let dropped = 0;
|
|
219
|
+
for (const line of content.split(/\r?\n/)) {
|
|
220
|
+
try {
|
|
221
|
+
(0, ignore_1.default)().add(line).ignores('.homegraph-probe');
|
|
222
|
+
kept.push(line);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
dropped++;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (dropped > 0) {
|
|
229
|
+
(0, errors_1.logWarn)(`Skipped ${dropped} unparseable pattern(s) in a .gitignore; the rest are applied.`, { file: giPath });
|
|
230
|
+
}
|
|
231
|
+
return kept.join('\n');
|
|
232
|
+
}
|
|
150
233
|
/**
|
|
151
234
|
* An `ignore` matcher seeded with the built-in defaults, merged with the project's
|
|
152
235
|
* root .gitignore so a negation there (e.g. `!vendor/`) overrides a default. Shared
|
|
@@ -156,15 +239,275 @@ const DEFAULT_IGNORE_PATTERNS = [
|
|
|
156
239
|
*/
|
|
157
240
|
function buildDefaultIgnore(rootDir) {
|
|
158
241
|
const ig = (0, ignore_1.default)().add(DEFAULT_IGNORE_PATTERNS);
|
|
242
|
+
const rootGitignore = path.join(rootDir, '.gitignore');
|
|
243
|
+
if (fs.existsSync(rootGitignore))
|
|
244
|
+
ig.add(readGitignorePatterns(rootGitignore));
|
|
245
|
+
return ig;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Defaults-only ignore matcher (no root `.gitignore` merged). Used wherever the
|
|
249
|
+
* parent repo's own ignore rules must NOT apply — inside embedded child repos,
|
|
250
|
+
* whose gitignore semantics their own `git ls-files` already enforced (#514).
|
|
251
|
+
*/
|
|
252
|
+
function defaultsOnlyIgnore() {
|
|
253
|
+
return (0, ignore_1.default)().add(DEFAULT_IGNORE_PATTERNS);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Matcher for the project's `homegraph.json` `includeIgnored` patterns — the
|
|
257
|
+
* explicit opt-in to index embedded git repos living inside gitignored
|
|
258
|
+
* directories (#622, #699). Returns `null` when the project opted in nothing,
|
|
259
|
+
* which is the zero-config DEFAULT: `.gitignore` is then fully respected and a
|
|
260
|
+
* gitignored directory (even one holding nested repos) is never walked or
|
|
261
|
+
* indexed (#970, #976). Built once per scan/sync/scope operation from the scan
|
|
262
|
+
* root and threaded down — never global, so multi-project daemons stay isolated.
|
|
263
|
+
*/
|
|
264
|
+
function loadIncludeIgnoredMatcher(rootDir) {
|
|
265
|
+
const patterns = (0, project_config_1.loadIncludeIgnoredPatterns)(rootDir);
|
|
266
|
+
return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* `git ls-files --directory` collapses a wholly-untracked/ignored directory into
|
|
270
|
+
* one entry — and when the command's own cwd is such a directory (the indexed
|
|
271
|
+
* root is itself a git-ignored subdir of an enclosing repo), git emits the
|
|
272
|
+
* literal `./` meaning "this entire directory". That sentinel is not a real
|
|
273
|
+
* nested path: feeding it to the `ignore` matcher throws ("path should be a
|
|
274
|
+
* `path.relative()`d string, but got "./""), which used to abort `buildScopeIgnore`
|
|
275
|
+
* and so break the MCP daemon's watcher/auto-sync on connect; and joining it back
|
|
276
|
+
* onto `repoDir` would just re-point at the cwd. Drop it wherever we consume
|
|
277
|
+
* `--directory` output. (#936)
|
|
278
|
+
*/
|
|
279
|
+
function isWholeCwdEntry(entry) {
|
|
280
|
+
return entry === './' || entry === '.' || entry === '';
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* List the gitignored DIRECTORIES of a repo (collapsed, trailing-slash form),
|
|
284
|
+
* relative to `repoDir`. These are invisible to every other `git ls-files` /
|
|
285
|
+
* `git status` mode — and in a multi-repo workspace they are exactly where the
|
|
286
|
+
* nested project repos live (a super-repo `.gitignore`s its child repos to keep
|
|
287
|
+
* `git status` quiet; that does not make them third-party code). (#514)
|
|
288
|
+
*/
|
|
289
|
+
function listIgnoredDirs(repoDir) {
|
|
159
290
|
try {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
ig.add(fs.readFileSync(rootGitignore, 'utf-8'));
|
|
291
|
+
const out = (0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-o', '-i', '--exclude-standard', '--directory'], { cwd: repoDir, encoding: 'utf-8', timeout: 30000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
292
|
+
return out.split('\0').filter((e) => e.endsWith('/') && !isWholeCwdEntry(e));
|
|
163
293
|
}
|
|
164
294
|
catch {
|
|
165
|
-
|
|
295
|
+
return [];
|
|
166
296
|
}
|
|
167
|
-
|
|
297
|
+
}
|
|
298
|
+
/** Max directory depth searched below an ignored dir for nested `.git` roots. */
|
|
299
|
+
const EMBEDDED_REPO_SEARCH_DEPTH = 4;
|
|
300
|
+
/** Max directories examined per search — a huge ignored data dir must never stall a scan/sync. */
|
|
301
|
+
const EMBEDDED_REPO_SEARCH_ENTRIES = 2000;
|
|
302
|
+
/**
|
|
303
|
+
* Classify a directory's `.git` entry for embedded-repo discovery.
|
|
304
|
+
*
|
|
305
|
+
* - A `.git` **directory** is an embedded clone — distinct first-party code a
|
|
306
|
+
* super-repo merely hides from git; index it (#193, #514).
|
|
307
|
+
* - A `.git` **file** is a pointer (`gitdir: …`). A git **worktree** points into
|
|
308
|
+
* the host repo's own `.git/worktrees/<name>`, so it is a second working view
|
|
309
|
+
* of a repo HomeGraph already indexes — indexing it just duplicates the whole
|
|
310
|
+
* graph N times; skip it (#848). A **submodule worktree** points into
|
|
311
|
+
* `.git/modules/<module>/worktrees/<name>` — same duplication, so skip it too
|
|
312
|
+
* (#945). A **submodule** checkout points into `.git/modules/<module>` (no
|
|
313
|
+
* `worktrees/` segment) and is distinct code, so index it as before.
|
|
314
|
+
*
|
|
315
|
+
* Returns `'none'` when there is no `.git` entry here.
|
|
316
|
+
*/
|
|
317
|
+
function classifyGitDir(absDir) {
|
|
318
|
+
let st;
|
|
319
|
+
try {
|
|
320
|
+
st = fs.statSync(path.join(absDir, '.git'));
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
return 'none';
|
|
324
|
+
}
|
|
325
|
+
if (st.isDirectory())
|
|
326
|
+
return 'embedded';
|
|
327
|
+
if (!st.isFile())
|
|
328
|
+
return 'none';
|
|
329
|
+
try {
|
|
330
|
+
const gitdir = fs.readFileSync(path.join(absDir, '.git'), 'utf8').match(/^gitdir:\s*(.+)$/m)?.[1]?.trim();
|
|
331
|
+
// A worktree's gitdir lives under some repo's `.git/worktrees/<name>` —
|
|
332
|
+
// either the top-level repo's (`.git/worktrees/`) or, for a worktree of a
|
|
333
|
+
// submodule, that submodule's gitdir (`.git/modules/<module>/worktrees/`).
|
|
334
|
+
// The optional `modules/<module>` segment covers the submodule case (#945).
|
|
335
|
+
// Match both separators so a Windows-style pointer is recognized too.
|
|
336
|
+
if (gitdir && /(^|[\\/])\.git[\\/](modules[\\/][^\\/]+[\\/])?worktrees[\\/]/.test(gitdir))
|
|
337
|
+
return 'worktree';
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
// Unreadable `.git` pointer — fall back to the prior "index it" behavior.
|
|
341
|
+
}
|
|
342
|
+
return 'embedded';
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Find git repositories nested under `absDir` (inclusive), shallow bounded BFS.
|
|
346
|
+
* Stops descending at each repo root found — contents belong to that repo's own
|
|
347
|
+
* enumeration. Skips default-ignored dirs (`node_modules` can contain `.git`
|
|
348
|
+
* from npm git-dependencies — that never makes it project code) and HomeGraph
|
|
349
|
+
* data dirs. Depth- and entry-capped so a huge ignored tree can't stall the scan.
|
|
350
|
+
*/
|
|
351
|
+
function findNestedGitRepos(absDir, relPrefix) {
|
|
352
|
+
const found = [];
|
|
353
|
+
const defaults = defaultsOnlyIgnore();
|
|
354
|
+
const queue = [
|
|
355
|
+
{ abs: absDir, rel: relPrefix, depth: 0 },
|
|
356
|
+
];
|
|
357
|
+
let examined = 0;
|
|
358
|
+
while (queue.length > 0) {
|
|
359
|
+
const { abs, rel, depth } = queue.shift();
|
|
360
|
+
if (++examined > EMBEDDED_REPO_SEARCH_ENTRIES) {
|
|
361
|
+
(0, errors_1.logDebug)('Embedded-repo search entry cap hit — deeper repos (if any) not discovered', { under: relPrefix });
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
const cls = classifyGitDir(abs);
|
|
365
|
+
if (cls === 'worktree') {
|
|
366
|
+
continue; // a git worktree duplicates an already-indexed repo (#848) — skip
|
|
367
|
+
}
|
|
368
|
+
if (cls === 'embedded') {
|
|
369
|
+
found.push(rel);
|
|
370
|
+
continue; // its own git handles everything below
|
|
371
|
+
}
|
|
372
|
+
if (depth >= EMBEDDED_REPO_SEARCH_DEPTH)
|
|
373
|
+
continue;
|
|
374
|
+
let entries;
|
|
375
|
+
try {
|
|
376
|
+
entries = fs.readdirSync(abs, { withFileTypes: true });
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
for (const entry of entries) {
|
|
382
|
+
if (!entry.isDirectory())
|
|
383
|
+
continue;
|
|
384
|
+
if (entry.name === '.git' || (0, directory_1.isHomeGraphDataDir)(entry.name))
|
|
385
|
+
continue;
|
|
386
|
+
const childRel = rel + entry.name + '/';
|
|
387
|
+
if (defaults.ignores(childRel))
|
|
388
|
+
continue;
|
|
389
|
+
queue.push({ abs: path.join(abs, entry.name), rel: childRel, depth: depth + 1 });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return found;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Workspace-scope ignore matcher. Ordinary paths get the root's matcher
|
|
396
|
+
* (built-in defaults + root `.gitignore`); paths inside an EMBEDDED repo get
|
|
397
|
+
* that repo's own matcher (defaults + its root `.gitignore`) — the parent's
|
|
398
|
+
* `.gitignore` hides a child repo from git, not from the index (#514). A
|
|
399
|
+
* directory path (trailing slash) that is an ANCESTOR of an embedded root is
|
|
400
|
+
* never ignored, so directory-pruning callers (the Linux per-directory
|
|
401
|
+
* watcher) still descend to reach the embedded repos.
|
|
402
|
+
*
|
|
403
|
+
* Single source of truth for indexer and watcher scope — they must not diverge.
|
|
404
|
+
*/
|
|
405
|
+
class ScopeIgnore {
|
|
406
|
+
rootMatcher;
|
|
407
|
+
embedded;
|
|
408
|
+
defaults = defaultsOnlyIgnore();
|
|
409
|
+
constructor(rootMatcher, embedded) {
|
|
410
|
+
this.rootMatcher = rootMatcher;
|
|
411
|
+
// Longest root first so paths in nested embedded repos hit the innermost matcher.
|
|
412
|
+
this.embedded = [...embedded].sort((a, b) => b.root.length - a.root.length);
|
|
413
|
+
}
|
|
414
|
+
ignores(rel) {
|
|
415
|
+
for (const { root, matcher } of this.embedded) {
|
|
416
|
+
if (rel.startsWith(root)) {
|
|
417
|
+
const inner = rel.slice(root.length);
|
|
418
|
+
if (inner === '')
|
|
419
|
+
return false;
|
|
420
|
+
// Built-in defaults apply to the FULL path uniformly (#407) — an
|
|
421
|
+
// embedded repo inside node_modules (an npm git-dependency) must stay
|
|
422
|
+
// excluded even though its own rules wouldn't ignore its files.
|
|
423
|
+
return this.defaults.ignores(rel) || matcher.ignores(inner);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
// Never prune a directory that leads to an embedded repo.
|
|
427
|
+
if (rel.endsWith('/') && this.embedded.some(({ root }) => root.startsWith(rel))) {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
return this.rootMatcher.ignores(rel);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
exports.ScopeIgnore = ScopeIgnore;
|
|
434
|
+
/**
|
|
435
|
+
* Build the workspace-scope matcher. When the caller already knows the
|
|
436
|
+
* embedded roots (the scanner discovers them during collection), pass them to
|
|
437
|
+
* skip rediscovery; otherwise they're discovered here (the watcher path).
|
|
438
|
+
*/
|
|
439
|
+
function buildScopeIgnore(rootDir, embeddedRoots) {
|
|
440
|
+
const roots = embeddedRoots ? [...embeddedRoots] : discoverEmbeddedRepoRoots(rootDir);
|
|
441
|
+
return new ScopeIgnore(buildDefaultIgnore(rootDir), roots.map((root) => ({ root, matcher: buildDefaultIgnore(path.join(rootDir, root)) })));
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Standalone discovery of every embedded repo root under `rootDir` (relative,
|
|
445
|
+
* trailing-slashed) — the untracked kind (#193) always, and the gitignored kind
|
|
446
|
+
* (#514) only for directories the project opted in via `homegraph.json`
|
|
447
|
+
* `includeIgnored` (#622, #699); otherwise `.gitignore` is respected and they
|
|
448
|
+
* are not discovered (#970, #976). Recursive (an embedded repo can embed further
|
|
449
|
+
* repos). Returns [] for non-git roots: the filesystem walk handles nested repos
|
|
450
|
+
* there already.
|
|
451
|
+
*/
|
|
452
|
+
function discoverEmbeddedRepoRoots(rootDir) {
|
|
453
|
+
try {
|
|
454
|
+
(0, child_process_1.execFileSync)('git', ['rev-parse', '--git-dir'], { cwd: rootDir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
return [];
|
|
458
|
+
}
|
|
459
|
+
const out = [];
|
|
460
|
+
const defaults = defaultsOnlyIgnore();
|
|
461
|
+
const includeIgnored = loadIncludeIgnoredMatcher(rootDir);
|
|
462
|
+
const visit = (repoAbs, prefix) => {
|
|
463
|
+
const candidates = [];
|
|
464
|
+
try {
|
|
465
|
+
const o = (0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-o', '--exclude-standard', '--directory'], { cwd: repoAbs, encoding: 'utf-8', timeout: 30000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
466
|
+
for (const e of o.split('\0')) {
|
|
467
|
+
if (e.endsWith('/') && !isWholeCwdEntry(e) && !defaults.ignores(e)) {
|
|
468
|
+
candidates.push(...findNestedGitRepos(path.join(repoAbs, e), e));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
catch { /* untracked listing failed — ignored-side discovery still runs */ }
|
|
473
|
+
candidates.push(...findIgnoredEmbeddedRepos(repoAbs, includeIgnored, prefix));
|
|
474
|
+
for (const rel of candidates) {
|
|
475
|
+
const full = (0, utils_1.normalizePath)(prefix + rel);
|
|
476
|
+
out.push(full);
|
|
477
|
+
visit(path.join(repoAbs, rel), full);
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
visit(rootDir, '');
|
|
481
|
+
return out;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Discover embedded repos hidden by `repoDir`'s OWN gitignore rules: for each
|
|
485
|
+
* gitignored directory, search for nested `.git` roots. Returns repo paths
|
|
486
|
+
* relative to `repoDir`, trailing-slashed.
|
|
487
|
+
*
|
|
488
|
+
* OPT-IN ONLY. Walking into a gitignored directory contradicts what every other
|
|
489
|
+
* tool (and HomeGraph's own `git ls-files` foundation) does — `.gitignore`
|
|
490
|
+
* excludes. So this returns `[]` unless the project opted the directory in via
|
|
491
|
+
* `homegraph.json` `includeIgnored`; without that, a gitignored dir — including
|
|
492
|
+
* a huge reference/data dir full of nested clones — is left untouched (#970,
|
|
493
|
+
* #976). When opted in, it restores the super-repo-of-clones behavior (#622,
|
|
494
|
+
* #699). `prefix` is the scan-root-relative path of `repoDir`, so a pattern like
|
|
495
|
+
* `services/` opts that whole subtree in at any recursion depth. Built-in
|
|
496
|
+
* default excludes (`node_modules`, …) are always skipped.
|
|
497
|
+
*/
|
|
498
|
+
function findIgnoredEmbeddedRepos(repoDir, includeIgnored, prefix) {
|
|
499
|
+
if (!includeIgnored)
|
|
500
|
+
return [];
|
|
501
|
+
const defaults = defaultsOnlyIgnore();
|
|
502
|
+
const repos = [];
|
|
503
|
+
for (const dir of listIgnoredDirs(repoDir)) {
|
|
504
|
+
if (defaults.ignores(dir))
|
|
505
|
+
continue;
|
|
506
|
+
if (!includeIgnored.ignores((0, utils_1.normalizePath)(prefix + dir)))
|
|
507
|
+
continue;
|
|
508
|
+
repos.push(...findNestedGitRepos(path.join(repoDir, dir), dir));
|
|
509
|
+
}
|
|
510
|
+
return repos;
|
|
168
511
|
}
|
|
169
512
|
/**
|
|
170
513
|
* Collect git-visible files (tracked + untracked, .gitignore-respected) from the
|
|
@@ -177,9 +520,15 @@ function buildDefaultIgnore(rootDir) {
|
|
|
177
520
|
* skips them entirely, and untracked output reports them only as an opaque
|
|
178
521
|
* "subdir/" entry (trailing slash) rather than expanding their files. Each
|
|
179
522
|
* embedded repo is its own git boundary, so we re-run `git ls-files` inside it.
|
|
180
|
-
* (See issue #193.)
|
|
523
|
+
* (See issue #193.) GITIGNORED embedded repos are invisible even to that; they
|
|
524
|
+
* are discovered separately via `findIgnoredEmbeddedRepos` (#514) but ONLY for
|
|
525
|
+
* directories the project opted in through `homegraph.json` `includeIgnored`
|
|
526
|
+
* (`includeIgnored` here, threaded from the scan root) — by default `.gitignore`
|
|
527
|
+
* is respected and they stay out (#970, #976). Every embedded repo root (however
|
|
528
|
+
* found) is recorded in `embeddedRoots` so callers can exempt its files from the
|
|
529
|
+
* parent's own gitignore rules.
|
|
181
530
|
*/
|
|
182
|
-
function collectGitFiles(repoDir, prefix, files) {
|
|
531
|
+
function collectGitFiles(repoDir, prefix, files, embeddedRoots, includeIgnored = null) {
|
|
183
532
|
const gitOpts = { cwd: repoDir, encoding: 'utf-8', timeout: 30000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true };
|
|
184
533
|
// Tracked files. --recurse-submodules pulls in files from active submodules,
|
|
185
534
|
// which the index would otherwise represent only as a commit pointer.
|
|
@@ -205,15 +554,29 @@ function collectGitFiles(repoDir, prefix, files) {
|
|
|
205
554
|
if (rel.endsWith('/')) {
|
|
206
555
|
// git only emits a trailing-slash directory entry for an embedded repo.
|
|
207
556
|
// Guard with a .git check anyway, and skip anything else exactly as git
|
|
208
|
-
// itself skips it (we never descend into a non-repo opaque dir).
|
|
557
|
+
// itself skips it (we never descend into a non-repo opaque dir). Never
|
|
558
|
+
// descend into default-ignored locations — an embedded repo inside
|
|
559
|
+
// node_modules is an npm git-dependency, not project code.
|
|
209
560
|
const childDir = path.join(repoDir, rel);
|
|
210
|
-
|
|
211
|
-
|
|
561
|
+
// A git worktree surfaces here as an opaque untracked dir too — skip it,
|
|
562
|
+
// it's a duplicate working view of an already-indexed repo (#848).
|
|
563
|
+
if (classifyGitDir(childDir) === 'embedded' && !defaultsOnlyIgnore().ignores(rel)) {
|
|
564
|
+
embeddedRoots?.add((0, utils_1.normalizePath)(prefix + rel));
|
|
565
|
+
collectGitFiles(childDir, prefix + rel, files, embeddedRoots, includeIgnored);
|
|
212
566
|
}
|
|
213
567
|
continue;
|
|
214
568
|
}
|
|
215
569
|
files.add((0, utils_1.normalizePath)(prefix + rel));
|
|
216
570
|
}
|
|
571
|
+
// Embedded repos hidden by THIS repo's ignore rules (`/packages/` in a
|
|
572
|
+
// super-repo .gitignore) never appear in any listing above. By default they
|
|
573
|
+
// stay hidden — `.gitignore` is respected (#970, #976). They are recursed into
|
|
574
|
+
// only when the project opted the directory in via `homegraph.json`
|
|
575
|
+
// `includeIgnored` (#622, #699), which `findIgnoredEmbeddedRepos` enforces.
|
|
576
|
+
for (const rel of findIgnoredEmbeddedRepos(repoDir, includeIgnored, prefix)) {
|
|
577
|
+
embeddedRoots?.add((0, utils_1.normalizePath)(prefix + rel));
|
|
578
|
+
collectGitFiles(path.join(repoDir, rel), prefix + rel, files, embeddedRoots, includeIgnored);
|
|
579
|
+
}
|
|
217
580
|
}
|
|
218
581
|
/**
|
|
219
582
|
* Get all files visible to git (tracked + untracked but not ignored).
|
|
@@ -239,11 +602,15 @@ function getGitVisibleFiles(rootDir) {
|
|
|
239
602
|
}
|
|
240
603
|
}
|
|
241
604
|
const files = new Set();
|
|
242
|
-
|
|
605
|
+
const embeddedRoots = new Set();
|
|
606
|
+
collectGitFiles(rootDir, '', files, embeddedRoots, loadIncludeIgnoredMatcher(rootDir));
|
|
243
607
|
// Apply built-in default ignores uniformly — to tracked files too, since
|
|
244
608
|
// committing a dependency/build dir doesn't make it project code. A
|
|
245
609
|
// `.gitignore` negation (e.g. `!vendor/`) is the explicit opt-in. (issue #407)
|
|
246
|
-
|
|
610
|
+
// Files inside an EMBEDDED repo are matched against that repo's own rules,
|
|
611
|
+
// not the parent's: the parent's .gitignore hides the child repo from git,
|
|
612
|
+
// not from the index. (#514)
|
|
613
|
+
const ig = buildScopeIgnore(rootDir, embeddedRoots);
|
|
247
614
|
return new Set([...files].filter((f) => !ig.ignores(f)));
|
|
248
615
|
}
|
|
249
616
|
catch {
|
|
@@ -253,38 +620,90 @@ function getGitVisibleFiles(rootDir) {
|
|
|
253
620
|
/**
|
|
254
621
|
* Use `git status` to detect changed files instead of scanning every file.
|
|
255
622
|
* Returns null on failure so callers fall back to full scan.
|
|
623
|
+
*
|
|
624
|
+
* Recurses into embedded repos — the untracked kind (#193: the parent's status
|
|
625
|
+
* collapses them to an opaque `?? subdir/` entry) always, and the gitignored
|
|
626
|
+
* kind (#514: they never appear in the parent's status at all) only for
|
|
627
|
+
* directories opted in via `homegraph.json` `includeIgnored` (#622, #699) —
|
|
628
|
+
* running `git status` inside each, so changes in a multi-repo workspace sync
|
|
629
|
+
* without a full rescan. By default a gitignored dir is left alone, matching the
|
|
630
|
+
* full-index scan (#970, #976). Deleting an ENTIRE embedded repo dir is the one
|
|
631
|
+
* case this cannot see (the child status that would report the deletions is gone
|
|
632
|
+
* with it); a full `homegraph index` reconciles that.
|
|
256
633
|
*/
|
|
257
634
|
function getGitChangedFiles(rootDir) {
|
|
258
635
|
try {
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
continue; // Minimum: "XY file"
|
|
266
|
-
const statusCode = line.substring(0, 2);
|
|
267
|
-
const filePath = (0, utils_1.normalizePath)(line.substring(3));
|
|
268
|
-
// Skip non-source files (git status already omits .gitignored paths).
|
|
269
|
-
if (!(0, grammars_1.isSourceFile)(filePath))
|
|
270
|
-
continue;
|
|
271
|
-
if (statusCode === '??') {
|
|
272
|
-
added.push(filePath);
|
|
273
|
-
}
|
|
274
|
-
else if (statusCode.includes('D')) {
|
|
275
|
-
deleted.push(filePath);
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
// M, MM, AM, A (staged), etc. — treat as modified
|
|
279
|
-
modified.push(filePath);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
return { modified, added, deleted };
|
|
636
|
+
const changes = { modified: [], added: [], deleted: [] };
|
|
637
|
+
// Custom extension → language overrides from the project's homegraph.json,
|
|
638
|
+
// so change detection sees the same custom-extension files the full index does.
|
|
639
|
+
const overrides = (0, project_config_1.loadExtensionOverrides)(rootDir);
|
|
640
|
+
collectGitStatus(rootDir, '', changes, overrides, loadIncludeIgnoredMatcher(rootDir));
|
|
641
|
+
return changes;
|
|
283
642
|
}
|
|
284
643
|
catch {
|
|
285
644
|
return null;
|
|
286
645
|
}
|
|
287
646
|
}
|
|
647
|
+
function collectGitStatus(repoDir, prefix, out, overrides, includeIgnored = null) {
|
|
648
|
+
const output = (0, child_process_1.execFileSync)('git', ['status', '--porcelain', '--no-renames'], { cwd: repoDir, encoding: 'utf-8', timeout: 10000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
649
|
+
// This repo's own ignore rules — built-in defaults (#407) plus its .gitignore.
|
|
650
|
+
// Change detection must exclude the SAME files the full index does, but git
|
|
651
|
+
// status hides neither: it ignores nothing for *tracked* paths, and the
|
|
652
|
+
// built-in defaults aren't gitignore at all. Without this filter a committed
|
|
653
|
+
// vendor/ dir, or a tracked file under a .gitignored dir, surfaces here as a
|
|
654
|
+
// change — so `homegraph status` (which reads getChangedFiles) reports a
|
|
655
|
+
// pending edit the full index never tracks and `sync` never clears. Matching
|
|
656
|
+
// repo-relative `rel` at each recursion level mirrors getGitVisibleFiles'
|
|
657
|
+
// ScopeIgnore: every embedded repo is judged by ITS OWN rules, never the
|
|
658
|
+
// parent's. (#766)
|
|
659
|
+
const ig = buildDefaultIgnore(repoDir);
|
|
660
|
+
const untrackedDirs = [];
|
|
661
|
+
for (const line of output.split('\n')) {
|
|
662
|
+
if (line.length < 4)
|
|
663
|
+
continue; // Minimum: "XY file"
|
|
664
|
+
const statusCode = line.substring(0, 2);
|
|
665
|
+
const rel = (0, utils_1.normalizePath)(line.substring(3));
|
|
666
|
+
// Untracked directory entries (trailing slash) may hide an embedded repo —
|
|
667
|
+
// collect for the recursion below instead of treating as a file.
|
|
668
|
+
if (statusCode === '??' && rel.endsWith('/')) {
|
|
669
|
+
untrackedDirs.push(rel);
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
const filePath = (0, utils_1.normalizePath)(prefix + rel);
|
|
673
|
+
if (!(0, grammars_1.isSourceFile)(filePath, overrides))
|
|
674
|
+
continue;
|
|
675
|
+
if (statusCode.includes('D')) {
|
|
676
|
+
// Deletions stay unfiltered: getChangedFiles acts on one only when the
|
|
677
|
+
// path is already tracked in the DB, where removal is always correct — and
|
|
678
|
+
// that lets a newly-excluded dir's stale rows clean themselves up. (#766)
|
|
679
|
+
out.deleted.push(filePath);
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
// Added (`??`) / modified files inside an excluded dir must not enter the
|
|
683
|
+
// index — match against the repo-relative path, same as the full scan. (#766)
|
|
684
|
+
if (ig.ignores(rel))
|
|
685
|
+
continue;
|
|
686
|
+
if (statusCode === '??') {
|
|
687
|
+
out.added.push(filePath);
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
// M, MM, AM, A (staged), etc. — treat as modified
|
|
691
|
+
out.modified.push(filePath);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
// Recurse embedded repos found under untracked dirs (at the dir itself or
|
|
695
|
+
// nested deeper). Gitignored dirs are walked only for the directories the
|
|
696
|
+
// project opted in via `includeIgnored`; by default `.gitignore` is respected
|
|
697
|
+
// and they are left alone (#970, #976), mirroring the full-index scan.
|
|
698
|
+
for (const rel of untrackedDirs) {
|
|
699
|
+
for (const repoRel of findNestedGitRepos(path.join(repoDir, rel), rel)) {
|
|
700
|
+
collectGitStatus(path.join(repoDir, repoRel), prefix + repoRel, out, overrides, includeIgnored);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
for (const rel of findIgnoredEmbeddedRepos(repoDir, includeIgnored, prefix)) {
|
|
704
|
+
collectGitStatus(path.join(repoDir, rel), prefix + rel, out, overrides, includeIgnored);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
288
707
|
/**
|
|
289
708
|
* Recursively scan a directory for source files.
|
|
290
709
|
*
|
|
@@ -293,13 +712,15 @@ function getGitChangedFiles(rootDir) {
|
|
|
293
712
|
* projects, falls back to a filesystem walk that parses .gitignore itself.
|
|
294
713
|
*/
|
|
295
714
|
function scanDirectory(rootDir, onProgress) {
|
|
715
|
+
// Custom extension → language overrides from the project's homegraph.json.
|
|
716
|
+
const overrides = (0, project_config_1.loadExtensionOverrides)(rootDir);
|
|
296
717
|
// Fast path: use git to get all visible files (respects .gitignore everywhere)
|
|
297
718
|
const gitFiles = getGitVisibleFiles(rootDir);
|
|
298
719
|
if (gitFiles) {
|
|
299
720
|
const files = [];
|
|
300
721
|
let count = 0;
|
|
301
722
|
for (const filePath of gitFiles) {
|
|
302
|
-
if ((0, grammars_1.isSourceFile)(filePath)) {
|
|
723
|
+
if ((0, grammars_1.isSourceFile)(filePath, overrides)) {
|
|
303
724
|
files.push(filePath);
|
|
304
725
|
count++;
|
|
305
726
|
onProgress?.(count, filePath);
|
|
@@ -315,12 +736,14 @@ function scanDirectory(rootDir, onProgress) {
|
|
|
315
736
|
* allowing worker threads to receive and render progress messages.
|
|
316
737
|
*/
|
|
317
738
|
async function scanDirectoryAsync(rootDir, onProgress) {
|
|
739
|
+
// Custom extension → language overrides from the project's homegraph.json.
|
|
740
|
+
const overrides = (0, project_config_1.loadExtensionOverrides)(rootDir);
|
|
318
741
|
const gitFiles = getGitVisibleFiles(rootDir);
|
|
319
742
|
if (gitFiles) {
|
|
320
743
|
const files = [];
|
|
321
744
|
let count = 0;
|
|
322
745
|
for (const filePath of gitFiles) {
|
|
323
|
-
if ((0, grammars_1.isSourceFile)(filePath)) {
|
|
746
|
+
if ((0, grammars_1.isSourceFile)(filePath, overrides)) {
|
|
324
747
|
files.push(filePath);
|
|
325
748
|
count++;
|
|
326
749
|
onProgress?.(count, filePath);
|
|
@@ -341,17 +764,17 @@ function scanDirectoryWalk(rootDir, onProgress) {
|
|
|
341
764
|
const files = [];
|
|
342
765
|
let count = 0;
|
|
343
766
|
const visitedDirs = new Set();
|
|
767
|
+
// Custom extension → language overrides from the project's homegraph.json.
|
|
768
|
+
const overrides = (0, project_config_1.loadExtensionOverrides)(rootDir);
|
|
344
769
|
const loadIgnore = (dir) => {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
return null;
|
|
770
|
+
const giPath = path.join(dir, '.gitignore');
|
|
771
|
+
if (!fs.existsSync(giPath))
|
|
772
|
+
return null;
|
|
773
|
+
// readGitignorePatterns is defensive: a non-UTF-8 (DLP-encrypted) or
|
|
774
|
+
// uncompilable .gitignore is skipped/filtered with a warning, never thrown
|
|
775
|
+
// (issue #682) — so the per-file `.ignores()` calls below can't crash.
|
|
776
|
+
const patterns = readGitignorePatterns(giPath);
|
|
777
|
+
return patterns ? { dir, ig: (0, ignore_1.default)().add(patterns) } : null;
|
|
355
778
|
};
|
|
356
779
|
const isIgnored = (fullPath, isDir, matchers) => {
|
|
357
780
|
for (const { dir, ig } of matchers) {
|
|
@@ -393,8 +816,9 @@ function scanDirectoryWalk(rootDir, onProgress) {
|
|
|
393
816
|
return;
|
|
394
817
|
}
|
|
395
818
|
for (const entry of entries) {
|
|
396
|
-
// Never descend into git internals or
|
|
397
|
-
|
|
819
|
+
// Never descend into git internals or any HomeGraph data directory
|
|
820
|
+
// (the active one or a sibling another environment created — #636).
|
|
821
|
+
if (entry.name === '.git' || (0, directory_1.isHomeGraphDataDir)(entry.name))
|
|
398
822
|
continue;
|
|
399
823
|
const fullPath = path.join(dir, entry.name);
|
|
400
824
|
const relativePath = (0, utils_1.normalizePath)(path.relative(rootDir, fullPath));
|
|
@@ -408,7 +832,7 @@ function scanDirectoryWalk(rootDir, onProgress) {
|
|
|
408
832
|
}
|
|
409
833
|
}
|
|
410
834
|
else if (stat.isFile()) {
|
|
411
|
-
if (!isIgnored(fullPath, false, active) && (0, grammars_1.isSourceFile)(relativePath)) {
|
|
835
|
+
if (!isIgnored(fullPath, false, active) && (0, grammars_1.isSourceFile)(relativePath, overrides)) {
|
|
412
836
|
files.push(relativePath);
|
|
413
837
|
count++;
|
|
414
838
|
onProgress?.(count, relativePath);
|
|
@@ -426,7 +850,7 @@ function scanDirectoryWalk(rootDir, onProgress) {
|
|
|
426
850
|
}
|
|
427
851
|
}
|
|
428
852
|
else if (entry.isFile()) {
|
|
429
|
-
if (!isIgnored(fullPath, false, active) && (0, grammars_1.isSourceFile)(relativePath)) {
|
|
853
|
+
if (!isIgnored(fullPath, false, active) && (0, grammars_1.isSourceFile)(relativePath, overrides)) {
|
|
430
854
|
files.push(relativePath);
|
|
431
855
|
count++;
|
|
432
856
|
onProgress?.(count, relativePath);
|
|
@@ -540,6 +964,10 @@ class ExtractionOrchestrator {
|
|
|
540
964
|
let filesErrored = 0;
|
|
541
965
|
let totalNodes = 0;
|
|
542
966
|
let totalEdges = 0;
|
|
967
|
+
// Custom extension → language overrides from the project's homegraph.json.
|
|
968
|
+
// Threaded into language detection so custom-extension files load the right
|
|
969
|
+
// grammar and store under the mapped language.
|
|
970
|
+
const overrides = (0, project_config_1.loadExtensionOverrides)(this.rootDir);
|
|
543
971
|
const log = verbose
|
|
544
972
|
? (msg) => { console.log(`[worker] ${msg}`); }
|
|
545
973
|
: (_msg) => { };
|
|
@@ -589,7 +1017,7 @@ class ExtractionOrchestrator {
|
|
|
589
1017
|
});
|
|
590
1018
|
await new Promise(resolve => setImmediate(resolve));
|
|
591
1019
|
// Detect needed languages and load grammars in the parse worker
|
|
592
|
-
const neededLanguages = [...new Set(files.map((f) => (0, grammars_1.detectLanguage)(f)))];
|
|
1020
|
+
const neededLanguages = [...new Set(files.map((f) => (0, grammars_1.detectLanguage)(f, undefined, overrides)))];
|
|
593
1021
|
// .h files default to 'c' but may be C++ — ensure cpp grammar is loaded when c is needed
|
|
594
1022
|
if (neededLanguages.includes('c') && !neededLanguages.includes('cpp')) {
|
|
595
1023
|
neededLanguages.push('cpp');
|
|
@@ -688,7 +1116,10 @@ class ExtractionOrchestrator {
|
|
|
688
1116
|
w.terminate().catch(() => { });
|
|
689
1117
|
}
|
|
690
1118
|
async function requestParse(filePath, content) {
|
|
691
|
-
|
|
1119
|
+
// Resolve the language on the main thread (where the project's
|
|
1120
|
+
// homegraph.json overrides are loaded) and hand it to the worker, so the
|
|
1121
|
+
// worker never needs the override map itself.
|
|
1122
|
+
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
692
1123
|
if (!WorkerClass || (0, grammars_1.requiresInProcessExtraction)(language)) {
|
|
693
1124
|
// In-process fallback (also required for languages whose extractors
|
|
694
1125
|
// cannot run in a worker thread).
|
|
@@ -717,7 +1148,7 @@ class ExtractionOrchestrator {
|
|
|
717
1148
|
worker.terminate().catch(() => { });
|
|
718
1149
|
}, timeoutMs);
|
|
719
1150
|
pendingParses.set(id, { resolve, reject, timer });
|
|
720
|
-
worker.postMessage({ type: 'parse', id, filePath, content, frameworkNames });
|
|
1151
|
+
worker.postMessage({ type: 'parse', id, filePath, content, frameworkNames, language });
|
|
721
1152
|
});
|
|
722
1153
|
}
|
|
723
1154
|
for (let i = 0; i < files.length; i += FILE_IO_BATCH_SIZE) {
|
|
@@ -739,7 +1170,10 @@ class ExtractionOrchestrator {
|
|
|
739
1170
|
// Read files in parallel (with path validation before any I/O)
|
|
740
1171
|
const fileContents = await Promise.all(batch.map(async (fp) => {
|
|
741
1172
|
try {
|
|
742
|
-
|
|
1173
|
+
// Indexing read: follow in-root symlinks the directory walk already
|
|
1174
|
+
// descended into (the `../` guard still applies) so files reached
|
|
1175
|
+
// via an in-root symlink-to-outside still index (#935).
|
|
1176
|
+
const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, fp, { allowSymlinkEscape: true });
|
|
743
1177
|
if (!fullPath) {
|
|
744
1178
|
(0, errors_1.logWarn)('Path traversal blocked in batch reader', { filePath: fp });
|
|
745
1179
|
return { filePath: fp, content: null, stats: null, error: new Error('Path traversal blocked') };
|
|
@@ -823,7 +1257,7 @@ class ExtractionOrchestrator {
|
|
|
823
1257
|
processed++;
|
|
824
1258
|
// Store in database on main thread (SQLite is not thread-safe)
|
|
825
1259
|
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
826
|
-
const language = (0, grammars_1.detectLanguage)(filePath, content);
|
|
1260
|
+
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
827
1261
|
this.storeExtractionResult(filePath, content, language, stats, result);
|
|
828
1262
|
}
|
|
829
1263
|
if (result.errors.length > 0) {
|
|
@@ -845,7 +1279,7 @@ class ExtractionOrchestrator {
|
|
|
845
1279
|
// Files with no symbols but no errors (yaml, twig, properties) are
|
|
846
1280
|
// tracked at the file level — count them as indexed so the CLI
|
|
847
1281
|
// doesn't misleadingly report "No files found to index".
|
|
848
|
-
const lang = (0, grammars_1.detectLanguage)(filePath, content);
|
|
1282
|
+
const lang = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
849
1283
|
if ((0, grammars_1.isFileLevelOnlyLanguage)(lang)) {
|
|
850
1284
|
filesIndexed++;
|
|
851
1285
|
}
|
|
@@ -898,7 +1332,7 @@ class ExtractionOrchestrator {
|
|
|
898
1332
|
continue;
|
|
899
1333
|
}
|
|
900
1334
|
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
901
|
-
const language = (0, grammars_1.detectLanguage)(filePath, content);
|
|
1335
|
+
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
902
1336
|
const stats = await fsp.stat(path.join(this.rootDir, filePath));
|
|
903
1337
|
this.storeExtractionResult(filePath, content, language, stats, result);
|
|
904
1338
|
const idx = errors.indexOf(errEntry);
|
|
@@ -946,7 +1380,7 @@ class ExtractionOrchestrator {
|
|
|
946
1380
|
continue;
|
|
947
1381
|
}
|
|
948
1382
|
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
949
|
-
const language = (0, grammars_1.detectLanguage)(filePath, fullContent);
|
|
1383
|
+
const language = (0, grammars_1.detectLanguage)(filePath, fullContent, overrides);
|
|
950
1384
|
const stats = await fsp.stat(path.join(this.rootDir, filePath));
|
|
951
1385
|
this.storeExtractionResult(filePath, fullContent, language, stats, result);
|
|
952
1386
|
const idx = errors.indexOf(errEntry);
|
|
@@ -1026,7 +1460,8 @@ class ExtractionOrchestrator {
|
|
|
1026
1460
|
* Index a single file
|
|
1027
1461
|
*/
|
|
1028
1462
|
async indexFile(relativePath) {
|
|
1029
|
-
|
|
1463
|
+
// Indexing read: follow in-root symlinks (the `../` guard still applies), #935.
|
|
1464
|
+
const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, relativePath, { allowSymlinkEscape: true });
|
|
1030
1465
|
if (!fullPath) {
|
|
1031
1466
|
return {
|
|
1032
1467
|
nodes: [],
|
|
@@ -1066,8 +1501,8 @@ class ExtractionOrchestrator {
|
|
|
1066
1501
|
* Used by the parallel batch reader to avoid redundant file I/O.
|
|
1067
1502
|
*/
|
|
1068
1503
|
async indexFileWithContent(relativePath, content, stats) {
|
|
1069
|
-
// Prevent
|
|
1070
|
-
const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, relativePath);
|
|
1504
|
+
// Prevent `../` traversal; follow in-root symlinks like the directory walk (#935).
|
|
1505
|
+
const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, relativePath, { allowSymlinkEscape: true });
|
|
1071
1506
|
if (!fullPath) {
|
|
1072
1507
|
(0, errors_1.logWarn)('Path traversal blocked in indexFileWithContent', { relativePath });
|
|
1073
1508
|
return {
|
|
@@ -1095,8 +1530,8 @@ class ExtractionOrchestrator {
|
|
|
1095
1530
|
durationMs: 0,
|
|
1096
1531
|
};
|
|
1097
1532
|
}
|
|
1098
|
-
// Detect language
|
|
1099
|
-
const language = (0, grammars_1.detectLanguage)(relativePath, content);
|
|
1533
|
+
// Detect language (honoring the project's homegraph.json extension overrides)
|
|
1534
|
+
const language = (0, grammars_1.detectLanguage)(relativePath, content, (0, project_config_1.loadExtensionOverrides)(this.rootDir));
|
|
1100
1535
|
if (!(0, grammars_1.isLanguageSupported)(language)) {
|
|
1101
1536
|
return {
|
|
1102
1537
|
nodes: [],
|
|
@@ -1127,6 +1562,25 @@ class ExtractionOrchestrator {
|
|
|
1127
1562
|
if (existingFile && existingFile.contentHash === contentHash) {
|
|
1128
1563
|
return; // No changes
|
|
1129
1564
|
}
|
|
1565
|
+
// Snapshot incoming cross-file edges BEFORE deleting this file's nodes.
|
|
1566
|
+
// `deleteFile` cascades to delete every edge whose source OR target is a
|
|
1567
|
+
// node in this file (edges.FK ... ON DELETE CASCADE). Edges whose SOURCE is
|
|
1568
|
+
// in this file are re-emitted by the extractor below, but edges whose SOURCE
|
|
1569
|
+
// is in a *different* (unchanged) file are not — they would be silently
|
|
1570
|
+
// dropped, which is issue #899: re-indexing a callee file severs `calls`/
|
|
1571
|
+
// `references` edges from callers that import it via module-attribute
|
|
1572
|
+
// access (`pkg.mod.fn(...)`).
|
|
1573
|
+
//
|
|
1574
|
+
// We snapshot the edge plus the target node's (name, kind) so we can
|
|
1575
|
+
// re-resolve to the re-indexed target's NEW id. Node ids are
|
|
1576
|
+
// `sha256(filePath:kind:name:line)`, so any line shift in the callee file
|
|
1577
|
+
// (e.g. a docstring-only edit above the symbol) changes every target id and
|
|
1578
|
+
// a naive re-insert by old id would silently drop every edge. Matching by
|
|
1579
|
+
// (filePath, kind, name) is stable across line shifts; if the symbol was
|
|
1580
|
+
// renamed/removed, no match is found and the edge stays dropped (correct).
|
|
1581
|
+
const crossFileIncomingEdges = existingFile
|
|
1582
|
+
? this.queries.getCrossFileIncomingEdgesWithTarget(filePath)
|
|
1583
|
+
: [];
|
|
1130
1584
|
// Delete existing data for this file
|
|
1131
1585
|
if (existingFile) {
|
|
1132
1586
|
this.queries.deleteFile(filePath);
|
|
@@ -1147,6 +1601,31 @@ class ExtractionOrchestrator {
|
|
|
1147
1601
|
this.queries.insertEdges(validEdges);
|
|
1148
1602
|
}
|
|
1149
1603
|
}
|
|
1604
|
+
// Re-insert cross-file incoming edges snapshotted before the delete,
|
|
1605
|
+
// re-resolving each edge's target to the re-indexed node's new id by
|
|
1606
|
+
// (filePath, kind, name). Node ids include the source line, so any line
|
|
1607
|
+
// shift in the callee file (e.g. a docstring-only edit above the symbol)
|
|
1608
|
+
// changes every target id and a naive re-insert by old id would drop them
|
|
1609
|
+
// all. `insertEdges` still filters to endpoints that exist, so edges whose
|
|
1610
|
+
// caller (source) was deleted, or whose callee (target) was renamed/removed
|
|
1611
|
+
// during the re-index (no match in `newTargetIds`), are dropped. This
|
|
1612
|
+
// closes the #899 edge-drop on `sync`.
|
|
1613
|
+
if (crossFileIncomingEdges.length > 0) {
|
|
1614
|
+
const newNodesByKindName = new Map();
|
|
1615
|
+
for (const n of validNodes) {
|
|
1616
|
+
newNodesByKindName.set(`${n.kind}\0${n.name}`, n.id);
|
|
1617
|
+
}
|
|
1618
|
+
const reinserted = [];
|
|
1619
|
+
for (const e of crossFileIncomingEdges) {
|
|
1620
|
+
const newTargetId = newNodesByKindName.get(`${e.targetKind}\0${e.targetName}`);
|
|
1621
|
+
if (newTargetId) {
|
|
1622
|
+
reinserted.push({ source: e.source, target: newTargetId, kind: e.kind, metadata: e.metadata, line: e.line, column: e.column, provenance: e.provenance });
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
if (reinserted.length > 0) {
|
|
1626
|
+
this.queries.insertEdges(reinserted);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1150
1629
|
// Insert unresolved references in batch with denormalized filePath/language
|
|
1151
1630
|
if (result.unresolvedReferences.length > 0) {
|
|
1152
1631
|
const insertedIds = new Set(validNodes.map((n) => n.id));
|
|
@@ -1206,7 +1685,7 @@ class ExtractionOrchestrator {
|
|
|
1206
1685
|
// whether or not the project uses git, and crucially also catches committed
|
|
1207
1686
|
// changes from `git pull`/`checkout`/`merge`/`rebase` — which `git status`
|
|
1208
1687
|
// cannot see, because the working tree is clean afterward.
|
|
1209
|
-
const currentFiles =
|
|
1688
|
+
const currentFiles = await scanDirectoryAsync(this.rootDir);
|
|
1210
1689
|
filesChecked = currentFiles.length;
|
|
1211
1690
|
const currentSet = new Set(currentFiles);
|
|
1212
1691
|
const trackedFiles = this.queries.getAllFiles();
|
|
@@ -1217,14 +1696,26 @@ class ExtractionOrchestrator {
|
|
|
1217
1696
|
// Removals: tracked in the DB but no longer a present source file. Check the
|
|
1218
1697
|
// filesystem directly — `scanDirectory` (via `git ls-files`) still lists a
|
|
1219
1698
|
// file deleted from disk but not yet staged, so set membership alone misses it.
|
|
1699
|
+
// `reconcileChecks` drives the cooperative yield shared with the adds/mods loop
|
|
1700
|
+
// below (see SYNC_RECONCILE_YIELD_INTERVAL / issue #905).
|
|
1701
|
+
let reconcileChecks = 0;
|
|
1220
1702
|
for (const tracked of trackedFiles) {
|
|
1221
1703
|
if (!currentSet.has(tracked.path) || !fs.existsSync(path.join(this.rootDir, tracked.path))) {
|
|
1222
1704
|
this.queries.deleteFile(tracked.path);
|
|
1223
1705
|
filesRemoved++;
|
|
1224
1706
|
}
|
|
1707
|
+
if (++reconcileChecks % SYNC_RECONCILE_YIELD_INTERVAL === 0) {
|
|
1708
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
1709
|
+
}
|
|
1225
1710
|
}
|
|
1226
1711
|
// Adds / modifications.
|
|
1227
1712
|
for (const filePath of currentFiles) {
|
|
1713
|
+
// Same cooperative yield as the removals loop — this is the other O(files)
|
|
1714
|
+
// synchronous-stat loop that wedges the main thread on a large repo (#905).
|
|
1715
|
+
// Yield at the top of the body so the `continue` fast-paths below still hit it.
|
|
1716
|
+
if (++reconcileChecks % SYNC_RECONCILE_YIELD_INTERVAL === 0) {
|
|
1717
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
1718
|
+
}
|
|
1228
1719
|
const fullPath = path.join(this.rootDir, filePath);
|
|
1229
1720
|
const tracked = trackedMap.get(filePath);
|
|
1230
1721
|
// Cheap pre-filter: an already-indexed file whose size AND mtime both match
|
|
@@ -1267,7 +1758,8 @@ class ExtractionOrchestrator {
|
|
|
1267
1758
|
}
|
|
1268
1759
|
// Load only grammars needed for changed files
|
|
1269
1760
|
if (filesToIndex.length > 0) {
|
|
1270
|
-
const
|
|
1761
|
+
const overrides = (0, project_config_1.loadExtensionOverrides)(this.rootDir);
|
|
1762
|
+
const neededLanguages = [...new Set(filesToIndex.map((f) => (0, grammars_1.detectLanguage)(f, undefined, overrides)))];
|
|
1271
1763
|
// .h files default to 'c' but may be C++ — ensure cpp grammar is loaded
|
|
1272
1764
|
if (neededLanguages.includes('c') && !neededLanguages.includes('cpp')) {
|
|
1273
1765
|
neededLanguages.push('cpp');
|