gitnexus 1.6.3-rc.8 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -5
- package/dist/_shared/graph/types.d.ts +16 -0
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/index.d.ts +20 -2
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js +11 -0
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/scope-resolution/def-index.js +2 -2
- package/dist/_shared/scope-resolution/def-index.js.map +1 -1
- package/dist/_shared/scope-resolution/method-dispatch-index.d.ts +8 -0
- package/dist/_shared/scope-resolution/method-dispatch-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/method-dispatch-index.js +2 -2
- package/dist/_shared/scope-resolution/method-dispatch-index.js.map +1 -1
- package/dist/_shared/scope-resolution/module-scope-index.d.ts +8 -0
- package/dist/_shared/scope-resolution/module-scope-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/module-scope-index.js +10 -2
- package/dist/_shared/scope-resolution/module-scope-index.js.map +1 -1
- package/dist/_shared/scope-resolution/parsed-file.d.ts +76 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/parsed-file.js +54 -0
- package/dist/_shared/scope-resolution/parsed-file.js.map +1 -0
- package/dist/_shared/scope-resolution/position-index.d.ts +12 -0
- package/dist/_shared/scope-resolution/position-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/position-index.js +2 -2
- package/dist/_shared/scope-resolution/position-index.js.map +1 -1
- package/dist/_shared/scope-resolution/qualified-name-index.js +2 -2
- package/dist/_shared/scope-resolution/qualified-name-index.js.map +1 -1
- package/dist/_shared/scope-resolution/reference-site.d.ts +75 -0
- package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/reference-site.js +24 -0
- package/dist/_shared/scope-resolution/reference-site.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/class-registry.d.ts +27 -0
- package/dist/_shared/scope-resolution/registries/class-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/class-registry.js +30 -0
- package/dist/_shared/scope-resolution/registries/class-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/context.d.ts +69 -0
- package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/context.js +44 -0
- package/dist/_shared/scope-resolution/registries/context.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/evidence.d.ts +56 -0
- package/dist/_shared/scope-resolution/registries/evidence.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/evidence.js +150 -0
- package/dist/_shared/scope-resolution/registries/evidence.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/field-registry.d.ts +26 -0
- package/dist/_shared/scope-resolution/registries/field-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/field-registry.js +31 -0
- package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts +81 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.js +332 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts +33 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.js +56 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/method-registry.d.ts +36 -0
- package/dist/_shared/scope-resolution/registries/method-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/method-registry.js +32 -0
- package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts +43 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.js +60 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.js.map +1 -0
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts +1 -10
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/resolve-type-ref.js +6 -0
- package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.d.ts +4 -4
- package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.js +3 -2
- package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
- package/dist/_shared/scope-resolution/shadow/aggregate.d.ts +6 -2
- package/dist/_shared/scope-resolution/shadow/aggregate.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/shadow/aggregate.js +5 -0
- package/dist/_shared/scope-resolution/shadow/aggregate.js.map +1 -1
- package/dist/_shared/scope-resolution/types.d.ts +11 -0
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/cli/ai-context.js +35 -4
- package/dist/cli/analyze.d.ts +27 -0
- package/dist/cli/analyze.js +31 -1
- package/dist/cli/clean.js +19 -1
- package/dist/cli/group.js +73 -0
- package/dist/cli/index-repo.js +8 -1
- package/dist/cli/index.js +26 -1
- package/dist/cli/list.js +11 -1
- package/dist/cli/remove.d.ts +30 -0
- package/dist/cli/remove.js +99 -0
- package/dist/cli/setup.js +185 -57
- package/dist/cli/tool.d.ts +5 -0
- package/dist/cli/tool.js +42 -0
- package/dist/config/ignore-service.d.ts +9 -0
- package/dist/config/ignore-service.js +80 -13
- package/dist/core/embedding-mode.d.ts +30 -0
- package/dist/core/embedding-mode.js +30 -0
- package/dist/core/embeddings/ast-utils.js +22 -22
- package/dist/core/embeddings/chunker.js +30 -25
- package/dist/core/embeddings/embedding-pipeline.d.ts +6 -0
- package/dist/core/embeddings/embedding-pipeline.js +15 -6
- package/dist/core/embeddings/text-generator.d.ts +1 -1
- package/dist/core/embeddings/text-generator.js +33 -24
- package/dist/core/embeddings/types.d.ts +43 -1
- package/dist/core/embeddings/types.js +101 -29
- package/dist/core/git-staleness.d.ts +18 -0
- package/dist/core/git-staleness.js +108 -0
- package/dist/core/graph/graph.js +115 -20
- package/dist/core/graph/types.d.ts +12 -1
- package/dist/core/group/config-parser.d.ts +4 -0
- package/dist/core/group/config-parser.js +18 -1
- package/dist/core/group/cross-impact.d.ts +41 -0
- package/dist/core/group/cross-impact.js +441 -0
- package/dist/core/group/extractors/http-patterns/php.js +126 -18
- package/dist/core/group/group-path-utils.d.ts +17 -0
- package/dist/core/group/group-path-utils.js +40 -0
- package/dist/core/group/resolve-at-member.d.ts +10 -0
- package/dist/core/group/resolve-at-member.js +31 -0
- package/dist/core/group/service.d.ts +9 -0
- package/dist/core/group/service.js +259 -25
- package/dist/core/group/types.d.ts +30 -0
- package/dist/core/ingestion/ast-cache.d.ts +16 -1
- package/dist/core/ingestion/ast-cache.js +14 -2
- package/dist/core/ingestion/call-processor.js +9 -0
- package/dist/core/ingestion/emit-references.d.ts +88 -0
- package/dist/core/ingestion/emit-references.js +229 -0
- package/dist/core/ingestion/filesystem-walker.js +6 -4
- package/dist/core/ingestion/finalize-orchestrator.d.ts +63 -0
- package/dist/core/ingestion/finalize-orchestrator.js +139 -0
- package/dist/core/ingestion/framework-detection.js +6 -2
- package/dist/core/ingestion/import-processor.js +4 -0
- package/dist/core/ingestion/import-resolvers/python.js +9 -6
- package/dist/core/ingestion/import-target-adapter.d.ts +73 -0
- package/dist/core/ingestion/import-target-adapter.js +95 -0
- package/dist/core/ingestion/language-provider.d.ts +36 -33
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
- package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
- package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
- package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
- package/dist/core/ingestion/languages/csharp/arity.js +37 -0
- package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
- package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
- package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
- package/dist/core/ingestion/languages/csharp/captures.js +249 -0
- package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
- package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
- package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
- package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
- package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
- package/dist/core/ingestion/languages/csharp/index.js +82 -0
- package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
- package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
- package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
- package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
- package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
- package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
- package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
- package/dist/core/ingestion/languages/csharp/query.js +515 -0
- package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
- package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
- package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
- package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
- package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
- package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
- package/dist/core/ingestion/languages/csharp.js +14 -0
- package/dist/core/ingestion/languages/python/arity-metadata.d.ts +24 -0
- package/dist/core/ingestion/languages/python/arity-metadata.js +45 -0
- package/dist/core/ingestion/languages/python/arity.d.ts +22 -0
- package/dist/core/ingestion/languages/python/arity.js +38 -0
- package/dist/core/ingestion/languages/python/cache-stats.d.ts +17 -0
- package/dist/core/ingestion/languages/python/cache-stats.js +28 -0
- package/dist/core/ingestion/languages/python/captures.d.ts +19 -0
- package/dist/core/ingestion/languages/python/captures.js +106 -0
- package/dist/core/ingestion/languages/python/import-decomposer.d.ts +15 -0
- package/dist/core/ingestion/languages/python/import-decomposer.js +112 -0
- package/dist/core/ingestion/languages/python/import-target.d.ts +21 -0
- package/dist/core/ingestion/languages/python/import-target.js +99 -0
- package/dist/core/ingestion/languages/python/index.d.ts +80 -0
- package/dist/core/ingestion/languages/python/index.js +80 -0
- package/dist/core/ingestion/languages/python/interpret.d.ts +15 -0
- package/dist/core/ingestion/languages/python/interpret.js +191 -0
- package/dist/core/ingestion/languages/python/merge-bindings.d.ts +16 -0
- package/dist/core/ingestion/languages/python/merge-bindings.js +44 -0
- package/dist/core/ingestion/languages/python/query.d.ts +9 -0
- package/dist/core/ingestion/languages/python/query.js +267 -0
- package/dist/core/ingestion/languages/python/receiver-binding.d.ts +21 -0
- package/dist/core/ingestion/languages/python/receiver-binding.js +116 -0
- package/dist/core/ingestion/languages/python/scope-resolver.d.ts +16 -0
- package/dist/core/ingestion/languages/python/scope-resolver.js +53 -0
- package/dist/core/ingestion/languages/python/simple-hooks.d.ts +23 -0
- package/dist/core/ingestion/languages/python/simple-hooks.js +35 -0
- package/dist/core/ingestion/languages/python.js +14 -0
- package/dist/core/ingestion/model/method-registry.d.ts +9 -0
- package/dist/core/ingestion/model/method-registry.js +4 -0
- package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +59 -0
- package/dist/core/ingestion/model/scope-resolution-indexes.js +42 -0
- package/dist/core/ingestion/model/semantic-model.d.ts +64 -0
- package/dist/core/ingestion/model/semantic-model.js +55 -0
- package/dist/core/ingestion/mro-processor.js +38 -22
- package/dist/core/ingestion/parsing-processor.d.ts +18 -1
- package/dist/core/ingestion/parsing-processor.js +45 -11
- package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
- package/dist/core/ingestion/pipeline-phases/index.js +1 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +10 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +17 -2
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +18 -0
- package/dist/core/ingestion/pipeline.js +2 -1
- package/dist/core/ingestion/registry-primary-flag.d.ts +86 -0
- package/dist/core/ingestion/registry-primary-flag.js +111 -0
- package/dist/core/ingestion/resolve-references.d.ts +63 -0
- package/dist/core/ingestion/resolve-references.js +175 -0
- package/dist/core/ingestion/scope-extractor-bridge.d.ts +32 -0
- package/dist/core/ingestion/scope-extractor-bridge.js +44 -0
- package/dist/core/ingestion/scope-extractor.d.ts +86 -0
- package/dist/core/ingestion/scope-extractor.js +758 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +372 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +212 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +43 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +79 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +57 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +112 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +17 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +46 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +19 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +30 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +37 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +113 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +38 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +73 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +42 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +198 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +27 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +131 -0
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +48 -0
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +130 -0
- package/dist/core/ingestion/scope-resolution/passes/mro.d.ts +42 -0
- package/dist/core/ingestion/scope-resolution/passes/mro.js +99 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +46 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +327 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +47 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +130 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
- package/dist/core/ingestion/scope-resolution/pipeline/registry.d.ts +17 -0
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +21 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +66 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +157 -0
- package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +36 -0
- package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +52 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +127 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +349 -0
- package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +52 -0
- package/dist/core/ingestion/scope-resolution/workspace-index.js +61 -0
- package/dist/core/ingestion/shadow-harness.d.ts +113 -0
- package/dist/core/ingestion/shadow-harness.js +148 -0
- package/dist/core/ingestion/utils/ast-helpers.d.ts +19 -1
- package/dist/core/ingestion/utils/ast-helpers.js +70 -0
- package/dist/core/ingestion/utils/max-file-size.d.ts +20 -0
- package/dist/core/ingestion/utils/max-file-size.js +52 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +9 -0
- package/dist/core/ingestion/workers/parse-worker.js +57 -21
- package/dist/core/lbug/lbug-adapter.d.ts +22 -2
- package/dist/core/lbug/lbug-adapter.js +58 -14
- package/dist/core/lbug/pool-adapter.d.ts +17 -0
- package/dist/core/lbug/pool-adapter.js +24 -14
- package/dist/core/run-analyze.d.ts +32 -0
- package/dist/core/run-analyze.js +74 -19
- package/dist/core/search/bm25-index.d.ts +18 -0
- package/dist/core/search/bm25-index.js +125 -12
- package/dist/core/tree-sitter/parser-loader.js +6 -1
- package/dist/mcp/local/local-backend.d.ts +67 -3
- package/dist/mcp/local/local-backend.js +296 -34
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +100 -17
- package/dist/mcp/tools.d.ts +4 -1
- package/dist/mcp/tools.js +75 -54
- package/dist/server/api.js +6 -2
- package/dist/storage/git.d.ts +49 -0
- package/dist/storage/git.js +111 -0
- package/dist/storage/repo-manager.d.ts +246 -1
- package/dist/storage/repo-manager.js +391 -9
- package/package.json +7 -6
- package/scripts/bench-scope-resolution.ts +134 -0
- package/scripts/ci-list-migrated-languages.ts +24 -0
- package/skills/gitnexus-cli.md +1 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shadow-mode parity harness — dual-run observability for the RFC #909
|
|
3
|
+
* registry rollout (RFC §6.3; Ring 2 PKG #923).
|
|
4
|
+
*
|
|
5
|
+
* ## What it does
|
|
6
|
+
*
|
|
7
|
+
* - Exposes `record({ language, callsite, legacy, newResult })` for
|
|
8
|
+
* every call site where the caller has BOTH a legacy-DAG resolution
|
|
9
|
+
* and a new `Registry.lookup` resolution.
|
|
10
|
+
* - Computes a `ShadowDiff` per record via shared `diffResolutions`
|
|
11
|
+
* (#918) and accumulates them in a per-language bucket.
|
|
12
|
+
* - At the end of a run, aggregates into a `ShadowParityReport` via
|
|
13
|
+
* shared `aggregateDiffs` (#918) — per-language parity %,
|
|
14
|
+
* evidence-kind breakdown of divergences, grand-total overall row.
|
|
15
|
+
* - Optionally persists the report as JSON under
|
|
16
|
+
* `.gitnexus/shadow-parity/` so the static dashboard at
|
|
17
|
+
* `gitnexus/shadow-parity-dashboard/` can render it offline.
|
|
18
|
+
*
|
|
19
|
+
* ## What it does NOT do
|
|
20
|
+
*
|
|
21
|
+
* - **Invoke either resolution path itself.** The caller must run
|
|
22
|
+
* legacy + `Registry.lookup` and pass results in. The harness is a
|
|
23
|
+
* side-car, not a dispatcher — this keeps call-processor integration
|
|
24
|
+
* surgical when it lands (tracked as a follow-up; the shared model
|
|
25
|
+
* doesn't dual-invoke on its own).
|
|
26
|
+
* - **Flip anything.** `REGISTRY_PRIMARY_<LANG>` lives in
|
|
27
|
+
* `registry-primary-flag.ts` (#924); the harness records the
|
|
28
|
+
* caller-supplied "which side is primary" bit for each record so the
|
|
29
|
+
* dashboard can label rows, but it does not consult the flag itself.
|
|
30
|
+
*
|
|
31
|
+
* ## Activation
|
|
32
|
+
*
|
|
33
|
+
* `GITNEXUS_SHADOW_MODE=1` (or `'true'`, `'yes'`, case-insensitive,
|
|
34
|
+
* trimmed) enables the harness. When disabled, `record()` is a cheap
|
|
35
|
+
* no-op: no accumulation, no allocation beyond the harness object
|
|
36
|
+
* itself. Callers can always construct a harness and hand it through;
|
|
37
|
+
* the "off" overhead is near-zero.
|
|
38
|
+
*
|
|
39
|
+
* ## Persistence shape
|
|
40
|
+
*
|
|
41
|
+
* When `persist()` is called, the harness writes TWO files:
|
|
42
|
+
*
|
|
43
|
+
* - `<outputDir>/<runId>.json` — the timestamped snapshot (immutable)
|
|
44
|
+
* - `<outputDir>/latest.json` — a pointer that the dashboard reads
|
|
45
|
+
*
|
|
46
|
+
* Both files contain the same `PersistedShadowReport` payload:
|
|
47
|
+
*
|
|
48
|
+
* {
|
|
49
|
+
* schemaVersion: 1,
|
|
50
|
+
* runId: "<iso-8601>-<rand>",
|
|
51
|
+
* generatedAt: "<iso-8601>",
|
|
52
|
+
* primaryByLanguage: { [lang]: "legacy" | "registry" },
|
|
53
|
+
* report: <ShadowParityReport>
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* Schema-version-gated so future format changes don't silently confuse
|
|
57
|
+
* older dashboards. The dashboard renders `report.perLanguage` rows and
|
|
58
|
+
* annotates each with `primaryByLanguage[lang]`.
|
|
59
|
+
*/
|
|
60
|
+
import { type Resolution, type ShadowCallsite, type ShadowParityReport, type SupportedLanguages } from '../../_shared/index.js';
|
|
61
|
+
/** Which side of the dual-run is considered authoritative for this language. */
|
|
62
|
+
export type PrimarySide = 'legacy' | 'registry';
|
|
63
|
+
/** One record per call site the caller dual-runs. */
|
|
64
|
+
export interface ShadowRecordInput {
|
|
65
|
+
readonly language: SupportedLanguages;
|
|
66
|
+
readonly callsite: ShadowCallsite;
|
|
67
|
+
readonly legacy: readonly Resolution[];
|
|
68
|
+
readonly newResult: readonly Resolution[];
|
|
69
|
+
/**
|
|
70
|
+
* Which side drove the actual runtime answer for this record. Lets the
|
|
71
|
+
* dashboard distinguish "registry-primary, legacy is shadow" from the
|
|
72
|
+
* default "legacy-primary, registry is shadow" without re-reading
|
|
73
|
+
* `REGISTRY_PRIMARY_<LANG>` env vars at render time.
|
|
74
|
+
*/
|
|
75
|
+
readonly primary: PrimarySide;
|
|
76
|
+
}
|
|
77
|
+
/** Persisted JSON shape. Schema-versioned for future migrations. */
|
|
78
|
+
export interface PersistedShadowReport {
|
|
79
|
+
readonly schemaVersion: 1;
|
|
80
|
+
readonly runId: string;
|
|
81
|
+
readonly generatedAt: string;
|
|
82
|
+
readonly primaryByLanguage: Readonly<Partial<Record<SupportedLanguages, PrimarySide>>>;
|
|
83
|
+
readonly report: ShadowParityReport;
|
|
84
|
+
}
|
|
85
|
+
export interface ShadowHarness {
|
|
86
|
+
/** `true` iff `GITNEXUS_SHADOW_MODE` is truthy. When `false`, `record()` is a no-op. */
|
|
87
|
+
readonly enabled: boolean;
|
|
88
|
+
/** Accumulate a dual-run observation. No-op when `enabled === false`. */
|
|
89
|
+
record(input: ShadowRecordInput): void;
|
|
90
|
+
/** Number of records accumulated so far. Useful for diagnostics / tests. */
|
|
91
|
+
size(): number;
|
|
92
|
+
/**
|
|
93
|
+
* Aggregate the accumulated records into a `ShadowParityReport`
|
|
94
|
+
* without persisting. Returns a deterministic snapshot each call;
|
|
95
|
+
* idempotent with respect to `record()` ordering.
|
|
96
|
+
*/
|
|
97
|
+
snapshot(now?: Date): ShadowParityReport;
|
|
98
|
+
/**
|
|
99
|
+
* Write the aggregated snapshot to JSON. Resolves to the path of the
|
|
100
|
+
* per-run file. Also writes/overwrites `latest.json` alongside.
|
|
101
|
+
*
|
|
102
|
+
* Creates `outputDir` if it doesn't exist.
|
|
103
|
+
*/
|
|
104
|
+
persist(outputDir: string, now?: Date): Promise<string>;
|
|
105
|
+
/** Reset the accumulator. Preserves `enabled`. */
|
|
106
|
+
clear(): void;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Construct a harness. Reads `GITNEXUS_SHADOW_MODE` at construction time
|
|
110
|
+
* (not per-`record()` call) so repeated no-op records don't re-check the
|
|
111
|
+
* env var in the hot path.
|
|
112
|
+
*/
|
|
113
|
+
export declare function createShadowHarness(): ShadowHarness;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shadow-mode parity harness — dual-run observability for the RFC #909
|
|
3
|
+
* registry rollout (RFC §6.3; Ring 2 PKG #923).
|
|
4
|
+
*
|
|
5
|
+
* ## What it does
|
|
6
|
+
*
|
|
7
|
+
* - Exposes `record({ language, callsite, legacy, newResult })` for
|
|
8
|
+
* every call site where the caller has BOTH a legacy-DAG resolution
|
|
9
|
+
* and a new `Registry.lookup` resolution.
|
|
10
|
+
* - Computes a `ShadowDiff` per record via shared `diffResolutions`
|
|
11
|
+
* (#918) and accumulates them in a per-language bucket.
|
|
12
|
+
* - At the end of a run, aggregates into a `ShadowParityReport` via
|
|
13
|
+
* shared `aggregateDiffs` (#918) — per-language parity %,
|
|
14
|
+
* evidence-kind breakdown of divergences, grand-total overall row.
|
|
15
|
+
* - Optionally persists the report as JSON under
|
|
16
|
+
* `.gitnexus/shadow-parity/` so the static dashboard at
|
|
17
|
+
* `gitnexus/shadow-parity-dashboard/` can render it offline.
|
|
18
|
+
*
|
|
19
|
+
* ## What it does NOT do
|
|
20
|
+
*
|
|
21
|
+
* - **Invoke either resolution path itself.** The caller must run
|
|
22
|
+
* legacy + `Registry.lookup` and pass results in. The harness is a
|
|
23
|
+
* side-car, not a dispatcher — this keeps call-processor integration
|
|
24
|
+
* surgical when it lands (tracked as a follow-up; the shared model
|
|
25
|
+
* doesn't dual-invoke on its own).
|
|
26
|
+
* - **Flip anything.** `REGISTRY_PRIMARY_<LANG>` lives in
|
|
27
|
+
* `registry-primary-flag.ts` (#924); the harness records the
|
|
28
|
+
* caller-supplied "which side is primary" bit for each record so the
|
|
29
|
+
* dashboard can label rows, but it does not consult the flag itself.
|
|
30
|
+
*
|
|
31
|
+
* ## Activation
|
|
32
|
+
*
|
|
33
|
+
* `GITNEXUS_SHADOW_MODE=1` (or `'true'`, `'yes'`, case-insensitive,
|
|
34
|
+
* trimmed) enables the harness. When disabled, `record()` is a cheap
|
|
35
|
+
* no-op: no accumulation, no allocation beyond the harness object
|
|
36
|
+
* itself. Callers can always construct a harness and hand it through;
|
|
37
|
+
* the "off" overhead is near-zero.
|
|
38
|
+
*
|
|
39
|
+
* ## Persistence shape
|
|
40
|
+
*
|
|
41
|
+
* When `persist()` is called, the harness writes TWO files:
|
|
42
|
+
*
|
|
43
|
+
* - `<outputDir>/<runId>.json` — the timestamped snapshot (immutable)
|
|
44
|
+
* - `<outputDir>/latest.json` — a pointer that the dashboard reads
|
|
45
|
+
*
|
|
46
|
+
* Both files contain the same `PersistedShadowReport` payload:
|
|
47
|
+
*
|
|
48
|
+
* {
|
|
49
|
+
* schemaVersion: 1,
|
|
50
|
+
* runId: "<iso-8601>-<rand>",
|
|
51
|
+
* generatedAt: "<iso-8601>",
|
|
52
|
+
* primaryByLanguage: { [lang]: "legacy" | "registry" },
|
|
53
|
+
* report: <ShadowParityReport>
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* Schema-version-gated so future format changes don't silently confuse
|
|
57
|
+
* older dashboards. The dashboard renders `report.perLanguage` rows and
|
|
58
|
+
* annotates each with `primaryByLanguage[lang]`.
|
|
59
|
+
*/
|
|
60
|
+
import * as fs from 'node:fs/promises';
|
|
61
|
+
import * as path from 'node:path';
|
|
62
|
+
import { aggregateDiffs, diffResolutions, } from '../../_shared/index.js';
|
|
63
|
+
/**
|
|
64
|
+
* Construct a harness. Reads `GITNEXUS_SHADOW_MODE` at construction time
|
|
65
|
+
* (not per-`record()` call) so repeated no-op records don't re-check the
|
|
66
|
+
* env var in the hot path.
|
|
67
|
+
*/
|
|
68
|
+
export function createShadowHarness() {
|
|
69
|
+
const enabled = parseShadowModeEnv(process.env['GITNEXUS_SHADOW_MODE']);
|
|
70
|
+
const records = [];
|
|
71
|
+
const primaryByLanguage = {};
|
|
72
|
+
const recordImpl = (input) => {
|
|
73
|
+
if (!enabled)
|
|
74
|
+
return;
|
|
75
|
+
const diff = diffResolutions(input.callsite, input.legacy, input.newResult);
|
|
76
|
+
records.push({ language: input.language, diff });
|
|
77
|
+
// Primary per-language is resolved by last-write. In practice a run
|
|
78
|
+
// is single-threaded with respect to flag readings, so this is
|
|
79
|
+
// deterministic; a language's primary cannot change mid-run.
|
|
80
|
+
primaryByLanguage[input.language] = input.primary;
|
|
81
|
+
};
|
|
82
|
+
const snapshotImpl = (now = new Date()) => {
|
|
83
|
+
return aggregateDiffs(records, now);
|
|
84
|
+
};
|
|
85
|
+
const persistImpl = async (outputDir, now = new Date()) => {
|
|
86
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
87
|
+
const report = snapshotImpl(now);
|
|
88
|
+
const runId = makeRunId(now);
|
|
89
|
+
const payload = {
|
|
90
|
+
schemaVersion: 1,
|
|
91
|
+
runId,
|
|
92
|
+
generatedAt: now.toISOString(),
|
|
93
|
+
primaryByLanguage,
|
|
94
|
+
report,
|
|
95
|
+
};
|
|
96
|
+
const json = JSON.stringify(payload, null, 2);
|
|
97
|
+
const perRunPath = path.join(outputDir, `${runId}.json`);
|
|
98
|
+
const latestPath = path.join(outputDir, 'latest.json');
|
|
99
|
+
await fs.writeFile(perRunPath, json, 'utf8');
|
|
100
|
+
await fs.writeFile(latestPath, json, 'utf8');
|
|
101
|
+
return perRunPath;
|
|
102
|
+
};
|
|
103
|
+
const clearImpl = () => {
|
|
104
|
+
records.length = 0;
|
|
105
|
+
for (const key of Object.keys(primaryByLanguage)) {
|
|
106
|
+
delete primaryByLanguage[key];
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
enabled,
|
|
111
|
+
record: recordImpl,
|
|
112
|
+
size: () => records.length,
|
|
113
|
+
snapshot: snapshotImpl,
|
|
114
|
+
persist: persistImpl,
|
|
115
|
+
clear: clearImpl,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// ─── Internal helpers ─────────────────────────────────────────────────────
|
|
119
|
+
/**
|
|
120
|
+
* Env-var parser for `GITNEXUS_SHADOW_MODE`. Accepts the same truthy
|
|
121
|
+
* conventions as `REGISTRY_PRIMARY_<LANG>` from #924: `'true'` / `'1'` /
|
|
122
|
+
* `'yes'`, case-insensitive, whitespace-trimmed. Anything else — including
|
|
123
|
+
* `undefined`, `''`, `'false'`, `'off'`, typos — is false.
|
|
124
|
+
*/
|
|
125
|
+
function parseShadowModeEnv(raw) {
|
|
126
|
+
if (raw === undefined)
|
|
127
|
+
return false;
|
|
128
|
+
const normalized = raw.trim().toLowerCase();
|
|
129
|
+
return normalized === 'true' || normalized === '1' || normalized === 'yes';
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Deterministic run id derived from the timestamp plus 4 random bytes
|
|
133
|
+
* of entropy. The timestamp comes first so files sort chronologically;
|
|
134
|
+
* the entropy suffix prevents collisions when multiple runs share a
|
|
135
|
+
* clock-second. Shape: `YYYYMMDD-HHMMSS-xxxxxxxx`.
|
|
136
|
+
*/
|
|
137
|
+
function makeRunId(now) {
|
|
138
|
+
const y = now.getUTCFullYear().toString().padStart(4, '0');
|
|
139
|
+
const m = (now.getUTCMonth() + 1).toString().padStart(2, '0');
|
|
140
|
+
const d = now.getUTCDate().toString().padStart(2, '0');
|
|
141
|
+
const h = now.getUTCHours().toString().padStart(2, '0');
|
|
142
|
+
const min = now.getUTCMinutes().toString().padStart(2, '0');
|
|
143
|
+
const s = now.getUTCSeconds().toString().padStart(2, '0');
|
|
144
|
+
const entropy = Math.floor(Math.random() * 0xffffffff)
|
|
145
|
+
.toString(16)
|
|
146
|
+
.padStart(8, '0');
|
|
147
|
+
return `${y}${m}${d}-${h}${min}${s}-${entropy}`;
|
|
148
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type Parser from 'tree-sitter';
|
|
2
|
-
import type { NodeLabel } from '../../../_shared/index.js';
|
|
2
|
+
import type { Capture, NodeLabel, Range } from '../../../_shared/index.js';
|
|
3
3
|
import type { LanguageProvider } from '../language-provider.js';
|
|
4
4
|
/** Tree-sitter AST node. Re-exported for use across ingestion modules. */
|
|
5
5
|
export type SyntaxNode = Parser.SyntaxNode;
|
|
@@ -69,3 +69,21 @@ export declare function findDescendant(root: SyntaxNode, type: string): SyntaxNo
|
|
|
69
69
|
export declare function extractStringContent(node: SyntaxNode | null | undefined): string | null;
|
|
70
70
|
/** Find the first direct named child of a tree-sitter node matching the given type. */
|
|
71
71
|
export declare function findChild(node: SyntaxNode, type: string): SyntaxNode | null;
|
|
72
|
+
/** Convert a tree-sitter node to a `Capture` with 1-based line numbers
|
|
73
|
+
* (matching RFC §2.1). The tag includes the leading `@`. */
|
|
74
|
+
export declare function nodeToCapture(name: string, node: SyntaxNode): Capture;
|
|
75
|
+
/** Build a `Capture` whose range mirrors `atNode` but whose `text` is
|
|
76
|
+
* caller-supplied. Used to synthesize markers that don't have a
|
|
77
|
+
* corresponding source token. */
|
|
78
|
+
export declare function syntheticCapture(name: string, atNode: SyntaxNode, text: string): Capture;
|
|
79
|
+
/** Walk a subtree to find a node whose range exactly matches AND whose
|
|
80
|
+
* type matches `expectedType` (when given). When multiple nodes share
|
|
81
|
+
* the range — e.g., `function_definition` and its inner `block` body
|
|
82
|
+
* for a one-liner — the type filter disambiguates.
|
|
83
|
+
*
|
|
84
|
+
* Iterative depth-first-left-to-right via an explicit stack. Children
|
|
85
|
+
* are pushed in reverse index order so LIFO pop visits them in source
|
|
86
|
+
* order. Prunes branches that can't contain the target range by
|
|
87
|
+
* row bounds — same optimization the prior recursive form used, minus
|
|
88
|
+
* the early-break since stack-push is cheap. */
|
|
89
|
+
export declare function findNodeAtRange(root: SyntaxNode, range: Range, expectedType?: string): SyntaxNode | null;
|
|
@@ -463,3 +463,73 @@ export function findChild(node, type) {
|
|
|
463
463
|
}
|
|
464
464
|
return null;
|
|
465
465
|
}
|
|
466
|
+
// ============================================================================
|
|
467
|
+
// Capture + range helpers (formerly python/ast-utils.ts — language-agnostic)
|
|
468
|
+
// ============================================================================
|
|
469
|
+
/** Convert a tree-sitter node to a `Capture` with 1-based line numbers
|
|
470
|
+
* (matching RFC §2.1). The tag includes the leading `@`. */
|
|
471
|
+
export function nodeToCapture(name, node) {
|
|
472
|
+
return {
|
|
473
|
+
name,
|
|
474
|
+
range: {
|
|
475
|
+
startLine: node.startPosition.row + 1,
|
|
476
|
+
startCol: node.startPosition.column,
|
|
477
|
+
endLine: node.endPosition.row + 1,
|
|
478
|
+
endCol: node.endPosition.column,
|
|
479
|
+
},
|
|
480
|
+
text: node.text,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
/** Build a `Capture` whose range mirrors `atNode` but whose `text` is
|
|
484
|
+
* caller-supplied. Used to synthesize markers that don't have a
|
|
485
|
+
* corresponding source token. */
|
|
486
|
+
export function syntheticCapture(name, atNode, text) {
|
|
487
|
+
return {
|
|
488
|
+
name,
|
|
489
|
+
range: {
|
|
490
|
+
startLine: atNode.startPosition.row + 1,
|
|
491
|
+
startCol: atNode.startPosition.column,
|
|
492
|
+
endLine: atNode.endPosition.row + 1,
|
|
493
|
+
endCol: atNode.endPosition.column,
|
|
494
|
+
},
|
|
495
|
+
text,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
function rangeMatches(node, range) {
|
|
499
|
+
return (node.startPosition.row + 1 === range.startLine &&
|
|
500
|
+
node.startPosition.column === range.startCol &&
|
|
501
|
+
node.endPosition.row + 1 === range.endLine &&
|
|
502
|
+
node.endPosition.column === range.endCol);
|
|
503
|
+
}
|
|
504
|
+
/** Walk a subtree to find a node whose range exactly matches AND whose
|
|
505
|
+
* type matches `expectedType` (when given). When multiple nodes share
|
|
506
|
+
* the range — e.g., `function_definition` and its inner `block` body
|
|
507
|
+
* for a one-liner — the type filter disambiguates.
|
|
508
|
+
*
|
|
509
|
+
* Iterative depth-first-left-to-right via an explicit stack. Children
|
|
510
|
+
* are pushed in reverse index order so LIFO pop visits them in source
|
|
511
|
+
* order. Prunes branches that can't contain the target range by
|
|
512
|
+
* row bounds — same optimization the prior recursive form used, minus
|
|
513
|
+
* the early-break since stack-push is cheap. */
|
|
514
|
+
export function findNodeAtRange(root, range, expectedType) {
|
|
515
|
+
const startRow = range.startLine - 1;
|
|
516
|
+
const endRow = range.endLine - 1;
|
|
517
|
+
const stack = [root];
|
|
518
|
+
while (stack.length > 0) {
|
|
519
|
+
const node = stack.pop();
|
|
520
|
+
if (rangeMatches(node, range) && (expectedType === undefined || node.type === expectedType)) {
|
|
521
|
+
return node;
|
|
522
|
+
}
|
|
523
|
+
for (let i = node.namedChildCount - 1; i >= 0; i--) {
|
|
524
|
+
const child = node.namedChild(i);
|
|
525
|
+
if (child === null)
|
|
526
|
+
continue;
|
|
527
|
+
if (child.endPosition.row < startRow)
|
|
528
|
+
continue;
|
|
529
|
+
if (child.startPosition.row > endRow)
|
|
530
|
+
continue;
|
|
531
|
+
stack.push(child);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Default threshold (512 KB). Files larger than this are skipped by the walker. */
|
|
2
|
+
export declare const DEFAULT_MAX_FILE_SIZE_BYTES: number;
|
|
3
|
+
/** Hard upper bound — tree-sitter refuses buffers above this regardless. */
|
|
4
|
+
export declare const MAX_FILE_SIZE_UPPER_BOUND_BYTES: number;
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the effective file-size skip threshold (bytes) for the walker.
|
|
7
|
+
* Reads `GITNEXUS_MAX_FILE_SIZE` (KB). Invalid values fall back to the default
|
|
8
|
+
* and emit a one-time warning. Values above the tree-sitter ceiling are clamped.
|
|
9
|
+
*/
|
|
10
|
+
export declare const getMaxFileSizeBytes: () => number;
|
|
11
|
+
/**
|
|
12
|
+
* Build the CLI banner message announcing an active file-size override.
|
|
13
|
+
* Returns `null` when the effective threshold equals the default — the caller
|
|
14
|
+
* should print nothing in that case. The returned message reflects the
|
|
15
|
+
* *effective* post-clamp threshold, not the raw env value, so operators reading
|
|
16
|
+
* startup output see the actual configuration the walker will use.
|
|
17
|
+
*/
|
|
18
|
+
export declare const getMaxFileSizeBannerMessage: () => string | null;
|
|
19
|
+
/** Test-only: reset the warn-once cache so repeated test runs can re-observe warnings. */
|
|
20
|
+
export declare const _resetMaxFileSizeWarnings: () => void;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { TREE_SITTER_MAX_BUFFER } from '../constants.js';
|
|
2
|
+
/** Default threshold (512 KB). Files larger than this are skipped by the walker. */
|
|
3
|
+
export const DEFAULT_MAX_FILE_SIZE_BYTES = 512 * 1024;
|
|
4
|
+
/** Hard upper bound — tree-sitter refuses buffers above this regardless. */
|
|
5
|
+
export const MAX_FILE_SIZE_UPPER_BOUND_BYTES = TREE_SITTER_MAX_BUFFER;
|
|
6
|
+
const warned = new Set();
|
|
7
|
+
const warnOnce = (key, message) => {
|
|
8
|
+
if (warned.has(key))
|
|
9
|
+
return;
|
|
10
|
+
warned.add(key);
|
|
11
|
+
console.warn(message);
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the effective file-size skip threshold (bytes) for the walker.
|
|
15
|
+
* Reads `GITNEXUS_MAX_FILE_SIZE` (KB). Invalid values fall back to the default
|
|
16
|
+
* and emit a one-time warning. Values above the tree-sitter ceiling are clamped.
|
|
17
|
+
*/
|
|
18
|
+
export const getMaxFileSizeBytes = () => {
|
|
19
|
+
const raw = process.env.GITNEXUS_MAX_FILE_SIZE;
|
|
20
|
+
if (!raw)
|
|
21
|
+
return DEFAULT_MAX_FILE_SIZE_BYTES;
|
|
22
|
+
const parsed = Number(raw);
|
|
23
|
+
if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
|
|
24
|
+
warnOnce(`invalid:${raw}`, ` GITNEXUS_MAX_FILE_SIZE must be a positive integer (KB), got "${raw}" — using default ${DEFAULT_MAX_FILE_SIZE_BYTES / 1024}KB`);
|
|
25
|
+
return DEFAULT_MAX_FILE_SIZE_BYTES;
|
|
26
|
+
}
|
|
27
|
+
const bytes = parsed * 1024;
|
|
28
|
+
if (bytes > MAX_FILE_SIZE_UPPER_BOUND_BYTES) {
|
|
29
|
+
warnOnce(`clamp:${raw}`, ` GITNEXUS_MAX_FILE_SIZE=${parsed}KB exceeds tree-sitter ceiling (${MAX_FILE_SIZE_UPPER_BOUND_BYTES / 1024}KB) — clamping`);
|
|
30
|
+
return MAX_FILE_SIZE_UPPER_BOUND_BYTES;
|
|
31
|
+
}
|
|
32
|
+
return bytes;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Build the CLI banner message announcing an active file-size override.
|
|
36
|
+
* Returns `null` when the effective threshold equals the default — the caller
|
|
37
|
+
* should print nothing in that case. The returned message reflects the
|
|
38
|
+
* *effective* post-clamp threshold, not the raw env value, so operators reading
|
|
39
|
+
* startup output see the actual configuration the walker will use.
|
|
40
|
+
*/
|
|
41
|
+
export const getMaxFileSizeBannerMessage = () => {
|
|
42
|
+
const effectiveBytes = getMaxFileSizeBytes();
|
|
43
|
+
if (effectiveBytes === DEFAULT_MAX_FILE_SIZE_BYTES)
|
|
44
|
+
return null;
|
|
45
|
+
const effectiveKb = effectiveBytes / 1024;
|
|
46
|
+
const defaultKb = DEFAULT_MAX_FILE_SIZE_BYTES / 1024;
|
|
47
|
+
return ` GITNEXUS_MAX_FILE_SIZE: effective threshold ${effectiveKb}KB (default ${defaultKb}KB)`;
|
|
48
|
+
};
|
|
49
|
+
/** Test-only: reset the warn-once cache so repeated test runs can re-observe warnings. */
|
|
50
|
+
export const _resetMaxFileSizeWarnings = () => {
|
|
51
|
+
warned.clear();
|
|
52
|
+
};
|
|
@@ -4,6 +4,7 @@ import { type MixedChainStep } from '../utils/call-analysis.js';
|
|
|
4
4
|
import type { ConstructorBinding } from '../type-env.js';
|
|
5
5
|
import type { NamedBinding } from '../named-bindings/types.js';
|
|
6
6
|
import type { NodeLabel } from '../../../_shared/index.js';
|
|
7
|
+
import type { ParsedFile } from '../../../_shared/index.js';
|
|
7
8
|
interface ParsedNode {
|
|
8
9
|
id: string;
|
|
9
10
|
label: string;
|
|
@@ -174,6 +175,14 @@ export interface ParseWorkerResult {
|
|
|
174
175
|
constructorBindings: FileConstructorBindings[];
|
|
175
176
|
/** All-scope type bindings from TypeEnv for BindingAccumulator (includes function-local). */
|
|
176
177
|
fileScopeBindings: FileScopeBindings[];
|
|
178
|
+
/**
|
|
179
|
+
* Per-file `ParsedFile` artifacts from the new scope-based resolution
|
|
180
|
+
* pipeline (RFC #909 Ring 2). Empty unless the file's provider implements
|
|
181
|
+
* `emitScopeCaptures` — default for every language today, so this is
|
|
182
|
+
* additive and leaves the legacy DAG untouched. Consumed by #921's
|
|
183
|
+
* finalize-orchestrator.
|
|
184
|
+
*/
|
|
185
|
+
parsedFiles: ParsedFile[];
|
|
177
186
|
skippedLanguages: Record<string, number>;
|
|
178
187
|
fileCount: number;
|
|
179
188
|
}
|
|
@@ -6,7 +6,8 @@ import Python from 'tree-sitter-python';
|
|
|
6
6
|
import Java from 'tree-sitter-java';
|
|
7
7
|
import C from 'tree-sitter-c';
|
|
8
8
|
import CPP from 'tree-sitter-cpp';
|
|
9
|
-
import
|
|
9
|
+
// Explicit subpath import — see parser-loader.ts for rationale (#1013).
|
|
10
|
+
import CSharp from 'tree-sitter-c-sharp/bindings/node/index.js';
|
|
10
11
|
import Go from 'tree-sitter-go';
|
|
11
12
|
import Rust from 'tree-sitter-rust';
|
|
12
13
|
import PHP from 'tree-sitter-php';
|
|
@@ -43,6 +44,7 @@ import { generateId } from '../../../lib/utils.js';
|
|
|
43
44
|
import { preprocessImportPath } from '../import-processor.js';
|
|
44
45
|
import { extractVueScript, extractTemplateComponents, isVueSetupTopLevel, } from '../vue-sfc-extractor.js';
|
|
45
46
|
import { buildMethodProps, arityForIdFromInfo, typeTagForId, constTagForId, buildCollisionGroups, } from '../utils/method-props.js';
|
|
47
|
+
import { extractParsedFile } from '../scope-extractor-bridge.js';
|
|
46
48
|
// ============================================================================
|
|
47
49
|
// Worker-local parser + language map
|
|
48
50
|
// ============================================================================
|
|
@@ -286,31 +288,47 @@ const findEnclosingFunctionId = (node, filePath, provider) => {
|
|
|
286
288
|
}
|
|
287
289
|
// Qualify with enclosing class to match definition-phase node IDs
|
|
288
290
|
const classInfo = cachedFindEnclosingClassInfo(current, filePath, provider.resolveEnclosingOwner);
|
|
289
|
-
const
|
|
291
|
+
const encLang = getLanguageFromFilename(filePath);
|
|
292
|
+
const standaloneMethodInfo = (finalLabel === 'Method' || finalLabel === 'Constructor') &&
|
|
293
|
+
encLang === SupportedLanguages.Go &&
|
|
294
|
+
provider.methodExtractor?.extractFromNode
|
|
295
|
+
? provider.methodExtractor.extractFromNode(current, {
|
|
296
|
+
filePath,
|
|
297
|
+
language: encLang,
|
|
298
|
+
})
|
|
299
|
+
: null;
|
|
300
|
+
const ownerName = classInfo?.className ?? standaloneMethodInfo?.receiverType ?? undefined;
|
|
301
|
+
const qualifiedName = ownerName ? `${ownerName}.${funcName}` : funcName;
|
|
290
302
|
// Include #<arity> suffix to match definition-phase Method/Constructor IDs.
|
|
291
303
|
// Use the same MethodExtractor (getMethodInfo) as the definition phase.
|
|
292
304
|
// When same-arity collisions exist, also append ~type1,type2.
|
|
293
305
|
let arity;
|
|
294
306
|
let encTypeTag = '';
|
|
295
307
|
if (finalLabel === 'Method' || finalLabel === 'Constructor') {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
308
|
+
if (standaloneMethodInfo) {
|
|
309
|
+
arity = standaloneMethodInfo.parameters.some((p) => p.isVariadic)
|
|
310
|
+
? undefined
|
|
311
|
+
: standaloneMethodInfo.parameters.length;
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
const classNode = findEnclosingClassNode(current) ?? findClassNodeByQualifiedName(current);
|
|
315
|
+
if (classNode && encLang) {
|
|
316
|
+
const methodMap = getMethodInfo(classNode, provider, {
|
|
317
|
+
filePath,
|
|
318
|
+
language: encLang,
|
|
319
|
+
});
|
|
320
|
+
const defLine = current.startPosition.row + 1;
|
|
321
|
+
const info = methodMap?.get(`${funcName}:${defLine}`);
|
|
322
|
+
if (info) {
|
|
323
|
+
arity = info.parameters.some((p) => p.isVariadic)
|
|
324
|
+
? undefined
|
|
325
|
+
: info.parameters.length;
|
|
326
|
+
if (methodMap && arity !== undefined) {
|
|
327
|
+
const g = buildCollisionGroups(methodMap);
|
|
328
|
+
encTypeTag =
|
|
329
|
+
typeTagForId(methodMap, funcName, arity, info, encLang, g) +
|
|
330
|
+
constTagForId(methodMap, funcName, arity, info, g);
|
|
331
|
+
}
|
|
314
332
|
}
|
|
315
333
|
}
|
|
316
334
|
}
|
|
@@ -415,6 +433,7 @@ const processBatch = (files, onProgress) => {
|
|
|
415
433
|
ormQueries: [],
|
|
416
434
|
constructorBindings: [],
|
|
417
435
|
fileScopeBindings: [],
|
|
436
|
+
parsedFiles: [],
|
|
418
437
|
skippedLanguages: {},
|
|
419
438
|
fileCount: 0,
|
|
420
439
|
};
|
|
@@ -1017,11 +1036,25 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1017
1036
|
console.warn(`Query execution failed for ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1018
1037
|
continue;
|
|
1019
1038
|
}
|
|
1039
|
+
const provider = getProvider(language);
|
|
1040
|
+
// RFC #909 Ring 2: produce a `ParsedFile` for the new scope-based
|
|
1041
|
+
// resolution pipeline. No-op (returns undefined) for every language
|
|
1042
|
+
// today — only fires once a provider implements `emitScopeCaptures`.
|
|
1043
|
+
// Runs BEFORE legacy extraction and its result is independent: a
|
|
1044
|
+
// failure here is caught inside `extractParsedFile` and does NOT
|
|
1045
|
+
// affect the legacy DAG path that follows.
|
|
1046
|
+
const parsedFile = extractParsedFile(provider, parseContent, file.path, (message) => {
|
|
1047
|
+
if (parentPort)
|
|
1048
|
+
parentPort.postMessage({ type: 'warning', message });
|
|
1049
|
+
else
|
|
1050
|
+
console.warn(message);
|
|
1051
|
+
});
|
|
1052
|
+
if (parsedFile !== undefined)
|
|
1053
|
+
result.parsedFiles.push(parsedFile);
|
|
1020
1054
|
// Pre-pass: extract heritage from query matches to build parentMap for buildTypeEnv.
|
|
1021
1055
|
// Heritage edges (EXTENDS/IMPLEMENTS) are created by heritage-processor which runs
|
|
1022
1056
|
// in PARALLEL with call-processor, so the graph edges don't exist when buildTypeEnv
|
|
1023
1057
|
// runs. This pre-pass makes parent class information available for type resolution.
|
|
1024
|
-
const provider = getProvider(language);
|
|
1025
1058
|
const fileParentMap = new Map();
|
|
1026
1059
|
if (provider.heritageExtractor) {
|
|
1027
1060
|
for (const match of matches) {
|
|
@@ -1804,6 +1837,7 @@ let accumulated = {
|
|
|
1804
1837
|
ormQueries: [],
|
|
1805
1838
|
constructorBindings: [],
|
|
1806
1839
|
fileScopeBindings: [],
|
|
1840
|
+
parsedFiles: [],
|
|
1807
1841
|
skippedLanguages: {},
|
|
1808
1842
|
fileCount: 0,
|
|
1809
1843
|
};
|
|
@@ -1830,6 +1864,7 @@ const mergeResult = (target, src) => {
|
|
|
1830
1864
|
appendAll(target.ormQueries, src.ormQueries);
|
|
1831
1865
|
appendAll(target.constructorBindings, src.constructorBindings);
|
|
1832
1866
|
appendAll(target.fileScopeBindings, src.fileScopeBindings);
|
|
1867
|
+
appendAll(target.parsedFiles, src.parsedFiles);
|
|
1833
1868
|
for (const [lang, count] of Object.entries(src.skippedLanguages)) {
|
|
1834
1869
|
target.skippedLanguages[lang] = (target.skippedLanguages[lang] || 0) + count;
|
|
1835
1870
|
}
|
|
@@ -1878,6 +1913,7 @@ parentPort.on('message', (msg) => {
|
|
|
1878
1913
|
ormQueries: [],
|
|
1879
1914
|
constructorBindings: [],
|
|
1880
1915
|
fileScopeBindings: [],
|
|
1916
|
+
parsedFiles: [],
|
|
1881
1917
|
skippedLanguages: {},
|
|
1882
1918
|
fileCount: 0,
|
|
1883
1919
|
};
|
|
@@ -123,9 +123,17 @@ export declare const deleteNodesForFile: (filePath: string, dbPath?: string) =>
|
|
|
123
123
|
export declare const getEmbeddingTableName: () => string;
|
|
124
124
|
/**
|
|
125
125
|
* Load the FTS extension (required before using FTS functions).
|
|
126
|
-
*
|
|
126
|
+
*
|
|
127
|
+
* Safe to call multiple times — when invoked without arguments, tracks loaded
|
|
128
|
+
* state via module-level `ftsLoaded`. When invoked with an explicit
|
|
129
|
+
* connection, loads on that connection and returns whether the load
|
|
130
|
+
* succeeded — letting callers (e.g. the pool adapter) track their own state.
|
|
131
|
+
*
|
|
132
|
+
* Tries `LOAD EXTENSION fts` first so previously-cached installs skip the
|
|
133
|
+
* network entirely; falls back to `INSTALL` + `LOAD` only when the extension
|
|
134
|
+
* hasn't been cached yet.
|
|
127
135
|
*/
|
|
128
|
-
export declare const loadFTSExtension: () => Promise<
|
|
136
|
+
export declare const loadFTSExtension: (targetConn?: lbug.Connection) => Promise<boolean>;
|
|
129
137
|
/**
|
|
130
138
|
* Load the VECTOR extension (required before using QUERY_VECTOR_INDEX).
|
|
131
139
|
* Safe to call multiple times -- tracks loaded state via module-level vectorExtensionLoaded.
|
|
@@ -139,6 +147,18 @@ export declare const loadVectorExtension: () => Promise<void>;
|
|
|
139
147
|
* @param stemmer - Stemming algorithm (default: 'porter')
|
|
140
148
|
*/
|
|
141
149
|
export declare const createFTSIndex: (tableName: string, indexName: string, properties: string[], stemmer?: string) => Promise<void>;
|
|
150
|
+
/**
|
|
151
|
+
* Lazy-create an FTS index, caching the fact in-process.
|
|
152
|
+
*
|
|
153
|
+
* Used by `queryFTS` so that `analyze` doesn't pay the ~440 ms × 5 fixed
|
|
154
|
+
* LadybugDB cost up-front (it dominates analyze on small repos). Instead,
|
|
155
|
+
* the cost is moved to the first `query`/`context` call in a session,
|
|
156
|
+
* where it's amortised across many lookups.
|
|
157
|
+
*
|
|
158
|
+
* Safe to call repeatedly — the in-process Set guarantees only the first
|
|
159
|
+
* call hits LadybugDB. `closeLbug` clears the cache so re-init starts fresh.
|
|
160
|
+
*/
|
|
161
|
+
export declare const ensureFTSIndex: (tableName: string, indexName: string, properties: string[], stemmer?: string) => Promise<void>;
|
|
142
162
|
/**
|
|
143
163
|
* Query a full-text search index
|
|
144
164
|
* @param tableName - The node table name
|