@quolu/lattice 0.70.0 → 0.71.0
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/package.json +1 -1
- package/sensor/dist/bin/lattice-sensor.js +746 -323
- package/sensor/dist/bin/viewer-gate.d.ts +17 -0
- package/sensor/dist/bin/viewer-gate.d.ts.map +1 -0
- package/sensor/dist/bin/viewer-gate.js +31 -0
- package/sensor/dist/bin/viewer-gate.js.map +1 -0
- package/sensor/dist/context/formatter.d.ts +7 -1
- package/sensor/dist/context/formatter.d.ts.map +1 -1
- package/sensor/dist/context/formatter.js +12 -6
- package/sensor/dist/context/formatter.js.map +1 -1
- package/sensor/dist/context/index.d.ts +12 -0
- package/sensor/dist/context/index.d.ts.map +1 -1
- package/sensor/dist/context/index.js +103 -16
- package/sensor/dist/context/index.js.map +1 -1
- package/sensor/dist/db/index.d.ts +23 -2
- package/sensor/dist/db/index.d.ts.map +1 -1
- package/sensor/dist/db/index.js +109 -31
- package/sensor/dist/db/index.js.map +1 -1
- package/sensor/dist/db/migrations.d.ts +1 -1
- package/sensor/dist/db/migrations.d.ts.map +1 -1
- package/sensor/dist/db/migrations.js +68 -1
- package/sensor/dist/db/migrations.js.map +1 -1
- package/sensor/dist/db/queries.d.ts +583 -6
- package/sensor/dist/db/queries.d.ts.map +1 -1
- package/sensor/dist/db/queries.js +1425 -57
- package/sensor/dist/db/queries.js.map +1 -1
- package/sensor/dist/db/schema.sql +27 -4
- package/sensor/dist/db/sqlite-adapter.d.ts +2 -0
- package/sensor/dist/db/sqlite-adapter.d.ts.map +1 -1
- package/sensor/dist/db/sqlite-adapter.js +56 -7
- package/sensor/dist/db/sqlite-adapter.js.map +1 -1
- package/sensor/dist/db/synthesis-stage.d.ts +12 -0
- package/sensor/dist/db/synthesis-stage.d.ts.map +1 -0
- package/sensor/dist/db/synthesis-stage.js +93 -0
- package/sensor/dist/db/synthesis-stage.js.map +1 -0
- package/sensor/dist/db/wal-valve.d.ts +32 -12
- package/sensor/dist/db/wal-valve.d.ts.map +1 -1
- package/sensor/dist/db/wal-valve.js +65 -22
- package/sensor/dist/db/wal-valve.js.map +1 -1
- package/sensor/dist/db/wsl-shared-index.d.ts +53 -0
- package/sensor/dist/db/wsl-shared-index.d.ts.map +1 -0
- package/sensor/dist/db/wsl-shared-index.js +121 -0
- package/sensor/dist/db/wsl-shared-index.js.map +1 -0
- package/sensor/dist/directory.d.ts +126 -2
- package/sensor/dist/directory.d.ts.map +1 -1
- package/sensor/dist/directory.js +285 -16
- package/sensor/dist/directory.js.map +1 -1
- package/sensor/dist/errors.d.ts +14 -0
- package/sensor/dist/errors.d.ts.map +1 -1
- package/sensor/dist/errors.js +16 -1
- package/sensor/dist/errors.js.map +1 -1
- package/sensor/dist/extraction/cfml-extractor.d.ts +1 -0
- package/sensor/dist/extraction/cfml-extractor.d.ts.map +1 -1
- package/sensor/dist/extraction/cfml-extractor.js +15 -3
- package/sensor/dist/extraction/cfml-extractor.js.map +1 -1
- package/sensor/dist/extraction/dfm-extractor.d.ts.map +1 -1
- package/sensor/dist/extraction/dfm-extractor.js +14 -10
- package/sensor/dist/extraction/dfm-extractor.js.map +1 -1
- package/sensor/dist/extraction/extraction-version.d.ts +1 -1
- package/sensor/dist/extraction/extraction-version.d.ts.map +1 -1
- package/sensor/dist/extraction/extraction-version.js +3 -1
- package/sensor/dist/extraction/extraction-version.js.map +1 -1
- package/sensor/dist/extraction/function-ref.d.ts +6 -4
- package/sensor/dist/extraction/function-ref.d.ts.map +1 -1
- package/sensor/dist/extraction/function-ref.js +35 -14
- package/sensor/dist/extraction/function-ref.js.map +1 -1
- package/sensor/dist/extraction/generated-detection.d.ts +48 -12
- package/sensor/dist/extraction/generated-detection.d.ts.map +1 -1
- package/sensor/dist/extraction/generated-detection.js +176 -11
- package/sensor/dist/extraction/generated-detection.js.map +1 -1
- package/sensor/dist/extraction/grammars.d.ts +38 -0
- package/sensor/dist/extraction/grammars.d.ts.map +1 -1
- package/sensor/dist/extraction/grammars.js +115 -3
- package/sensor/dist/extraction/grammars.js.map +1 -1
- package/sensor/dist/extraction/index.d.ts +218 -14
- package/sensor/dist/extraction/index.d.ts.map +1 -1
- package/sensor/dist/extraction/index.js +887 -175
- package/sensor/dist/extraction/index.js.map +1 -1
- package/sensor/dist/extraction/languages/c-cpp.d.ts +58 -0
- package/sensor/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/sensor/dist/extraction/languages/c-cpp.js +310 -19
- package/sensor/dist/extraction/languages/c-cpp.js.map +1 -1
- package/sensor/dist/extraction/languages/dart.d.ts.map +1 -1
- package/sensor/dist/extraction/languages/dart.js +8 -3
- package/sensor/dist/extraction/languages/dart.js.map +1 -1
- package/sensor/dist/extraction/languages/erlang.d.ts.map +1 -1
- package/sensor/dist/extraction/languages/erlang.js +58 -22
- package/sensor/dist/extraction/languages/erlang.js.map +1 -1
- package/sensor/dist/extraction/languages/javascript.js +1 -1
- package/sensor/dist/extraction/languages/javascript.js.map +1 -1
- package/sensor/dist/extraction/languages/kotlin.d.ts.map +1 -1
- package/sensor/dist/extraction/languages/kotlin.js +169 -36
- package/sensor/dist/extraction/languages/kotlin.js.map +1 -1
- package/sensor/dist/extraction/languages/lua.d.ts.map +1 -1
- package/sensor/dist/extraction/languages/lua.js +4 -1
- package/sensor/dist/extraction/languages/lua.js.map +1 -1
- package/sensor/dist/extraction/languages/objc.d.ts.map +1 -1
- package/sensor/dist/extraction/languages/objc.js +4 -0
- package/sensor/dist/extraction/languages/objc.js.map +1 -1
- package/sensor/dist/extraction/languages/python.d.ts.map +1 -1
- package/sensor/dist/extraction/languages/python.js +71 -0
- package/sensor/dist/extraction/languages/python.js.map +1 -1
- package/sensor/dist/extraction/languages/rust.d.ts +20 -0
- package/sensor/dist/extraction/languages/rust.d.ts.map +1 -1
- package/sensor/dist/extraction/languages/rust.js +50 -20
- package/sensor/dist/extraction/languages/rust.js.map +1 -1
- package/sensor/dist/extraction/languages/scala.d.ts.map +1 -1
- package/sensor/dist/extraction/languages/scala.js +18 -3
- package/sensor/dist/extraction/languages/scala.js.map +1 -1
- package/sensor/dist/extraction/languages/swift.d.ts +15 -0
- package/sensor/dist/extraction/languages/swift.d.ts.map +1 -1
- package/sensor/dist/extraction/languages/swift.js +50 -0
- package/sensor/dist/extraction/languages/swift.js.map +1 -1
- package/sensor/dist/extraction/languages/typescript.d.ts.map +1 -1
- package/sensor/dist/extraction/languages/typescript.js +9 -2
- package/sensor/dist/extraction/languages/typescript.js.map +1 -1
- package/sensor/dist/extraction/liquid-extractor.d.ts +18 -0
- package/sensor/dist/extraction/liquid-extractor.d.ts.map +1 -1
- package/sensor/dist/extraction/liquid-extractor.js +98 -22
- package/sensor/dist/extraction/liquid-extractor.js.map +1 -1
- package/sensor/dist/extraction/mybatis-extractor.d.ts.map +1 -1
- package/sensor/dist/extraction/mybatis-extractor.js +6 -4
- package/sensor/dist/extraction/mybatis-extractor.js.map +1 -1
- package/sensor/dist/extraction/parse-pool.d.ts +7 -0
- package/sensor/dist/extraction/parse-pool.d.ts.map +1 -1
- package/sensor/dist/extraction/parse-pool.js +20 -1
- package/sensor/dist/extraction/parse-pool.js.map +1 -1
- package/sensor/dist/extraction/syntax-tokens.d.ts +103 -0
- package/sensor/dist/extraction/syntax-tokens.d.ts.map +1 -0
- package/sensor/dist/extraction/syntax-tokens.js +381 -0
- package/sensor/dist/extraction/syntax-tokens.js.map +1 -0
- package/sensor/dist/extraction/tree-sitter-helpers.d.ts +5 -0
- package/sensor/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
- package/sensor/dist/extraction/tree-sitter-helpers.js +17 -0
- package/sensor/dist/extraction/tree-sitter-helpers.js.map +1 -1
- package/sensor/dist/extraction/tree-sitter-types.d.ts +31 -3
- package/sensor/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/sensor/dist/extraction/tree-sitter.d.ts +101 -18
- package/sensor/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/sensor/dist/extraction/tree-sitter.js +928 -134
- package/sensor/dist/extraction/tree-sitter.js.map +1 -1
- package/sensor/dist/extraction/wasm/tree-sitter-scala.wasm +0 -0
- package/sensor/dist/file-limits.d.ts +40 -0
- package/sensor/dist/file-limits.d.ts.map +1 -0
- package/sensor/dist/file-limits.js +147 -0
- package/sensor/dist/file-limits.js.map +1 -0
- package/sensor/dist/graph/branch-guards.d.ts +265 -0
- package/sensor/dist/graph/branch-guards.d.ts.map +1 -0
- package/sensor/dist/graph/branch-guards.js +2325 -0
- package/sensor/dist/graph/branch-guards.js.map +1 -0
- package/sensor/dist/graph/dead-code.d.ts +251 -0
- package/sensor/dist/graph/dead-code.d.ts.map +1 -0
- package/sensor/dist/graph/dead-code.js +776 -0
- package/sensor/dist/graph/dead-code.js.map +1 -0
- package/sensor/dist/graph/dynamic-boundary-report.d.ts +121 -0
- package/sensor/dist/graph/dynamic-boundary-report.d.ts.map +1 -0
- package/sensor/dist/graph/dynamic-boundary-report.js +283 -0
- package/sensor/dist/graph/dynamic-boundary-report.js.map +1 -0
- package/sensor/dist/graph/index.d.ts +4 -0
- package/sensor/dist/graph/index.d.ts.map +1 -1
- package/sensor/dist/graph/index.js +16 -1
- package/sensor/dist/graph/index.js.map +1 -1
- package/sensor/dist/graph/named-symbol-flow.d.ts +125 -0
- package/sensor/dist/graph/named-symbol-flow.d.ts.map +1 -0
- package/sensor/dist/graph/named-symbol-flow.js +552 -0
- package/sensor/dist/graph/named-symbol-flow.js.map +1 -0
- package/sensor/dist/graph/queries.d.ts.map +1 -1
- package/sensor/dist/graph/queries.js +2 -0
- package/sensor/dist/graph/queries.js.map +1 -1
- package/sensor/dist/graph/symbol-lookup.d.ts +83 -0
- package/sensor/dist/graph/symbol-lookup.d.ts.map +1 -0
- package/sensor/dist/graph/symbol-lookup.js +181 -0
- package/sensor/dist/graph/symbol-lookup.js.map +1 -0
- package/sensor/dist/graph/traversal.d.ts +9 -0
- package/sensor/dist/graph/traversal.d.ts.map +1 -1
- package/sensor/dist/graph/traversal.js +62 -36
- package/sensor/dist/graph/traversal.js.map +1 -1
- package/sensor/dist/graph/type-hierarchy.d.ts +155 -0
- package/sensor/dist/graph/type-hierarchy.d.ts.map +1 -0
- package/sensor/dist/graph/type-hierarchy.js +382 -0
- package/sensor/dist/graph/type-hierarchy.js.map +1 -0
- package/sensor/dist/index.d.ts +272 -10
- package/sensor/dist/index.d.ts.map +1 -1
- package/sensor/dist/index.js +510 -34
- package/sensor/dist/index.js.map +1 -1
- package/sensor/dist/mcp/answer-freshness.d.ts +10 -0
- package/sensor/dist/mcp/answer-freshness.d.ts.map +1 -0
- package/sensor/dist/mcp/answer-freshness.js +68 -0
- package/sensor/dist/mcp/answer-freshness.js.map +1 -0
- package/sensor/dist/mcp/daemon-manager.d.ts +1 -1
- package/sensor/dist/mcp/daemon-manager.d.ts.map +1 -1
- package/sensor/dist/mcp/daemon-manager.js +17 -1
- package/sensor/dist/mcp/daemon-manager.js.map +1 -1
- package/sensor/dist/mcp/daemon-paths.d.ts +8 -0
- package/sensor/dist/mcp/daemon-paths.d.ts.map +1 -1
- package/sensor/dist/mcp/daemon-paths.js +65 -2
- package/sensor/dist/mcp/daemon-paths.js.map +1 -1
- package/sensor/dist/mcp/daemon-registry.d.ts +17 -4
- package/sensor/dist/mcp/daemon-registry.d.ts.map +1 -1
- package/sensor/dist/mcp/daemon-registry.js +133 -23
- package/sensor/dist/mcp/daemon-registry.js.map +1 -1
- package/sensor/dist/mcp/daemon.d.ts +26 -13
- package/sensor/dist/mcp/daemon.d.ts.map +1 -1
- package/sensor/dist/mcp/daemon.js +126 -32
- package/sensor/dist/mcp/daemon.js.map +1 -1
- package/sensor/dist/mcp/dynamic-boundaries.d.ts +1 -10
- package/sensor/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
- package/sensor/dist/mcp/dynamic-boundaries.js +46 -42
- package/sensor/dist/mcp/dynamic-boundaries.js.map +1 -1
- package/sensor/dist/mcp/engine.d.ts +31 -9
- package/sensor/dist/mcp/engine.d.ts.map +1 -1
- package/sensor/dist/mcp/engine.js +190 -79
- package/sensor/dist/mcp/engine.js.map +1 -1
- package/sensor/dist/mcp/explore-dedup.d.ts +140 -0
- package/sensor/dist/mcp/explore-dedup.d.ts.map +1 -0
- package/sensor/dist/mcp/explore-dedup.js +239 -0
- package/sensor/dist/mcp/explore-dedup.js.map +1 -0
- package/sensor/dist/mcp/explore-diagnostics.d.ts +289 -0
- package/sensor/dist/mcp/explore-diagnostics.d.ts.map +1 -0
- package/sensor/dist/mcp/explore-diagnostics.js +558 -0
- package/sensor/dist/mcp/explore-diagnostics.js.map +1 -0
- package/sensor/dist/mcp/explore-session-state.d.ts +217 -0
- package/sensor/dist/mcp/explore-session-state.d.ts.map +1 -0
- package/sensor/dist/mcp/explore-session-state.js +322 -0
- package/sensor/dist/mcp/explore-session-state.js.map +1 -0
- package/sensor/dist/mcp/index-freshness-worker.d.ts +2 -0
- package/sensor/dist/mcp/index-freshness-worker.d.ts.map +1 -0
- package/sensor/dist/mcp/index-freshness-worker.js +30 -0
- package/sensor/dist/mcp/index-freshness-worker.js.map +1 -0
- package/sensor/dist/mcp/index-freshness.d.ts +7 -0
- package/sensor/dist/mcp/index-freshness.d.ts.map +1 -0
- package/sensor/dist/mcp/index-freshness.js +94 -0
- package/sensor/dist/mcp/index-freshness.js.map +1 -0
- package/sensor/dist/mcp/index.d.ts +3 -1
- package/sensor/dist/mcp/index.d.ts.map +1 -1
- package/sensor/dist/mcp/index.js +109 -12
- package/sensor/dist/mcp/index.js.map +1 -1
- package/sensor/dist/mcp/liveness-watchdog.d.ts +4 -1
- package/sensor/dist/mcp/liveness-watchdog.d.ts.map +1 -1
- package/sensor/dist/mcp/liveness-watchdog.js +11 -2
- package/sensor/dist/mcp/liveness-watchdog.js.map +1 -1
- package/sensor/dist/mcp/project-lifecycle.d.ts +9 -0
- package/sensor/dist/mcp/project-lifecycle.d.ts.map +1 -0
- package/sensor/dist/mcp/project-lifecycle.js +182 -0
- package/sensor/dist/mcp/project-lifecycle.js.map +1 -0
- package/sensor/dist/mcp/proxy.d.ts.map +1 -1
- package/sensor/dist/mcp/proxy.js +15 -1
- package/sensor/dist/mcp/proxy.js.map +1 -1
- package/sensor/dist/mcp/query-pool.d.ts +2 -2
- package/sensor/dist/mcp/query-pool.d.ts.map +1 -1
- package/sensor/dist/mcp/query-pool.js +9 -7
- package/sensor/dist/mcp/query-pool.js.map +1 -1
- package/sensor/dist/mcp/query-worker.js +2 -2
- package/sensor/dist/mcp/query-worker.js.map +1 -1
- package/sensor/dist/mcp/server-instructions.d.ts +2 -2
- package/sensor/dist/mcp/server-instructions.d.ts.map +1 -1
- package/sensor/dist/mcp/server-instructions.js +13 -6
- package/sensor/dist/mcp/server-instructions.js.map +1 -1
- package/sensor/dist/mcp/session.d.ts +16 -0
- package/sensor/dist/mcp/session.d.ts.map +1 -1
- package/sensor/dist/mcp/session.js +36 -13
- package/sensor/dist/mcp/session.js.map +1 -1
- package/sensor/dist/mcp/startup-handshake.d.ts +3 -1
- package/sensor/dist/mcp/startup-handshake.d.ts.map +1 -1
- package/sensor/dist/mcp/startup-handshake.js +10 -2
- package/sensor/dist/mcp/startup-handshake.js.map +1 -1
- package/sensor/dist/mcp/tools.d.ts +496 -38
- package/sensor/dist/mcp/tools.d.ts.map +1 -1
- package/sensor/dist/mcp/tools.js +4021 -772
- package/sensor/dist/mcp/tools.js.map +1 -1
- package/sensor/dist/mcp/writer-lock.d.ts +61 -0
- package/sensor/dist/mcp/writer-lock.d.ts.map +1 -0
- package/sensor/dist/mcp/writer-lock.js +260 -0
- package/sensor/dist/mcp/writer-lock.js.map +1 -0
- package/sensor/dist/project-config.d.ts +23 -0
- package/sensor/dist/project-config.d.ts.map +1 -1
- package/sensor/dist/project-config.js +39 -2
- package/sensor/dist/project-config.js.map +1 -1
- package/sensor/dist/resolution/alias-binding.d.ts +56 -0
- package/sensor/dist/resolution/alias-binding.d.ts.map +1 -0
- package/sensor/dist/resolution/alias-binding.js +127 -0
- package/sensor/dist/resolution/alias-binding.js.map +1 -0
- package/sensor/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
- package/sensor/dist/resolution/c-fnptr-synthesizer.js +21 -19
- package/sensor/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
- package/sensor/dist/resolution/callback-synthesizer.d.ts +23 -1
- package/sensor/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/sensor/dist/resolution/callback-synthesizer.js +356 -155
- package/sensor/dist/resolution/callback-synthesizer.js.map +1 -1
- package/sensor/dist/resolution/cpp-constructor.d.ts +4 -0
- package/sensor/dist/resolution/cpp-constructor.d.ts.map +1 -0
- package/sensor/dist/resolution/cpp-constructor.js +123 -0
- package/sensor/dist/resolution/cpp-constructor.js.map +1 -0
- package/sensor/dist/resolution/cpp-macro-visibility.d.ts +12 -0
- package/sensor/dist/resolution/cpp-macro-visibility.d.ts.map +1 -0
- package/sensor/dist/resolution/cpp-macro-visibility.js +342 -0
- package/sensor/dist/resolution/cpp-macro-visibility.js.map +1 -0
- package/sensor/dist/resolution/expo-router-synthesizer.d.ts +32 -0
- package/sensor/dist/resolution/expo-router-synthesizer.d.ts.map +1 -0
- package/sensor/dist/resolution/expo-router-synthesizer.js +189 -0
- package/sensor/dist/resolution/expo-router-synthesizer.js.map +1 -0
- package/sensor/dist/resolution/frameworks/csharp.d.ts.map +1 -1
- package/sensor/dist/resolution/frameworks/csharp.js +96 -2
- package/sensor/dist/resolution/frameworks/csharp.js.map +1 -1
- package/sensor/dist/resolution/frameworks/expo-router.d.ts +189 -0
- package/sensor/dist/resolution/frameworks/expo-router.d.ts.map +1 -0
- package/sensor/dist/resolution/frameworks/expo-router.js +785 -0
- package/sensor/dist/resolution/frameworks/expo-router.js.map +1 -0
- package/sensor/dist/resolution/frameworks/express.d.ts.map +1 -1
- package/sensor/dist/resolution/frameworks/express.js +251 -16
- package/sensor/dist/resolution/frameworks/express.js.map +1 -1
- package/sensor/dist/resolution/frameworks/index.d.ts +5 -0
- package/sensor/dist/resolution/frameworks/index.d.ts.map +1 -1
- package/sensor/dist/resolution/frameworks/index.js +29 -1
- package/sensor/dist/resolution/frameworks/index.js.map +1 -1
- package/sensor/dist/resolution/frameworks/java.d.ts.map +1 -1
- package/sensor/dist/resolution/frameworks/java.js +79 -47
- package/sensor/dist/resolution/frameworks/java.js.map +1 -1
- package/sensor/dist/resolution/frameworks/laravel.d.ts.map +1 -1
- package/sensor/dist/resolution/frameworks/laravel.js +294 -4
- package/sensor/dist/resolution/frameworks/laravel.js.map +1 -1
- package/sensor/dist/resolution/frameworks/nestjs.d.ts.map +1 -1
- package/sensor/dist/resolution/frameworks/nestjs.js +6 -14
- package/sensor/dist/resolution/frameworks/nestjs.js.map +1 -1
- package/sensor/dist/resolution/frameworks/nextjs.d.ts +76 -0
- package/sensor/dist/resolution/frameworks/nextjs.d.ts.map +1 -0
- package/sensor/dist/resolution/frameworks/nextjs.js +328 -0
- package/sensor/dist/resolution/frameworks/nextjs.js.map +1 -0
- package/sensor/dist/resolution/frameworks/object-literal.d.ts +32 -0
- package/sensor/dist/resolution/frameworks/object-literal.d.ts.map +1 -0
- package/sensor/dist/resolution/frameworks/object-literal.js +139 -0
- package/sensor/dist/resolution/frameworks/object-literal.js.map +1 -0
- package/sensor/dist/resolution/frameworks/package-deps.d.ts +14 -0
- package/sensor/dist/resolution/frameworks/package-deps.d.ts.map +1 -0
- package/sensor/dist/resolution/frameworks/package-deps.js +78 -0
- package/sensor/dist/resolution/frameworks/package-deps.js.map +1 -0
- package/sensor/dist/resolution/frameworks/python.d.ts.map +1 -1
- package/sensor/dist/resolution/frameworks/python.js +166 -0
- package/sensor/dist/resolution/frameworks/python.js.map +1 -1
- package/sensor/dist/resolution/frameworks/react-native.d.ts +26 -0
- package/sensor/dist/resolution/frameworks/react-native.d.ts.map +1 -1
- package/sensor/dist/resolution/frameworks/react-native.js +153 -14
- package/sensor/dist/resolution/frameworks/react-native.js.map +1 -1
- package/sensor/dist/resolution/frameworks/react-router.d.ts +46 -0
- package/sensor/dist/resolution/frameworks/react-router.d.ts.map +1 -0
- package/sensor/dist/resolution/frameworks/react-router.js +187 -0
- package/sensor/dist/resolution/frameworks/react-router.js.map +1 -0
- package/sensor/dist/resolution/frameworks/react.d.ts +2 -1
- package/sensor/dist/resolution/frameworks/react.d.ts.map +1 -1
- package/sensor/dist/resolution/frameworks/react.js +188 -139
- package/sensor/dist/resolution/frameworks/react.js.map +1 -1
- package/sensor/dist/resolution/frameworks/svelte.d.ts.map +1 -1
- package/sensor/dist/resolution/frameworks/svelte.js +6 -1
- package/sensor/dist/resolution/frameworks/svelte.js.map +1 -1
- package/sensor/dist/resolution/frameworks/sveltekit-router.d.ts +36 -0
- package/sensor/dist/resolution/frameworks/sveltekit-router.d.ts.map +1 -0
- package/sensor/dist/resolution/frameworks/sveltekit-router.js +156 -0
- package/sensor/dist/resolution/frameworks/sveltekit-router.js.map +1 -0
- package/sensor/dist/resolution/frameworks/swift.d.ts.map +1 -1
- package/sensor/dist/resolution/frameworks/swift.js +140 -99
- package/sensor/dist/resolution/frameworks/swift.js.map +1 -1
- package/sensor/dist/resolution/frameworks/tanstack-router.d.ts +84 -0
- package/sensor/dist/resolution/frameworks/tanstack-router.d.ts.map +1 -0
- package/sensor/dist/resolution/frameworks/tanstack-router.js +437 -0
- package/sensor/dist/resolution/frameworks/tanstack-router.js.map +1 -0
- package/sensor/dist/resolution/frameworks/vue-router.d.ts +82 -0
- package/sensor/dist/resolution/frameworks/vue-router.d.ts.map +1 -0
- package/sensor/dist/resolution/frameworks/vue-router.js +377 -0
- package/sensor/dist/resolution/frameworks/vue-router.js.map +1 -0
- package/sensor/dist/resolution/import-resolver.d.ts +38 -0
- package/sensor/dist/resolution/import-resolver.d.ts.map +1 -1
- package/sensor/dist/resolution/import-resolver.js +394 -28
- package/sensor/dist/resolution/import-resolver.js.map +1 -1
- package/sensor/dist/resolution/index.d.ts +89 -25
- package/sensor/dist/resolution/index.d.ts.map +1 -1
- package/sensor/dist/resolution/index.js +573 -148
- package/sensor/dist/resolution/index.js.map +1 -1
- package/sensor/dist/resolution/js-builtins.d.ts +13 -0
- package/sensor/dist/resolution/js-builtins.d.ts.map +1 -0
- package/sensor/dist/resolution/js-builtins.js +46 -0
- package/sensor/dist/resolution/js-builtins.js.map +1 -0
- package/sensor/dist/resolution/name-matcher.d.ts +99 -22
- package/sensor/dist/resolution/name-matcher.d.ts.map +1 -1
- package/sensor/dist/resolution/name-matcher.js +2220 -86
- package/sensor/dist/resolution/name-matcher.js.map +1 -1
- package/sensor/dist/resolution/next-router-synthesizer.d.ts +27 -0
- package/sensor/dist/resolution/next-router-synthesizer.d.ts.map +1 -0
- package/sensor/dist/resolution/next-router-synthesizer.js +113 -0
- package/sensor/dist/resolution/next-router-synthesizer.js.map +1 -0
- package/sensor/dist/resolution/path-aliases.d.ts +6 -4
- package/sensor/dist/resolution/path-aliases.d.ts.map +1 -1
- package/sensor/dist/resolution/path-aliases.js +125 -17
- package/sensor/dist/resolution/path-aliases.js.map +1 -1
- package/sensor/dist/resolution/react-router-synthesizer.d.ts +33 -0
- package/sensor/dist/resolution/react-router-synthesizer.d.ts.map +1 -0
- package/sensor/dist/resolution/react-router-synthesizer.js +126 -0
- package/sensor/dist/resolution/react-router-synthesizer.js.map +1 -0
- package/sensor/dist/resolution/resolver-pool.d.ts +2 -0
- package/sensor/dist/resolution/resolver-pool.d.ts.map +1 -1
- package/sensor/dist/resolution/resolver-pool.js +4 -0
- package/sensor/dist/resolution/resolver-pool.js.map +1 -1
- package/sensor/dist/resolution/strip-comments.d.ts +6 -0
- package/sensor/dist/resolution/strip-comments.d.ts.map +1 -1
- package/sensor/dist/resolution/strip-comments.js +58 -0
- package/sensor/dist/resolution/strip-comments.js.map +1 -1
- package/sensor/dist/resolution/sveltekit-synthesizer.d.ts +58 -0
- package/sensor/dist/resolution/sveltekit-synthesizer.d.ts.map +1 -0
- package/sensor/dist/resolution/sveltekit-synthesizer.js +173 -0
- package/sensor/dist/resolution/sveltekit-synthesizer.js.map +1 -0
- package/sensor/dist/resolution/swift-type-visibility.d.ts +52 -0
- package/sensor/dist/resolution/swift-type-visibility.d.ts.map +1 -0
- package/sensor/dist/resolution/swift-type-visibility.js +328 -0
- package/sensor/dist/resolution/swift-type-visibility.js.map +1 -0
- package/sensor/dist/resolution/synth-utils.d.ts +26 -0
- package/sensor/dist/resolution/synth-utils.d.ts.map +1 -0
- package/sensor/dist/resolution/synth-utils.js +75 -0
- package/sensor/dist/resolution/synth-utils.js.map +1 -0
- package/sensor/dist/resolution/tanstack-router-synthesizer.d.ts +31 -0
- package/sensor/dist/resolution/tanstack-router-synthesizer.d.ts.map +1 -0
- package/sensor/dist/resolution/tanstack-router-synthesizer.js +114 -0
- package/sensor/dist/resolution/tanstack-router-synthesizer.js.map +1 -0
- package/sensor/dist/resolution/tier-synthesizer.d.ts +54 -0
- package/sensor/dist/resolution/tier-synthesizer.d.ts.map +1 -0
- package/sensor/dist/resolution/tier-synthesizer.js +1294 -0
- package/sensor/dist/resolution/tier-synthesizer.js.map +1 -0
- package/sensor/dist/resolution/types.d.ts +78 -2
- package/sensor/dist/resolution/types.d.ts.map +1 -1
- package/sensor/dist/resolution/types.js +64 -0
- package/sensor/dist/resolution/types.js.map +1 -1
- package/sensor/dist/resolution/vue-router-synthesizer.d.ts +30 -0
- package/sensor/dist/resolution/vue-router-synthesizer.d.ts.map +1 -0
- package/sensor/dist/resolution/vue-router-synthesizer.js +115 -0
- package/sensor/dist/resolution/vue-router-synthesizer.js.map +1 -0
- package/sensor/dist/search/identifier-segments.d.ts +10 -0
- package/sensor/dist/search/identifier-segments.d.ts.map +1 -1
- package/sensor/dist/search/identifier-segments.js +29 -0
- package/sensor/dist/search/identifier-segments.js.map +1 -1
- package/sensor/dist/search/query-paths.d.ts +106 -0
- package/sensor/dist/search/query-paths.d.ts.map +1 -0
- package/sensor/dist/search/query-paths.js +513 -0
- package/sensor/dist/search/query-paths.js.map +1 -0
- package/sensor/dist/search/query-utils.d.ts +22 -2
- package/sensor/dist/search/query-utils.d.ts.map +1 -1
- package/sensor/dist/search/query-utils.js +56 -16
- package/sensor/dist/search/query-utils.js.map +1 -1
- package/sensor/dist/sync/watch-policy.d.ts +14 -0
- package/sensor/dist/sync/watch-policy.d.ts.map +1 -1
- package/sensor/dist/sync/watch-policy.js +19 -0
- package/sensor/dist/sync/watch-policy.js.map +1 -1
- package/sensor/dist/sync/watcher.d.ts +74 -35
- package/sensor/dist/sync/watcher.d.ts.map +1 -1
- package/sensor/dist/sync/watcher.js +281 -99
- package/sensor/dist/sync/watcher.js.map +1 -1
- package/sensor/dist/telemetry/index.d.ts +4 -2
- package/sensor/dist/telemetry/index.d.ts.map +1 -1
- package/sensor/dist/telemetry/index.js +92 -41
- package/sensor/dist/telemetry/index.js.map +1 -1
- package/sensor/dist/types.d.ts +18 -2
- package/sensor/dist/types.d.ts.map +1 -1
- package/sensor/dist/types.js +2 -0
- package/sensor/dist/types.js.map +1 -1
- package/sensor/dist/ui-server/api/deadcode.d.ts +84 -0
- package/sensor/dist/ui-server/api/deadcode.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/deadcode.js +136 -0
- package/sensor/dist/ui-server/api/deadcode.js.map +1 -0
- package/sensor/dist/ui-server/api/effects.d.ts +94 -0
- package/sensor/dist/ui-server/api/effects.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/effects.js +527 -0
- package/sensor/dist/ui-server/api/effects.js.map +1 -0
- package/sensor/dist/ui-server/api/entrypoints.d.ts +92 -0
- package/sensor/dist/ui-server/api/entrypoints.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/entrypoints.js +361 -0
- package/sensor/dist/ui-server/api/entrypoints.js.map +1 -0
- package/sensor/dist/ui-server/api/events.d.ts +183 -0
- package/sensor/dist/ui-server/api/events.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/events.js +430 -0
- package/sensor/dist/ui-server/api/events.js.map +1 -0
- package/sensor/dist/ui-server/api/file.d.ts +66 -0
- package/sensor/dist/ui-server/api/file.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/file.js +235 -0
- package/sensor/dist/ui-server/api/file.js.map +1 -0
- package/sensor/dist/ui-server/api/filecode.d.ts +126 -0
- package/sensor/dist/ui-server/api/filecode.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/filecode.js +205 -0
- package/sensor/dist/ui-server/api/filecode.js.map +1 -0
- package/sensor/dist/ui-server/api/flow.d.ts +226 -0
- package/sensor/dist/ui-server/api/flow.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/flow.js +612 -0
- package/sensor/dist/ui-server/api/flow.js.map +1 -0
- package/sensor/dist/ui-server/api/hierarchy.d.ts +81 -0
- package/sensor/dist/ui-server/api/hierarchy.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/hierarchy.js +92 -0
- package/sensor/dist/ui-server/api/hierarchy.js.map +1 -0
- package/sensor/dist/ui-server/api/index.d.ts +90 -0
- package/sensor/dist/ui-server/api/index.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/index.js +332 -0
- package/sensor/dist/ui-server/api/index.js.map +1 -0
- package/sensor/dist/ui-server/api/map.d.ts +256 -0
- package/sensor/dist/ui-server/api/map.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/map.js +760 -0
- package/sensor/dist/ui-server/api/map.js.map +1 -0
- package/sensor/dist/ui-server/api/node.d.ts +81 -0
- package/sensor/dist/ui-server/api/node.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/node.js +388 -0
- package/sensor/dist/ui-server/api/node.js.map +1 -0
- package/sensor/dist/ui-server/api/nodes.d.ts +25 -0
- package/sensor/dist/ui-server/api/nodes.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/nodes.js +45 -0
- package/sensor/dist/ui-server/api/nodes.js.map +1 -0
- package/sensor/dist/ui-server/api/program.d.ts +139 -0
- package/sensor/dist/ui-server/api/program.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/program.js +315 -0
- package/sensor/dist/ui-server/api/program.js.map +1 -0
- package/sensor/dist/ui-server/api/respond.d.ts +76 -0
- package/sensor/dist/ui-server/api/respond.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/respond.js +186 -0
- package/sensor/dist/ui-server/api/respond.js.map +1 -0
- package/sensor/dist/ui-server/api/route-roots.d.ts +39 -0
- package/sensor/dist/ui-server/api/route-roots.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/route-roots.js +96 -0
- package/sensor/dist/ui-server/api/route-roots.js.map +1 -0
- package/sensor/dist/ui-server/api/routes.d.ts +59 -0
- package/sensor/dist/ui-server/api/routes.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/routes.js +108 -0
- package/sensor/dist/ui-server/api/routes.js.map +1 -0
- package/sensor/dist/ui-server/api/screens.d.ts +117 -0
- package/sensor/dist/ui-server/api/screens.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/screens.js +611 -0
- package/sensor/dist/ui-server/api/screens.js.map +1 -0
- package/sensor/dist/ui-server/api/search.d.ts +29 -0
- package/sensor/dist/ui-server/api/search.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/search.js +232 -0
- package/sensor/dist/ui-server/api/search.js.map +1 -0
- package/sensor/dist/ui-server/api/session.d.ts +52 -0
- package/sensor/dist/ui-server/api/session.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/session.js +196 -0
- package/sensor/dist/ui-server/api/session.js.map +1 -0
- package/sensor/dist/ui-server/api/source.d.ts +200 -0
- package/sensor/dist/ui-server/api/source.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/source.js +422 -0
- package/sensor/dist/ui-server/api/source.js.map +1 -0
- package/sensor/dist/ui-server/api/stats.d.ts +27 -0
- package/sensor/dist/ui-server/api/stats.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/stats.js +164 -0
- package/sensor/dist/ui-server/api/stats.js.map +1 -0
- package/sensor/dist/ui-server/api/steps.d.ts +274 -0
- package/sensor/dist/ui-server/api/steps.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/steps.js +1544 -0
- package/sensor/dist/ui-server/api/steps.js.map +1 -0
- package/sensor/dist/ui-server/api/trail-store.d.ts +142 -0
- package/sensor/dist/ui-server/api/trail-store.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/trail-store.js +325 -0
- package/sensor/dist/ui-server/api/trail-store.js.map +1 -0
- package/sensor/dist/ui-server/api/trails.d.ts +163 -0
- package/sensor/dist/ui-server/api/trails.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/trails.js +369 -0
- package/sensor/dist/ui-server/api/trails.js.map +1 -0
- package/sensor/dist/ui-server/api/when.d.ts +133 -0
- package/sensor/dist/ui-server/api/when.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/when.js +275 -0
- package/sensor/dist/ui-server/api/when.js.map +1 -0
- package/sensor/dist/ui-server/api/wire.d.ts +179 -0
- package/sensor/dist/ui-server/api/wire.d.ts.map +1 -0
- package/sensor/dist/ui-server/api/wire.js +231 -0
- package/sensor/dist/ui-server/api/wire.js.map +1 -0
- package/sensor/dist/ui-server/assets.d.ts +40 -0
- package/sensor/dist/ui-server/assets.d.ts.map +1 -0
- package/sensor/dist/ui-server/assets.js +110 -0
- package/sensor/dist/ui-server/assets.js.map +1 -0
- package/sensor/dist/ui-server/constants.d.ts +32 -0
- package/sensor/dist/ui-server/constants.d.ts.map +1 -0
- package/sensor/dist/ui-server/constants.js +35 -0
- package/sensor/dist/ui-server/constants.js.map +1 -0
- package/sensor/dist/ui-server/highlight/index.d.ts +113 -0
- package/sensor/dist/ui-server/highlight/index.d.ts.map +1 -0
- package/sensor/dist/ui-server/highlight/index.js +307 -0
- package/sensor/dist/ui-server/highlight/index.js.map +1 -0
- package/sensor/dist/ui-server/highlight/languages.d.ts +36 -0
- package/sensor/dist/ui-server/highlight/languages.d.ts.map +1 -0
- package/sensor/dist/ui-server/highlight/languages.js +50 -0
- package/sensor/dist/ui-server/highlight/languages.js.map +1 -0
- package/sensor/dist/ui-server/index.d.ts +92 -0
- package/sensor/dist/ui-server/index.d.ts.map +1 -0
- package/sensor/dist/ui-server/index.js +399 -0
- package/sensor/dist/ui-server/index.js.map +1 -0
- package/sensor/dist/ui-server/open-browser.d.ts +31 -0
- package/sensor/dist/ui-server/open-browser.d.ts.map +1 -0
- package/sensor/dist/ui-server/open-browser.js +79 -0
- package/sensor/dist/ui-server/open-browser.js.map +1 -0
- package/sensor/dist/ui-server/security.d.ts +127 -0
- package/sensor/dist/ui-server/security.d.ts.map +1 -0
- package/sensor/dist/ui-server/security.js +332 -0
- package/sensor/dist/ui-server/security.js.map +1 -0
- package/sensor/dist/ui-server/static.d.ts +45 -0
- package/sensor/dist/ui-server/static.d.ts.map +1 -0
- package/sensor/dist/ui-server/static.js +165 -0
- package/sensor/dist/ui-server/static.js.map +1 -0
- package/sensor/dist/utils.d.ts +14 -2
- package/sensor/dist/utils.d.ts.map +1 -1
- package/sensor/dist/utils.js +25 -10
- package/sensor/dist/utils.js.map +1 -1
- package/src/sensor-adapter.mjs +5 -1
|
@@ -41,12 +41,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
41
41
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
42
|
};
|
|
43
43
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
-
exports.loadAllGrammars = exports.loadGrammarsForLanguages = exports.initGrammars = exports.getSupportedLanguages = exports.isGrammarLoaded = exports.isLanguageSupported = exports.isSourceFile = exports.detectLanguage = exports.extractFromSource = exports.ExtractionOrchestrator = exports.ScopeIgnore = void 0;
|
|
44
|
+
exports.loadAllGrammars = exports.loadGrammarsForLanguages = exports.initGrammars = exports.getSupportedLanguages = exports.isGrammarLoaded = exports.isLanguageSupported = exports.isSourceFile = exports.detectLanguage = exports.extractFromSource = exports.ExtractionOrchestrator = exports.INDEXED_AT_COMMIT_KEY = exports.ScopeIgnore = exports.oversizeStamp = void 0;
|
|
45
45
|
exports.hashContent = hashContent;
|
|
46
46
|
exports.buildDefaultIgnore = buildDefaultIgnore;
|
|
47
|
+
exports.preloadLanguagesForFiles = preloadLanguagesForFiles;
|
|
47
48
|
exports.buildScopeIgnore = buildScopeIgnore;
|
|
48
49
|
exports.discoverEmbeddedRepoRoots = discoverEmbeddedRepoRoots;
|
|
49
50
|
exports.findUnindexedIgnoredRepos = findUnindexedIgnoredRepos;
|
|
51
|
+
exports.getGitChangedFiles = getGitChangedFiles;
|
|
52
|
+
exports.getGitHeadSha = getGitHeadSha;
|
|
53
|
+
exports.canTrustGitFastPath = canTrustGitFastPath;
|
|
50
54
|
exports.scanDirectory = scanDirectory;
|
|
51
55
|
exports.scanDirectoryAsync = scanDirectoryAsync;
|
|
52
56
|
const fs = __importStar(require("fs"));
|
|
@@ -60,6 +64,7 @@ const tree_sitter_1 = require("./tree-sitter");
|
|
|
60
64
|
const parse_pool_1 = require("./parse-pool");
|
|
61
65
|
const store_writer_1 = require("./store-writer");
|
|
62
66
|
const kernel_1 = require("./kernel");
|
|
67
|
+
const generated_detection_1 = require("./generated-detection");
|
|
63
68
|
const grammars_1 = require("./grammars");
|
|
64
69
|
const project_config_1 = require("../project-config");
|
|
65
70
|
const directory_1 = require("../directory");
|
|
@@ -68,6 +73,8 @@ const utils_1 = require("../utils");
|
|
|
68
73
|
const ignore_1 = __importDefault(require("ignore"));
|
|
69
74
|
const frameworks_1 = require("../resolution/frameworks");
|
|
70
75
|
const cooperative_yield_1 = require("../resolution/cooperative-yield");
|
|
76
|
+
const file_limits_1 = require("../file-limits");
|
|
77
|
+
Object.defineProperty(exports, "oversizeStamp", { enumerable: true, get: function () { return file_limits_1.oversizeStamp; } });
|
|
71
78
|
/**
|
|
72
79
|
* Number of files to read in parallel during indexing.
|
|
73
80
|
* File reads are I/O-bound; batching overlaps I/O wait with CPU parse work.
|
|
@@ -107,11 +114,26 @@ function hashContent(content) {
|
|
|
107
114
|
return crypto.createHash('sha256').update(content).digest('hex');
|
|
108
115
|
}
|
|
109
116
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
117
|
+
* What change detection hashes for a file: its text when it is under the size
|
|
118
|
+
* limit, the size stamp when it is over — an oversize file is never decoded.
|
|
119
|
+
* `null` when the bytes show a `.ts` file is an MPEG transport stream, not
|
|
120
|
+
* TypeScript (#1910): decided from the bytes already read, so it costs no I/O
|
|
121
|
+
* and runs only for a file that is new or changed — never once per `.ts` file
|
|
122
|
+
* at discovery, which on a slow disk is a random read per file per scan.
|
|
113
123
|
*/
|
|
114
|
-
|
|
124
|
+
function readSourceOrStamp(fullPath) {
|
|
125
|
+
const { stats, bytes } = (0, file_limits_1.readBoundedSourceSync)(fullPath);
|
|
126
|
+
if (bytes === null)
|
|
127
|
+
return (0, file_limits_1.oversizeStamp)(stats.size);
|
|
128
|
+
return isMpegTsBytes(fullPath, bytes) ? null : bytes.toString('utf8');
|
|
129
|
+
}
|
|
130
|
+
/** Whether these bytes, read from `filePath`, are a `.ts` MPEG transport stream (#1910). */
|
|
131
|
+
function isMpegTsBytes(filePath, bytes) {
|
|
132
|
+
if (!(0, grammars_1.hasMpegTsExtension)(filePath) || !(0, grammars_1.isMpegTransportStream)(bytes.subarray(0, grammars_1.MPEG_TS_SNIFF_BYTES)))
|
|
133
|
+
return false;
|
|
134
|
+
(0, errors_1.logDebug)('Skipping MPEG transport stream named .ts — not TypeScript', { filePath });
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
115
137
|
/**
|
|
116
138
|
* Directory names that are dependency, build, cache, or tooling output across the
|
|
117
139
|
* languages/frameworks LatticeSensor supports — curated from the canonical
|
|
@@ -191,6 +213,13 @@ const DEFAULT_IGNORE_PATTERNS = [
|
|
|
191
213
|
'bazel-*/', // Bazel output symlink trees
|
|
192
214
|
// Android resource dirs at any depth, with their qualifier variants (#1047).
|
|
193
215
|
...ANDROID_RES_TYPES.map((t) => `**/res/${t}*/`),
|
|
216
|
+
// `build` is also a legal JVM package segment. Keep it under conventional
|
|
217
|
+
// source roots (any source set: main, test, androidTest, ...) while continuing
|
|
218
|
+
// to exclude module/build output (#1642). Unignore only the directory, not its
|
|
219
|
+
// subtree: other defaults still apply.
|
|
220
|
+
'!**/src/*/java/**/build/',
|
|
221
|
+
'!**/src/*/kotlin/**/build/',
|
|
222
|
+
'!**/src/*/scala/**/build/',
|
|
194
223
|
];
|
|
195
224
|
/** True if `buf` decodes as strict UTF-8 (no invalid byte sequences). */
|
|
196
225
|
function isValidUtf8(buf) {
|
|
@@ -260,18 +289,119 @@ function readGitignorePatterns(giPath) {
|
|
|
260
289
|
}
|
|
261
290
|
return kept.join('\n');
|
|
262
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Resolve the repository GIT_DIR for `repoRoot` (a `.git` directory, or the
|
|
294
|
+
* target of a `.git` file pointer). Null when this isn't a git checkout.
|
|
295
|
+
*/
|
|
296
|
+
function resolveGitDir(repoRoot) {
|
|
297
|
+
const gitPath = path.join(repoRoot, '.git');
|
|
298
|
+
let st;
|
|
299
|
+
try {
|
|
300
|
+
st = fs.statSync(gitPath);
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
if (st.isDirectory())
|
|
306
|
+
return gitPath;
|
|
307
|
+
if (!st.isFile())
|
|
308
|
+
return null;
|
|
309
|
+
try {
|
|
310
|
+
const raw = fs.readFileSync(gitPath, 'utf8').match(/^gitdir:\s*(.+)$/m)?.[1]?.trim();
|
|
311
|
+
if (!raw)
|
|
312
|
+
return null;
|
|
313
|
+
return path.isAbsolute(raw) ? path.normalize(raw) : path.resolve(repoRoot, raw);
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/** Expand a leading `~/` the way git does for `core.excludesFile`. */
|
|
320
|
+
function expandUserPath(p) {
|
|
321
|
+
if (p === '~')
|
|
322
|
+
return os.homedir();
|
|
323
|
+
if (p.startsWith('~/'))
|
|
324
|
+
return path.join(os.homedir(), p.slice(2));
|
|
325
|
+
return p;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Root-relative exclude patterns from git sources that are NOT the root
|
|
329
|
+
* `.gitignore`: `.git/info/exclude` and `core.excludesFile`. Same semantics as
|
|
330
|
+
* the root `.gitignore`, so they merge into {@link buildDefaultIgnore}. Without
|
|
331
|
+
* these, the watcher / FS-walk scope silently diverged from
|
|
332
|
+
* `git ls-files --exclude-standard` (#1728).
|
|
333
|
+
*/
|
|
334
|
+
function readGitExcludeExtraPatterns(rootDir) {
|
|
335
|
+
const chunks = [];
|
|
336
|
+
const gitDir = resolveGitDir(rootDir);
|
|
337
|
+
if (gitDir) {
|
|
338
|
+
const excludePath = path.join(gitDir, 'info', 'exclude');
|
|
339
|
+
if (fs.existsSync(excludePath)) {
|
|
340
|
+
const patterns = readGitignorePatterns(excludePath);
|
|
341
|
+
if (patterns)
|
|
342
|
+
chunks.push(patterns);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
const configured = (0, child_process_1.execFileSync)('git', ['-C', rootDir, 'config', '--get', 'core.excludesFile'], { encoding: 'utf8', timeout: 5_000, stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
347
|
+
if (configured) {
|
|
348
|
+
const abs = expandUserPath(configured);
|
|
349
|
+
if (fs.existsSync(abs)) {
|
|
350
|
+
const patterns = readGitignorePatterns(abs);
|
|
351
|
+
if (patterns)
|
|
352
|
+
chunks.push(patterns);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
// No git, unset, or timeout — leave extras empty.
|
|
358
|
+
}
|
|
359
|
+
return chunks.join('\n');
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Directories `git ls-files -o -i --exclude-standard --directory` reports as
|
|
363
|
+
* ignored-untracked. Seeded into {@link ScopeIgnore} so nested `.gitignore`
|
|
364
|
+
* effects (and any exclude-standard rule the flat matcher might miss) prune the
|
|
365
|
+
* watcher the same way the indexer skips them (#1728).
|
|
366
|
+
*/
|
|
367
|
+
function listGitIgnoredDirectories(rootDir) {
|
|
368
|
+
try {
|
|
369
|
+
const out = (0, child_process_1.execFileSync)('git', ['-C', rootDir, 'ls-files', '-z', '-o', '-i', '--exclude-standard', '--directory'], {
|
|
370
|
+
encoding: 'utf8',
|
|
371
|
+
timeout: 60_000,
|
|
372
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
373
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
374
|
+
});
|
|
375
|
+
const dirs = [];
|
|
376
|
+
for (const entry of out.split('\0')) {
|
|
377
|
+
if (!entry)
|
|
378
|
+
continue;
|
|
379
|
+
dirs.push(entry.endsWith('/') ? entry : `${entry}/`);
|
|
380
|
+
}
|
|
381
|
+
return dirs;
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
return [];
|
|
385
|
+
}
|
|
386
|
+
}
|
|
263
387
|
/**
|
|
264
388
|
* An `ignore` matcher seeded with the built-in defaults, merged with the project's
|
|
265
|
-
* root .gitignore so a negation there (e.g. `!vendor/`) overrides a default
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
389
|
+
* root .gitignore so a negation there (e.g. `!vendor/`) overrides a default, plus
|
|
390
|
+
* git's other root-relative exclude files (`.git/info/exclude`, `core.excludesFile`)
|
|
391
|
+
* so watcher / FS-walk scope matches `git ls-files --exclude-standard` (#1728).
|
|
392
|
+
* Shared by both enumeration paths so behavior is identical with or without git —
|
|
393
|
+
* and so the defaults apply to tracked files too (committing a dependency dir
|
|
394
|
+
* doesn't make it project code; the explicit `.gitignore` negation is the only
|
|
395
|
+
* opt-in).
|
|
269
396
|
*/
|
|
270
397
|
function buildDefaultIgnore(rootDir) {
|
|
271
398
|
const ig = (0, ignore_1.default)().add(DEFAULT_IGNORE_PATTERNS);
|
|
272
399
|
const rootGitignore = path.join(rootDir, '.gitignore');
|
|
273
400
|
if (fs.existsSync(rootGitignore))
|
|
274
401
|
ig.add(readGitignorePatterns(rootGitignore));
|
|
402
|
+
const extra = readGitExcludeExtraPatterns(rootDir);
|
|
403
|
+
if (extra)
|
|
404
|
+
ig.add(extra);
|
|
275
405
|
return ig;
|
|
276
406
|
}
|
|
277
407
|
/**
|
|
@@ -590,15 +720,38 @@ function findNestedGitRepos(absDir, relPrefix) {
|
|
|
590
720
|
}
|
|
591
721
|
/**
|
|
592
722
|
* Workspace-scope ignore matcher. Ordinary paths get the root's matcher
|
|
593
|
-
* (built-in defaults + root `.gitignore`
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
*
|
|
723
|
+
* (built-in defaults + root `.gitignore` + `.git/info/exclude` +
|
|
724
|
+
* `core.excludesFile`, plus directories `git ls-files --exclude-standard`
|
|
725
|
+
* reports as ignored); paths inside an EMBEDDED repo get that repo's own
|
|
726
|
+
* matcher — the parent's `.gitignore` hides a child repo from git, not from
|
|
727
|
+
* the index (#514). A directory path (trailing slash) that is an ANCESTOR of
|
|
728
|
+
* an embedded root is never ignored, so directory-pruning callers (the Linux
|
|
729
|
+
* per-directory watcher) still descend to reach the embedded repos.
|
|
599
730
|
*
|
|
600
|
-
*
|
|
731
|
+
* Shared by the indexer (scoped sync / skip checks) and the watcher so their
|
|
732
|
+
* scope cannot diverge from each other or from `git ls-files --exclude-standard`
|
|
733
|
+
* (#1728).
|
|
601
734
|
*/
|
|
735
|
+
/**
|
|
736
|
+
* The grammars to preload for a file set.
|
|
737
|
+
*
|
|
738
|
+
* Path-only detection calls every `.h` file C, but parse-time detection reads
|
|
739
|
+
* the source and can reclassify it as C++ or Objective-C (`detectLanguage`
|
|
740
|
+
* with a `source` argument). Workers only ever get the grammars named here, so
|
|
741
|
+
* a header that turns out to be Objective-C in a project with no `.m` file
|
|
742
|
+
* found no parser and failed with `Failed to get parser for language: objc`
|
|
743
|
+
* (#1628). C++ was already covered; Objective-C was not.
|
|
744
|
+
*/
|
|
745
|
+
function preloadLanguagesForFiles(files, overrides) {
|
|
746
|
+
const languages = [...new Set(files.map((f) => (0, grammars_1.detectLanguage)(f, undefined, overrides)))];
|
|
747
|
+
if (languages.includes('c')) {
|
|
748
|
+
for (const ambiguous of ['cpp', 'objc']) {
|
|
749
|
+
if (!languages.includes(ambiguous))
|
|
750
|
+
languages.push(ambiguous);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return languages;
|
|
754
|
+
}
|
|
602
755
|
class ScopeIgnore {
|
|
603
756
|
rootMatcher;
|
|
604
757
|
exclude;
|
|
@@ -677,7 +830,14 @@ exports.ScopeIgnore = ScopeIgnore;
|
|
|
677
830
|
function buildScopeIgnore(rootDir, embeddedRoots) {
|
|
678
831
|
const roots = embeddedRoots ? [...embeddedRoots] : discoverEmbeddedRepoRoots(rootDir);
|
|
679
832
|
const include = loadIncludeMatcher(rootDir);
|
|
680
|
-
|
|
833
|
+
// Root matcher already has defaults + root `.gitignore` + info/exclude +
|
|
834
|
+
// core.excludesFile. Seed ignored-untracked directories from git so nested
|
|
835
|
+
// `.gitignore` effects prune the watcher identically to the indexer (#1728).
|
|
836
|
+
const rootMatcher = buildDefaultIgnore(rootDir);
|
|
837
|
+
for (const dir of listGitIgnoredDirectories(rootDir)) {
|
|
838
|
+
rootMatcher.add(dir);
|
|
839
|
+
}
|
|
840
|
+
return new ScopeIgnore(rootMatcher, roots.map((root) => ({ root, matcher: buildDefaultIgnore(path.join(rootDir, root)) })), loadExcludeMatcher(rootDir), include, include ? includeStaticRoots((0, project_config_1.loadIncludePatterns)(rootDir)) : []);
|
|
681
841
|
}
|
|
682
842
|
/**
|
|
683
843
|
* Whether an embedded repo found as a tracked gitlink (mode 160000, #1031/#1033)
|
|
@@ -744,7 +904,7 @@ function discoverEmbeddedRepoRoots(rootDir) {
|
|
|
744
904
|
// same way collectGitFiles does, keeping watcher scope == indexer scope.
|
|
745
905
|
// (#1031, #1033)
|
|
746
906
|
try {
|
|
747
|
-
const staged = (
|
|
907
|
+
const staged = lsFilesStaged({ cwd: repoAbs, encoding: 'utf-8', timeout: 30000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
748
908
|
const repoIgnore = buildDefaultIgnore(repoAbs);
|
|
749
909
|
for (const entry of staged.split('\0')) {
|
|
750
910
|
if (!entry || entry.slice(0, 6) !== '160000')
|
|
@@ -864,6 +1024,30 @@ function findIgnoredEmbeddedRepos(repoDir, includeIgnored, prefix) {
|
|
|
864
1024
|
}
|
|
865
1025
|
return repos;
|
|
866
1026
|
}
|
|
1027
|
+
/**
|
|
1028
|
+
* `git ls-files -z -s`, expanding submodules where git allows it.
|
|
1029
|
+
*
|
|
1030
|
+
* `--recurse-submodules` could not be combined with `-s` before git 2.36:
|
|
1031
|
+
* `builtin/ls-files.c` listed `show_stage` among the modes that die, and the
|
|
1032
|
+
* check is unconditional — it does not look at whether the repo actually has
|
|
1033
|
+
* submodules, so every call fails on older git. Ubuntu 22.04 LTS (2.34.1) and
|
|
1034
|
+
* Debian 11 (2.30.2) are both below that line.
|
|
1035
|
+
*
|
|
1036
|
+
* Letting the throw escape cost far more than submodule expansion: it unwound
|
|
1037
|
+
* the whole git-visible pass, so `includeIgnored`, gitlink recursion and the
|
|
1038
|
+
* `lattice-sensor.json` include allowlist silently stopped applying and files went
|
|
1039
|
+
* missing from the index with no error (#1549). Retry without the flag instead
|
|
1040
|
+
* — `-s` is the part that matters here, since gitlink detection reads the mode
|
|
1041
|
+
* bits, and embedded repos are reached through the gitlink recursion anyway.
|
|
1042
|
+
*/
|
|
1043
|
+
function lsFilesStaged(gitOpts) {
|
|
1044
|
+
try {
|
|
1045
|
+
return (0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-s', '--recurse-submodules'], gitOpts);
|
|
1046
|
+
}
|
|
1047
|
+
catch {
|
|
1048
|
+
return (0, child_process_1.execFileSync)('git', ['ls-files', '-z', '-s'], gitOpts);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
867
1051
|
/**
|
|
868
1052
|
* Collect git-visible files (tracked + untracked, .gitignore-respected) from the
|
|
869
1053
|
* git repository rooted at `repoDir`, adding each to `files` with `prefix`
|
|
@@ -908,7 +1092,12 @@ function collectGitFiles(repoDir, prefix, files, embeddedRoots, includeIgnored =
|
|
|
908
1092
|
// on disk → those files are silently dropped from the index. (#541) With -s the
|
|
909
1093
|
// path follows a TAB after the `<mode> <object> <stage>` prefix.
|
|
910
1094
|
const gitlinkRels = [];
|
|
911
|
-
|
|
1095
|
+
// Lattice owns `.lattice/` (its ToDo store, run worktrees, adapter registry):
|
|
1096
|
+
// the FS walks and the watcher never descend into it (isLatticeStateDir), and
|
|
1097
|
+
// git's listing must agree — an untracked script written there by a run is not
|
|
1098
|
+
// project source, and counting it made status report the index stale mid-run.
|
|
1099
|
+
const inLatticeState = (rel) => rel.split('/').some((part) => (0, directory_1.isLatticeStateDir)(part));
|
|
1100
|
+
const tracked = lsFilesStaged(gitOpts);
|
|
912
1101
|
for (const entry of tracked.split('\0')) {
|
|
913
1102
|
if (!entry)
|
|
914
1103
|
continue;
|
|
@@ -916,6 +1105,8 @@ function collectGitFiles(repoDir, prefix, files, embeddedRoots, includeIgnored =
|
|
|
916
1105
|
if (tab === -1)
|
|
917
1106
|
continue; // --stage always emits "<mode> <object> <stage>\t<path>"
|
|
918
1107
|
const rel = entry.slice(tab + 1);
|
|
1108
|
+
if (inLatticeState(rel))
|
|
1109
|
+
continue;
|
|
919
1110
|
if (entry.slice(0, 6) === '160000') {
|
|
920
1111
|
gitlinkRels.push(rel); // an unexpanded gitlink — recursed into below, not a source file itself
|
|
921
1112
|
continue;
|
|
@@ -929,6 +1120,8 @@ function collectGitFiles(repoDir, prefix, files, embeddedRoots, includeIgnored =
|
|
|
929
1120
|
for (const rel of untracked.split('\0')) {
|
|
930
1121
|
if (!rel)
|
|
931
1122
|
continue;
|
|
1123
|
+
if (inLatticeState(rel))
|
|
1124
|
+
continue;
|
|
932
1125
|
if (rel.endsWith('/')) {
|
|
933
1126
|
// git only emits a trailing-slash directory entry for an embedded repo.
|
|
934
1127
|
// Guard with a .git check anyway, and skip anything else exactly as git
|
|
@@ -999,6 +1192,10 @@ function getGitVisibleFiles(rootDir) {
|
|
|
999
1192
|
// git check-ignore exits 0 if the path IS ignored, 1 if not
|
|
1000
1193
|
(0, child_process_1.execFileSync)('git', ['check-ignore', '-q', path.resolve(rootDir)], { cwd: rootDir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
1001
1194
|
// Directory is gitignored by parent repo — fall back to filesystem walk
|
|
1195
|
+
(0, errors_1.logDebug)('project root is gitignored by a parent repo — falling back to filesystem walk', {
|
|
1196
|
+
rootDir,
|
|
1197
|
+
gitRoot,
|
|
1198
|
+
});
|
|
1002
1199
|
return null;
|
|
1003
1200
|
}
|
|
1004
1201
|
catch {
|
|
@@ -1025,7 +1222,20 @@ function getGitVisibleFiles(rootDir) {
|
|
|
1025
1222
|
visible.add(f);
|
|
1026
1223
|
return visible;
|
|
1027
1224
|
}
|
|
1028
|
-
catch {
|
|
1225
|
+
catch (error) {
|
|
1226
|
+
// Any failure here (git missing, a `git rev-parse`/`ls-files` timeout or
|
|
1227
|
+
// buffer overrun under load, an unreadable repo, unsupported flag combo on
|
|
1228
|
+
// older git, etc.) silently sent every caller to `scanDirectoryWalk` with
|
|
1229
|
+
// zero signal that the fast git-delegated path was skipped — making reports
|
|
1230
|
+
// like #1567 (nested-`.gitignore`-excluded `node_modules` walked into)
|
|
1231
|
+
// hard to triage, since both ignore implementations look correct in
|
|
1232
|
+
// isolation but there was no way to tell which one ran. Log it under the
|
|
1233
|
+
// existing LATTICE_SENSOR_DEBUG gate so a future report can confirm or rule out
|
|
1234
|
+
// the fallback in one step.
|
|
1235
|
+
(0, errors_1.logDebug)('git-based file listing unavailable — falling back to filesystem walk', {
|
|
1236
|
+
rootDir,
|
|
1237
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1238
|
+
});
|
|
1029
1239
|
return null;
|
|
1030
1240
|
}
|
|
1031
1241
|
}
|
|
@@ -1043,21 +1253,102 @@ function getGitVisibleFiles(rootDir) {
|
|
|
1043
1253
|
* case this cannot see (the child status that would report the deletions is gone
|
|
1044
1254
|
* with it); a full `lattice sensor index` reconciles that.
|
|
1045
1255
|
*/
|
|
1046
|
-
function getGitChangedFiles(rootDir) {
|
|
1256
|
+
function getGitChangedFiles(rootDir, sinceCommit) {
|
|
1047
1257
|
try {
|
|
1258
|
+
// `git status` only ever describes the WORKING TREE, so a change that has
|
|
1259
|
+
// been committed leaves no entry and never enters the candidate set — the
|
|
1260
|
+
// hash comparison in getChangedFiles is correct but is never reached for
|
|
1261
|
+
// it, and `pendingChanges` reads 0 while the index is genuinely behind
|
|
1262
|
+
// (#1829). `sinceCommit` — the commit the index was last brought up to
|
|
1263
|
+
// date at — adds the other half: what has been committed since. Callers
|
|
1264
|
+
// that hold no such stamp still get exactly what they always did, the
|
|
1265
|
+
// working-tree changes.
|
|
1048
1266
|
const changes = { modified: [], added: [], deleted: [] };
|
|
1049
1267
|
// Custom extension → language overrides from the project's lattice-sensor.json,
|
|
1050
1268
|
// so change detection sees the same custom-extension files the full index does.
|
|
1051
1269
|
const overrides = (0, project_config_1.loadExtensionOverrides)(rootDir);
|
|
1052
|
-
collectGitStatus(rootDir, '', changes, overrides, loadIncludeIgnoredMatcher(rootDir), loadExcludeMatcher(rootDir));
|
|
1270
|
+
collectGitStatus(rootDir, '', changes, overrides, loadIncludeIgnoredMatcher(rootDir), loadExcludeMatcher(rootDir), sinceCommit ?? undefined);
|
|
1053
1271
|
return changes;
|
|
1054
1272
|
}
|
|
1055
1273
|
catch {
|
|
1056
1274
|
return null;
|
|
1057
1275
|
}
|
|
1058
1276
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1277
|
+
/**
|
|
1278
|
+
* Metadata key: the commit the index was last brought up to date at. Written by
|
|
1279
|
+
* a full index AND by every successful sync — unlike the extraction stamp, which
|
|
1280
|
+
* a sync must not advance because it only touches a subset of files. This one is
|
|
1281
|
+
* about the tree; failed file paths remain explicit retry candidates. (#1829)
|
|
1282
|
+
*/
|
|
1283
|
+
exports.INDEXED_AT_COMMIT_KEY = 'indexed_at_commit';
|
|
1284
|
+
/** HEAD's commit sha, or null in a non-git repo or one with no commits yet. */
|
|
1285
|
+
function getGitHeadSha(rootDir) {
|
|
1286
|
+
try {
|
|
1287
|
+
return (0, child_process_1.execFileSync)('git', ['rev-parse', 'HEAD'], {
|
|
1288
|
+
cwd: rootDir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true,
|
|
1289
|
+
}).trim() || null;
|
|
1290
|
+
}
|
|
1291
|
+
catch {
|
|
1292
|
+
return null;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* NUL-delimited status/path pairs for every path committed
|
|
1297
|
+
* between `sinceCommit` and HEAD. Empty when the stamp IS HEAD, which is the
|
|
1298
|
+
* common case — one cheap git call on the hot path.
|
|
1299
|
+
*/
|
|
1300
|
+
function gitCommittedChangesSince(repoDir, sinceCommit) {
|
|
1301
|
+
// NUL framing preserves Unicode, quotes, tabs and newlines in Git paths.
|
|
1302
|
+
// Let command failures reach getGitChangedFiles: [] would falsely mean clean.
|
|
1303
|
+
const out = (0, child_process_1.execFileSync)('git', ['diff', '--relative', '--name-status', '--no-renames', '-z', sinceCommit, 'HEAD', '--', '.'], {
|
|
1304
|
+
cwd: repoDir, encoding: 'utf-8', timeout: 10000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true,
|
|
1305
|
+
});
|
|
1306
|
+
return out.split('\0');
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Can an INDEX trust the git fast path, given the commit it was built at?
|
|
1310
|
+
*
|
|
1311
|
+
* False means "fall back to the full scan" — the expensive path that compares
|
|
1312
|
+
* every file on disk against the DB, and the only correct read when git cannot
|
|
1313
|
+
* say what happened between the stamp and now:
|
|
1314
|
+
*
|
|
1315
|
+
* - stamp present but unknown to this repo (rebase, gc, shallow clone, a stamp
|
|
1316
|
+
* from a different checkout) — history moved under the index.
|
|
1317
|
+
* - stamp absent while the repo HAS commits — an index built before stamping
|
|
1318
|
+
* existed. One full scan; the next sync stamps it and the fast path returns.
|
|
1319
|
+
*
|
|
1320
|
+
* A repo with NO commits keeps the fast path with or without a stamp: every
|
|
1321
|
+
* file is untracked, so `git status` already sees all of them. Callers with no
|
|
1322
|
+
* index behind them (the exported `getGitChangedFiles`) never ask this — a
|
|
1323
|
+
* working-tree diff is the whole of what they wanted. (#1829)
|
|
1324
|
+
*/
|
|
1325
|
+
function canTrustGitFastPath(rootDir, sinceCommit) {
|
|
1326
|
+
const head = getGitHeadSha(rootDir);
|
|
1327
|
+
if (head == null)
|
|
1328
|
+
return true; // no commits (or not a git repo — caller handles that)
|
|
1329
|
+
if (!sinceCommit)
|
|
1330
|
+
return false;
|
|
1331
|
+
if (sinceCommit === head)
|
|
1332
|
+
return true;
|
|
1333
|
+
try {
|
|
1334
|
+
(0, child_process_1.execFileSync)('git', ['cat-file', '-e', `${sinceCommit}^{commit}`], {
|
|
1335
|
+
cwd: rootDir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true,
|
|
1336
|
+
});
|
|
1337
|
+
return true;
|
|
1338
|
+
}
|
|
1339
|
+
catch {
|
|
1340
|
+
return false;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
function collectGitStatus(repoDir, prefix, out, overrides, includeIgnored = null, exclude = null, sinceCommit) {
|
|
1344
|
+
const output = (0, child_process_1.execFileSync)('git',
|
|
1345
|
+
// `-uall` lists individual untracked files instead of collapsing an
|
|
1346
|
+
// entirely-untracked directory into one `?? dir/` entry, which would
|
|
1347
|
+
// otherwise be dropped here (only embedded git repos are recursed into
|
|
1348
|
+
// below). Nested untracked git repos still collapse to `?? repo/` even
|
|
1349
|
+
// with `-uall` — git never crosses a repo boundary — so the recursion
|
|
1350
|
+
// still handles them. (#1213)
|
|
1351
|
+
['status', '--porcelain', '--no-renames', '-z', '-uall'], { cwd: repoDir, encoding: 'utf-8', timeout: 10000, maxBuffer: 50 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
1061
1352
|
// This repo's own ignore rules — built-in defaults (#407) plus its .gitignore.
|
|
1062
1353
|
// Change detection must exclude the SAME files the full index does, but git
|
|
1063
1354
|
// status hides neither: it ignores nothing for *tracked* paths, and the
|
|
@@ -1069,45 +1360,68 @@ function collectGitStatus(repoDir, prefix, out, overrides, includeIgnored = null
|
|
|
1069
1360
|
// ScopeIgnore: every embedded repo is judged by ITS OWN rules, never the
|
|
1070
1361
|
// parent's. (#766)
|
|
1071
1362
|
const ig = buildDefaultIgnore(repoDir);
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
continue; // Minimum: "XY file"
|
|
1076
|
-
const statusCode = line.substring(0, 2);
|
|
1077
|
-
const rel = (0, utils_1.normalizePath)(line.substring(3));
|
|
1078
|
-
// Untracked directory entries (trailing slash) may hide an embedded repo —
|
|
1079
|
-
// collect for the recursion below instead of treating as a file.
|
|
1080
|
-
if (statusCode === '??' && rel.endsWith('/')) {
|
|
1081
|
-
untrackedDirs.push(rel);
|
|
1082
|
-
continue;
|
|
1083
|
-
}
|
|
1363
|
+
// One classifier for both candidate sources below, so a committed change is
|
|
1364
|
+
// filtered by exactly the rules a working-tree change is (#766, #999, #1829).
|
|
1365
|
+
const classify = (statusCode, rel) => {
|
|
1084
1366
|
const filePath = (0, utils_1.normalizePath)(prefix + rel);
|
|
1085
1367
|
if (!(0, grammars_1.isSourceFile)(filePath, overrides))
|
|
1086
|
-
|
|
1368
|
+
return;
|
|
1087
1369
|
if (statusCode.includes('D')) {
|
|
1088
1370
|
// Deletions stay unfiltered: getChangedFiles acts on one only when the
|
|
1089
1371
|
// path is already tracked in the DB, where removal is always correct — and
|
|
1090
1372
|
// that lets a newly-excluded dir's stale rows clean themselves up. (#766)
|
|
1091
1373
|
out.deleted.push(filePath);
|
|
1092
|
-
|
|
1374
|
+
return;
|
|
1093
1375
|
}
|
|
1376
|
+
// Lattice owns `.lattice/`: the full scan and git listing skip it, so a
|
|
1377
|
+
// change there is never pending (a run's untracked scripts would otherwise
|
|
1378
|
+
// read as an added file and mark the index stale).
|
|
1379
|
+
if (filePath.split('/').some((part) => (0, directory_1.isLatticeStateDir)(part)))
|
|
1380
|
+
return;
|
|
1094
1381
|
// Added (`??`) / modified files inside an excluded dir must not enter the
|
|
1095
1382
|
// index — match against the repo-relative path, same as the full scan. (#766)
|
|
1096
1383
|
if (ig.ignores(rel))
|
|
1097
|
-
|
|
1384
|
+
return;
|
|
1098
1385
|
// User `lattice-sensor.json` `exclude` (#999) is project-root-relative, so it's
|
|
1099
1386
|
// matched against the full path — sync must not re-add a tracked file the
|
|
1100
1387
|
// full index now keeps out. Deletions above stay unfiltered so a file that
|
|
1101
1388
|
// WAS indexed before an exclude was added still cleans itself out.
|
|
1102
1389
|
if (exclude && exclude.ignores(filePath))
|
|
1103
|
-
|
|
1390
|
+
return;
|
|
1104
1391
|
if (statusCode === '??') {
|
|
1105
1392
|
out.added.push(filePath);
|
|
1106
1393
|
}
|
|
1107
1394
|
else {
|
|
1108
|
-
// M, MM, AM, A (staged), etc. — treat as modified
|
|
1395
|
+
// M, MM, AM, A (staged), etc. — treat as modified. getChangedFiles
|
|
1396
|
+
// re-decides added-vs-modified from the DB, so a committed `A` that the
|
|
1397
|
+
// index never saw still lands in `added`.
|
|
1109
1398
|
out.modified.push(filePath);
|
|
1110
1399
|
}
|
|
1400
|
+
};
|
|
1401
|
+
const untrackedDirs = [];
|
|
1402
|
+
for (const line of output.split('\0')) {
|
|
1403
|
+
if (line.length < 4)
|
|
1404
|
+
continue; // Minimum: "XY file"
|
|
1405
|
+
const statusCode = line.substring(0, 2);
|
|
1406
|
+
const rel = (0, utils_1.normalizePath)(line.substring(3));
|
|
1407
|
+
// Untracked directory entries (trailing slash) may hide an embedded repo —
|
|
1408
|
+
// collect for the recursion below instead of treating as a file.
|
|
1409
|
+
if (statusCode === '??' && rel.endsWith('/')) {
|
|
1410
|
+
untrackedDirs.push(rel);
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1413
|
+
classify(statusCode, rel);
|
|
1414
|
+
}
|
|
1415
|
+
// Committed but unindexed: everything between the commit this index was last
|
|
1416
|
+
// brought up to date at and HEAD. `git status` cannot see these — committing
|
|
1417
|
+
// is precisely what removes a file from its output — so without this pass a
|
|
1418
|
+
// `git commit` makes a real pending change read as zero (#1829). The stamp
|
|
1419
|
+
// belongs to the ROOT repo, so the embedded-repo recursion below passes none.
|
|
1420
|
+
if (sinceCommit) {
|
|
1421
|
+
const fields = gitCommittedChangesSince(repoDir, sinceCommit);
|
|
1422
|
+
for (let i = 0; i + 1 < fields.length; i += 2) {
|
|
1423
|
+
classify(`${fields[i].charAt(0)} `, (0, utils_1.normalizePath)(fields[i + 1]));
|
|
1424
|
+
}
|
|
1111
1425
|
}
|
|
1112
1426
|
// Recurse embedded repos found under untracked dirs (at the dir itself or
|
|
1113
1427
|
// nested deeper). Gitignored dirs are walked only for the directories the
|
|
@@ -1149,11 +1463,16 @@ function scanDirectory(rootDir, onProgress) {
|
|
|
1149
1463
|
// Fallback: walk filesystem for non-git projects
|
|
1150
1464
|
return scanDirectoryWalk(rootDir, onProgress);
|
|
1151
1465
|
}
|
|
1152
|
-
/**
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1466
|
+
/** Record one file the scan declined to index. */
|
|
1467
|
+
function tallySkip(stats, rel) {
|
|
1468
|
+
if (!stats)
|
|
1469
|
+
return;
|
|
1470
|
+
const ext = path.extname(rel).toLowerCase();
|
|
1471
|
+
if (!ext)
|
|
1472
|
+
return;
|
|
1473
|
+
stats.unsupportedByExtension.set(ext, (stats.unsupportedByExtension.get(ext) ?? 0) + 1);
|
|
1474
|
+
}
|
|
1475
|
+
async function scanDirectoryAsync(rootDir, onProgress, stats) {
|
|
1157
1476
|
// Custom extension → language overrides from the project's lattice-sensor.json.
|
|
1158
1477
|
const overrides = (0, project_config_1.loadExtensionOverrides)(rootDir);
|
|
1159
1478
|
const gitFiles = getGitVisibleFiles(rootDir);
|
|
@@ -1170,15 +1489,18 @@ async function scanDirectoryAsync(rootDir, onProgress) {
|
|
|
1170
1489
|
await new Promise(r => setImmediate(r));
|
|
1171
1490
|
}
|
|
1172
1491
|
}
|
|
1492
|
+
else {
|
|
1493
|
+
tallySkip(stats, filePath);
|
|
1494
|
+
}
|
|
1173
1495
|
}
|
|
1174
1496
|
return files;
|
|
1175
1497
|
}
|
|
1176
|
-
return scanDirectoryWalk(rootDir, onProgress);
|
|
1498
|
+
return scanDirectoryWalk(rootDir, onProgress, stats);
|
|
1177
1499
|
}
|
|
1178
1500
|
/**
|
|
1179
1501
|
* Filesystem walk fallback for non-git projects.
|
|
1180
1502
|
*/
|
|
1181
|
-
function scanDirectoryWalk(rootDir, onProgress) {
|
|
1503
|
+
function scanDirectoryWalk(rootDir, onProgress, stats) {
|
|
1182
1504
|
const files = [];
|
|
1183
1505
|
let count = 0;
|
|
1184
1506
|
const visitedDirs = new Set();
|
|
@@ -1250,10 +1572,15 @@ function scanDirectoryWalk(rootDir, onProgress) {
|
|
|
1250
1572
|
}
|
|
1251
1573
|
}
|
|
1252
1574
|
else if (stat.isFile()) {
|
|
1253
|
-
if (!isIgnored(fullPath, false, active)
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1575
|
+
if (!isIgnored(fullPath, false, active)) {
|
|
1576
|
+
if ((0, grammars_1.isSourceFile)(relativePath, overrides)) {
|
|
1577
|
+
files.push(relativePath);
|
|
1578
|
+
count++;
|
|
1579
|
+
onProgress?.(count, relativePath);
|
|
1580
|
+
}
|
|
1581
|
+
else {
|
|
1582
|
+
tallySkip(stats, relativePath);
|
|
1583
|
+
}
|
|
1257
1584
|
}
|
|
1258
1585
|
}
|
|
1259
1586
|
}
|
|
@@ -1268,10 +1595,15 @@ function scanDirectoryWalk(rootDir, onProgress) {
|
|
|
1268
1595
|
}
|
|
1269
1596
|
}
|
|
1270
1597
|
else if (entry.isFile()) {
|
|
1271
|
-
if (!isIgnored(fullPath, false, active)
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1598
|
+
if (!isIgnored(fullPath, false, active)) {
|
|
1599
|
+
if ((0, grammars_1.isSourceFile)(relativePath, overrides)) {
|
|
1600
|
+
files.push(relativePath);
|
|
1601
|
+
count++;
|
|
1602
|
+
onProgress?.(count, relativePath);
|
|
1603
|
+
}
|
|
1604
|
+
else {
|
|
1605
|
+
tallySkip(stats, relativePath);
|
|
1606
|
+
}
|
|
1275
1607
|
}
|
|
1276
1608
|
}
|
|
1277
1609
|
}
|
|
@@ -1344,10 +1676,57 @@ class ExtractionOrchestrator {
|
|
|
1344
1676
|
* hasn't run yet so single-file re-index paths can detect on the spot.
|
|
1345
1677
|
*/
|
|
1346
1678
|
detectedFrameworkNames = null;
|
|
1679
|
+
/**
|
|
1680
|
+
* Scope matcher for SCOPED syncs, memoized on the mtimes of the two root
|
|
1681
|
+
* files it is derived from (`lattice-sensor.json`, `.gitignore`). See
|
|
1682
|
+
* {@link scopedSyncMatcher}.
|
|
1683
|
+
*/
|
|
1684
|
+
scopedMatcher = null;
|
|
1347
1685
|
constructor(rootDir, queries) {
|
|
1348
1686
|
this.rootDir = rootDir;
|
|
1349
1687
|
this.queries = queries;
|
|
1350
1688
|
}
|
|
1689
|
+
/**
|
|
1690
|
+
* The scope matcher a scoped sync applies to the paths it was handed — the
|
|
1691
|
+
* same `buildScopeIgnore` the full scan uses, so an explicitly-passed path
|
|
1692
|
+
* that is OUT of scope (a user `exclude` in `lattice-sensor.json`, a `.gitignore`
|
|
1693
|
+
* rule, a built-in default) is treated exactly as the full walk would treat
|
|
1694
|
+
* it: absent, hence removed if tracked, never parsed (#1590).
|
|
1695
|
+
*
|
|
1696
|
+
* Memoized on the root config + root `.gitignore` mtimes: building the
|
|
1697
|
+
* matcher runs embedded-repo discovery (`git ls-files`), which would defeat
|
|
1698
|
+
* the scoped path's whole point (skipping O(repo) work) if paid per sync.
|
|
1699
|
+
* Two `stat`s per sync while nothing changed. An embedded repo created
|
|
1700
|
+
* between config edits joins the scoped matcher on the next full sync, the
|
|
1701
|
+
* same lifecycle the watcher's own matcher already has.
|
|
1702
|
+
*/
|
|
1703
|
+
scopedSyncMatcher() {
|
|
1704
|
+
// Bust when any root-level exclude source the matcher reads may have
|
|
1705
|
+
// changed. Nested `.gitignore` edits force a full watcher sync, which
|
|
1706
|
+
// clears this cache (see the full-reconcile branch in sync()).
|
|
1707
|
+
const gitDir = resolveGitDir(this.rootDir);
|
|
1708
|
+
const key = [
|
|
1709
|
+
project_config_1.PROJECT_CONFIG_FILENAME,
|
|
1710
|
+
'.gitignore',
|
|
1711
|
+
gitDir ? path.join(gitDir, 'info', 'exclude') : '',
|
|
1712
|
+
]
|
|
1713
|
+
.map((name) => {
|
|
1714
|
+
if (!name)
|
|
1715
|
+
return '-';
|
|
1716
|
+
try {
|
|
1717
|
+
return String(fs.statSync(path.isAbsolute(name) ? name : path.join(this.rootDir, name)).mtimeMs);
|
|
1718
|
+
}
|
|
1719
|
+
catch {
|
|
1720
|
+
return '-';
|
|
1721
|
+
}
|
|
1722
|
+
})
|
|
1723
|
+
.join('|');
|
|
1724
|
+
if (this.scopedMatcher && this.scopedMatcher.key === key)
|
|
1725
|
+
return this.scopedMatcher.matcher;
|
|
1726
|
+
const matcher = buildScopeIgnore(this.rootDir);
|
|
1727
|
+
this.scopedMatcher = { key, matcher };
|
|
1728
|
+
return matcher;
|
|
1729
|
+
}
|
|
1351
1730
|
/**
|
|
1352
1731
|
* Build a filesystem-backed ResolutionContext sufficient for framework
|
|
1353
1732
|
* detection. Graph-query methods (getNodesByName etc.) return empty because
|
|
@@ -1381,7 +1760,9 @@ class ExtractionOrchestrator {
|
|
|
1381
1760
|
if (!full)
|
|
1382
1761
|
return null;
|
|
1383
1762
|
try {
|
|
1384
|
-
|
|
1763
|
+
// Framework detectors scan source by name; a file over the size
|
|
1764
|
+
// limit was never indexed and must not be decoded here either (#1910).
|
|
1765
|
+
return (0, file_limits_1.readBoundedSourceSync)(full).bytes?.toString('utf8') ?? null;
|
|
1385
1766
|
}
|
|
1386
1767
|
catch {
|
|
1387
1768
|
return null;
|
|
@@ -1463,6 +1844,7 @@ class ExtractionOrchestrator {
|
|
|
1463
1844
|
// early-run 5-10s single stalls were observed on 95k-file repos but never
|
|
1464
1845
|
// attributed — these labels settle scan vs framework-detect vs grammars.
|
|
1465
1846
|
const tScan = Date.now();
|
|
1847
|
+
const skipStats = { unsupportedByExtension: new Map() };
|
|
1466
1848
|
const files = await scanDirectoryAsync(this.rootDir, (current, file) => {
|
|
1467
1849
|
onProgress?.({
|
|
1468
1850
|
phase: 'scanning',
|
|
@@ -1470,9 +1852,26 @@ class ExtractionOrchestrator {
|
|
|
1470
1852
|
total: 0,
|
|
1471
1853
|
currentFile: file,
|
|
1472
1854
|
});
|
|
1473
|
-
});
|
|
1855
|
+
}, skipStats);
|
|
1474
1856
|
if (process.env.LATTICE_SENSOR_SYNTH_TIMINGS)
|
|
1475
1857
|
console.error(`[phase-timing] scan: ${Date.now() - tScan}ms (${files.length} files)`);
|
|
1858
|
+
/** Only meaningful when nothing was indexable — see IndexResult (#1502). */
|
|
1859
|
+
const skipSummary = () => {
|
|
1860
|
+
let total = 0;
|
|
1861
|
+
for (const n of skipStats.unsupportedByExtension.values())
|
|
1862
|
+
total += n;
|
|
1863
|
+
if (total === 0)
|
|
1864
|
+
return {};
|
|
1865
|
+
const top = [...skipStats.unsupportedByExtension.entries()]
|
|
1866
|
+
.map(([ext, count]) => ({ ext, count }))
|
|
1867
|
+
.sort((a, b) => b.count - a.count || a.ext.localeCompare(b.ext))
|
|
1868
|
+
.slice(0, 5);
|
|
1869
|
+
return { filesSkippedUnsupported: total, topUnsupportedExtensions: top };
|
|
1870
|
+
};
|
|
1871
|
+
// A re-index over an existing DB skips unchanged-hash files at the store,
|
|
1872
|
+
// which would preserve wiped zero-node rows (#1541) — drop them first so
|
|
1873
|
+
// this run stores their files fresh. No-op on a fresh DB.
|
|
1874
|
+
this.healZeroNodeRows();
|
|
1476
1875
|
// Detect frameworks once per indexAll run using the scanned file list.
|
|
1477
1876
|
// Names are passed to each parse call so framework-specific extractors
|
|
1478
1877
|
// (route nodes, middleware, etc.) run after the tree-sitter pass.
|
|
@@ -1508,11 +1907,7 @@ class ExtractionOrchestrator {
|
|
|
1508
1907
|
});
|
|
1509
1908
|
await new Promise(resolve => setImmediate(resolve));
|
|
1510
1909
|
// Detect needed languages and load grammars in the parse worker
|
|
1511
|
-
const neededLanguages =
|
|
1512
|
-
// .h files default to 'c' but may be C++ — ensure cpp grammar is loaded when c is needed
|
|
1513
|
-
if (neededLanguages.includes('c') && !neededLanguages.includes('cpp')) {
|
|
1514
|
-
neededLanguages.push('cpp');
|
|
1515
|
-
}
|
|
1910
|
+
const neededLanguages = preloadLanguagesForFiles(files, overrides);
|
|
1516
1911
|
// Parse files on a pool of worker threads (keeps the main thread free for UI
|
|
1517
1912
|
// and uses every core). Falls back to in-process parsing when the compiled
|
|
1518
1913
|
// worker is unavailable (e.g. running from source in tests).
|
|
@@ -1619,29 +2014,27 @@ class ExtractionOrchestrator {
|
|
|
1619
2014
|
// Store: on the writer thread when active (fresh DB — bundles applied
|
|
1620
2015
|
// in the same file order this chain dispatches them), else on the main
|
|
1621
2016
|
// thread (SQLite connections are per-thread).
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
if (
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
});
|
|
1635
|
-
}
|
|
1636
|
-
else {
|
|
1637
|
-
storeWriter.send(this.buildFreshStoreBundle(filePath, content, language, stats, result));
|
|
1638
|
-
}
|
|
1639
|
-
await storeWriter.waitBelow(STORE_WRITER_WINDOW);
|
|
2017
|
+
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
2018
|
+
if (storeWriter) {
|
|
2019
|
+
if (result.kernelBuffers) {
|
|
2020
|
+
// Buffers go to the writer as-is; the worker decodes + finalizes.
|
|
2021
|
+
// The main thread's only per-file work stays O(1) + the content hash.
|
|
2022
|
+
storeWriter.send({
|
|
2023
|
+
kernel: true,
|
|
2024
|
+
filePath,
|
|
2025
|
+
language,
|
|
2026
|
+
buffers: result.kernelBuffers,
|
|
2027
|
+
file: this.buildFileRecord(filePath, content, language, stats, nodeCount, result.errors),
|
|
2028
|
+
});
|
|
1640
2029
|
}
|
|
1641
2030
|
else {
|
|
1642
|
-
|
|
1643
|
-
await this.storeExtractionResult(filePath, content, language, stats, materialized, commitYield);
|
|
2031
|
+
storeWriter.send(this.buildFreshStoreBundle(filePath, content, language, stats, result));
|
|
1644
2032
|
}
|
|
2033
|
+
await storeWriter.waitBelow(STORE_WRITER_WINDOW);
|
|
2034
|
+
}
|
|
2035
|
+
else {
|
|
2036
|
+
const materialized = (0, kernel_1.materializeKernelResult)(result, filePath, language);
|
|
2037
|
+
await this.storeExtractionResult(filePath, content, language, stats, materialized, commitYield);
|
|
1645
2038
|
}
|
|
1646
2039
|
if (result.errors.length > 0) {
|
|
1647
2040
|
for (const err of result.errors) {
|
|
@@ -1672,16 +2065,19 @@ class ExtractionOrchestrator {
|
|
|
1672
2065
|
}
|
|
1673
2066
|
onProgress?.({ phase: 'parsing', current: processed, total, currentFile: filePath });
|
|
1674
2067
|
};
|
|
1675
|
-
const recordParseFailure = (filePath, err) => {
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
2068
|
+
const recordParseFailure = async (filePath, content, stats, err) => {
|
|
2069
|
+
await storeResult(filePath, content, stats, {
|
|
2070
|
+
nodes: [],
|
|
2071
|
+
edges: [],
|
|
2072
|
+
unresolvedReferences: [],
|
|
2073
|
+
errors: [{
|
|
2074
|
+
message: err instanceof Error ? err.message : String(err),
|
|
2075
|
+
filePath,
|
|
2076
|
+
severity: 'error',
|
|
2077
|
+
code: 'parse_error',
|
|
2078
|
+
}],
|
|
2079
|
+
durationMs: 0,
|
|
1683
2080
|
});
|
|
1684
|
-
onProgress?.({ phase: 'parsing', current: processed, total });
|
|
1685
2081
|
};
|
|
1686
2082
|
// Commit buffered parses to the DB in file order, advancing the cursor over
|
|
1687
2083
|
// contiguous completed results. Runs after each parse settles (and once more
|
|
@@ -1706,7 +2102,7 @@ class ExtractionOrchestrator {
|
|
|
1706
2102
|
if (item.ok)
|
|
1707
2103
|
await storeResult(item.filePath, item.content, item.stats, item.result);
|
|
1708
2104
|
else
|
|
1709
|
-
recordParseFailure(item.filePath, item.err);
|
|
2105
|
+
await recordParseFailure(item.filePath, item.content, item.stats, item.err);
|
|
1710
2106
|
}
|
|
1711
2107
|
}
|
|
1712
2108
|
catch (err) {
|
|
@@ -1726,7 +2122,7 @@ class ExtractionOrchestrator {
|
|
|
1726
2122
|
completed.set(seq, { ok: true, filePath, content, stats, result });
|
|
1727
2123
|
}
|
|
1728
2124
|
catch (parseErr) {
|
|
1729
|
-
completed.set(seq, { ok: false, filePath, err: parseErr });
|
|
2125
|
+
completed.set(seq, { ok: false, filePath, content, stats, err: parseErr });
|
|
1730
2126
|
}
|
|
1731
2127
|
flushOrdered();
|
|
1732
2128
|
})();
|
|
@@ -1739,8 +2135,11 @@ class ExtractionOrchestrator {
|
|
|
1739
2135
|
// window has room. When nothing is in flight but the window is still full,
|
|
1740
2136
|
// the async commit chain is what's behind — await it so the cursor
|
|
1741
2137
|
// advances (buffered items hold whole file contents, so this bound is
|
|
1742
|
-
// load-bearing for memory).
|
|
1743
|
-
|
|
2138
|
+
// load-bearing for memory). Once a store has failed the cursor never
|
|
2139
|
+
// moves again — flushOrdered returns at once — so stop waiting and let
|
|
2140
|
+
// the drain below rethrow; waiting would spin on microtasks forever,
|
|
2141
|
+
// pinning a core and starving every timer in the process.
|
|
2142
|
+
while (nextSeq - nextToStore >= windowSize && !flushError && !aborted) {
|
|
1744
2143
|
if (inFlight.size > 0)
|
|
1745
2144
|
await Promise.race(inFlight);
|
|
1746
2145
|
else
|
|
@@ -1765,8 +2164,20 @@ class ExtractionOrchestrator {
|
|
|
1765
2164
|
(0, errors_1.logWarn)('Path traversal blocked in batch reader', { filePath: fp });
|
|
1766
2165
|
return { filePath: fp, content: null, stats: null, error: new Error('Path traversal blocked') };
|
|
1767
2166
|
}
|
|
1768
|
-
|
|
1769
|
-
|
|
2167
|
+
// Stat first: a file over the size limit is stored as skipped
|
|
2168
|
+
// without ever being read or decoded (#1910), so ten oversize
|
|
2169
|
+
// fixtures in one I/O batch no longer cost their size in RSS.
|
|
2170
|
+
const { stats, bytes } = await (0, file_limits_1.readBoundedSource)(fullPath);
|
|
2171
|
+
if (bytes === null) {
|
|
2172
|
+
return { filePath: fp, content: (0, file_limits_1.oversizeStamp)(stats.size), stats, error: null };
|
|
2173
|
+
}
|
|
2174
|
+
// Read bytes, not text: a `.ts` that is really an MPEG transport
|
|
2175
|
+
// stream (#1910) is recognised from its head here, at no extra I/O,
|
|
2176
|
+
// and never decoded or parsed.
|
|
2177
|
+
if (isMpegTsBytes(fp, bytes)) {
|
|
2178
|
+
return { filePath: fp, content: null, stats: null, error: null, skipped: true };
|
|
2179
|
+
}
|
|
2180
|
+
const content = bytes.toString('utf-8');
|
|
1770
2181
|
return { filePath: fp, content, stats, error: null };
|
|
1771
2182
|
}
|
|
1772
2183
|
catch (err) {
|
|
@@ -1775,11 +2186,17 @@ class ExtractionOrchestrator {
|
|
|
1775
2186
|
}));
|
|
1776
2187
|
// Dispatch each readable file into the bounded parse window; the window
|
|
1777
2188
|
// stores results on the main thread as they arrive.
|
|
1778
|
-
for (const { filePath, content, stats, error } of fileContents) {
|
|
2189
|
+
for (const { filePath, content, stats, error, skipped } of fileContents) {
|
|
1779
2190
|
if (signal?.aborted) {
|
|
1780
2191
|
aborted = true;
|
|
1781
2192
|
break;
|
|
1782
2193
|
}
|
|
2194
|
+
if (skipped) {
|
|
2195
|
+
// Not a source file after all — counted as done, stored as nothing.
|
|
2196
|
+
processed++;
|
|
2197
|
+
onProgress?.({ phase: 'parsing', current: processed, total });
|
|
2198
|
+
continue;
|
|
2199
|
+
}
|
|
1783
2200
|
if (error || content === null || stats === null) {
|
|
1784
2201
|
processed++;
|
|
1785
2202
|
filesErrored++;
|
|
@@ -1792,21 +2209,24 @@ class ExtractionOrchestrator {
|
|
|
1792
2209
|
onProgress?.({ phase: 'parsing', current: processed, total });
|
|
1793
2210
|
continue;
|
|
1794
2211
|
}
|
|
1795
|
-
// Honour
|
|
2212
|
+
// Honour MAX_SOURCE_FILE_SIZE_BYTES. Without this check, vendored generated
|
|
1796
2213
|
// headers, minified bundles, and other multi-MB files get indexed,
|
|
1797
2214
|
// wasting WASM heap and the worker recycle budget on inputs with no
|
|
1798
2215
|
// useful symbols. The single-file extractFile path already enforces
|
|
1799
2216
|
// this; the bulk path used to silently skip the check.
|
|
1800
|
-
if (stats.size >
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
2217
|
+
if (stats.size > file_limits_1.MAX_SOURCE_FILE_SIZE_BYTES) {
|
|
2218
|
+
await storeResult(filePath, content, stats, {
|
|
2219
|
+
nodes: [],
|
|
2220
|
+
edges: [],
|
|
2221
|
+
unresolvedReferences: [],
|
|
2222
|
+
errors: [{
|
|
2223
|
+
message: `File exceeds max size (${stats.size} > ${file_limits_1.MAX_SOURCE_FILE_SIZE_BYTES})`,
|
|
2224
|
+
filePath,
|
|
2225
|
+
severity: 'warning',
|
|
2226
|
+
code: 'size_exceeded',
|
|
2227
|
+
}],
|
|
2228
|
+
durationMs: 0,
|
|
1808
2229
|
});
|
|
1809
|
-
onProgress?.({ phase: 'parsing', current: processed, total });
|
|
1810
2230
|
continue;
|
|
1811
2231
|
}
|
|
1812
2232
|
// Parse on the pool (main thread stays unblocked). Errors/timeouts are
|
|
@@ -1824,6 +2244,9 @@ class ExtractionOrchestrator {
|
|
|
1824
2244
|
if (flushError) {
|
|
1825
2245
|
if (storeWriter)
|
|
1826
2246
|
await storeWriter.close();
|
|
2247
|
+
// Its worker threads would otherwise outlive the failed index.
|
|
2248
|
+
if (pool)
|
|
2249
|
+
await pool.destroy();
|
|
1827
2250
|
throw flushError;
|
|
1828
2251
|
}
|
|
1829
2252
|
// All bundles are posted; wait for the writer to apply them, then close
|
|
@@ -1852,6 +2275,7 @@ class ExtractionOrchestrator {
|
|
|
1852
2275
|
filesSkipped,
|
|
1853
2276
|
filesErrored,
|
|
1854
2277
|
filesDiscovered: total,
|
|
2278
|
+
...skipSummary(),
|
|
1855
2279
|
nodesCreated: totalNodes,
|
|
1856
2280
|
edgesCreated: totalEdges,
|
|
1857
2281
|
errors: [{ message: 'Aborted', severity: 'error' }, ...errors],
|
|
@@ -1894,7 +2318,11 @@ class ExtractionOrchestrator {
|
|
|
1894
2318
|
const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, filePath);
|
|
1895
2319
|
if (!fullPath)
|
|
1896
2320
|
continue;
|
|
1897
|
-
|
|
2321
|
+
// Bounded like the first read: the file may have grown since (#1910).
|
|
2322
|
+
const bytes = (await (0, file_limits_1.readBoundedSource)(fullPath)).bytes;
|
|
2323
|
+
if (bytes === null)
|
|
2324
|
+
continue;
|
|
2325
|
+
content = bytes.toString('utf8');
|
|
1898
2326
|
}
|
|
1899
2327
|
catch {
|
|
1900
2328
|
continue;
|
|
@@ -1907,8 +2335,15 @@ class ExtractionOrchestrator {
|
|
|
1907
2335
|
stillFailing.push(errEntry);
|
|
1908
2336
|
continue;
|
|
1909
2337
|
}
|
|
2338
|
+
// The pool hands kernel results back as an undecoded buffer transport
|
|
2339
|
+
// (`nodes`/`edges` EMPTY, tables in kernelBuffers). The main loop
|
|
2340
|
+
// decodes or forwards to the store worker; this path stores directly,
|
|
2341
|
+
// so decode here — otherwise a kernel-language retry passes the gate
|
|
2342
|
+
// below via `errors.length === 0`, stores nothing, and the file is
|
|
2343
|
+
// permanently recorded as "(0 symbols)" with the error erased (#1541).
|
|
2344
|
+
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
2345
|
+
result = (0, kernel_1.materializeKernelResult)(result, filePath, language);
|
|
1910
2346
|
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
1911
|
-
const language = (0, grammars_1.detectLanguage)(filePath, content, overrides);
|
|
1912
2347
|
const stats = await fsp.stat(path.join(this.rootDir, filePath));
|
|
1913
2348
|
await this.storeExtractionResult(filePath, content, language, stats, result, commitYield);
|
|
1914
2349
|
const idx = errors.indexOf(errEntry);
|
|
@@ -1937,7 +2372,10 @@ class ExtractionOrchestrator {
|
|
|
1937
2372
|
const fullPath = (0, utils_1.validatePathWithinRoot)(this.rootDir, filePath);
|
|
1938
2373
|
if (!fullPath)
|
|
1939
2374
|
continue;
|
|
1940
|
-
|
|
2375
|
+
const bytes = (await (0, file_limits_1.readBoundedSource)(fullPath)).bytes;
|
|
2376
|
+
if (bytes === null)
|
|
2377
|
+
continue;
|
|
2378
|
+
fullContent = bytes.toString('utf8');
|
|
1941
2379
|
}
|
|
1942
2380
|
catch {
|
|
1943
2381
|
continue;
|
|
@@ -1955,13 +2393,20 @@ class ExtractionOrchestrator {
|
|
|
1955
2393
|
catch {
|
|
1956
2394
|
continue;
|
|
1957
2395
|
}
|
|
2396
|
+
// Same undecoded-transport hazard as the first retry pass (#1541).
|
|
2397
|
+
const language = (0, grammars_1.detectLanguage)(filePath, fullContent, overrides);
|
|
2398
|
+
result = (0, kernel_1.materializeKernelResult)(result, filePath, language);
|
|
1958
2399
|
if (result.nodes.length > 0 || result.errors.length === 0) {
|
|
1959
|
-
const language = (0, grammars_1.detectLanguage)(filePath, fullContent, overrides);
|
|
1960
2400
|
const stats = await fsp.stat(path.join(this.rootDir, filePath));
|
|
1961
2401
|
await this.storeExtractionResult(filePath, fullContent, language, stats, result, commitYield);
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2402
|
+
// Salvaged from comment-stripped source: keep a visible trace in
|
|
2403
|
+
// the summary instead of erasing the failure outright — the
|
|
2404
|
+
// stored result may be missing whatever the failing parse choked
|
|
2405
|
+
// on, and a silently "clean" file here is how an index quietly
|
|
2406
|
+
// disagrees with a later per-file sync of the same bytes (#1565).
|
|
2407
|
+
errEntry.severity = 'warning';
|
|
2408
|
+
errEntry.code = 'salvaged_stripped';
|
|
2409
|
+
errEntry.message = `Indexed from comment-stripped source after repeated parse failures (symbols may be incomplete until the file is re-indexed): ${errEntry.message}`;
|
|
1965
2410
|
filesErrored--;
|
|
1966
2411
|
filesIndexed++;
|
|
1967
2412
|
totalNodes += result.nodes.length;
|
|
@@ -1980,6 +2425,7 @@ class ExtractionOrchestrator {
|
|
|
1980
2425
|
filesSkipped,
|
|
1981
2426
|
filesErrored,
|
|
1982
2427
|
filesDiscovered: total,
|
|
2428
|
+
...skipSummary(),
|
|
1983
2429
|
nodesCreated: totalNodes,
|
|
1984
2430
|
edgesCreated: totalEdges,
|
|
1985
2431
|
errors,
|
|
@@ -2050,8 +2496,14 @@ class ExtractionOrchestrator {
|
|
|
2050
2496
|
let content;
|
|
2051
2497
|
let stats;
|
|
2052
2498
|
try {
|
|
2053
|
-
|
|
2054
|
-
|
|
2499
|
+
// An oversize file is stored as skipped; its bytes are never needed (#1910).
|
|
2500
|
+
const read = await (0, file_limits_1.readBoundedSource)(fullPath);
|
|
2501
|
+
stats = read.stats;
|
|
2502
|
+
// An MPEG transport stream named `.ts` is not TypeScript (#1910).
|
|
2503
|
+
if (read.bytes !== null && isMpegTsBytes(relativePath, read.bytes)) {
|
|
2504
|
+
return { nodes: [], edges: [], unresolvedReferences: [], errors: [], durationMs: 0 };
|
|
2505
|
+
}
|
|
2506
|
+
content = read.bytes === null ? (0, file_limits_1.oversizeStamp)(stats.size) : read.bytes.toString('utf8');
|
|
2055
2507
|
}
|
|
2056
2508
|
catch (error) {
|
|
2057
2509
|
return {
|
|
@@ -2088,15 +2540,16 @@ class ExtractionOrchestrator {
|
|
|
2088
2540
|
durationMs: 0,
|
|
2089
2541
|
};
|
|
2090
2542
|
}
|
|
2543
|
+
const language = (0, grammars_1.detectLanguage)(relativePath, content, (0, project_config_1.loadExtensionOverrides)(this.rootDir));
|
|
2091
2544
|
// Check file size
|
|
2092
|
-
if (stats.size >
|
|
2093
|
-
|
|
2545
|
+
if (stats.size > file_limits_1.MAX_SOURCE_FILE_SIZE_BYTES) {
|
|
2546
|
+
const result = {
|
|
2094
2547
|
nodes: [],
|
|
2095
2548
|
edges: [],
|
|
2096
2549
|
unresolvedReferences: [],
|
|
2097
2550
|
errors: [
|
|
2098
2551
|
{
|
|
2099
|
-
message: `File exceeds max size (${stats.size} > ${
|
|
2552
|
+
message: `File exceeds max size (${stats.size} > ${file_limits_1.MAX_SOURCE_FILE_SIZE_BYTES})`,
|
|
2100
2553
|
filePath: relativePath,
|
|
2101
2554
|
severity: 'warning',
|
|
2102
2555
|
code: 'size_exceeded',
|
|
@@ -2104,9 +2557,10 @@ class ExtractionOrchestrator {
|
|
|
2104
2557
|
],
|
|
2105
2558
|
durationMs: 0,
|
|
2106
2559
|
};
|
|
2560
|
+
await this.storeExtractionResult(relativePath, content, language, stats, result, (0, cooperative_yield_1.createYielder)());
|
|
2561
|
+
return result;
|
|
2107
2562
|
}
|
|
2108
2563
|
// Detect language (honoring the project's lattice-sensor.json extension overrides)
|
|
2109
|
-
const language = (0, grammars_1.detectLanguage)(relativePath, content, (0, project_config_1.loadExtensionOverrides)(this.rootDir));
|
|
2110
2564
|
if (!(0, grammars_1.isLanguageSupported)(language)) {
|
|
2111
2565
|
return {
|
|
2112
2566
|
nodes: [],
|
|
@@ -2122,15 +2576,43 @@ class ExtractionOrchestrator {
|
|
|
2122
2576
|
const frameworkNames = this.ensureDetectedFrameworks();
|
|
2123
2577
|
const result = (0, tree_sitter_1.extractFromSource)(relativePath, content, language, frameworkNames);
|
|
2124
2578
|
// Store in database
|
|
2125
|
-
|
|
2126
|
-
await this.storeExtractionResult(relativePath, content, language, stats, result, (0, cooperative_yield_1.createYielder)());
|
|
2127
|
-
}
|
|
2579
|
+
await this.storeExtractionResult(relativePath, content, language, stats, result, (0, cooperative_yield_1.createYielder)());
|
|
2128
2580
|
return result;
|
|
2129
2581
|
}
|
|
2130
2582
|
/**
|
|
2131
2583
|
* Store extraction result in database
|
|
2132
2584
|
*/
|
|
2585
|
+
/**
|
|
2586
|
+
* Delete file rows recorded with ZERO nodes so their files re-index.
|
|
2587
|
+
*
|
|
2588
|
+
* No extraction path stores an empty, error-free result for a
|
|
2589
|
+
* symbol-bearing language — even an empty file keeps its file node — so a
|
|
2590
|
+
* zero-node row is a wiped one (#1541: an interrupted parse's retry stored
|
|
2591
|
+
* an undecoded kernel transport). The wiped row's content hash matches the
|
|
2592
|
+
* on-disk bytes, so every hash-based reconcile skips the file forever;
|
|
2593
|
+
* deleting the row lets the normal add path repair it. File-level-only
|
|
2594
|
+
* languages (yaml, twig, properties) are left alone. Deleting a zero-node
|
|
2595
|
+
* row cascades nothing: it has no nodes, so no edges or refs either.
|
|
2596
|
+
*/
|
|
2597
|
+
healZeroNodeRows() {
|
|
2598
|
+
for (const f of this.queries.getAllFiles()) {
|
|
2599
|
+
// A zero-node row WITH recorded errors is a deliberate skip marker
|
|
2600
|
+
// (#1557: oversized / repeatedly-unparseable files are persisted with
|
|
2601
|
+
// their reason so syncs stop retrying them) — leave those alone. The
|
|
2602
|
+
// #1541 wipe rows are the error-FREE zero-node rows.
|
|
2603
|
+
if (f.nodeCount === 0 &&
|
|
2604
|
+
!(0, grammars_1.isFileLevelOnlyLanguage)(f.language) &&
|
|
2605
|
+
(f.errors === undefined || f.errors.length === 0)) {
|
|
2606
|
+
this.queries.deleteFile(f.path);
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2133
2610
|
async storeExtractionResult(filePath, content, language, stats, result, onYield) {
|
|
2611
|
+
// A kernel result can arrive as an undecoded buffer transport (empty
|
|
2612
|
+
// node/edge arrays, tables riding in kernelBuffers). Decode it before
|
|
2613
|
+
// storing — persisting the transport as-is records the file as having no
|
|
2614
|
+
// symbols at all (#1541). No-op for already-decoded results.
|
|
2615
|
+
result = (0, kernel_1.materializeKernelResult)(result, filePath, language);
|
|
2134
2616
|
// Bulk inserts run in bounded sub-transactions with a yield between, so a
|
|
2135
2617
|
// giant generated file (tens of thousands of symbols) can't block the
|
|
2136
2618
|
// event loop — and the #850 watchdog heartbeat — for the whole store.
|
|
@@ -2139,7 +2621,12 @@ class ExtractionOrchestrator {
|
|
|
2139
2621
|
// is unchanged: a partially-stored file has no record and re-indexes.
|
|
2140
2622
|
const STORE_CHUNK = 2000;
|
|
2141
2623
|
const contentHash = hashContent(content);
|
|
2142
|
-
// Check if file already exists and hasn't changed
|
|
2624
|
+
// Check if file already exists and hasn't changed. A skip/failure MARKER
|
|
2625
|
+
// row (zero nodes + recorded errors, #1557) never blocks a store carrying
|
|
2626
|
+
// real content: markers are written BEFORE the retry pass under the same
|
|
2627
|
+
// content hash, so treating them as "no changes" would silently discard a
|
|
2628
|
+
// successful retry's symbols — a permanent empty file presented as
|
|
2629
|
+
// recovered (the #1541 wipe, reintroduced through the marker path).
|
|
2143
2630
|
const existingFile = this.queries.getFileByPath(filePath);
|
|
2144
2631
|
if (existingFile && existingFile.contentHash === contentHash
|
|
2145
2632
|
&& existingFile.extractionVersion >= extraction_version_1.EXTRACTION_VERSION) {
|
|
@@ -2147,8 +2634,16 @@ class ExtractionOrchestrator {
|
|
|
2147
2634
|
// one. `>=` not `===`: rewriting a newer engine's row with this engine's
|
|
2148
2635
|
// output is a downgrade, and two engines sharing an index would overwrite
|
|
2149
2636
|
// each other forever.
|
|
2150
|
-
|
|
2637
|
+
const existingIsMarker = existingFile.nodeCount === 0 && (existingFile.errors?.length ?? 0) > 0;
|
|
2638
|
+
const incomingHasContent = result.nodes.length > 0;
|
|
2639
|
+
if (!existingIsMarker || !incomingHasContent) {
|
|
2640
|
+
return; // No changes
|
|
2641
|
+
}
|
|
2151
2642
|
}
|
|
2643
|
+
// Re-decided on every re-index of a changed file, so a banner added (or
|
|
2644
|
+
// removed) by an edit is reflected on the next sync (#1500). Computed after
|
|
2645
|
+
// the unchanged-file early return so untouched files pay nothing.
|
|
2646
|
+
const generated = (0, generated_detection_1.detectGeneratedFile)(filePath, content);
|
|
2152
2647
|
// Snapshot incoming cross-file edges BEFORE deleting this file's nodes.
|
|
2153
2648
|
// `deleteFile` cascades to delete every edge whose source OR target is a
|
|
2154
2649
|
// node in this file (edges.FK ... ON DELETE CASCADE). Edges whose SOURCE is
|
|
@@ -2209,6 +2704,7 @@ class ExtractionOrchestrator {
|
|
|
2209
2704
|
nodeCount: result.nodes.length,
|
|
2210
2705
|
extractionVersion: extraction_version_1.EXTRACTION_VERSION,
|
|
2211
2706
|
errors: result.errors.length > 0 ? result.errors : undefined,
|
|
2707
|
+
generated,
|
|
2212
2708
|
},
|
|
2213
2709
|
});
|
|
2214
2710
|
if (crossFileIncomingEdges.length > 0) {
|
|
@@ -2265,6 +2761,7 @@ class ExtractionOrchestrator {
|
|
|
2265
2761
|
nodeCount: result.nodes.length,
|
|
2266
2762
|
extractionVersion: extraction_version_1.EXTRACTION_VERSION,
|
|
2267
2763
|
errors: result.errors.length > 0 ? result.errors : undefined,
|
|
2764
|
+
generated,
|
|
2268
2765
|
};
|
|
2269
2766
|
this.queries.upsertFile(fileRecord);
|
|
2270
2767
|
}
|
|
@@ -2285,6 +2782,10 @@ class ExtractionOrchestrator {
|
|
|
2285
2782
|
nodeCount,
|
|
2286
2783
|
extractionVersion: extraction_version_1.EXTRACTION_VERSION,
|
|
2287
2784
|
errors: resultErrors.length > 0 ? resultErrors : undefined,
|
|
2785
|
+
// Decided here, once, while the content is already in memory — never at
|
|
2786
|
+
// query time (#1500). The header scan short-circuits on a single
|
|
2787
|
+
// substring test for ~every hand-written file.
|
|
2788
|
+
generated: (0, generated_detection_1.detectGeneratedFile)(filePath, content),
|
|
2288
2789
|
};
|
|
2289
2790
|
}
|
|
2290
2791
|
buildFreshStoreBundle(filePath, content, language, stats, result) {
|
|
@@ -2322,6 +2823,64 @@ class ExtractionOrchestrator {
|
|
|
2322
2823
|
this.queries.insertUnresolvedRefsBatch(resurrected);
|
|
2323
2824
|
}
|
|
2324
2825
|
}
|
|
2826
|
+
/**
|
|
2827
|
+
* Re-open, for re-resolution, every resolution edge whose answer this sync
|
|
2828
|
+
* may have changed — the fix for index drift (CG-33).
|
|
2829
|
+
*
|
|
2830
|
+
* Incremental sync re-resolves only the references IN the changed files, but
|
|
2831
|
+
* resolution's answer is a function of the WHOLE graph: a reference binds to
|
|
2832
|
+
* one of the same-named definitions project-wide, so adding or removing a
|
|
2833
|
+
* definition of `pct` can change which `pct` every other file's `pct(...)`
|
|
2834
|
+
* should bind to. Those other files are never revisited, and their references
|
|
2835
|
+
* resolved successfully once and were deleted from `unresolved_refs`, so
|
|
2836
|
+
* nothing existed to revisit them with — the index kept an answer that was
|
|
2837
|
+
* correct against an older graph. Measured on lattice sensor's own long-lived
|
|
2838
|
+
* index: 4.3% of distinct edges differed from a clean rebuild, in BOTH
|
|
2839
|
+
* directions, overwhelmingly `calls`. See docs/benchmarks/index-drift-cg33.md.
|
|
2840
|
+
*
|
|
2841
|
+
* This deletes each affected edge and re-inserts it as the reference that
|
|
2842
|
+
* created it (the refName/refKind stamp), status='pending', for the sync's
|
|
2843
|
+
* resolution sweep to bind against the post-sync graph — the same input a
|
|
2844
|
+
* full rebuild resolves from, which is what makes the two converge.
|
|
2845
|
+
*
|
|
2846
|
+
* Deliberately conservative in three ways, because a wrong deletion is a
|
|
2847
|
+
* permanent edge loss while a missed rebind is only residual drift:
|
|
2848
|
+
* - an edge with no refName stamp (synthesized, or built by an engine older
|
|
2849
|
+
* than the stamp) is left ALONE rather than reconstructed from the target's
|
|
2850
|
+
* plain name, same rule as `resurrectRefFromDroppedEdge`;
|
|
2851
|
+
* - edges whose source is in a file this sync already re-extracted are
|
|
2852
|
+
* skipped — their references were re-resolved from scratch moments ago;
|
|
2853
|
+
* - very common names are skipped by the per-name ceiling in
|
|
2854
|
+
* `getResolutionEdgesByTargetName`.
|
|
2855
|
+
*
|
|
2856
|
+
* Returns the number of references resurrected.
|
|
2857
|
+
*/
|
|
2858
|
+
resurrectStaleResolutionEdges(definitionDelta, changedFilePaths) {
|
|
2859
|
+
if (definitionDelta.length === 0)
|
|
2860
|
+
return 0;
|
|
2861
|
+
const alreadyFresh = new Set(changedFilePaths);
|
|
2862
|
+
const candidates = this.queries.getResolutionEdgesByTargetName(definitionDelta);
|
|
2863
|
+
const edgeIds = [];
|
|
2864
|
+
const refs = [];
|
|
2865
|
+
for (const e of candidates) {
|
|
2866
|
+
if (alreadyFresh.has(e.sourceFilePath))
|
|
2867
|
+
continue;
|
|
2868
|
+
const ref = resurrectRefFromDroppedEdge(e);
|
|
2869
|
+
if (!ref)
|
|
2870
|
+
continue; // no stamp — never delete what we cannot restore
|
|
2871
|
+
edgeIds.push(e.edgeId);
|
|
2872
|
+
refs.push(ref);
|
|
2873
|
+
}
|
|
2874
|
+
if (refs.length === 0)
|
|
2875
|
+
return 0;
|
|
2876
|
+
// Delete first. The sweep re-inserts whichever edge resolution now picks,
|
|
2877
|
+
// and `insertEdges` is INSERT OR IGNORE against idx_edges_identity — so a
|
|
2878
|
+
// rebind to the same target is a clean no-op, but leaving the old row in
|
|
2879
|
+
// place for a rebind ELSEWHERE would keep both, turning drift into
|
|
2880
|
+
// duplication.
|
|
2881
|
+
this.queries.replaceResolutionEdgesWithUnresolvedRefs(edgeIds, refs);
|
|
2882
|
+
return refs.length;
|
|
2883
|
+
}
|
|
2325
2884
|
/**
|
|
2326
2885
|
* Sync the index with the current file state.
|
|
2327
2886
|
*
|
|
@@ -2340,7 +2899,15 @@ class ExtractionOrchestrator {
|
|
|
2340
2899
|
* set is not exactly known (directory removals, event overflow): the full
|
|
2341
2900
|
* scan-diff remains the ground truth those cases need (#1285).
|
|
2342
2901
|
*/
|
|
2343
|
-
scopedPaths
|
|
2902
|
+
scopedPaths,
|
|
2903
|
+
/**
|
|
2904
|
+
* Writer-side WAL pressure valve (#1539). Called after every changed file
|
|
2905
|
+
* is stored, when no extraction transaction is open, so a checkpoint can
|
|
2906
|
+
* safely catch up before the next file grows the WAL further.
|
|
2907
|
+
*/
|
|
2908
|
+
backpressure,
|
|
2909
|
+
/** Inspect changed inputs before deletion/re-extraction cascades their edges. */
|
|
2910
|
+
onFileChange) {
|
|
2344
2911
|
await (0, grammars_1.initGrammars)(); // Initialize WASM runtime (grammars loaded lazily below)
|
|
2345
2912
|
const startTime = Date.now();
|
|
2346
2913
|
let filesChecked = 0;
|
|
@@ -2350,12 +2917,17 @@ class ExtractionOrchestrator {
|
|
|
2350
2917
|
let extractionHealed = 0;
|
|
2351
2918
|
let nodesUpdated = 0;
|
|
2352
2919
|
const changedFilePaths = [];
|
|
2920
|
+
// `file\0name` definition pairs for the files this sync touches, sampled
|
|
2921
|
+
// BEFORE their nodes are replaced/deleted. Compared against the post-store
|
|
2922
|
+
// pairs below to derive `definitionDelta` (CG-33).
|
|
2923
|
+
const pairsBefore = new Set();
|
|
2353
2924
|
onProgress?.({
|
|
2354
2925
|
phase: 'scanning',
|
|
2355
2926
|
current: 0,
|
|
2356
2927
|
total: 0,
|
|
2357
2928
|
});
|
|
2358
2929
|
const filesToIndex = [];
|
|
2930
|
+
const failedFilePaths = [];
|
|
2359
2931
|
// === Filesystem reconcile (git-independent) ===
|
|
2360
2932
|
// The source of truth for "what changed" is the filesystem vs the indexed
|
|
2361
2933
|
// state — never git. We enumerate the current source files and reconcile
|
|
@@ -2375,7 +2947,20 @@ class ExtractionOrchestrator {
|
|
|
2375
2947
|
// reads `filesChecked === 0 && durationMs === 0` as the
|
|
2376
2948
|
// lock-unavailable signature (#449).
|
|
2377
2949
|
const unique = [...new Set(scopedPaths)];
|
|
2378
|
-
|
|
2950
|
+
// A scoped path is "present" only if it exists AND is in scope — the
|
|
2951
|
+
// same two gates the full walk applies (source extension, scope
|
|
2952
|
+
// matcher). Without the scope gate a caller's stale view of scope
|
|
2953
|
+
// leaked straight into the index: the watcher re-parsed a file the
|
|
2954
|
+
// user had just excluded in `lattice-sensor.json` while `lattice sensor sync`
|
|
2955
|
+
// removed it (#1590). Out-of-scope paths fall out of `currentFiles`,
|
|
2956
|
+
// so a tracked one takes the removal branch below, exactly as a full
|
|
2957
|
+
// sync would treat it. (`include`-forced paths pass: ScopeIgnore
|
|
2958
|
+
// applies the include precedence itself.)
|
|
2959
|
+
const scope = this.scopedSyncMatcher();
|
|
2960
|
+
const overrides = (0, project_config_1.loadExtensionOverrides)(this.rootDir);
|
|
2961
|
+
currentFiles = unique.filter((p) => (0, grammars_1.isSourceFile)(p, overrides) &&
|
|
2962
|
+
!scope.ignores(p) &&
|
|
2963
|
+
fs.existsSync(path.join(this.rootDir, p)));
|
|
2379
2964
|
trackedFiles = [];
|
|
2380
2965
|
for (const p of unique) {
|
|
2381
2966
|
const rec = this.queries.getFileByPath(p);
|
|
@@ -2387,10 +2972,17 @@ class ExtractionOrchestrator {
|
|
|
2387
2972
|
console.error(`[phase-timing] sync-scoped: ${Date.now() - tSyncScan}ms (${unique.length} paths, ${trackedFiles.length} tracked)`);
|
|
2388
2973
|
}
|
|
2389
2974
|
else {
|
|
2975
|
+
// Full reconcile: drop the memoized scope matcher so a nested
|
|
2976
|
+
// `.gitignore` / exclude-standard change that forced this full sync is
|
|
2977
|
+
// visible to the next scoped sync (#1728).
|
|
2978
|
+
this.scopedMatcher = null;
|
|
2390
2979
|
currentFiles = await scanDirectoryAsync(this.rootDir);
|
|
2391
2980
|
if (process.env.LATTICE_SENSOR_SYNTH_TIMINGS)
|
|
2392
2981
|
console.error(`[phase-timing] sync-scan: ${Date.now() - tSyncScan}ms (${currentFiles.length} files)`);
|
|
2393
2982
|
filesChecked = currentFiles.length;
|
|
2983
|
+
// Full reconcile only (scoped syncs must not touch rows outside their
|
|
2984
|
+
// scope): drop zero-node rows so the wiped files re-index as adds below.
|
|
2985
|
+
this.healZeroNodeRows();
|
|
2394
2986
|
const tTracked = Date.now();
|
|
2395
2987
|
trackedFiles = this.queries.getAllFiles();
|
|
2396
2988
|
if (process.env.LATTICE_SENSOR_SYNTH_TIMINGS)
|
|
@@ -2407,26 +2999,34 @@ class ExtractionOrchestrator {
|
|
|
2407
2999
|
// `reconcileChecks` drives the cooperative yield shared with the adds/mods loop
|
|
2408
3000
|
// below (see SYNC_RECONCILE_YIELD_INTERVAL / issue #905).
|
|
2409
3001
|
let reconcileChecks = 0;
|
|
3002
|
+
const removeTracked = (tracked) => {
|
|
3003
|
+
// Before the cascade deletes them, resurrect incoming cross-file
|
|
3004
|
+
// resolution edges as their original refs (#1240 removal case): the
|
|
3005
|
+
// callers live in files this sync will NOT revisit, so this is their
|
|
3006
|
+
// only chance to rebind to an alternative definition — or to park as
|
|
3007
|
+
// failed until the symbol reappears somewhere. (A deleted file whose
|
|
3008
|
+
// CALLERS are also being deleted is fine: their nodes cascade later
|
|
3009
|
+
// in this loop and take the resurrected rows with them.)
|
|
3010
|
+
// Every name this file defined is about to stop existing here, which
|
|
3011
|
+
// narrows the candidate set for that name repo-wide (CG-33).
|
|
3012
|
+
for (const pair of this.queries.getNodeNamePairsByFiles([tracked.path]))
|
|
3013
|
+
pairsBefore.add(pair);
|
|
3014
|
+
const incoming = this.queries.getCrossFileIncomingEdgesWithTarget(tracked.path);
|
|
3015
|
+
if (incoming.length > 0) {
|
|
3016
|
+
const resurrected = incoming
|
|
3017
|
+
.map((e) => resurrectRefFromDroppedEdge(e))
|
|
3018
|
+
.filter((r) => r !== null);
|
|
3019
|
+
if (resurrected.length > 0) {
|
|
3020
|
+
this.queries.insertUnresolvedRefsBatch(resurrected);
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
onFileChange?.(tracked.path);
|
|
3024
|
+
this.queries.deleteFile(tracked.path);
|
|
3025
|
+
filesRemoved++;
|
|
3026
|
+
};
|
|
2410
3027
|
for (const tracked of trackedFiles) {
|
|
2411
3028
|
if (!currentSet.has(tracked.path) || !fs.existsSync(path.join(this.rootDir, tracked.path))) {
|
|
2412
|
-
|
|
2413
|
-
// resolution edges as their original refs (#1240 removal case): the
|
|
2414
|
-
// callers live in files this sync will NOT revisit, so this is their
|
|
2415
|
-
// only chance to rebind to an alternative definition — or to park as
|
|
2416
|
-
// failed until the symbol reappears somewhere. (A deleted file whose
|
|
2417
|
-
// CALLERS are also being deleted is fine: their nodes cascade later
|
|
2418
|
-
// in this loop and take the resurrected rows with them.)
|
|
2419
|
-
const incoming = this.queries.getCrossFileIncomingEdgesWithTarget(tracked.path);
|
|
2420
|
-
if (incoming.length > 0) {
|
|
2421
|
-
const resurrected = incoming
|
|
2422
|
-
.map((e) => resurrectRefFromDroppedEdge(e))
|
|
2423
|
-
.filter((r) => r !== null);
|
|
2424
|
-
if (resurrected.length > 0) {
|
|
2425
|
-
this.queries.insertUnresolvedRefsBatch(resurrected);
|
|
2426
|
-
}
|
|
2427
|
-
}
|
|
2428
|
-
this.queries.deleteFile(tracked.path);
|
|
2429
|
-
filesRemoved++;
|
|
3029
|
+
removeTracked(tracked);
|
|
2430
3030
|
}
|
|
2431
3031
|
if (++reconcileChecks % SYNC_RECONCILE_YIELD_INTERVAL === 0) {
|
|
2432
3032
|
await new Promise((resolve) => setImmediate(resolve));
|
|
@@ -2467,39 +3067,54 @@ class ExtractionOrchestrator {
|
|
|
2467
3067
|
}
|
|
2468
3068
|
catch (error) {
|
|
2469
3069
|
(0, errors_1.logDebug)('Skipping unstattable file during sync', { filePath, error: String(error) });
|
|
3070
|
+
failedFilePaths.push(filePath);
|
|
2470
3071
|
continue;
|
|
2471
3072
|
}
|
|
2472
3073
|
}
|
|
2473
3074
|
// New, or size/mtime changed — read + hash to confirm a real content change.
|
|
3075
|
+
// (An oversize file hashes as its size stamp, unread — #1910.)
|
|
2474
3076
|
let content;
|
|
2475
3077
|
try {
|
|
2476
|
-
content =
|
|
3078
|
+
content = readSourceOrStamp(fullPath);
|
|
2477
3079
|
}
|
|
2478
3080
|
catch (error) {
|
|
2479
3081
|
(0, errors_1.logDebug)('Skipping unreadable file during sync', { filePath, error: String(error) });
|
|
3082
|
+
failedFilePaths.push(filePath);
|
|
3083
|
+
continue;
|
|
3084
|
+
}
|
|
3085
|
+
// Not source after all — an MPEG transport stream named `.ts` (#1910):
|
|
3086
|
+
// a new one is ignored, a tracked file that became one is removed.
|
|
3087
|
+
if (content === null) {
|
|
3088
|
+
if (tracked)
|
|
3089
|
+
removeTracked(tracked);
|
|
2480
3090
|
continue;
|
|
2481
3091
|
}
|
|
2482
3092
|
const contentHash = hashContent(content);
|
|
2483
3093
|
if (!tracked) {
|
|
3094
|
+
onFileChange?.(filePath, content);
|
|
2484
3095
|
filesToIndex.push(filePath);
|
|
2485
3096
|
changedFilePaths.push(filePath);
|
|
2486
3097
|
filesAdded++;
|
|
2487
3098
|
}
|
|
2488
3099
|
else if (tracked.contentHash !== contentHash) {
|
|
3100
|
+
onFileChange?.(filePath, content);
|
|
2489
3101
|
filesToIndex.push(filePath);
|
|
2490
3102
|
changedFilePaths.push(filePath);
|
|
2491
3103
|
filesModified++;
|
|
2492
3104
|
}
|
|
2493
3105
|
}
|
|
3106
|
+
// Sampled here — after the add/modify classification, before any file is
|
|
3107
|
+
// re-extracted — because `storeExtractionResult` deletes a file's nodes
|
|
3108
|
+
// before inserting the new ones, so this is the last point the pre-edit
|
|
3109
|
+
// definition set is readable (CG-33).
|
|
3110
|
+
if (filesToIndex.length > 0) {
|
|
3111
|
+
for (const pair of this.queries.getNodeNamePairsByFiles(filesToIndex))
|
|
3112
|
+
pairsBefore.add(pair);
|
|
3113
|
+
}
|
|
2494
3114
|
// Load only grammars needed for changed files
|
|
2495
3115
|
if (filesToIndex.length > 0) {
|
|
2496
3116
|
const overrides = (0, project_config_1.loadExtensionOverrides)(this.rootDir);
|
|
2497
|
-
|
|
2498
|
-
// .h files default to 'c' but may be C++ — ensure cpp grammar is loaded
|
|
2499
|
-
if (neededLanguages.includes('c') && !neededLanguages.includes('cpp')) {
|
|
2500
|
-
neededLanguages.push('cpp');
|
|
2501
|
-
}
|
|
2502
|
-
await (0, grammars_1.loadGrammarsForLanguages)(neededLanguages);
|
|
3117
|
+
await (0, grammars_1.loadGrammarsForLanguages)(preloadLanguagesForFiles(filesToIndex, overrides));
|
|
2503
3118
|
}
|
|
2504
3119
|
// Index changed files
|
|
2505
3120
|
const total = filesToIndex.length;
|
|
@@ -2512,7 +3127,12 @@ class ExtractionOrchestrator {
|
|
|
2512
3127
|
currentFile: filePath,
|
|
2513
3128
|
});
|
|
2514
3129
|
const result = await this.indexFile(filePath);
|
|
3130
|
+
if (result.errors.some(e => e.severity === 'error'))
|
|
3131
|
+
failedFilePaths.push(filePath);
|
|
2515
3132
|
nodesUpdated += result.nodes.length;
|
|
3133
|
+
const pause = backpressure?.();
|
|
3134
|
+
if (pause)
|
|
3135
|
+
await pause;
|
|
2516
3136
|
}
|
|
2517
3137
|
// The sync pass above re-extracted every stale-stamp file it saw. When no
|
|
2518
3138
|
// stale rows remain, the whole index reflects current extraction semantics
|
|
@@ -2529,6 +3149,28 @@ class ExtractionOrchestrator {
|
|
|
2529
3149
|
}
|
|
2530
3150
|
catch { /* metadata is advisory — never fail a sync over it */ }
|
|
2531
3151
|
}
|
|
3152
|
+
// Names whose definition set this sync changed: a `file\0name` pair present
|
|
3153
|
+
// before but not after (removed/renamed away) or after but not before
|
|
3154
|
+
// (added). A pair on both sides is untouched as far as resolution's
|
|
3155
|
+
// candidate set is concerned — only its node id moved, which
|
|
3156
|
+
// reattachCrossFileEdges already follows — so an edit that only changes
|
|
3157
|
+
// bodies yields an empty delta and no downstream rebind work (CG-33).
|
|
3158
|
+
//
|
|
3159
|
+
// Compared per FILE, not as one name set over the whole batch: a commit
|
|
3160
|
+
// that adds `collect` to a new file while an unrelated changed file already
|
|
3161
|
+
// defined `collect` must still flag the name, and a bare name set cancels
|
|
3162
|
+
// exactly that case out. That miss left the largest residual class in the
|
|
3163
|
+
// first measurement of this fix.
|
|
3164
|
+
const pairsAfter = this.queries.getNodeNamePairsByFiles(filesToIndex);
|
|
3165
|
+
const deltaNames = new Set();
|
|
3166
|
+
const nameOf = (pair) => pair.slice(pair.indexOf('\0') + 1);
|
|
3167
|
+
for (const pair of pairsBefore)
|
|
3168
|
+
if (!pairsAfter.has(pair))
|
|
3169
|
+
deltaNames.add(nameOf(pair));
|
|
3170
|
+
for (const pair of pairsAfter)
|
|
3171
|
+
if (!pairsBefore.has(pair))
|
|
3172
|
+
deltaNames.add(nameOf(pair));
|
|
3173
|
+
const definitionDelta = [...deltaNames];
|
|
2532
3174
|
return {
|
|
2533
3175
|
filesChecked,
|
|
2534
3176
|
filesAdded,
|
|
@@ -2538,48 +3180,112 @@ class ExtractionOrchestrator {
|
|
|
2538
3180
|
nodesUpdated,
|
|
2539
3181
|
durationMs: Date.now() - startTime,
|
|
2540
3182
|
changedFilePaths: changedFilePaths.length > 0 ? changedFilePaths : undefined,
|
|
3183
|
+
...(failedFilePaths.length > 0 ? { failedFilePaths } : {}),
|
|
3184
|
+
definitionDelta: definitionDelta.length > 0 ? definitionDelta : undefined,
|
|
2541
3185
|
};
|
|
2542
3186
|
}
|
|
3187
|
+
indexedDirtyPaths(stamp) {
|
|
3188
|
+
try {
|
|
3189
|
+
const state = JSON.parse(this.queries.getMetadata('indexed_dirty_paths') ?? 'null');
|
|
3190
|
+
if (!state || state.commit !== (stamp ?? '') || !Array.isArray(state.paths))
|
|
3191
|
+
return null;
|
|
3192
|
+
if (!state.paths.every((p) => typeof p === 'string' && p.length > 0 &&
|
|
3193
|
+
!path.isAbsolute(p) && !p.split('/').includes('..')))
|
|
3194
|
+
return null;
|
|
3195
|
+
return state.paths;
|
|
3196
|
+
}
|
|
3197
|
+
catch {
|
|
3198
|
+
return null;
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
/** Capture before file reads. In-flight/failed full writes must not claim freshness. */
|
|
3202
|
+
beginGitIndexState(full) {
|
|
3203
|
+
const head = getGitHeadSha(this.rootDir) ?? '';
|
|
3204
|
+
const stamp = this.queries.getMetadata(exports.INDEXED_AT_COMMIT_KEY) ?? '';
|
|
3205
|
+
const prior = this.indexedDirtyPaths(stamp);
|
|
3206
|
+
const status = getGitChangedFiles(this.rootDir);
|
|
3207
|
+
const dirty = status ? [...new Set([
|
|
3208
|
+
...(full ? [] : prior ?? []), ...status.added, ...status.modified, ...status.deleted,
|
|
3209
|
+
])] : null;
|
|
3210
|
+
if (full || prior === null || dirty === null) {
|
|
3211
|
+
this.queries.setMetadata(exports.INDEXED_AT_COMMIT_KEY, '');
|
|
3212
|
+
this.queries.setMetadata('indexed_dirty_paths', '');
|
|
3213
|
+
}
|
|
3214
|
+
else {
|
|
3215
|
+
// Scoped writes leave the commit alone and retain dirty paths before the
|
|
3216
|
+
// first write, so a crash cannot forget an indexed uncommitted edit.
|
|
3217
|
+
this.queries.setMetadata('indexed_dirty_paths', JSON.stringify({ commit: stamp, paths: dirty }));
|
|
3218
|
+
}
|
|
3219
|
+
return { head, stamp, dirty };
|
|
3220
|
+
}
|
|
3221
|
+
finishGitIndexState(snapshot, full, retries = []) {
|
|
3222
|
+
const after = getGitChangedFiles(this.rootDir);
|
|
3223
|
+
if (!snapshot.dirty || !after)
|
|
3224
|
+
return;
|
|
3225
|
+
const commit = full ? snapshot.head : snapshot.stamp;
|
|
3226
|
+
const paths = [...new Set([...snapshot.dirty, ...after.added, ...after.modified, ...after.deleted, ...retries])].sort();
|
|
3227
|
+
// The embedded commit makes a torn pair fail closed: readers reject a dirty
|
|
3228
|
+
// set that doesn't match the separately stored commit. Write the set first.
|
|
3229
|
+
this.queries.setMetadata('indexed_dirty_paths', JSON.stringify({ commit, paths }));
|
|
3230
|
+
if (full)
|
|
3231
|
+
this.queries.setMetadata(exports.INDEXED_AT_COMMIT_KEY, commit);
|
|
3232
|
+
}
|
|
2543
3233
|
/**
|
|
2544
3234
|
* Get files that have changed since last index.
|
|
2545
3235
|
* Uses git status as a fast path when available, falling back to full scan.
|
|
2546
3236
|
*/
|
|
2547
3237
|
getChangedFiles() {
|
|
2548
|
-
|
|
3238
|
+
// The commit this index was last brought up to date at. Absent on an index
|
|
3239
|
+
// built before stamping existed — getGitChangedFiles then declines the fast
|
|
3240
|
+
// path and the full scan below answers correctly, once, until a sync or a
|
|
3241
|
+
// full index writes the stamp. (#1829)
|
|
3242
|
+
let sinceCommit = null;
|
|
3243
|
+
try {
|
|
3244
|
+
sinceCommit = this.queries.getMetadata(exports.INDEXED_AT_COMMIT_KEY) ?? null;
|
|
3245
|
+
}
|
|
3246
|
+
catch { /* advisory */ }
|
|
3247
|
+
const dirtyPaths = this.indexedDirtyPaths(sinceCommit);
|
|
3248
|
+
const gitChanges = dirtyPaths !== null && canTrustGitFastPath(this.rootDir, sinceCommit)
|
|
3249
|
+
? getGitChangedFiles(this.rootDir, sinceCommit)
|
|
3250
|
+
: null;
|
|
2549
3251
|
if (gitChanges) {
|
|
2550
3252
|
// === Git fast path ===
|
|
2551
3253
|
const added = [];
|
|
2552
3254
|
const modified = [];
|
|
2553
3255
|
const removed = [];
|
|
2554
|
-
//
|
|
2555
|
-
|
|
3256
|
+
// Git supplies candidates, never the verdict. A committed deletion may
|
|
3257
|
+
// have been recreated locally; a previously indexed dirty path may have
|
|
3258
|
+
// vanished from git status after restore. Classify current disk vs DB once.
|
|
3259
|
+
const candidates = new Set([...gitChanges.deleted, ...gitChanges.modified, ...gitChanges.added, ...dirtyPaths]);
|
|
3260
|
+
const scope = this.scopedSyncMatcher();
|
|
3261
|
+
const overrides = (0, project_config_1.loadExtensionOverrides)(this.rootDir);
|
|
3262
|
+
for (const filePath of candidates) {
|
|
2556
3263
|
const tracked = this.queries.getFileByPath(filePath);
|
|
2557
|
-
if (tracked) {
|
|
2558
|
-
removed.push(filePath);
|
|
2559
|
-
}
|
|
2560
|
-
}
|
|
2561
|
-
// Modified + added files — read + hash, compare with DB. Untracked (`??`)
|
|
2562
|
-
// files stay untracked in git even after indexing, so they must be
|
|
2563
|
-
// hash-compared like modified files instead of always counting as added —
|
|
2564
|
-
// otherwise status reports them as pending forever. (See issue #206.)
|
|
2565
|
-
for (const filePath of [...gitChanges.modified, ...gitChanges.added]) {
|
|
2566
3264
|
const fullPath = path.join(this.rootDir, filePath);
|
|
3265
|
+
if (!(0, grammars_1.isSourceFile)(filePath, overrides) || scope.ignores(filePath) || !fs.existsSync(fullPath)) {
|
|
3266
|
+
if (tracked)
|
|
3267
|
+
removed.push(filePath);
|
|
3268
|
+
continue;
|
|
3269
|
+
}
|
|
2567
3270
|
let content;
|
|
2568
3271
|
try {
|
|
2569
|
-
content =
|
|
3272
|
+
content = readSourceOrStamp(fullPath);
|
|
2570
3273
|
}
|
|
2571
3274
|
catch (error) {
|
|
2572
3275
|
(0, errors_1.logDebug)('Skipping unreadable file while detecting changes', { filePath, error: String(error) });
|
|
2573
3276
|
continue;
|
|
2574
3277
|
}
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
if (
|
|
2578
|
-
|
|
3278
|
+
// A `.ts` that is an MPEG transport stream is not source (#1910), so an
|
|
3279
|
+
// untracked clip is never pending and a tracked file that became one is gone.
|
|
3280
|
+
if (content === null) {
|
|
3281
|
+
if (tracked)
|
|
3282
|
+
removed.push(filePath);
|
|
3283
|
+
continue;
|
|
2579
3284
|
}
|
|
2580
|
-
|
|
3285
|
+
if (!tracked)
|
|
3286
|
+
added.push(filePath);
|
|
3287
|
+
else if (tracked.contentHash !== hashContent(content))
|
|
2581
3288
|
modified.push(filePath);
|
|
2582
|
-
}
|
|
2583
3289
|
}
|
|
2584
3290
|
// Rows written by a different extractor are pending even though git sees
|
|
2585
3291
|
// no change — the observer moved, not the file. Without this, the git
|
|
@@ -2613,14 +3319,20 @@ class ExtractionOrchestrator {
|
|
|
2613
3319
|
const fullPath = path.join(this.rootDir, filePath);
|
|
2614
3320
|
let content;
|
|
2615
3321
|
try {
|
|
2616
|
-
content =
|
|
3322
|
+
content = readSourceOrStamp(fullPath);
|
|
2617
3323
|
}
|
|
2618
3324
|
catch (error) {
|
|
2619
3325
|
(0, errors_1.logDebug)('Skipping unreadable file while detecting changes', { filePath, error: String(error) });
|
|
2620
3326
|
continue;
|
|
2621
3327
|
}
|
|
2622
|
-
const contentHash = hashContent(content);
|
|
2623
3328
|
const tracked = trackedMap.get(filePath);
|
|
3329
|
+
// An MPEG transport stream named `.ts` (#1910) is not source.
|
|
3330
|
+
if (content === null) {
|
|
3331
|
+
if (tracked)
|
|
3332
|
+
removed.push(filePath);
|
|
3333
|
+
continue;
|
|
3334
|
+
}
|
|
3335
|
+
const contentHash = hashContent(content);
|
|
2624
3336
|
if (!tracked) {
|
|
2625
3337
|
added.push(filePath);
|
|
2626
3338
|
}
|