homegraph 1.1.3 → 1.4.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/README.md +43 -18
- package/dist/arkts/ohos-api-index.d.ts +15 -0
- package/dist/arkts/ohos-api-index.d.ts.map +1 -0
- package/dist/arkts/ohos-api-index.js +190 -0
- package/dist/arkts/ohos-api-index.js.map +1 -0
- package/dist/arkts/ohos-sdk-input.d.ts +36 -0
- package/dist/arkts/ohos-sdk-input.d.ts.map +1 -0
- package/dist/arkts/ohos-sdk-input.js +214 -0
- package/dist/arkts/ohos-sdk-input.js.map +1 -0
- package/dist/bin/command-supervision.d.ts +12 -0
- package/dist/bin/command-supervision.d.ts.map +1 -0
- package/dist/bin/command-supervision.js +86 -0
- package/dist/bin/command-supervision.js.map +1 -0
- package/dist/bin/homegraph.d.ts +1 -1
- package/dist/bin/homegraph.js +709 -168
- package/dist/bin/homegraph.js.map +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +85 -0
- package/dist/context/index.js.map +1 -1
- package/dist/db/index.d.ts +84 -5
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +217 -14
- 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 +58 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/queries.d.ts +131 -2
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +473 -9
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +52 -1
- package/dist/db/wal-valve.d.ts +106 -0
- package/dist/db/wal-valve.d.ts.map +1 -0
- package/dist/db/wal-valve.js +208 -0
- package/dist/db/wal-valve.js.map +1 -0
- package/dist/directory.d.ts +36 -0
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +274 -0
- package/dist/directory.js.map +1 -1
- package/dist/extraction/arkts-batch-worker.d.ts +2 -0
- package/dist/extraction/arkts-batch-worker.d.ts.map +1 -0
- package/dist/extraction/arkts-batch-worker.js +28 -0
- package/dist/extraction/arkts-batch-worker.js.map +1 -0
- package/dist/extraction/cfml-extractor.d.ts +107 -0
- package/dist/extraction/cfml-extractor.d.ts.map +1 -0
- package/dist/extraction/cfml-extractor.js +494 -0
- package/dist/extraction/cfml-extractor.js.map +1 -0
- package/dist/extraction/context.d.ts +11 -0
- package/dist/extraction/context.d.ts.map +1 -1
- package/dist/extraction/context.js +20 -0
- package/dist/extraction/context.js.map +1 -1
- package/dist/extraction/grammars.d.ts +25 -1
- package/dist/extraction/grammars.d.ts.map +1 -1
- package/dist/extraction/grammars.js +177 -24
- package/dist/extraction/grammars.js.map +1 -1
- package/dist/extraction/index.d.ts +55 -2
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +923 -381
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/languages/arkts-state-decorators.d.ts +13 -0
- package/dist/extraction/languages/arkts-state-decorators.d.ts.map +1 -0
- package/dist/extraction/languages/arkts-state-decorators.js +26 -0
- package/dist/extraction/languages/arkts-state-decorators.js.map +1 -0
- package/dist/extraction/languages/arkts-viewtree.d.ts +4 -2
- package/dist/extraction/languages/arkts-viewtree.d.ts.map +1 -1
- package/dist/extraction/languages/arkts-viewtree.js +21 -6
- package/dist/extraction/languages/arkts-viewtree.js.map +1 -1
- package/dist/extraction/languages/arkts.d.ts +98 -2
- package/dist/extraction/languages/arkts.d.ts.map +1 -1
- package/dist/extraction/languages/arkts.js +1290 -61
- package/dist/extraction/languages/arkts.js.map +1 -1
- package/dist/extraction/languages/c-cpp.d.ts +98 -0
- package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/dist/extraction/languages/c-cpp.js +563 -1
- package/dist/extraction/languages/c-cpp.js.map +1 -1
- package/dist/extraction/languages/cfquery.d.ts +12 -0
- package/dist/extraction/languages/cfquery.d.ts.map +1 -0
- package/dist/extraction/languages/cfquery.js +28 -0
- package/dist/extraction/languages/cfquery.js.map +1 -0
- package/dist/extraction/languages/cfscript.d.ts +3 -0
- package/dist/extraction/languages/cfscript.d.ts.map +1 -0
- package/dist/extraction/languages/cfscript.js +73 -0
- package/dist/extraction/languages/cfscript.js.map +1 -0
- package/dist/extraction/languages/cobol.d.ts +33 -0
- package/dist/extraction/languages/cobol.d.ts.map +1 -0
- package/dist/extraction/languages/cobol.js +499 -0
- package/dist/extraction/languages/cobol.js.map +1 -0
- package/dist/extraction/languages/erlang.d.ts +3 -0
- package/dist/extraction/languages/erlang.d.ts.map +1 -0
- package/dist/extraction/languages/erlang.js +350 -0
- package/dist/extraction/languages/erlang.js.map +1 -0
- package/dist/extraction/languages/index.d.ts.map +1 -1
- package/dist/extraction/languages/index.js +16 -0
- package/dist/extraction/languages/index.js.map +1 -1
- package/dist/extraction/languages/nix.d.ts +3 -0
- package/dist/extraction/languages/nix.d.ts.map +1 -0
- package/dist/extraction/languages/nix.js +294 -0
- package/dist/extraction/languages/nix.js.map +1 -0
- package/dist/extraction/languages/ohos-api-consumer.d.ts +34 -0
- package/dist/extraction/languages/ohos-api-consumer.d.ts.map +1 -0
- package/dist/extraction/languages/ohos-api-consumer.js +283 -0
- package/dist/extraction/languages/ohos-api-consumer.js.map +1 -0
- package/dist/extraction/languages/solidity.d.ts +3 -0
- package/dist/extraction/languages/solidity.d.ts.map +1 -0
- package/dist/extraction/languages/solidity.js +293 -0
- package/dist/extraction/languages/solidity.js.map +1 -0
- package/dist/extraction/languages/terraform.d.ts +3 -0
- package/dist/extraction/languages/terraform.d.ts.map +1 -0
- package/dist/extraction/languages/terraform.js +641 -0
- package/dist/extraction/languages/terraform.js.map +1 -0
- package/dist/extraction/languages/vbnet.d.ts +11 -0
- package/dist/extraction/languages/vbnet.d.ts.map +1 -0
- package/dist/extraction/languages/vbnet.js +141 -0
- package/dist/extraction/languages/vbnet.js.map +1 -0
- package/dist/extraction/mybatis-extractor.d.ts +30 -10
- package/dist/extraction/mybatis-extractor.d.ts.map +1 -1
- package/dist/extraction/mybatis-extractor.js +140 -38
- package/dist/extraction/mybatis-extractor.js.map +1 -1
- package/dist/extraction/parse-pool.d.ts +152 -0
- package/dist/extraction/parse-pool.d.ts.map +1 -0
- package/dist/extraction/parse-pool.js +387 -0
- package/dist/extraction/parse-pool.js.map +1 -0
- package/dist/extraction/parse-worker.js +9 -2
- package/dist/extraction/parse-worker.js.map +1 -1
- package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
- package/dist/extraction/tree-sitter-helpers.js +1 -0
- package/dist/extraction/tree-sitter-helpers.js.map +1 -1
- package/dist/extraction/tree-sitter-types.d.ts +20 -1
- package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.d.ts +59 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +952 -37
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
- package/dist/graph/traversal.d.ts.map +1 -1
- package/dist/graph/traversal.js +77 -18
- package/dist/graph/traversal.js.map +1 -1
- package/dist/index.d.ts +85 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +426 -9
- package/dist/index.js.map +1 -1
- package/dist/installer/index.d.ts +42 -0
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +97 -2
- package/dist/installer/index.js.map +1 -1
- package/dist/mcp/daemon-paths.d.ts +30 -3
- package/dist/mcp/daemon-paths.d.ts.map +1 -1
- package/dist/mcp/daemon-paths.js +50 -10
- package/dist/mcp/daemon-paths.js.map +1 -1
- package/dist/mcp/daemon-registry.d.ts.map +1 -1
- package/dist/mcp/daemon-registry.js +7 -3
- package/dist/mcp/daemon-registry.js.map +1 -1
- package/dist/mcp/daemon.d.ts +73 -3
- package/dist/mcp/daemon.d.ts.map +1 -1
- package/dist/mcp/daemon.js +258 -40
- package/dist/mcp/daemon.js.map +1 -1
- package/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
- package/dist/mcp/dynamic-boundaries.js +2 -1
- package/dist/mcp/dynamic-boundaries.js.map +1 -1
- package/dist/mcp/early-ppid.d.ts +26 -0
- package/dist/mcp/early-ppid.d.ts.map +1 -0
- package/dist/mcp/early-ppid.js +29 -0
- package/dist/mcp/early-ppid.js.map +1 -0
- package/dist/mcp/engine.d.ts +16 -0
- package/dist/mcp/engine.d.ts.map +1 -1
- package/dist/mcp/engine.js +100 -2
- package/dist/mcp/engine.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +66 -49
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/liveness-watchdog.d.ts +18 -1
- package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
- package/dist/mcp/liveness-watchdog.js +73 -13
- package/dist/mcp/liveness-watchdog.js.map +1 -1
- package/dist/mcp/memory-budget.d.ts +30 -0
- package/dist/mcp/memory-budget.d.ts.map +1 -0
- package/dist/mcp/memory-budget.js +123 -0
- package/dist/mcp/memory-budget.js.map +1 -0
- package/dist/mcp/ppid-watchdog.d.ts +18 -0
- package/dist/mcp/ppid-watchdog.d.ts.map +1 -1
- package/dist/mcp/ppid-watchdog.js +37 -0
- package/dist/mcp/ppid-watchdog.js.map +1 -1
- package/dist/mcp/proxy.d.ts.map +1 -1
- package/dist/mcp/proxy.js +88 -4
- package/dist/mcp/proxy.js.map +1 -1
- package/dist/mcp/query-cache.d.ts +56 -0
- package/dist/mcp/query-cache.d.ts.map +1 -0
- package/dist/mcp/query-cache.js +261 -0
- package/dist/mcp/query-cache.js.map +1 -0
- package/dist/mcp/query-pool.d.ts +128 -0
- package/dist/mcp/query-pool.d.ts.map +1 -0
- package/dist/mcp/query-pool.js +374 -0
- package/dist/mcp/query-pool.js.map +1 -0
- package/dist/mcp/query-worker.d.ts +24 -0
- package/dist/mcp/query-worker.d.ts.map +1 -0
- package/dist/mcp/query-worker.js +87 -0
- package/dist/mcp/query-worker.js.map +1 -0
- package/dist/mcp/server-instructions.d.ts +4 -29
- package/dist/mcp/server-instructions.d.ts.map +1 -1
- package/dist/mcp/server-instructions.js +39 -86
- package/dist/mcp/server-instructions.js.map +1 -1
- package/dist/mcp/session.d.ts +14 -0
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +28 -1
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/startup-handshake.d.ts +44 -0
- package/dist/mcp/startup-handshake.d.ts.map +1 -0
- package/dist/mcp/startup-handshake.js +73 -0
- package/dist/mcp/startup-handshake.js.map +1 -0
- package/dist/mcp/tools.d.ts +179 -2
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +3027 -192
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/transport.d.ts.map +1 -1
- package/dist/mcp/transport.js +18 -0
- package/dist/mcp/transport.js.map +1 -1
- package/dist/project-config.d.ts +58 -0
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +141 -2
- package/dist/project-config.js.map +1 -1
- package/dist/reasoning/login.js +1 -1
- package/dist/reasoning/login.js.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.d.ts +2 -29
- package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.js +866 -148
- package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
- package/dist/resolution/callback-synthesizer.d.ts +9 -1
- package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/dist/resolution/callback-synthesizer.js +1232 -183
- package/dist/resolution/callback-synthesizer.js.map +1 -1
- package/dist/resolution/cooperative-yield.d.ts +32 -0
- package/dist/resolution/cooperative-yield.d.ts.map +1 -0
- package/dist/resolution/cooperative-yield.js +42 -0
- package/dist/resolution/cooperative-yield.js.map +1 -0
- package/dist/resolution/frameworks/cics.d.ts +20 -0
- package/dist/resolution/frameworks/cics.d.ts.map +1 -0
- package/dist/resolution/frameworks/cics.js +90 -0
- package/dist/resolution/frameworks/cics.js.map +1 -0
- package/dist/resolution/frameworks/index.d.ts.map +1 -1
- package/dist/resolution/frameworks/index.js +3 -0
- package/dist/resolution/frameworks/index.js.map +1 -1
- package/dist/resolution/frameworks/java.d.ts.map +1 -1
- package/dist/resolution/frameworks/java.js +14 -6
- package/dist/resolution/frameworks/java.js.map +1 -1
- package/dist/resolution/frameworks/terraform.d.ts +38 -0
- package/dist/resolution/frameworks/terraform.d.ts.map +1 -0
- package/dist/resolution/frameworks/terraform.js +277 -0
- package/dist/resolution/frameworks/terraform.js.map +1 -0
- package/dist/resolution/goframe-synthesizer.d.ts +2 -1
- package/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
- package/dist/resolution/goframe-synthesizer.js +8 -3
- package/dist/resolution/goframe-synthesizer.js.map +1 -1
- package/dist/resolution/import-resolver.d.ts +7 -0
- package/dist/resolution/import-resolver.d.ts.map +1 -1
- package/dist/resolution/import-resolver.js +129 -4
- package/dist/resolution/import-resolver.js.map +1 -1
- package/dist/resolution/index.d.ts +65 -3
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +477 -59
- package/dist/resolution/index.js.map +1 -1
- package/dist/resolution/name-matcher.d.ts +22 -3
- package/dist/resolution/name-matcher.d.ts.map +1 -1
- package/dist/resolution/name-matcher.js +657 -39
- 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 +49 -0
- package/dist/resolution/strip-comments.js.map +1 -1
- package/dist/resolution/types.d.ts +29 -0
- package/dist/resolution/types.d.ts.map +1 -1
- package/dist/resolution/workspace-packages.d.ts +10 -0
- package/dist/resolution/workspace-packages.d.ts.map +1 -1
- package/dist/resolution/workspace-packages.js +142 -4
- package/dist/resolution/workspace-packages.js.map +1 -1
- package/dist/search/identifier-segments.d.ts +60 -0
- package/dist/search/identifier-segments.d.ts.map +1 -0
- package/dist/search/identifier-segments.js +176 -0
- package/dist/search/identifier-segments.js.map +1 -0
- package/dist/search/query-utils.d.ts +201 -0
- package/dist/search/query-utils.d.ts.map +1 -1
- package/dist/search/query-utils.js +919 -2
- package/dist/search/query-utils.js.map +1 -1
- package/dist/spec/config.d.ts +7 -11
- package/dist/spec/config.d.ts.map +1 -1
- package/dist/spec/config.js +77 -48
- package/dist/spec/config.js.map +1 -1
- package/dist/spec/db/fragment-node.d.ts +1 -0
- package/dist/spec/db/fragment-node.d.ts.map +1 -1
- package/dist/spec/db/fragment-node.js +8 -0
- package/dist/spec/db/fragment-node.js.map +1 -1
- package/dist/spec/db/fts.d.ts +14 -0
- package/dist/spec/db/fts.d.ts.map +1 -1
- package/dist/spec/db/fts.js +43 -4
- package/dist/spec/db/fts.js.map +1 -1
- package/dist/spec/db/schema.d.ts +6 -3
- package/dist/spec/db/schema.d.ts.map +1 -1
- package/dist/spec/db/schema.js +36 -4
- package/dist/spec/db/schema.js.map +1 -1
- package/dist/spec/db/schema.sql +11 -1
- package/dist/spec/evolve/logic-checker.d.ts +2 -2
- package/dist/spec/evolve/logic-checker.d.ts.map +1 -1
- package/dist/spec/evolve/logic-checker.js +3 -27
- package/dist/spec/evolve/logic-checker.js.map +1 -1
- package/dist/spec/evolve/pipeline.d.ts +21 -19
- package/dist/spec/evolve/pipeline.d.ts.map +1 -1
- package/dist/spec/evolve/pipeline.js +284 -31
- package/dist/spec/evolve/pipeline.js.map +1 -1
- package/dist/spec/evolve/spec-rewriter.d.ts +2 -2
- package/dist/spec/evolve/spec-rewriter.d.ts.map +1 -1
- package/dist/spec/evolve/spec-rewriter.js +7 -31
- package/dist/spec/evolve/spec-rewriter.js.map +1 -1
- package/dist/spec/graph/queries.d.ts +97 -0
- package/dist/spec/graph/queries.d.ts.map +1 -1
- package/dist/spec/graph/queries.js +225 -0
- package/dist/spec/graph/queries.js.map +1 -1
- package/dist/spec/llm/client.d.ts +29 -0
- package/dist/spec/llm/client.d.ts.map +1 -0
- package/dist/spec/llm/client.js +123 -0
- package/dist/spec/llm/client.js.map +1 -0
- package/dist/spec/llm/index.d.ts +3 -0
- package/dist/spec/llm/index.d.ts.map +1 -0
- package/dist/spec/llm/index.js +11 -0
- package/dist/spec/llm/index.js.map +1 -0
- package/dist/spec/llm/prompts.d.ts +13 -0
- package/dist/spec/llm/prompts.d.ts.map +1 -0
- package/dist/spec/llm/prompts.js +75 -0
- package/dist/spec/llm/prompts.js.map +1 -0
- package/dist/spec/mining/git-scanner.d.ts +12 -0
- package/dist/spec/mining/git-scanner.d.ts.map +1 -1
- package/dist/spec/mining/git-scanner.js +41 -0
- package/dist/spec/mining/git-scanner.js.map +1 -1
- package/dist/spec/mining/pipeline.d.ts.map +1 -1
- package/dist/spec/mining/pipeline.js +14 -1
- package/dist/spec/mining/pipeline.js.map +1 -1
- package/dist/spec/utils.d.ts +2 -1
- package/dist/spec/utils.d.ts.map +1 -1
- package/dist/spec/utils.js +3 -1
- package/dist/spec/utils.js.map +1 -1
- package/dist/sync/git-hooks.d.ts.map +1 -1
- package/dist/sync/git-hooks.js +2 -0
- package/dist/sync/git-hooks.js.map +1 -1
- package/dist/sync/watcher.d.ts +10 -5
- package/dist/sync/watcher.d.ts.map +1 -1
- package/dist/sync/watcher.js +51 -14
- package/dist/sync/watcher.js.map +1 -1
- package/dist/sync/worktree.d.ts +9 -0
- package/dist/sync/worktree.d.ts.map +1 -1
- package/dist/sync/worktree.js +45 -0
- package/dist/sync/worktree.js.map +1 -1
- package/dist/types.d.ts +25 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -1
- package/dist/ui/shimmer-progress.d.ts +2 -0
- package/dist/ui/shimmer-progress.d.ts.map +1 -1
- package/dist/ui/shimmer-progress.js +19 -2
- package/dist/ui/shimmer-progress.js.map +1 -1
- package/dist/upgrade/index.d.ts +32 -0
- package/dist/upgrade/index.d.ts.map +1 -1
- package/dist/upgrade/index.js +163 -15
- package/dist/upgrade/index.js.map +1 -1
- package/dist/upgrade/remove-binary.d.ts +87 -0
- package/dist/upgrade/remove-binary.d.ts.map +1 -0
- package/dist/upgrade/remove-binary.js +289 -0
- package/dist/upgrade/remove-binary.js.map +1 -0
- package/dist/upgrade/update-check.d.ts +92 -0
- package/dist/upgrade/update-check.d.ts.map +1 -0
- package/dist/upgrade/update-check.js +258 -0
- package/dist/upgrade/update-check.js.map +1 -0
- package/package.json +4 -4
- package/scripts/agent-eval/repro-concurrent-explore.mjs +119 -0
- package/scripts/agent-eval/repro-daemon-clients.mjs +125 -0
- package/scripts/agent-eval/run-all.sh +6 -0
- package/scripts/build-bundle.sh +5 -0
- package/scripts/exp_boundary_eval/README.md +247 -0
- package/scripts/exp_boundary_eval/_test_mcp_chain.py +78 -0
- package/scripts/exp_boundary_eval/_test_stdin.py +8 -0
- package/scripts/exp_boundary_eval/_utils.py +1116 -0
- package/scripts/exp_boundary_eval/analyze.py +1313 -0
- package/scripts/exp_boundary_eval/deveco_arm.py +519 -0
- package/scripts/exp_boundary_eval/run_all.py +378 -0
- package/scripts/exp_boundary_eval/run_one.py +165 -0
- package/scripts/exp_boundary_eval/run_session.py +158 -0
- package/scripts/exp_boundary_eval/setup.py +120 -0
- package/scripts/exp_boundary_eval/win_mcp_launcher.py +73 -0
- package/scripts/exp_boundary_eval/win_mcp_stdio_wrap.js +36 -0
- package/scripts/exp_boundary_eval/win_node_launcher.py +24 -0
- package/scripts/npm-shim.js +32 -3
- package/scripts/ohos-sdk-publish.mjs +133 -0
- package/scripts/qa_eval/README.md +0 -407
- package/scripts/qa_eval/_test_deveco_probe.py +0 -41
- package/scripts/qa_eval/agent_runner.py +0 -526
- package/scripts/qa_eval/data/.gitignore +0 -4
- package/scripts/qa_eval/data/test-set.jsonl +0 -2
- package/scripts/qa_eval/eval_metrics.py +0 -274
- package/scripts/qa_eval/external_agent.py +0 -976
- package/scripts/qa_eval/llm_config.py +0 -92
- package/scripts/qa_eval/memory_monitor.py +0 -132
- package/scripts/qa_eval/my_answer_accuracy.py +0 -187
- package/scripts/qa_eval/requirements.txt +0 -2
- package/scripts/qa_eval/run_pipeline.py +0 -804
- package/scripts/qa_eval/stats_efficiency.py +0 -279
- package/scripts/qa_eval/stats_scores.py +0 -207
package/dist/extraction/index.js
CHANGED
|
@@ -46,14 +46,17 @@ exports.hashContent = hashContent;
|
|
|
46
46
|
exports.buildDefaultIgnore = buildDefaultIgnore;
|
|
47
47
|
exports.buildScopeIgnore = buildScopeIgnore;
|
|
48
48
|
exports.discoverEmbeddedRepoRoots = discoverEmbeddedRepoRoots;
|
|
49
|
+
exports.findUnindexedIgnoredRepos = findUnindexedIgnoredRepos;
|
|
49
50
|
exports.scanDirectory = scanDirectory;
|
|
50
51
|
exports.scanDirectoryAsync = scanDirectoryAsync;
|
|
51
52
|
const fs = __importStar(require("fs"));
|
|
52
53
|
const fsp = __importStar(require("fs/promises"));
|
|
53
54
|
const path = __importStar(require("path"));
|
|
55
|
+
const os = __importStar(require("os"));
|
|
54
56
|
const crypto = __importStar(require("crypto"));
|
|
55
57
|
const child_process_1 = require("child_process");
|
|
56
58
|
const tree_sitter_1 = require("./tree-sitter");
|
|
59
|
+
const parse_pool_1 = require("./parse-pool");
|
|
57
60
|
const grammars_1 = require("./grammars");
|
|
58
61
|
const project_config_1 = require("../project-config");
|
|
59
62
|
const directory_1 = require("../directory");
|
|
@@ -61,6 +64,8 @@ const errors_1 = require("../errors");
|
|
|
61
64
|
const utils_1 = require("../utils");
|
|
62
65
|
const ignore_1 = __importDefault(require("ignore"));
|
|
63
66
|
const frameworks_1 = require("../resolution/frameworks");
|
|
67
|
+
const context_1 = require("./context");
|
|
68
|
+
const arkts_1 = require("./languages/arkts");
|
|
64
69
|
/**
|
|
65
70
|
* Number of files to read in parallel during indexing.
|
|
66
71
|
* File reads are I/O-bound; batching overlaps I/O wait with CPU parse work.
|
|
@@ -155,12 +160,34 @@ const DEFAULT_IGNORE_DIRS = new Set([
|
|
|
155
160
|
// Generic cache
|
|
156
161
|
'.cache',
|
|
157
162
|
]);
|
|
163
|
+
/**
|
|
164
|
+
* Android resource directory types. A `res/` tree holds ONLY non-code resources —
|
|
165
|
+
* layouts, drawables, value bags (strings/colors/styles), menus, navigation
|
|
166
|
+
* graphs — split into one typed subdirectory per kind, optionally density/locale/
|
|
167
|
+
* version-qualified (`values-es`, `drawable-hdpi`, `layout-v21`, …). None of it
|
|
168
|
+
* yields an extractable code symbol, yet on an Android app it DOMINATES the tree
|
|
169
|
+
* (one report: 26k XML files = 97% of the project, 0 symbols), bloating the DB,
|
|
170
|
+
* slowing indexing, and skewing both the file count and `codegraph_explore`
|
|
171
|
+
* results (#1047). So these are excluded by default. The structure is
|
|
172
|
+
* self-identifying — a non-Android project has no `res/layout/` etc., so it's
|
|
173
|
+
* untouched — and the only XML that DOES produce symbols (MyBatis mappers) lives
|
|
174
|
+
* under `src/main/resources/`, never `res/`, so nothing useful is dropped.
|
|
175
|
+
* `res/raw/` is deliberately NOT here: it holds arbitrary bundled assets that can
|
|
176
|
+
* be code-ish (a `.sql` schema, a `.js`), so we leave it indexed. Override any of
|
|
177
|
+
* these with a `.gitignore` negation (e.g. `!res/values/`).
|
|
178
|
+
*/
|
|
179
|
+
const ANDROID_RES_TYPES = [
|
|
180
|
+
'anim', 'animator', 'color', 'drawable', 'font', 'layout',
|
|
181
|
+
'menu', 'mipmap', 'navigation', 'transition', 'values', 'xml',
|
|
182
|
+
];
|
|
158
183
|
/** Gitignore-style patterns for the `ignore` matcher: the dirs above plus a few globs. */
|
|
159
184
|
const DEFAULT_IGNORE_PATTERNS = [
|
|
160
185
|
...Array.from(DEFAULT_IGNORE_DIRS, (d) => `${d}/`),
|
|
161
186
|
'*.egg-info/', // Python packaging metadata
|
|
162
187
|
'cmake-build-*/', // CLion / CMake build trees
|
|
163
188
|
'bazel-*/', // Bazel output symlink trees
|
|
189
|
+
// Android resource dirs at any depth, with their qualifier variants (#1047).
|
|
190
|
+
...ANDROID_RES_TYPES.map((t) => `**/res/${t}*/`),
|
|
164
191
|
];
|
|
165
192
|
/** True if `buf` decodes as strict UTF-8 (no invalid byte sequences). */
|
|
166
193
|
function isValidUtf8(buf) {
|
|
@@ -265,6 +292,173 @@ function loadIncludeIgnoredMatcher(rootDir) {
|
|
|
265
292
|
const patterns = (0, project_config_1.loadIncludeIgnoredPatterns)(rootDir);
|
|
266
293
|
return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
|
|
267
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Matcher for the project's `codegraph.json` `exclude` patterns — paths to keep
|
|
297
|
+
* OUT of the index even when git-tracked, which `.gitignore` cannot do (#999).
|
|
298
|
+
* The escape hatch for a committed vendor/theme/SDK directory. Returns `null`
|
|
299
|
+
* when nothing is excluded (the zero-config default → no overhead). Matched
|
|
300
|
+
* against project-root-relative paths, so it applies uniformly across the whole
|
|
301
|
+
* workspace, including inside embedded repos (excluding `static/` means gone
|
|
302
|
+
* everywhere). Built once per scan/sync/scope operation from the scan root.
|
|
303
|
+
*/
|
|
304
|
+
function loadExcludeMatcher(rootDir) {
|
|
305
|
+
const patterns = (0, project_config_1.loadExcludePatterns)(rootDir);
|
|
306
|
+
return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Matcher for the project's `homegraph.json` `include` patterns — first-party
|
|
310
|
+
* source to force INTO the index even when `.gitignore` drops it (the general
|
|
311
|
+
* whitelist `includeIgnored` never was — that one only revives *embedded git
|
|
312
|
+
* repos*). The case it exists for: a project under a second VCS (SVN/Perforce)
|
|
313
|
+
* `.gitignore`s its own real source so it stays out of Git, yet we still want it
|
|
314
|
+
* indexed. Returns `null` when nothing is force-included (the zero-config
|
|
315
|
+
* default → no overhead, no extra walk). Built once per scan/sync/scope
|
|
316
|
+
* operation from the scan root.
|
|
317
|
+
*/
|
|
318
|
+
function loadIncludeMatcher(rootDir) {
|
|
319
|
+
const patterns = (0, project_config_1.loadIncludePatterns)(rootDir);
|
|
320
|
+
return patterns.length > 0 ? (0, ignore_1.default)().add(patterns) : null;
|
|
321
|
+
}
|
|
322
|
+
/** Glob metacharacters that end the static (literal) prefix of an `include` pattern. */
|
|
323
|
+
const GLOB_META = /[*?[\]{}!]/;
|
|
324
|
+
/**
|
|
325
|
+
* The static directory prefix of each `include` pattern — the literal leading
|
|
326
|
+
* path up to the first glob segment — trailing-slashed, used to (a) walk only
|
|
327
|
+
* the opted-in subtrees in `collectIncludedFiles` and (b) let `ScopeIgnore` keep
|
|
328
|
+
* the watcher descending toward them. `Tools/` stays `Tools/`; a recursive
|
|
329
|
+
* `Tools/**` glob yields `Tools/`; `src/local/file.ts` yields `src/local/` (the
|
|
330
|
+
* file's dir); a pattern that starts with a glob (like a leading `**`) yields
|
|
331
|
+
* `''`, meaning "no static root — walk the whole tree". Duplicates and roots
|
|
332
|
+
* nested under a broader root are collapsed so each subtree is walked once.
|
|
333
|
+
*/
|
|
334
|
+
function includeStaticRoots(patterns) {
|
|
335
|
+
const roots = new Set();
|
|
336
|
+
for (const pattern of patterns) {
|
|
337
|
+
let p = pattern.replace(/^\/+/, '');
|
|
338
|
+
const trailingSlash = p.endsWith('/');
|
|
339
|
+
if (trailingSlash)
|
|
340
|
+
p = p.slice(0, -1);
|
|
341
|
+
const segs = p.split('/').filter(Boolean);
|
|
342
|
+
const lead = [];
|
|
343
|
+
for (const s of segs) {
|
|
344
|
+
if (GLOB_META.test(s))
|
|
345
|
+
break;
|
|
346
|
+
lead.push(s);
|
|
347
|
+
}
|
|
348
|
+
const hadWildcard = lead.length < segs.length;
|
|
349
|
+
// A wholly-literal pattern with no trailing slash names a file (or a dir we
|
|
350
|
+
// can't tell apart) — drop its last segment so we walk the containing dir
|
|
351
|
+
// and let the matcher pick the file. A trailing slash or a glob means the
|
|
352
|
+
// remaining `lead` is already the directory to walk.
|
|
353
|
+
if (!hadWildcard && !trailingSlash && lead.length > 0)
|
|
354
|
+
lead.pop();
|
|
355
|
+
if (lead.length === 0) {
|
|
356
|
+
roots.clear();
|
|
357
|
+
roots.add('');
|
|
358
|
+
return ['']; // a top-level glob forces a whole-tree walk; nothing narrower matters
|
|
359
|
+
}
|
|
360
|
+
roots.add(lead.join('/') + '/');
|
|
361
|
+
}
|
|
362
|
+
// Collapse roots nested under a broader one (e.g. drop `a/b/` if `a/` is present).
|
|
363
|
+
const all = [...roots];
|
|
364
|
+
return all.filter((r) => !all.some((other) => other !== r && r.startsWith(other)));
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Actively discover the source files an `include` whitelist forces in. `git
|
|
368
|
+
* ls-files` never lists gitignored files, so a filtered filesystem walk of just
|
|
369
|
+
* the opted-in subtrees (`includeStaticRoots`) is the only way to find them.
|
|
370
|
+
* Returns project-root-relative, normalized source-file paths.
|
|
371
|
+
*
|
|
372
|
+
* A file is collected when it MATCHES `include`, is NOT hit by `exclude` (an
|
|
373
|
+
* explicit exclude always wins), is a recognized source file, and does not live
|
|
374
|
+
* under a built-in default-ignored dir (`node_modules`, `dist`, …), `.git`, or
|
|
375
|
+
* HomeGraph's data dir — those are never resurfaced, mirroring `ScopeIgnore`.
|
|
376
|
+
* `.gitignore` is deliberately NOT consulted: overriding it is the whole point.
|
|
377
|
+
*/
|
|
378
|
+
function collectIncludedFiles(rootDir, include, exclude, roots, overrides) {
|
|
379
|
+
const out = new Set();
|
|
380
|
+
const defaults = defaultsOnlyIgnore();
|
|
381
|
+
const visited = new Set();
|
|
382
|
+
const consider = (abs, rel, isDir) => {
|
|
383
|
+
if (isDir) {
|
|
384
|
+
if (defaults.ignores(rel + '/'))
|
|
385
|
+
return; // never node_modules/dist/… via include
|
|
386
|
+
// An explicit `exclude` always wins over `include`; prune the whole subtree
|
|
387
|
+
// here so a large excluded dir (a committed frontend's own vendored deps,
|
|
388
|
+
// build output, …) is never walked — the per-file guard below still catches
|
|
389
|
+
// anything a directory pattern doesn't, so this is a pure efficiency win.
|
|
390
|
+
if (exclude && exclude.ignores(rel + '/'))
|
|
391
|
+
return;
|
|
392
|
+
walk(abs);
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
if (defaults.ignores(rel))
|
|
396
|
+
return;
|
|
397
|
+
if (!include.ignores(rel))
|
|
398
|
+
return;
|
|
399
|
+
if (exclude && exclude.ignores(rel))
|
|
400
|
+
return;
|
|
401
|
+
if (!(0, grammars_1.isSourceFile)(rel, overrides))
|
|
402
|
+
return;
|
|
403
|
+
out.add(rel);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
function walk(absDir) {
|
|
407
|
+
let realDir;
|
|
408
|
+
try {
|
|
409
|
+
realDir = fs.realpathSync(absDir);
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (visited.has(realDir))
|
|
415
|
+
return; // symlink-cycle guard
|
|
416
|
+
visited.add(realDir);
|
|
417
|
+
let entries;
|
|
418
|
+
try {
|
|
419
|
+
entries = fs.readdirSync(absDir, { withFileTypes: true });
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
for (const entry of entries) {
|
|
425
|
+
if (entry.name === '.git' || (0, directory_1.isHomeGraphDataDir)(entry.name))
|
|
426
|
+
continue;
|
|
427
|
+
const abs = path.join(absDir, entry.name);
|
|
428
|
+
const rel = (0, utils_1.normalizePath)(path.relative(rootDir, abs));
|
|
429
|
+
if (!rel || rel.startsWith('..'))
|
|
430
|
+
continue;
|
|
431
|
+
if (entry.isSymbolicLink()) {
|
|
432
|
+
try {
|
|
433
|
+
const st = fs.statSync(fs.realpathSync(abs));
|
|
434
|
+
consider(abs, rel, st.isDirectory());
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
// broken symlink — skip
|
|
438
|
+
}
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
consider(abs, rel, entry.isDirectory());
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
for (const root of roots) {
|
|
445
|
+
walk(root === '' ? rootDir : path.join(rootDir, root));
|
|
446
|
+
}
|
|
447
|
+
return out;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* The included source files (`homegraph.json` `include`) for a scan root, or an
|
|
451
|
+
* empty set when nothing is force-included. Centralizes loading the matcher,
|
|
452
|
+
* roots, exclude, and overrides so both enumeration paths (git and filesystem
|
|
453
|
+
* walk) add the same files.
|
|
454
|
+
*/
|
|
455
|
+
function collectIncludedFilesForRoot(rootDir) {
|
|
456
|
+
const include = loadIncludeMatcher(rootDir);
|
|
457
|
+
if (!include)
|
|
458
|
+
return new Set();
|
|
459
|
+
const roots = includeStaticRoots((0, project_config_1.loadIncludePatterns)(rootDir));
|
|
460
|
+
return collectIncludedFiles(rootDir, include, loadExcludeMatcher(rootDir), roots, (0, project_config_1.loadExtensionOverrides)(rootDir));
|
|
461
|
+
}
|
|
268
462
|
/**
|
|
269
463
|
* `git ls-files --directory` collapses a wholly-untracked/ignored directory into
|
|
270
464
|
* one entry — and when the command's own cwd is such a directory (the indexed
|
|
@@ -404,14 +598,55 @@ function findNestedGitRepos(absDir, relPrefix) {
|
|
|
404
598
|
*/
|
|
405
599
|
class ScopeIgnore {
|
|
406
600
|
rootMatcher;
|
|
601
|
+
exclude;
|
|
602
|
+
include;
|
|
603
|
+
includeRoots;
|
|
407
604
|
embedded;
|
|
408
605
|
defaults = defaultsOnlyIgnore();
|
|
409
|
-
constructor(rootMatcher, embedded
|
|
606
|
+
constructor(rootMatcher, embedded,
|
|
607
|
+
/**
|
|
608
|
+
* Project `homegraph.json` `exclude` patterns (#999), matched against the
|
|
609
|
+
* full root-relative path. Wins over everything else — an explicit user
|
|
610
|
+
* exclude applies even to tracked files and even inside embedded repos.
|
|
611
|
+
*/
|
|
612
|
+
exclude = null,
|
|
613
|
+
/**
|
|
614
|
+
* Project `homegraph.json` `include` patterns — first-party source forced
|
|
615
|
+
* INTO the index despite `.gitignore`. When a path matches, it is NOT
|
|
616
|
+
* ignored (so the watcher watches it), overriding `.gitignore`/`rootMatcher`
|
|
617
|
+
* — but never `exclude` (checked first) and never a built-in default-ignored
|
|
618
|
+
* dir. `includeRoots` are the static prefixes so a gitignored ANCESTOR
|
|
619
|
+
* directory of an included subtree still isn't pruned by the directory
|
|
620
|
+
* walker/watcher.
|
|
621
|
+
*/
|
|
622
|
+
include = null, includeRoots = []) {
|
|
410
623
|
this.rootMatcher = rootMatcher;
|
|
624
|
+
this.exclude = exclude;
|
|
625
|
+
this.include = include;
|
|
626
|
+
this.includeRoots = includeRoots;
|
|
411
627
|
// Longest root first so paths in nested embedded repos hit the innermost matcher.
|
|
412
628
|
this.embedded = [...embedded].sort((a, b) => b.root.length - a.root.length);
|
|
413
629
|
}
|
|
414
630
|
ignores(rel) {
|
|
631
|
+
// User `exclude` (#999) is checked first and against the full root-relative
|
|
632
|
+
// path: it must drop git-TRACKED paths (which `.gitignore` can't) and apply
|
|
633
|
+
// everywhere, including ancestors of embedded repos.
|
|
634
|
+
if (this.exclude && this.exclude.ignores(rel))
|
|
635
|
+
return true;
|
|
636
|
+
// User `include`: force first-party source in despite `.gitignore`. Never
|
|
637
|
+
// resurfaces a built-in default-ignored dir (node_modules/dist/…), so an
|
|
638
|
+
// include pattern can't accidentally pull in dependency/build trees.
|
|
639
|
+
if (this.include && !this.defaults.ignores(rel)) {
|
|
640
|
+
if (rel.endsWith('/')) {
|
|
641
|
+
// A directory on (or leading to) an included subtree must stay walkable
|
|
642
|
+
// so the watcher/walker descends to reach the forced-in files.
|
|
643
|
+
if (this.includeRoots.some((r) => r.startsWith(rel) || rel.startsWith(r)))
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
else if (this.include.ignores(rel)) {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
415
650
|
for (const { root, matcher } of this.embedded) {
|
|
416
651
|
if (rel.startsWith(root)) {
|
|
417
652
|
const inner = rel.slice(root.length);
|
|
@@ -438,7 +673,37 @@ exports.ScopeIgnore = ScopeIgnore;
|
|
|
438
673
|
*/
|
|
439
674
|
function buildScopeIgnore(rootDir, embeddedRoots) {
|
|
440
675
|
const roots = embeddedRoots ? [...embeddedRoots] : discoverEmbeddedRepoRoots(rootDir);
|
|
441
|
-
|
|
676
|
+
const include = loadIncludeMatcher(rootDir);
|
|
677
|
+
return new ScopeIgnore(buildDefaultIgnore(rootDir), roots.map((root) => ({ root, matcher: buildDefaultIgnore(path.join(rootDir, root)) })), loadExcludeMatcher(rootDir), include, include ? includeStaticRoots((0, project_config_1.loadIncludePatterns)(rootDir)) : []);
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Whether an embedded repo found as a tracked gitlink (mode 160000, #1031/#1033)
|
|
681
|
+
* must be SKIPPED rather than indexed. A gitlink is tracked, so `.gitignore`
|
|
682
|
+
* can't untrack it — but the discovery passes for it must still honor the same
|
|
683
|
+
* scope rules as every other path, or a gitignored reference/data dir full of
|
|
684
|
+
* `git add`ed clones gets pulled into the index against the user's stated intent
|
|
685
|
+
* (#1065). Two reasons to skip:
|
|
686
|
+
* 1. It sits in a built-in default-ignored location — an npm git-dependency
|
|
687
|
+
* under `node_modules` is never project code; not even an explicit opt-in
|
|
688
|
+
* revives it (matches `findIgnoredEmbeddedRepos`).
|
|
689
|
+
* 2. The parent repo's own `.gitignore` covers its path and the project did
|
|
690
|
+
* NOT opt that path in via `homegraph.json` `includeIgnored`. The gitignore
|
|
691
|
+
* rule is the user's stated intent to keep that path out of scope, exactly
|
|
692
|
+
* as for an UNtracked embedded repo — respect it by default, opt back in
|
|
693
|
+
* with `includeIgnored` (#514, #970, #976).
|
|
694
|
+
* `relDir` is repoDir-relative (trailing-slashed); `prefix` is repoDir's
|
|
695
|
+
* scan-root-relative path so the `includeIgnored` pattern is matched on the full
|
|
696
|
+
* scan-root-relative path. `defaults` is `defaultsOnlyIgnore()` and `repoIgnore`
|
|
697
|
+
* is `buildDefaultIgnore(repoDir)` (defaults + the repo's own `.gitignore`),
|
|
698
|
+
* both passed in so they're built once per repo level rather than per gitlink.
|
|
699
|
+
*/
|
|
700
|
+
function gitlinkEmbeddedRepoSkipped(relDir, prefix, defaults, repoIgnore, includeIgnored) {
|
|
701
|
+
if (defaults.ignores(relDir))
|
|
702
|
+
return true; // default-ignored — never index, opt-in can't revive
|
|
703
|
+
if (!repoIgnore.ignores(relDir))
|
|
704
|
+
return false; // not ignored at all — index as before (#1031/#1033)
|
|
705
|
+
// Gitignored by the repo's own rules — skip unless the project opted it in.
|
|
706
|
+
return !includeIgnored?.ignores((0, utils_1.normalizePath)(prefix + relDir));
|
|
442
707
|
}
|
|
443
708
|
/**
|
|
444
709
|
* Standalone discovery of every embedded repo root under `rootDir` (relative,
|
|
@@ -470,6 +735,25 @@ function discoverEmbeddedRepoRoots(rootDir) {
|
|
|
470
735
|
}
|
|
471
736
|
}
|
|
472
737
|
catch { /* untracked listing failed — ignored-side discovery still runs */ }
|
|
738
|
+
// Unexpanded gitlinks (mode 160000) with a real checkout on disk — embedded
|
|
739
|
+
// repos `git add`ed without `.gitmodules`, or submodules not active here. The
|
|
740
|
+
// untracked listing above can't see them (they're tracked), so find them the
|
|
741
|
+
// same way collectGitFiles does, keeping watcher scope == indexer scope.
|
|
742
|
+
// (#1031, #1033)
|
|
743
|
+
try {
|
|
744
|
+
const gitOpts = { cwd: repoAbs, encoding: 'utf-8', timeout: 30000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true };
|
|
745
|
+
const repoIgnore = buildDefaultIgnore(repoAbs);
|
|
746
|
+
for (const rel of listGitlinkRels(gitOpts)) {
|
|
747
|
+
const relDir = rel.endsWith('/') ? rel : rel + '/';
|
|
748
|
+
// A gitlink under a gitignored path is respected (not indexed) unless the
|
|
749
|
+
// project opted it in — same rule as the untracked-ignored kind (#1065).
|
|
750
|
+
if (gitlinkEmbeddedRepoSkipped(relDir, prefix, defaults, repoIgnore, includeIgnored))
|
|
751
|
+
continue;
|
|
752
|
+
if (classifyGitDir(path.join(repoAbs, rel)) === 'embedded')
|
|
753
|
+
candidates.push(relDir);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
catch { /* staged listing failed — other discovery still runs */ }
|
|
473
757
|
candidates.push(...findIgnoredEmbeddedRepos(repoAbs, includeIgnored, prefix));
|
|
474
758
|
for (const rel of candidates) {
|
|
475
759
|
const full = (0, utils_1.normalizePath)(prefix + rel);
|
|
@@ -480,6 +764,50 @@ function discoverEmbeddedRepoRoots(rootDir) {
|
|
|
480
764
|
visit(rootDir, '');
|
|
481
765
|
return out;
|
|
482
766
|
}
|
|
767
|
+
/**
|
|
768
|
+
* Cap on how many skipped gitignored repos the CLI hint enumerates — a huge
|
|
769
|
+
* gitignored data dir full of clones must never turn the hint scan into a long
|
|
770
|
+
* walk. Enough to make the point; the caller says "+N more" past this.
|
|
771
|
+
*/
|
|
772
|
+
const UNINDEXED_IGNORED_REPO_HINT_CAP = 100;
|
|
773
|
+
/**
|
|
774
|
+
* The INVERSE of the gitignored side of {@link discoverEmbeddedRepoRoots}:
|
|
775
|
+
* nested git repositories under a gitignored directory that the project has NOT
|
|
776
|
+
* opted into via `homegraph.json` `includeIgnored`. These are real repos the
|
|
777
|
+
* default `init`/`index` deliberately skips because `.gitignore` excludes them
|
|
778
|
+
* (#970, #976) — most visibly the "super-repo `.gitignore`s its child repos"
|
|
779
|
+
* layout (#1156), where `init` at the parent correctly indexes ~nothing while
|
|
780
|
+
* `init` inside each child works. The CLI uses this to turn that silent empty
|
|
781
|
+
* index into an actionable hint: it names the skipped repos and offers to opt
|
|
782
|
+
* them in. Paths are `rootDir`-relative and trailing-slashed (valid
|
|
783
|
+
* `includeIgnored` patterns as-is). Returns `[]` for a non-git root (a
|
|
784
|
+
* filesystem walk already descends into nested repos there), skips built-in
|
|
785
|
+
* default-ignored dirs (`node_modules`, …), and is bounded so it never stalls
|
|
786
|
+
* on a giant ignored tree.
|
|
787
|
+
*/
|
|
788
|
+
function findUnindexedIgnoredRepos(rootDir) {
|
|
789
|
+
try {
|
|
790
|
+
(0, child_process_1.execFileSync)('git', ['rev-parse', '--git-dir'], { cwd: rootDir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
791
|
+
}
|
|
792
|
+
catch {
|
|
793
|
+
return [];
|
|
794
|
+
}
|
|
795
|
+
const defaults = defaultsOnlyIgnore();
|
|
796
|
+
const includeIgnored = loadIncludeIgnoredMatcher(rootDir);
|
|
797
|
+
const repos = [];
|
|
798
|
+
for (const dir of listIgnoredDirs(rootDir)) {
|
|
799
|
+
if (defaults.ignores(dir))
|
|
800
|
+
continue; // node_modules etc. — never project code
|
|
801
|
+
if (includeIgnored?.ignores((0, utils_1.normalizePath)(dir)))
|
|
802
|
+
continue; // already opted in — nothing to nag about
|
|
803
|
+
for (const repo of findNestedGitRepos(path.join(rootDir, dir), dir)) {
|
|
804
|
+
repos.push(repo);
|
|
805
|
+
if (repos.length >= UNINDEXED_IGNORED_REPO_HINT_CAP)
|
|
806
|
+
return repos;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return repos;
|
|
810
|
+
}
|
|
483
811
|
/**
|
|
484
812
|
* Discover embedded repos hidden by `repoDir`'s OWN gitignore rules: for each
|
|
485
813
|
* gitignored directory, search for nested `.git` roots. Returns repo paths
|
|
@@ -510,24 +838,65 @@ function findIgnoredEmbeddedRepos(repoDir, includeIgnored, prefix) {
|
|
|
510
838
|
return repos;
|
|
511
839
|
}
|
|
512
840
|
/**
|
|
513
|
-
* Collect
|
|
514
|
-
* git repository rooted at `repoDir`, adding each to `files` with `prefix`
|
|
515
|
-
* prepended so paths stay relative to the original scan root.
|
|
841
|
+
* Collect tracked paths and unexpanded gitlinks (mode 160000) from `git ls-files`.
|
|
516
842
|
*
|
|
517
|
-
*
|
|
518
|
-
* (
|
|
519
|
-
*
|
|
520
|
-
* skips them entirely, and untracked output reports them only as an opaque
|
|
521
|
-
* "subdir/" entry (trailing slash) rather than expanding their files. Each
|
|
522
|
-
* embedded repo is its own git boundary, so we re-run `git ls-files` inside it.
|
|
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.
|
|
843
|
+
* Prefer one `-s --recurse-submodules` pass (git ≥2.38). Older git rejects that
|
|
844
|
+
* combo ("unsupported mode"), so fall back to `-c --recurse-submodules` for
|
|
845
|
+
* expanded submodule files plus a plain `-s` pass for gitlink detection (#1031).
|
|
530
846
|
*/
|
|
847
|
+
function listTrackedGitFiles(gitOpts, prefix, files) {
|
|
848
|
+
const gitlinkRels = [];
|
|
849
|
+
const ingestStaged = (tracked) => {
|
|
850
|
+
for (const entry of tracked.split('\0')) {
|
|
851
|
+
if (!entry)
|
|
852
|
+
continue;
|
|
853
|
+
const tab = entry.indexOf('\t');
|
|
854
|
+
if (tab === -1)
|
|
855
|
+
continue; // --stage always emits "<mode> <object> <stage>\t<path>"
|
|
856
|
+
const rel = entry.slice(tab + 1);
|
|
857
|
+
if (entry.slice(0, 6) === '160000') {
|
|
858
|
+
gitlinkRels.push(rel); // an unexpanded gitlink — recursed into below, not a source file itself
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
files.add((0, utils_1.normalizePath)(prefix + rel));
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
try {
|
|
865
|
+
ingestStaged((0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-s', '--recurse-submodules'], gitOpts));
|
|
866
|
+
return gitlinkRels;
|
|
867
|
+
}
|
|
868
|
+
catch {
|
|
869
|
+
// Older git: submodule expansion and gitlink mode detection need two passes.
|
|
870
|
+
const cached = (0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-c', '--recurse-submodules'], gitOpts);
|
|
871
|
+
for (const rel of cached.split('\0')) {
|
|
872
|
+
if (rel)
|
|
873
|
+
files.add((0, utils_1.normalizePath)(prefix + rel));
|
|
874
|
+
}
|
|
875
|
+
ingestStaged((0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-s'], gitOpts));
|
|
876
|
+
return gitlinkRels;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
/** Unexpanded gitlink paths (mode 160000) from `git ls-files --stage`. */
|
|
880
|
+
function listGitlinkRels(gitOpts) {
|
|
881
|
+
const rels = [];
|
|
882
|
+
const ingest = (staged) => {
|
|
883
|
+
for (const entry of staged.split('\0')) {
|
|
884
|
+
if (!entry || entry.slice(0, 6) !== '160000')
|
|
885
|
+
continue;
|
|
886
|
+
const tab = entry.indexOf('\t');
|
|
887
|
+
if (tab === -1)
|
|
888
|
+
continue;
|
|
889
|
+
rels.push(entry.slice(tab + 1));
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
try {
|
|
893
|
+
ingest((0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-s', '--recurse-submodules'], gitOpts));
|
|
894
|
+
}
|
|
895
|
+
catch {
|
|
896
|
+
ingest((0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-s'], gitOpts));
|
|
897
|
+
}
|
|
898
|
+
return rels;
|
|
899
|
+
}
|
|
531
900
|
function collectGitFiles(repoDir, prefix, files, embeddedRoots, includeIgnored = null) {
|
|
532
901
|
const gitOpts = { cwd: repoDir, encoding: 'utf-8', timeout: 30000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true };
|
|
533
902
|
// Tracked files. --recurse-submodules pulls in files from active submodules,
|
|
@@ -535,15 +904,24 @@ function collectGitFiles(repoDir, prefix, files, embeddedRoots, includeIgnored =
|
|
|
535
904
|
// Without this, monorepos using submodules index 0 files. (See issue #147.)
|
|
536
905
|
// Note: --recurse-submodules only supports -c/--cached and --stage modes — it
|
|
537
906
|
// can't be combined with -o, so untracked files are gathered separately below.
|
|
907
|
+
//
|
|
908
|
+
// We use --stage (-s) rather than -c so each entry carries its file mode. That
|
|
909
|
+
// lets us spot gitlink entries (mode 160000) that --recurse-submodules did NOT
|
|
910
|
+
// expand: a nested repo `git add`ed without a `.gitmodules` entry, or a
|
|
911
|
+
// submodule that isn't active/initialized in this checkout. Such a gitlink
|
|
912
|
+
// falls through every pass — it's tracked, so the untracked `-o` listing below
|
|
913
|
+
// never reports it, and --recurse-submodules only expands ACTIVE submodules —
|
|
914
|
+
// so its source would be silently skipped, leaving only the super-repo's own
|
|
915
|
+
// files indexed. We collect those gitlinks here and recurse into them below.
|
|
916
|
+
// (An active submodule is expanded inline by --recurse-submodules and so never
|
|
917
|
+
// surfaces as a 160000 entry — only the unhandled gitlinks do.) (#1031, #1033)
|
|
918
|
+
//
|
|
538
919
|
// -z gives NUL-separated, unquoted output so non-ASCII (e.g. CJK) paths
|
|
539
920
|
// survive verbatim. Without it git octal-escapes and double-quotes such paths
|
|
540
921
|
// (the core.quotepath default), and the quoted form never matches a real file
|
|
541
|
-
// on disk → those files are silently dropped from the index. (#541)
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
if (rel)
|
|
545
|
-
files.add((0, utils_1.normalizePath)(prefix + rel));
|
|
546
|
-
}
|
|
922
|
+
// on disk → those files are silently dropped from the index. (#541) With -s the
|
|
923
|
+
// path follows a TAB after the `<mode> <object> <stage>` prefix.
|
|
924
|
+
const gitlinkRels = listTrackedGitFiles(gitOpts, prefix, files);
|
|
547
925
|
// Untracked files (submodules manage their own untracked state). Embedded git
|
|
548
926
|
// repos surface here as a single "subdir/" entry that git refuses to descend
|
|
549
927
|
// into — recurse into those as their own repos so their source gets indexed.
|
|
@@ -568,6 +946,32 @@ function collectGitFiles(repoDir, prefix, files, embeddedRoots, includeIgnored =
|
|
|
568
946
|
}
|
|
569
947
|
files.add((0, utils_1.normalizePath)(prefix + rel));
|
|
570
948
|
}
|
|
949
|
+
// Gitlink entries (mode 160000) that --recurse-submodules left unexpanded —
|
|
950
|
+
// an embedded repo `git add`ed without `.gitmodules`, or a submodule not
|
|
951
|
+
// active/initialized in this checkout. When such a gitlink has a real working
|
|
952
|
+
// tree on disk it is distinct first-party code we must index as its own
|
|
953
|
+
// embedded repo: the tracked pass skipped its contents and the untracked pass
|
|
954
|
+
// never sees it (it's tracked, not "other"). A gitlink with no checkout on disk
|
|
955
|
+
// (an uninitialized submodule — empty dir, no `.git`) has nothing to index and
|
|
956
|
+
// is left alone, as is a submodule worktree (a duplicate view, #945). (#1031, #1033)
|
|
957
|
+
if (gitlinkRels.length > 0) {
|
|
958
|
+
const defaults = defaultsOnlyIgnore();
|
|
959
|
+
const repoIgnore = buildDefaultIgnore(repoDir);
|
|
960
|
+
for (const rel of gitlinkRels) {
|
|
961
|
+
const relDir = rel.endsWith('/') ? rel : rel + '/';
|
|
962
|
+
// A gitlink under a gitignored path is respected (not indexed) unless the
|
|
963
|
+
// project opted it in via `includeIgnored` — keep tracked gitlinks under
|
|
964
|
+
// the same scope rule as the untracked-ignored kind below (#1065).
|
|
965
|
+
if (gitlinkEmbeddedRepoSkipped(relDir, prefix, defaults, repoIgnore, includeIgnored))
|
|
966
|
+
continue;
|
|
967
|
+
const childDir = path.join(repoDir, rel);
|
|
968
|
+
// 'embedded' = a real .git checkout on disk; 'worktree' and 'none' are skipped.
|
|
969
|
+
if (classifyGitDir(childDir) !== 'embedded')
|
|
970
|
+
continue;
|
|
971
|
+
embeddedRoots?.add((0, utils_1.normalizePath)(prefix + relDir));
|
|
972
|
+
collectGitFiles(childDir, prefix + relDir, files, embeddedRoots, includeIgnored);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
571
975
|
// Embedded repos hidden by THIS repo's ignore rules (`/packages/` in a
|
|
572
976
|
// super-repo .gitignore) never appear in any listing above. By default they
|
|
573
977
|
// stay hidden — `.gitignore` is respected (#970, #976). They are recursed into
|
|
@@ -611,7 +1015,11 @@ function getGitVisibleFiles(rootDir) {
|
|
|
611
1015
|
// not the parent's: the parent's .gitignore hides the child repo from git,
|
|
612
1016
|
// not from the index. (#514)
|
|
613
1017
|
const ig = buildScopeIgnore(rootDir, embeddedRoots);
|
|
614
|
-
|
|
1018
|
+
const visible = new Set([...files].filter((f) => !ig.ignores(f)));
|
|
1019
|
+
// Force-include first-party source whitelisted in `homegraph.json` `include`.
|
|
1020
|
+
for (const f of collectIncludedFilesForRoot(rootDir))
|
|
1021
|
+
visible.add(f);
|
|
1022
|
+
return visible;
|
|
615
1023
|
}
|
|
616
1024
|
catch {
|
|
617
1025
|
return null;
|
|
@@ -637,14 +1045,14 @@ function getGitChangedFiles(rootDir) {
|
|
|
637
1045
|
// Custom extension → language overrides from the project's homegraph.json,
|
|
638
1046
|
// so change detection sees the same custom-extension files the full index does.
|
|
639
1047
|
const overrides = (0, project_config_1.loadExtensionOverrides)(rootDir);
|
|
640
|
-
collectGitStatus(rootDir, '', changes, overrides, loadIncludeIgnoredMatcher(rootDir));
|
|
1048
|
+
collectGitStatus(rootDir, '', changes, overrides, loadIncludeIgnoredMatcher(rootDir), loadExcludeMatcher(rootDir));
|
|
641
1049
|
return changes;
|
|
642
1050
|
}
|
|
643
1051
|
catch {
|
|
644
1052
|
return null;
|
|
645
1053
|
}
|
|
646
1054
|
}
|
|
647
|
-
function collectGitStatus(repoDir, prefix, out, overrides, includeIgnored = null) {
|
|
1055
|
+
function collectGitStatus(repoDir, prefix, out, overrides, includeIgnored = null, exclude = null) {
|
|
648
1056
|
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
1057
|
// This repo's own ignore rules — built-in defaults (#407) plus its .gitignore.
|
|
650
1058
|
// Change detection must exclude the SAME files the full index does, but git
|
|
@@ -683,6 +1091,12 @@ function collectGitStatus(repoDir, prefix, out, overrides, includeIgnored = null
|
|
|
683
1091
|
// index — match against the repo-relative path, same as the full scan. (#766)
|
|
684
1092
|
if (ig.ignores(rel))
|
|
685
1093
|
continue;
|
|
1094
|
+
// User `codegraph.json` `exclude` (#999) is project-root-relative, so it's
|
|
1095
|
+
// matched against the full path — sync must not re-add a tracked file the
|
|
1096
|
+
// full index now keeps out. Deletions above stay unfiltered so a file that
|
|
1097
|
+
// WAS indexed before an exclude was added still cleans itself out.
|
|
1098
|
+
if (exclude && exclude.ignores(filePath))
|
|
1099
|
+
continue;
|
|
686
1100
|
if (statusCode === '??') {
|
|
687
1101
|
out.added.push(filePath);
|
|
688
1102
|
}
|
|
@@ -697,11 +1111,11 @@ function collectGitStatus(repoDir, prefix, out, overrides, includeIgnored = null
|
|
|
697
1111
|
// and they are left alone (#970, #976), mirroring the full-index scan.
|
|
698
1112
|
for (const rel of untrackedDirs) {
|
|
699
1113
|
for (const repoRel of findNestedGitRepos(path.join(repoDir, rel), rel)) {
|
|
700
|
-
collectGitStatus(path.join(repoDir, repoRel), prefix + repoRel, out, overrides, includeIgnored);
|
|
1114
|
+
collectGitStatus(path.join(repoDir, repoRel), prefix + repoRel, out, overrides, includeIgnored, exclude);
|
|
701
1115
|
}
|
|
702
1116
|
}
|
|
703
1117
|
for (const rel of findIgnoredEmbeddedRepos(repoDir, includeIgnored, prefix)) {
|
|
704
|
-
collectGitStatus(path.join(repoDir, rel), prefix + rel, out, overrides, includeIgnored);
|
|
1118
|
+
collectGitStatus(path.join(repoDir, rel), prefix + rel, out, overrides, includeIgnored, exclude);
|
|
705
1119
|
}
|
|
706
1120
|
}
|
|
707
1121
|
/**
|
|
@@ -860,9 +1274,60 @@ function scanDirectoryWalk(rootDir, onProgress) {
|
|
|
860
1274
|
}
|
|
861
1275
|
// Seed a base matcher with the built-in default ignores (merged with the root
|
|
862
1276
|
// .gitignore so a negation can override). Nested .gitignores still layer per-dir.
|
|
863
|
-
|
|
1277
|
+
const baseMatchers = [{ dir: rootDir, ig: buildDefaultIgnore(rootDir) }];
|
|
1278
|
+
// Project `codegraph.json` `exclude` patterns (#999), rooted at the project so
|
|
1279
|
+
// `isIgnored` matches them against root-relative paths — same coverage the
|
|
1280
|
+
// git path gets via ScopeIgnore, for non-git projects.
|
|
1281
|
+
const exclude = loadExcludeMatcher(rootDir);
|
|
1282
|
+
if (exclude)
|
|
1283
|
+
baseMatchers.push({ dir: rootDir, ig: exclude });
|
|
1284
|
+
walk(rootDir, baseMatchers);
|
|
1285
|
+
// Force-include first-party source whitelisted in `homegraph.json` `include`.
|
|
1286
|
+
const included = collectIncludedFilesForRoot(rootDir);
|
|
1287
|
+
if (included.size > 0) {
|
|
1288
|
+
const seen = new Set(files);
|
|
1289
|
+
for (const f of included) {
|
|
1290
|
+
if (!seen.has(f)) {
|
|
1291
|
+
files.push(f);
|
|
1292
|
+
seen.add(f);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
864
1296
|
return files;
|
|
865
1297
|
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Extraction orchestrator
|
|
1300
|
+
*/
|
|
1301
|
+
/**
|
|
1302
|
+
* Resurrect a resolution edge that is about to be dropped (its target symbol
|
|
1303
|
+
* was removed, renamed, or its whole file deleted) as the ORIGINAL unresolved
|
|
1304
|
+
* reference that created it, read from the refName/refKind stamp
|
|
1305
|
+
* `createEdges` writes into edge metadata. Inserted as status='pending', the
|
|
1306
|
+
* ref is consumed by the same sync's resolution sweep: it rebinds to an
|
|
1307
|
+
* alternative definition if one exists, or parks as status='failed' where the
|
|
1308
|
+
* #1240 retry finds it if the symbol later reappears.
|
|
1309
|
+
*
|
|
1310
|
+
* Returns null — drop silently, the pre-#1240 behavior — for edges without a
|
|
1311
|
+
* refName stamp (created before the stamp existed, or synthesized): rebuilding
|
|
1312
|
+
* a ref from the target's plain node name would strip the receiver/qualifier
|
|
1313
|
+
* context the original text carried (`h.greet` → `greet`) and could rebind
|
|
1314
|
+
* somewhere a full re-index never would. Silent beats wrong.
|
|
1315
|
+
*/
|
|
1316
|
+
function resurrectRefFromDroppedEdge(e) {
|
|
1317
|
+
const refName = e.metadata?.refName;
|
|
1318
|
+
if (typeof refName !== 'string' || refName.length === 0)
|
|
1319
|
+
return null;
|
|
1320
|
+
const refKind = typeof e.metadata?.refKind === 'string' ? e.metadata.refKind : e.kind;
|
|
1321
|
+
return {
|
|
1322
|
+
fromNodeId: e.source,
|
|
1323
|
+
referenceName: refName,
|
|
1324
|
+
referenceKind: refKind,
|
|
1325
|
+
line: e.line ?? 0,
|
|
1326
|
+
column: e.column ?? 0,
|
|
1327
|
+
filePath: e.sourceFilePath,
|
|
1328
|
+
language: e.sourceLanguage,
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
866
1331
|
/**
|
|
867
1332
|
* Extraction orchestrator
|
|
868
1333
|
*/
|
|
@@ -992,268 +1457,101 @@ class ExtractionOrchestrator {
|
|
|
992
1457
|
// between runs is picked up without restarting the process.
|
|
993
1458
|
this.detectedFrameworkNames = null;
|
|
994
1459
|
const frameworkNames = this.ensureDetectedFrameworks(files);
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
errors: [{ message: 'Aborted', severity: 'error' }],
|
|
1004
|
-
durationMs: Date.now() - startTime,
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
// Phase 2: Parse files in a worker thread (keeps main thread unblocked for UI)
|
|
1008
|
-
const total = files.length;
|
|
1009
|
-
let processed = 0;
|
|
1010
|
-
// Emit parsing phase immediately so the progress bar appears during worker setup.
|
|
1011
|
-
// The yield lets the shimmer worker flush the phase transition to stdout before
|
|
1012
|
-
// the main thread starts synchronous grammar detection work.
|
|
1013
|
-
onProgress?.({
|
|
1014
|
-
phase: 'parsing',
|
|
1015
|
-
current: 0,
|
|
1016
|
-
total,
|
|
1017
|
-
});
|
|
1018
|
-
await new Promise(resolve => setImmediate(resolve));
|
|
1019
|
-
// Detect needed languages and load grammars in the parse worker
|
|
1020
|
-
const neededLanguages = [...new Set(files.map((f) => (0, grammars_1.detectLanguage)(f, undefined, overrides)))];
|
|
1021
|
-
// .h files default to 'c' but may be C++ — ensure cpp grammar is loaded when c is needed
|
|
1022
|
-
if (neededLanguages.includes('c') && !neededLanguages.includes('cpp')) {
|
|
1023
|
-
neededLanguages.push('cpp');
|
|
1024
|
-
}
|
|
1025
|
-
// Try to use a worker thread for parsing (keeps main thread unblocked for UI).
|
|
1026
|
-
// Falls back to in-process parsing if the compiled worker is unavailable (e.g. tests).
|
|
1027
|
-
const parseWorkerPath = path.join(__dirname, 'parse-worker.js');
|
|
1028
|
-
const useWorker = fs.existsSync(parseWorkerPath);
|
|
1029
|
-
let WorkerClass = null;
|
|
1030
|
-
if (useWorker) {
|
|
1031
|
-
const { Worker } = await Promise.resolve().then(() => __importStar(require('worker_threads')));
|
|
1032
|
-
WorkerClass = Worker;
|
|
1033
|
-
}
|
|
1034
|
-
else {
|
|
1035
|
-
// In-process fallback: load grammars locally
|
|
1036
|
-
await (0, grammars_1.loadGrammarsForLanguages)(neededLanguages);
|
|
1037
|
-
}
|
|
1038
|
-
// --- Worker lifecycle management ---
|
|
1039
|
-
// The worker can crash (OOM in WASM) or hang on pathological files.
|
|
1040
|
-
// We track pending parse promises and handle both cases:
|
|
1041
|
-
// - Timeout: terminate + restart the worker, reject the timed-out request
|
|
1042
|
-
// - Crash: reject all pending promises, restart for remaining files
|
|
1043
|
-
let parseWorker = null;
|
|
1044
|
-
let nextId = 0;
|
|
1045
|
-
let workerParseCount = 0;
|
|
1046
|
-
const pendingParses = new Map();
|
|
1047
|
-
function rejectAllPending(reason) {
|
|
1048
|
-
for (const [id, pending] of pendingParses) {
|
|
1049
|
-
clearTimeout(pending.timer);
|
|
1050
|
-
pendingParses.delete(id);
|
|
1051
|
-
pending.reject(new Error(reason));
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
function attachWorkerHandlers(w) {
|
|
1055
|
-
w.on('message', (msg) => {
|
|
1056
|
-
if (msg.type === 'parse-result' && msg.id !== undefined) {
|
|
1057
|
-
const pending = pendingParses.get(msg.id);
|
|
1058
|
-
if (pending) {
|
|
1059
|
-
clearTimeout(pending.timer);
|
|
1060
|
-
pendingParses.delete(msg.id);
|
|
1061
|
-
pending.resolve(msg.result);
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
});
|
|
1065
|
-
w.on('error', (err) => {
|
|
1066
|
-
(0, errors_1.logWarn)('Parse worker error', { error: err.message });
|
|
1067
|
-
rejectAllPending(`Worker error: ${err.message}`);
|
|
1068
|
-
});
|
|
1069
|
-
w.on('exit', (code) => {
|
|
1070
|
-
if (code !== 0 && pendingParses.size > 0) {
|
|
1071
|
-
(0, errors_1.logWarn)('Parse worker exited unexpectedly', { code });
|
|
1072
|
-
rejectAllPending(`Worker exited with code ${code}`);
|
|
1073
|
-
}
|
|
1074
|
-
// Clear reference so we know to respawn, reset count so
|
|
1075
|
-
// the fresh worker gets a full cycle before recycling.
|
|
1076
|
-
if (parseWorker === w) {
|
|
1077
|
-
parseWorker = null;
|
|
1078
|
-
workerParseCount = 0;
|
|
1079
|
-
}
|
|
1080
|
-
});
|
|
1081
|
-
}
|
|
1082
|
-
async function ensureWorker() {
|
|
1083
|
-
if (parseWorker)
|
|
1084
|
-
return parseWorker;
|
|
1085
|
-
log('Spawning new parse worker...');
|
|
1086
|
-
parseWorker = new WorkerClass(parseWorkerPath);
|
|
1087
|
-
attachWorkerHandlers(parseWorker);
|
|
1088
|
-
// Load grammars in the new worker
|
|
1089
|
-
await new Promise((resolve, reject) => {
|
|
1090
|
-
parseWorker.once('message', (msg) => {
|
|
1091
|
-
if (msg.type === 'grammars-loaded')
|
|
1092
|
-
resolve();
|
|
1093
|
-
else
|
|
1094
|
-
reject(new Error(`Unexpected message: ${msg.type}`));
|
|
1095
|
-
});
|
|
1096
|
-
parseWorker.postMessage({ type: 'load-grammars', languages: neededLanguages });
|
|
1097
|
-
});
|
|
1098
|
-
return parseWorker;
|
|
1099
|
-
}
|
|
1100
|
-
if (WorkerClass) {
|
|
1101
|
-
await ensureWorker();
|
|
1102
|
-
}
|
|
1103
|
-
/**
|
|
1104
|
-
* Recycle the worker thread to reclaim WASM memory.
|
|
1105
|
-
* Terminates the current worker and clears the reference so
|
|
1106
|
-
* ensureWorker() will spawn a fresh one on the next call.
|
|
1107
|
-
*/
|
|
1108
|
-
function recycleWorker() {
|
|
1109
|
-
if (!parseWorker)
|
|
1110
|
-
return;
|
|
1111
|
-
log(`Recycling worker after ${workerParseCount} parses (heap: ${Math.round(process.memoryUsage().rss / 1024 / 1024)}MB RSS)`);
|
|
1112
|
-
const w = parseWorker;
|
|
1113
|
-
parseWorker = null;
|
|
1114
|
-
workerParseCount = 0;
|
|
1115
|
-
// Fire-and-forget: worker.terminate() can hang if WASM is stuck
|
|
1116
|
-
w.terminate().catch(() => { });
|
|
1117
|
-
}
|
|
1118
|
-
async function requestParse(filePath, content) {
|
|
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);
|
|
1123
|
-
if (!WorkerClass || (0, grammars_1.requiresInProcessExtraction)(language)) {
|
|
1124
|
-
// In-process fallback (also required for languages whose extractors
|
|
1125
|
-
// cannot run in a worker thread).
|
|
1126
|
-
return (0, tree_sitter_1.extractFromSource)(filePath, content, language, frameworkNames);
|
|
1127
|
-
}
|
|
1128
|
-
// Recycle the worker before the next parse if we've hit the threshold.
|
|
1129
|
-
// This destroys the WASM linear memory (which can grow but never shrink)
|
|
1130
|
-
// and starts a fresh worker with a clean heap.
|
|
1131
|
-
if (workerParseCount >= WORKER_RECYCLE_INTERVAL) {
|
|
1132
|
-
await recycleWorker();
|
|
1133
|
-
}
|
|
1134
|
-
const worker = await ensureWorker();
|
|
1135
|
-
const id = nextId++;
|
|
1136
|
-
workerParseCount++;
|
|
1137
|
-
// Scale timeout for large files: base 10s + 10s per 100KB
|
|
1138
|
-
const timeoutMs = PARSE_TIMEOUT_MS + Math.floor(content.length / 100_000) * 10_000;
|
|
1139
|
-
return new Promise((resolve, reject) => {
|
|
1140
|
-
const timer = setTimeout(() => {
|
|
1141
|
-
pendingParses.delete(id);
|
|
1142
|
-
log(`TIMEOUT: ${filePath} exceeded ${timeoutMs}ms — killing worker`);
|
|
1143
|
-
// Reject FIRST — worker.terminate() can hang if WASM is stuck
|
|
1144
|
-
parseWorker = null;
|
|
1145
|
-
workerParseCount = 0;
|
|
1146
|
-
reject(new Error(`Parse timed out after ${timeoutMs}ms`));
|
|
1147
|
-
// Fire-and-forget: kill the stuck worker in the background
|
|
1148
|
-
worker.terminate().catch(() => { });
|
|
1149
|
-
}, timeoutMs);
|
|
1150
|
-
pendingParses.set(id, { resolve, reject, timer });
|
|
1151
|
-
worker.postMessage({ type: 'parse', id, filePath, content, frameworkNames, language });
|
|
1460
|
+
(0, arkts_1.resetArkTSBatch)();
|
|
1461
|
+
(0, context_1.setArkTSBatchProgressCallback)((batchProgress) => {
|
|
1462
|
+
onProgress?.({
|
|
1463
|
+
phase: 'arkts-batch',
|
|
1464
|
+
current: batchProgress.current,
|
|
1465
|
+
total: batchProgress.total,
|
|
1466
|
+
currentFile: batchProgress.currentFile,
|
|
1467
|
+
subphase: batchProgress.subphase,
|
|
1152
1468
|
});
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1469
|
+
});
|
|
1470
|
+
try {
|
|
1155
1471
|
if (signal?.aborted) {
|
|
1156
|
-
if (parseWorker)
|
|
1157
|
-
parseWorker.terminate().catch(() => { });
|
|
1158
1472
|
return {
|
|
1159
1473
|
success: false,
|
|
1160
|
-
filesIndexed,
|
|
1161
|
-
filesSkipped,
|
|
1162
|
-
filesErrored,
|
|
1163
|
-
nodesCreated:
|
|
1164
|
-
edgesCreated:
|
|
1165
|
-
errors: [{ message: 'Aborted', severity: 'error' }
|
|
1474
|
+
filesIndexed: 0,
|
|
1475
|
+
filesSkipped: 0,
|
|
1476
|
+
filesErrored: 0,
|
|
1477
|
+
nodesCreated: 0,
|
|
1478
|
+
edgesCreated: 0,
|
|
1479
|
+
errors: [{ message: 'Aborted', severity: 'error' }],
|
|
1166
1480
|
durationMs: Date.now() - startTime,
|
|
1167
1481
|
};
|
|
1168
1482
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
//
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
}
|
|
1205
|
-
// Report progress before parsing (show current file being worked on)
|
|
1206
|
-
onProgress?.({
|
|
1207
|
-
phase: 'parsing',
|
|
1208
|
-
current: processed,
|
|
1209
|
-
total,
|
|
1210
|
-
currentFile: filePath,
|
|
1483
|
+
// Phase 2: Parse files in a worker thread (keeps main thread unblocked for UI)
|
|
1484
|
+
const total = files.length;
|
|
1485
|
+
let processed = 0;
|
|
1486
|
+
// Emit parsing phase immediately so the progress bar appears during worker setup.
|
|
1487
|
+
// The yield lets the shimmer worker flush the phase transition to stdout before
|
|
1488
|
+
// the main thread starts synchronous grammar detection work.
|
|
1489
|
+
onProgress?.({
|
|
1490
|
+
phase: 'parsing',
|
|
1491
|
+
current: 0,
|
|
1492
|
+
total,
|
|
1493
|
+
});
|
|
1494
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
1495
|
+
// Detect needed languages and load grammars in the parse worker
|
|
1496
|
+
const neededLanguages = [...new Set(files.map((f) => (0, grammars_1.detectLanguage)(f, undefined, overrides)))];
|
|
1497
|
+
// .h files default to 'c' but may be C++ — ensure cpp grammar is loaded when c is needed
|
|
1498
|
+
if (neededLanguages.includes('c') && !neededLanguages.includes('cpp')) {
|
|
1499
|
+
neededLanguages.push('cpp');
|
|
1500
|
+
}
|
|
1501
|
+
// Parse files on a pool of worker threads (keeps the main thread free for UI
|
|
1502
|
+
// and uses every core). Falls back to in-process parsing when the compiled
|
|
1503
|
+
// worker is unavailable (e.g. running from source in tests).
|
|
1504
|
+
const parseWorkerPath = path.join(__dirname, 'parse-worker.js');
|
|
1505
|
+
const useWorker = fs.existsSync(parseWorkerPath);
|
|
1506
|
+
let pool = null;
|
|
1507
|
+
if (useWorker) {
|
|
1508
|
+
// CODEGRAPH_PARSE_WORKERS: explicit worker count; 1 = the old single-worker
|
|
1509
|
+
// behaviour (the conservative rollback). Unset → clamp(cores-1, 1, 8).
|
|
1510
|
+
const poolSize = (0, parse_pool_1.resolveParsePoolSize)(process.env.CODEGRAPH_PARSE_WORKERS, os.cpus().length);
|
|
1511
|
+
pool = new parse_pool_1.ParseWorkerPool({
|
|
1512
|
+
languages: neededLanguages,
|
|
1513
|
+
size: poolSize,
|
|
1514
|
+
workerScriptPath: parseWorkerPath,
|
|
1515
|
+
recycleInterval: WORKER_RECYCLE_INTERVAL,
|
|
1516
|
+
parseTimeoutMs: PARSE_TIMEOUT_MS,
|
|
1517
|
+
log,
|
|
1211
1518
|
});
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
if (stats.size > MAX_FILE_SIZE) {
|
|
1229
|
-
processed++;
|
|
1230
|
-
filesSkipped++;
|
|
1231
|
-
errors.push({
|
|
1232
|
-
message: `File exceeds max size (${stats.size} > ${MAX_FILE_SIZE})`,
|
|
1233
|
-
filePath,
|
|
1234
|
-
severity: 'warning',
|
|
1235
|
-
code: 'size_exceeded',
|
|
1236
|
-
});
|
|
1237
|
-
onProgress?.({ phase: 'parsing', current: processed, total });
|
|
1238
|
-
continue;
|
|
1239
|
-
}
|
|
1240
|
-
// Parse in worker thread (main thread stays unblocked).
|
|
1241
|
-
// Wrapped in try/catch to handle worker timeouts and crashes gracefully.
|
|
1242
|
-
let result;
|
|
1243
|
-
try {
|
|
1244
|
-
result = await requestParse(filePath, content);
|
|
1245
|
-
}
|
|
1246
|
-
catch (parseErr) {
|
|
1247
|
-
processed++;
|
|
1248
|
-
filesErrored++;
|
|
1249
|
-
errors.push({
|
|
1250
|
-
message: parseErr instanceof Error ? parseErr.message : String(parseErr),
|
|
1251
|
-
filePath,
|
|
1252
|
-
severity: 'error',
|
|
1253
|
-
code: 'parse_error',
|
|
1254
|
-
});
|
|
1255
|
-
continue;
|
|
1519
|
+
log(`Parse worker pool: ${poolSize} worker(s)`);
|
|
1520
|
+
}
|
|
1521
|
+
else {
|
|
1522
|
+
// In-process fallback: load grammars locally and parse on the main thread.
|
|
1523
|
+
await (0, grammars_1.loadGrammarsForLanguages)(neededLanguages);
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Parse one file: on the pool when available (the promise REJECTS on a worker
|
|
1527
|
+
* crash/timeout — the caller records it and the retry pass re-attempts), or
|
|
1528
|
+
* in-process synchronously as the no-worker fallback. The language is resolved
|
|
1529
|
+
* here on the main thread, where the codegraph.json overrides are loaded.
|
|
1530
|
+
*/
|
|
1531
|
+
const parseFile = (filePath, content) => {
|
|
1532
|
+
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1533
|
+
if (!pool || (0, grammars_1.requiresInProcessExtraction)(language)) {
|
|
1534
|
+
return Promise.resolve((0, tree_sitter_1.extractFromSource)(filePath, content, language, frameworkNames));
|
|
1256
1535
|
}
|
|
1536
|
+
return pool.requestParse({ filePath, content, language, frameworkNames });
|
|
1537
|
+
};
|
|
1538
|
+
// --- Bounded rolling-window dispatch, ordered commit ---
|
|
1539
|
+
// Reads stay batched/parallel; parses run concurrently across the pool; the
|
|
1540
|
+
// SQLite store stays on the main thread (it isn't thread-safe). Crucially we
|
|
1541
|
+
// COMMIT results in original file order, not parse-completion order: the
|
|
1542
|
+
// resolution phase (run after indexing) resolves an ambiguous reference to one
|
|
1543
|
+
// of several same-named candidates by the nodes' DB insertion order, so a
|
|
1544
|
+
// stable commit order keeps the resulting graph deterministic — byte-identical
|
|
1545
|
+
// to the single-worker path — instead of drifting with parse timing. The
|
|
1546
|
+
// `completed` buffer holds at most ~windowSize out-of-order results, so memory
|
|
1547
|
+
// stays bounded.
|
|
1548
|
+
const windowSize = pool ? Math.max(4, pool.size * 2) : 1;
|
|
1549
|
+
const inFlight = new Set();
|
|
1550
|
+
const completed = new Map();
|
|
1551
|
+
let nextSeq = 0; // file-order sequence assigned at dispatch
|
|
1552
|
+
let nextToStore = 0; // cursor: next sequence to commit
|
|
1553
|
+
let aborted = false;
|
|
1554
|
+
const storeResult = (filePath, content, stats, result) => {
|
|
1257
1555
|
processed++;
|
|
1258
1556
|
// Store in database on main thread (SQLite is not thread-safe)
|
|
1259
1557
|
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
@@ -1277,8 +1575,8 @@ class ExtractionOrchestrator {
|
|
|
1277
1575
|
}
|
|
1278
1576
|
else {
|
|
1279
1577
|
// Files with no symbols but no errors (yaml, twig, properties) are
|
|
1280
|
-
// tracked at the file level — count them as indexed so the CLI
|
|
1281
|
-
//
|
|
1578
|
+
// tracked at the file level — count them as indexed so the CLI doesn't
|
|
1579
|
+
// misleadingly report "No files found to index".
|
|
1282
1580
|
const lang = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1283
1581
|
if ((0, grammars_1.isFileLevelOnlyLanguage)(lang)) {
|
|
1284
1582
|
filesIndexed++;
|
|
@@ -1287,102 +1585,230 @@ class ExtractionOrchestrator {
|
|
|
1287
1585
|
filesSkipped++;
|
|
1288
1586
|
}
|
|
1289
1587
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, filePath);
|
|
1319
|
-
if (!fullPath)
|
|
1320
|
-
continue;
|
|
1321
|
-
content = await fsp.readFile(fullPath, 'utf-8');
|
|
1322
|
-
}
|
|
1323
|
-
catch {
|
|
1324
|
-
continue;
|
|
1588
|
+
onProgress?.({ phase: 'parsing', current: processed, total, currentFile: filePath });
|
|
1589
|
+
};
|
|
1590
|
+
const recordParseFailure = (filePath, err) => {
|
|
1591
|
+
processed++;
|
|
1592
|
+
filesErrored++;
|
|
1593
|
+
errors.push({
|
|
1594
|
+
message: err instanceof Error ? err.message : String(err),
|
|
1595
|
+
filePath,
|
|
1596
|
+
severity: 'error',
|
|
1597
|
+
code: 'parse_error',
|
|
1598
|
+
});
|
|
1599
|
+
onProgress?.({ phase: 'parsing', current: processed, total });
|
|
1600
|
+
};
|
|
1601
|
+
// Commit buffered parses to the DB in file order, advancing the cursor over
|
|
1602
|
+
// contiguous completed results. Runs after each parse settles (and once more
|
|
1603
|
+
// after the drain). storeResult / recordParseFailure run here single-threaded,
|
|
1604
|
+
// so shared counters and SQLite writes never race despite parallel parsing.
|
|
1605
|
+
const flushOrdered = () => {
|
|
1606
|
+
if (aborted)
|
|
1607
|
+
return;
|
|
1608
|
+
while (completed.has(nextToStore)) {
|
|
1609
|
+
const item = completed.get(nextToStore);
|
|
1610
|
+
completed.delete(nextToStore);
|
|
1611
|
+
nextToStore++;
|
|
1612
|
+
if (item.ok)
|
|
1613
|
+
storeResult(item.filePath, item.content, item.stats, item.result);
|
|
1614
|
+
else
|
|
1615
|
+
recordParseFailure(item.filePath, item.err);
|
|
1325
1616
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1617
|
+
};
|
|
1618
|
+
// Dispatch one file's parse (parses run concurrently across the pool), tagged
|
|
1619
|
+
// with its file-order sequence so flushOrdered commits results in order. The
|
|
1620
|
+
// backpressure below bounds how far parsing runs ahead of the in-order commit.
|
|
1621
|
+
const feed = async (filePath, content, stats) => {
|
|
1622
|
+
const seq = nextSeq++;
|
|
1623
|
+
const p = (async () => {
|
|
1624
|
+
try {
|
|
1625
|
+
const result = await parseFile(filePath, content);
|
|
1626
|
+
completed.set(seq, { ok: true, filePath, content, stats, result });
|
|
1627
|
+
}
|
|
1628
|
+
catch (parseErr) {
|
|
1629
|
+
completed.set(seq, { ok: false, filePath, err: parseErr });
|
|
1630
|
+
}
|
|
1631
|
+
flushOrdered();
|
|
1632
|
+
})();
|
|
1633
|
+
const tracked = p.finally(() => { inFlight.delete(tracked); });
|
|
1634
|
+
inFlight.add(tracked);
|
|
1635
|
+
// Backpressure on the dispatched-but-not-yet-committed count (in-flight +
|
|
1636
|
+
// buffered), not just in-flight: a slow file sitting at the commit cursor
|
|
1637
|
+
// lets later parses finish and buffer, which would otherwise grow without
|
|
1638
|
+
// bound. Wait for parses to settle (each may advance the cursor) until the
|
|
1639
|
+
// window has room. `inFlight.size > 0` guards against an empty race — the
|
|
1640
|
+
// cursor file is always still in flight when the window is full.
|
|
1641
|
+
while (nextSeq - nextToStore >= windowSize && inFlight.size > 0) {
|
|
1642
|
+
await Promise.race(inFlight);
|
|
1329
1643
|
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1644
|
+
};
|
|
1645
|
+
const firstEtsFile = files.find((f) => (0, grammars_1.detectLanguage)(f, undefined, overrides) === 'arkts');
|
|
1646
|
+
if (firstEtsFile) {
|
|
1647
|
+
await (0, arkts_1.primeArkTSBatch)(this.rootDir, this.queries, firstEtsFile);
|
|
1648
|
+
errors.push(...(0, arkts_1.drainArkTSIndexNotices)());
|
|
1649
|
+
}
|
|
1650
|
+
for (let i = 0; i < files.length; i += FILE_IO_BATCH_SIZE) {
|
|
1651
|
+
if (signal?.aborted) {
|
|
1652
|
+
aborted = true;
|
|
1653
|
+
break;
|
|
1333
1654
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1655
|
+
const batch = files.slice(i, i + FILE_IO_BATCH_SIZE);
|
|
1656
|
+
// Read files in parallel (with path validation before any I/O)
|
|
1657
|
+
const fileContents = await Promise.all(batch.map(async (fp) => {
|
|
1658
|
+
if ((0, arkts_1.isArkTSBatchPersisted)(fp)) {
|
|
1659
|
+
return {
|
|
1660
|
+
filePath: fp,
|
|
1661
|
+
content: null,
|
|
1662
|
+
stats: null,
|
|
1663
|
+
error: null,
|
|
1664
|
+
arktsBatchSkipped: true,
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
try {
|
|
1668
|
+
// Indexing read: follow in-root symlinks the directory walk already
|
|
1669
|
+
// descended into (the `../` guard still applies) so files reached
|
|
1670
|
+
// via an in-root symlink-to-outside still index (#935).
|
|
1671
|
+
const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, fp, { allowSymlinkEscape: true });
|
|
1672
|
+
if (!fullPath) {
|
|
1673
|
+
(0, errors_1.logWarn)('Path traversal blocked in batch reader', { filePath: fp });
|
|
1674
|
+
return { filePath: fp, content: null, stats: null, error: new Error('Path traversal blocked') };
|
|
1675
|
+
}
|
|
1676
|
+
const content = await fsp.readFile(fullPath, 'utf-8');
|
|
1677
|
+
const stats = await fsp.stat(fullPath);
|
|
1678
|
+
return { filePath: fp, content, stats, error: null };
|
|
1679
|
+
}
|
|
1680
|
+
catch (err) {
|
|
1681
|
+
return { filePath: fp, content: null, stats: null, error: err };
|
|
1682
|
+
}
|
|
1683
|
+
}));
|
|
1684
|
+
// Dispatch each readable file into the bounded parse window; the window
|
|
1685
|
+
// stores results on the main thread as they arrive.
|
|
1686
|
+
for (const fileEntry of fileContents) {
|
|
1687
|
+
const { filePath, content, stats, error } = fileEntry;
|
|
1688
|
+
const arktsBatchSkipped = 'arktsBatchSkipped' in fileEntry && fileEntry.arktsBatchSkipped;
|
|
1689
|
+
if (signal?.aborted) {
|
|
1690
|
+
aborted = true;
|
|
1691
|
+
break;
|
|
1692
|
+
}
|
|
1693
|
+
if ((0, arkts_1.isArkTSBatchPersisted)(filePath) || arktsBatchSkipped) {
|
|
1694
|
+
processed++;
|
|
1695
|
+
filesIndexed++;
|
|
1696
|
+
onProgress?.({ phase: 'parsing', current: processed, total, currentFile: filePath });
|
|
1697
|
+
continue;
|
|
1698
|
+
}
|
|
1699
|
+
if (!(0, context_1.isArktsBatchRunning)()) {
|
|
1700
|
+
onProgress?.({
|
|
1701
|
+
phase: 'parsing',
|
|
1702
|
+
current: processed,
|
|
1703
|
+
total,
|
|
1704
|
+
currentFile: filePath,
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
if (error || content === null || stats === null) {
|
|
1708
|
+
processed++;
|
|
1709
|
+
filesErrored++;
|
|
1710
|
+
errors.push({
|
|
1711
|
+
message: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`,
|
|
1712
|
+
filePath,
|
|
1713
|
+
severity: 'error',
|
|
1714
|
+
code: 'read_error',
|
|
1715
|
+
});
|
|
1716
|
+
onProgress?.({ phase: 'parsing', current: processed, total });
|
|
1717
|
+
continue;
|
|
1718
|
+
}
|
|
1719
|
+
// Honour MAX_FILE_SIZE. Without this check, vendored generated
|
|
1720
|
+
// headers, minified bundles, and other multi-MB files get indexed,
|
|
1721
|
+
// wasting WASM heap and the worker recycle budget on inputs with no
|
|
1722
|
+
// useful symbols. The single-file extractFile path already enforces
|
|
1723
|
+
// this; the bulk path used to silently skip the check.
|
|
1724
|
+
if (stats.size > MAX_FILE_SIZE) {
|
|
1725
|
+
processed++;
|
|
1726
|
+
filesSkipped++;
|
|
1727
|
+
errors.push({
|
|
1728
|
+
message: `File exceeds max size (${stats.size} > ${MAX_FILE_SIZE})`,
|
|
1729
|
+
filePath,
|
|
1730
|
+
severity: 'warning',
|
|
1731
|
+
code: 'size_exceeded',
|
|
1732
|
+
});
|
|
1733
|
+
onProgress?.({ phase: 'parsing', current: processed, total });
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
// Parse on the pool (main thread stays unblocked). Errors/timeouts are
|
|
1737
|
+
// handled inside feed() → recordParseFailure, feeding the retry pass.
|
|
1738
|
+
await feed(filePath, content, stats);
|
|
1346
1739
|
}
|
|
1740
|
+
if (aborted)
|
|
1741
|
+
break;
|
|
1742
|
+
}
|
|
1743
|
+
// Drain parses still in flight (skip on abort — we tear down below instead),
|
|
1744
|
+
// then commit any results the cursor hasn't reached yet.
|
|
1745
|
+
if (!aborted) {
|
|
1746
|
+
await Promise.all(inFlight);
|
|
1747
|
+
flushOrdered();
|
|
1748
|
+
}
|
|
1749
|
+
if (signal?.aborted || aborted) {
|
|
1750
|
+
if (pool)
|
|
1751
|
+
await pool.destroy();
|
|
1752
|
+
return {
|
|
1753
|
+
success: false,
|
|
1754
|
+
filesIndexed,
|
|
1755
|
+
filesSkipped,
|
|
1756
|
+
filesErrored,
|
|
1757
|
+
filesDiscovered: total,
|
|
1758
|
+
nodesCreated: totalNodes,
|
|
1759
|
+
edgesCreated: totalEdges,
|
|
1760
|
+
errors: [{ message: 'Aborted', severity: 'error' }, ...errors],
|
|
1761
|
+
durationMs: Date.now() - startTime,
|
|
1762
|
+
};
|
|
1347
1763
|
}
|
|
1348
|
-
//
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1764
|
+
// Report 100% so the progress bar doesn't hang at 99%
|
|
1765
|
+
onProgress?.({
|
|
1766
|
+
phase: 'parsing',
|
|
1767
|
+
current: total,
|
|
1768
|
+
total,
|
|
1769
|
+
});
|
|
1770
|
+
// Yield so the shimmer worker's buffered stdout writes can flush.
|
|
1771
|
+
// Worker thread stdout is proxied through the main thread's event loop,
|
|
1772
|
+
// so synchronous work here blocks the animation from rendering.
|
|
1773
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
1774
|
+
// Retry pass: files that failed due to WASM memory corruption may succeed
|
|
1775
|
+
// on a fresh worker with a clean heap. Recycle before each attempt so
|
|
1776
|
+
// every file gets the absolute cleanest WASM state possible.
|
|
1777
|
+
const retryableErrors = errors.filter((e) => e.code === 'parse_error' && e.filePath &&
|
|
1778
|
+
(e.message.includes('Worker exited') || e.message.includes('memory access out of bounds')));
|
|
1779
|
+
if (retryableErrors.length > 0 && pool) {
|
|
1780
|
+
log(`Retrying ${retryableErrors.length} files that failed due to WASM memory errors...`);
|
|
1781
|
+
// Fresh WASM heaps for the retry phase. A retry that still crashes its
|
|
1782
|
+
// worker makes the pool respawn it, so later retries keep landing on clean
|
|
1783
|
+
// workers too.
|
|
1784
|
+
pool.recycleAll();
|
|
1785
|
+
const stillFailing = [];
|
|
1786
|
+
for (const errEntry of retryableErrors) {
|
|
1355
1787
|
const filePath = errEntry.filePath;
|
|
1356
1788
|
if (signal?.aborted)
|
|
1357
1789
|
break;
|
|
1358
|
-
|
|
1359
|
-
let fullContent;
|
|
1790
|
+
let content;
|
|
1360
1791
|
try {
|
|
1361
1792
|
const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, filePath);
|
|
1362
1793
|
if (!fullPath)
|
|
1363
1794
|
continue;
|
|
1364
|
-
|
|
1795
|
+
content = await fsp.readFile(fullPath, 'utf-8');
|
|
1365
1796
|
}
|
|
1366
1797
|
catch {
|
|
1367
1798
|
continue;
|
|
1368
1799
|
}
|
|
1369
|
-
// Strip lines that are entirely comments (preserving line numbers
|
|
1370
|
-
// by replacing with empty lines so node positions stay correct)
|
|
1371
|
-
const stripped = fullContent
|
|
1372
|
-
.split('\n')
|
|
1373
|
-
.map(line => /^\s*\/\//.test(line) ? '' : line)
|
|
1374
|
-
.join('\n');
|
|
1375
1800
|
let result;
|
|
1376
1801
|
try {
|
|
1377
|
-
result = await
|
|
1802
|
+
result = await parseFile(filePath, content);
|
|
1378
1803
|
}
|
|
1379
1804
|
catch {
|
|
1805
|
+
stillFailing.push(errEntry);
|
|
1380
1806
|
continue;
|
|
1381
1807
|
}
|
|
1382
1808
|
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
1383
|
-
const language = (0, grammars_1.detectLanguage)(filePath,
|
|
1809
|
+
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1384
1810
|
const stats = await fsp.stat(path.join(this.rootDir, filePath));
|
|
1385
|
-
this.storeExtractionResult(filePath,
|
|
1811
|
+
this.storeExtractionResult(filePath, content, language, stats, result);
|
|
1386
1812
|
const idx = errors.indexOf(errEntry);
|
|
1387
1813
|
if (idx >= 0)
|
|
1388
1814
|
errors.splice(idx, 1);
|
|
@@ -1390,26 +1816,77 @@ class ExtractionOrchestrator {
|
|
|
1390
1816
|
filesIndexed++;
|
|
1391
1817
|
totalNodes += result.nodes.length;
|
|
1392
1818
|
totalEdges += result.edges.length;
|
|
1393
|
-
log(`Retry
|
|
1819
|
+
log(`Retry OK: ${filePath} (${result.nodes.length} nodes)`);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
// Last resort: for files that still crash on a clean worker, strip
|
|
1823
|
+
// comment-only lines to reduce WASM memory pressure. Many compiler
|
|
1824
|
+
// test files are 90%+ comments (CHECK directives) that don't contribute
|
|
1825
|
+
// code nodes but consume parser memory.
|
|
1826
|
+
if (stillFailing.length > 0) {
|
|
1827
|
+
log(`${stillFailing.length} files still failing — retrying with comments stripped...`);
|
|
1828
|
+
pool.recycleAll();
|
|
1829
|
+
for (const errEntry of stillFailing) {
|
|
1830
|
+
const filePath = errEntry.filePath;
|
|
1831
|
+
if (signal?.aborted)
|
|
1832
|
+
break;
|
|
1833
|
+
let fullContent;
|
|
1834
|
+
try {
|
|
1835
|
+
const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, filePath);
|
|
1836
|
+
if (!fullPath)
|
|
1837
|
+
continue;
|
|
1838
|
+
fullContent = await fsp.readFile(fullPath, 'utf-8');
|
|
1839
|
+
}
|
|
1840
|
+
catch {
|
|
1841
|
+
continue;
|
|
1842
|
+
}
|
|
1843
|
+
// Strip lines that are entirely comments (preserving line numbers
|
|
1844
|
+
// by replacing with empty lines so node positions stay correct)
|
|
1845
|
+
const stripped = fullContent
|
|
1846
|
+
.split('\n')
|
|
1847
|
+
.map(line => /^\s*\/\//.test(line) ? '' : line)
|
|
1848
|
+
.join('\n');
|
|
1849
|
+
let result;
|
|
1850
|
+
try {
|
|
1851
|
+
result = await parseFile(filePath, stripped);
|
|
1852
|
+
}
|
|
1853
|
+
catch {
|
|
1854
|
+
continue;
|
|
1855
|
+
}
|
|
1856
|
+
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
1857
|
+
const language = (0, grammars_1.detectLanguage)(filePath, fullContent, overrides);
|
|
1858
|
+
const stats = await fsp.stat(path.join(this.rootDir, filePath));
|
|
1859
|
+
this.storeExtractionResult(filePath, fullContent, language, stats, result);
|
|
1860
|
+
const idx = errors.indexOf(errEntry);
|
|
1861
|
+
if (idx >= 0)
|
|
1862
|
+
errors.splice(idx, 1);
|
|
1863
|
+
filesErrored--;
|
|
1864
|
+
filesIndexed++;
|
|
1865
|
+
totalNodes += result.nodes.length;
|
|
1866
|
+
totalEdges += result.edges.length;
|
|
1867
|
+
log(`Retry (stripped) OK: ${filePath} (${result.nodes.length} nodes)`);
|
|
1868
|
+
}
|
|
1394
1869
|
}
|
|
1395
1870
|
}
|
|
1396
1871
|
}
|
|
1872
|
+
// Shut down the parse worker pool.
|
|
1873
|
+
if (pool)
|
|
1874
|
+
await pool.destroy();
|
|
1875
|
+
return {
|
|
1876
|
+
success: filesIndexed > 0 || errors.filter((e) => e.severity === 'error').length === 0,
|
|
1877
|
+
filesIndexed,
|
|
1878
|
+
filesSkipped,
|
|
1879
|
+
filesErrored,
|
|
1880
|
+
filesDiscovered: total,
|
|
1881
|
+
nodesCreated: totalNodes,
|
|
1882
|
+
edgesCreated: totalEdges,
|
|
1883
|
+
errors,
|
|
1884
|
+
durationMs: Date.now() - startTime,
|
|
1885
|
+
};
|
|
1397
1886
|
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
if (parseWorker) {
|
|
1401
|
-
parseWorker.terminate().catch(() => { });
|
|
1887
|
+
finally {
|
|
1888
|
+
(0, context_1.setArkTSBatchProgressCallback)(null);
|
|
1402
1889
|
}
|
|
1403
|
-
return {
|
|
1404
|
-
success: filesIndexed > 0 || errors.filter((e) => e.severity === 'error').length === 0,
|
|
1405
|
-
filesIndexed,
|
|
1406
|
-
filesSkipped,
|
|
1407
|
-
filesErrored,
|
|
1408
|
-
nodesCreated: totalNodes,
|
|
1409
|
-
edgesCreated: totalEdges,
|
|
1410
|
-
errors,
|
|
1411
|
-
durationMs: Date.now() - startTime,
|
|
1412
|
-
};
|
|
1413
1890
|
}
|
|
1414
1891
|
/**
|
|
1415
1892
|
* Index specific files
|
|
@@ -1532,6 +2009,15 @@ class ExtractionOrchestrator {
|
|
|
1532
2009
|
}
|
|
1533
2010
|
// Detect language (honoring the project's homegraph.json extension overrides)
|
|
1534
2011
|
const language = (0, grammars_1.detectLanguage)(relativePath, content, (0, project_config_1.loadExtensionOverrides)(this.rootDir));
|
|
2012
|
+
if ((0, arkts_1.isArkTSBatchPersisted)(relativePath)) {
|
|
2013
|
+
return {
|
|
2014
|
+
nodes: [],
|
|
2015
|
+
edges: [],
|
|
2016
|
+
unresolvedReferences: [],
|
|
2017
|
+
errors: [],
|
|
2018
|
+
durationMs: 0,
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
1535
2021
|
if (!(0, grammars_1.isLanguageSupported)(language)) {
|
|
1536
2022
|
return {
|
|
1537
2023
|
nodes: [],
|
|
@@ -1616,15 +2102,24 @@ class ExtractionOrchestrator {
|
|
|
1616
2102
|
newNodesByKindName.set(`${n.kind}\0${n.name}`, n.id);
|
|
1617
2103
|
}
|
|
1618
2104
|
const reinserted = [];
|
|
2105
|
+
const resurrected = [];
|
|
1619
2106
|
for (const e of crossFileIncomingEdges) {
|
|
1620
2107
|
const newTargetId = newNodesByKindName.get(`${e.targetKind}\0${e.targetName}`);
|
|
1621
2108
|
if (newTargetId) {
|
|
1622
2109
|
reinserted.push({ source: e.source, target: newTargetId, kind: e.kind, metadata: e.metadata, line: e.line, column: e.column, provenance: e.provenance });
|
|
1623
2110
|
}
|
|
2111
|
+
else {
|
|
2112
|
+
const ref = resurrectRefFromDroppedEdge(e);
|
|
2113
|
+
if (ref)
|
|
2114
|
+
resurrected.push(ref);
|
|
2115
|
+
}
|
|
1624
2116
|
}
|
|
1625
2117
|
if (reinserted.length > 0) {
|
|
1626
2118
|
this.queries.insertEdges(reinserted);
|
|
1627
2119
|
}
|
|
2120
|
+
if (resurrected.length > 0) {
|
|
2121
|
+
this.queries.insertUnresolvedRefsBatch(resurrected);
|
|
2122
|
+
}
|
|
1628
2123
|
}
|
|
1629
2124
|
// Insert unresolved references in batch with denormalized filePath/language
|
|
1630
2125
|
if (result.unresolvedReferences.length > 0) {
|
|
@@ -1701,6 +2196,17 @@ class ExtractionOrchestrator {
|
|
|
1701
2196
|
let reconcileChecks = 0;
|
|
1702
2197
|
for (const tracked of trackedFiles) {
|
|
1703
2198
|
if (!currentSet.has(tracked.path) || !fs.existsSync(path.join(this.rootDir, tracked.path))) {
|
|
2199
|
+
// Before the cascade deletes them, resurrect incoming cross-file
|
|
2200
|
+
// resolution edges as their original refs (#1240 removal case).
|
|
2201
|
+
const incoming = this.queries.getCrossFileIncomingEdgesWithTarget(tracked.path);
|
|
2202
|
+
if (incoming.length > 0) {
|
|
2203
|
+
const resurrected = incoming
|
|
2204
|
+
.map((e) => resurrectRefFromDroppedEdge(e))
|
|
2205
|
+
.filter((r) => r !== null);
|
|
2206
|
+
if (resurrected.length > 0) {
|
|
2207
|
+
this.queries.insertUnresolvedRefsBatch(resurrected);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
1704
2210
|
this.queries.deleteFile(tracked.path);
|
|
1705
2211
|
filesRemoved++;
|
|
1706
2212
|
}
|
|
@@ -1714,6 +2220,11 @@ class ExtractionOrchestrator {
|
|
|
1714
2220
|
// synchronous-stat loop that wedges the main thread on a large repo (#905).
|
|
1715
2221
|
// Yield at the top of the body so the `continue` fast-paths below still hit it.
|
|
1716
2222
|
if (++reconcileChecks % SYNC_RECONCILE_YIELD_INTERVAL === 0) {
|
|
2223
|
+
onProgress?.({
|
|
2224
|
+
phase: 'scanning',
|
|
2225
|
+
current: reconcileChecks,
|
|
2226
|
+
total: currentFiles.length,
|
|
2227
|
+
});
|
|
1717
2228
|
await new Promise((resolve) => setImmediate(resolve));
|
|
1718
2229
|
}
|
|
1719
2230
|
const fullPath = path.join(this.rootDir, filePath);
|
|
@@ -1768,16 +2279,47 @@ class ExtractionOrchestrator {
|
|
|
1768
2279
|
}
|
|
1769
2280
|
// Index changed files
|
|
1770
2281
|
const total = filesToIndex.length;
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
2282
|
+
const overrides = (0, project_config_1.loadExtensionOverrides)(this.rootDir);
|
|
2283
|
+
const needsArkTS = filesToIndex.some((f) => (0, grammars_1.detectLanguage)(f, undefined, overrides) === 'arkts');
|
|
2284
|
+
if (needsArkTS) {
|
|
2285
|
+
(0, context_1.setArkTSBatchProgressCallback)((batchProgress) => {
|
|
2286
|
+
onProgress?.({
|
|
2287
|
+
phase: 'arkts-batch',
|
|
2288
|
+
current: batchProgress.current,
|
|
2289
|
+
total: batchProgress.total,
|
|
2290
|
+
currentFile: batchProgress.currentFile,
|
|
2291
|
+
subphase: batchProgress.subphase,
|
|
2292
|
+
});
|
|
1778
2293
|
});
|
|
1779
|
-
const
|
|
1780
|
-
|
|
2294
|
+
const firstEts = filesToIndex.find((f) => (0, grammars_1.detectLanguage)(f, undefined, overrides) === 'arkts');
|
|
2295
|
+
await (0, arkts_1.primeArkTSBatch)(this.rootDir, this.queries, firstEts);
|
|
2296
|
+
}
|
|
2297
|
+
try {
|
|
2298
|
+
for (let i = 0; i < filesToIndex.length; i++) {
|
|
2299
|
+
const filePath = filesToIndex[i];
|
|
2300
|
+
if ((0, arkts_1.isArkTSBatchPersisted)(filePath)) {
|
|
2301
|
+
onProgress?.({
|
|
2302
|
+
phase: 'parsing',
|
|
2303
|
+
current: i + 1,
|
|
2304
|
+
total,
|
|
2305
|
+
currentFile: filePath,
|
|
2306
|
+
});
|
|
2307
|
+
continue;
|
|
2308
|
+
}
|
|
2309
|
+
onProgress?.({
|
|
2310
|
+
phase: 'parsing',
|
|
2311
|
+
current: i + 1,
|
|
2312
|
+
total,
|
|
2313
|
+
currentFile: filePath,
|
|
2314
|
+
});
|
|
2315
|
+
const result = await this.indexFile(filePath);
|
|
2316
|
+
nodesUpdated += result.nodes.length;
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
finally {
|
|
2320
|
+
if (needsArkTS) {
|
|
2321
|
+
(0, context_1.setArkTSBatchProgressCallback)(null);
|
|
2322
|
+
}
|
|
1781
2323
|
}
|
|
1782
2324
|
return {
|
|
1783
2325
|
filesChecked,
|