gitnexus 1.6.8-rc.2 → 1.6.8-rc.21
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 +20 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/symbol-definition.d.ts +4 -0
- package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
- package/dist/cli/ai-context.js +1 -0
- package/dist/cli/analyze-config.js +40 -0
- package/dist/cli/analyze.d.ts +22 -0
- package/dist/cli/analyze.js +36 -6
- package/dist/cli/clean.d.ts +1 -0
- package/dist/cli/clean.js +43 -1
- package/dist/cli/eval-server.js +43 -0
- package/dist/cli/help-i18n.js +11 -0
- package/dist/cli/i18n/en.d.ts +13 -0
- package/dist/cli/i18n/en.js +13 -0
- package/dist/cli/i18n/resources.d.ts +26 -0
- package/dist/cli/i18n/zh-CN.d.ts +13 -0
- package/dist/cli/i18n/zh-CN.js +13 -0
- package/dist/cli/index.js +19 -0
- package/dist/cli/list.js +12 -0
- package/dist/cli/optional-grammars.d.ts +6 -8
- package/dist/cli/optional-grammars.js +8 -11
- package/dist/cli/skill-gen.js +1 -0
- package/dist/cli/status.js +26 -5
- package/dist/cli/tool.d.ts +12 -1
- package/dist/cli/tool.js +41 -1
- package/dist/core/embeddings/embedder.js +4 -0
- package/dist/core/embeddings/embedding-pipeline.js +27 -16
- package/dist/core/embeddings/onnxruntime-common-resolver.d.ts +6 -0
- package/dist/core/embeddings/onnxruntime-common-resolver.js +130 -0
- package/dist/core/graph/import-cycles.d.ts +10 -0
- package/dist/core/graph/import-cycles.js +103 -0
- package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
- package/dist/core/group/extractors/http-patterns/java.js +3 -50
- package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
- package/dist/core/group/extractors/include-extractor.js +7 -7
- package/dist/core/ingestion/cfg/cfg-builder.d.ts +64 -0
- package/dist/core/ingestion/cfg/cfg-builder.js +129 -0
- package/dist/core/ingestion/cfg/collect.d.ts +30 -0
- package/dist/core/ingestion/cfg/collect.js +34 -0
- package/dist/core/ingestion/cfg/control-flow-context.d.ts +97 -0
- package/dist/core/ingestion/cfg/control-flow-context.js +113 -0
- package/dist/core/ingestion/cfg/emit.d.ts +144 -0
- package/dist/core/ingestion/cfg/emit.js +315 -0
- package/dist/core/ingestion/cfg/reaching-defs.d.ts +90 -0
- package/dist/core/ingestion/cfg/reaching-defs.js +364 -0
- package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
- package/dist/core/ingestion/cfg/traversal-result.js +2 -0
- package/dist/core/ingestion/cfg/types.d.ts +231 -0
- package/dist/core/ingestion/cfg/types.js +13 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +144 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1032 -0
- package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
- package/dist/core/ingestion/cfg/visitors/typescript.js +584 -0
- package/dist/core/ingestion/language-provider.d.ts +36 -2
- package/dist/core/ingestion/languages/c-cpp.js +11 -4
- package/dist/core/ingestion/languages/cpp/arity-metadata.js +6 -2
- package/dist/core/ingestion/languages/cpp/captures.js +24 -1
- package/dist/core/ingestion/languages/cpp/query.js +23 -0
- package/dist/core/ingestion/languages/java.js +3 -0
- package/dist/core/ingestion/languages/kotlin/query.js +3 -2
- package/dist/core/ingestion/languages/kotlin.js +5 -1
- package/dist/core/ingestion/languages/typescript.js +5 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
- package/dist/core/ingestion/method-extractors/generic.js +1 -0
- package/dist/core/ingestion/method-types.d.ts +2 -0
- package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
- package/dist/core/ingestion/model/symbol-table.js +1 -0
- package/dist/core/ingestion/parsing-processor.js +22 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
- package/dist/core/ingestion/pipeline-phases/routes.js +64 -14
- package/dist/core/ingestion/pipeline.d.ts +57 -0
- package/dist/core/ingestion/route-extractors/spring-shared.d.ts +50 -0
- package/dist/core/ingestion/route-extractors/spring-shared.js +80 -0
- package/dist/core/ingestion/route-extractors/spring.d.ts +35 -0
- package/dist/core/ingestion/route-extractors/spring.js +136 -0
- package/dist/core/ingestion/scope-extractor.js +3 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +12 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +145 -24
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +6 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +21 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +207 -0
- package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
- package/dist/core/ingestion/taint/emit.d.ts +124 -0
- package/dist/core/ingestion/taint/emit.js +204 -0
- package/dist/core/ingestion/taint/match.d.ts +153 -0
- package/dist/core/ingestion/taint/match.js +278 -0
- package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
- package/dist/core/ingestion/taint/path-codec.js +190 -0
- package/dist/core/ingestion/taint/propagate.d.ts +216 -0
- package/dist/core/ingestion/taint/propagate.js +664 -0
- package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
- package/dist/core/ingestion/taint/site-safety.js +98 -0
- package/dist/core/ingestion/taint/source-sink-config.d.ts +94 -23
- package/dist/core/ingestion/taint/source-sink-config.js +11 -11
- package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
- package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
- package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
- package/dist/core/ingestion/taint/typescript-model.js +102 -0
- package/dist/core/ingestion/utils/method-props.js +1 -0
- package/dist/core/ingestion/workers/clone-safety.d.ts +109 -0
- package/dist/core/ingestion/workers/clone-safety.js +465 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +11 -0
- package/dist/core/ingestion/workers/parse-worker.js +70 -53
- package/dist/core/ingestion/workers/post-result.d.ts +22 -0
- package/dist/core/ingestion/workers/post-result.js +87 -0
- package/dist/core/ingestion/workers/result-merge.d.ts +20 -0
- package/dist/core/ingestion/workers/result-merge.js +43 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +15 -0
- package/dist/core/ingestion/workers/worker-pool.js +33 -16
- package/dist/core/lbug/lbug-adapter.d.ts +19 -0
- package/dist/core/lbug/lbug-adapter.js +56 -1
- package/dist/core/run-analyze.d.ts +87 -0
- package/dist/core/run-analyze.js +280 -25
- package/dist/core/tree-sitter/parser-loader.js +5 -4
- package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
- package/dist/core/tree-sitter/vendored-grammars.js +57 -0
- package/dist/mcp/core/embedder.js +4 -0
- package/dist/mcp/local/local-backend.d.ts +86 -2
- package/dist/mcp/local/local-backend.js +657 -27
- package/dist/mcp/resources.js +1 -0
- package/dist/mcp/tools.d.ts +9 -0
- package/dist/mcp/tools.js +109 -0
- package/dist/server/analyze-launch.d.ts +29 -0
- package/dist/server/analyze-launch.js +137 -0
- package/dist/server/analyze-upload.d.ts +33 -0
- package/dist/server/analyze-upload.js +123 -0
- package/dist/server/analyze-worker-ipc.d.ts +58 -0
- package/dist/server/analyze-worker-ipc.js +16 -0
- package/dist/server/analyze-worker.d.ts +17 -1
- package/dist/server/analyze-worker.js +7 -1
- package/dist/server/api.js +46 -140
- package/dist/server/git-clone.d.ts +1 -0
- package/dist/server/git-clone.js +1 -1
- package/dist/server/middleware.d.ts +11 -0
- package/dist/server/middleware.js +27 -0
- package/dist/server/upload-ingest.d.ts +56 -0
- package/dist/server/upload-ingest.js +276 -0
- package/dist/server/upload-paths.d.ts +31 -0
- package/dist/server/upload-paths.js +51 -0
- package/dist/server/upload-sweep.d.ts +20 -0
- package/dist/server/upload-sweep.js +57 -0
- package/dist/storage/branch-index.d.ts +52 -0
- package/dist/storage/branch-index.js +65 -0
- package/dist/storage/git.d.ts +11 -0
- package/dist/storage/git.js +28 -0
- package/dist/storage/parse-cache.d.ts +22 -1
- package/dist/storage/parse-cache.js +32 -10
- package/dist/storage/repo-manager.d.ts +122 -10
- package/dist/storage/repo-manager.js +162 -21
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +42 -8
- package/hooks/claude/gitnexus-hook.cjs +36 -8
- package/hooks/claude/hook-db-lock-probe.cjs +123 -2
- package/package.json +4 -2
- package/scripts/assert-publish-grammar-coverage.cjs +31 -0
- package/scripts/build-tree-sitter-grammars.cjs +16 -10
- package/skills/gitnexus-guide.md +11 -0
- package/vendor/tree-sitter-c/package.json +1 -1
- package/vendor/tree-sitter-dart/package.json +1 -1
- package/vendor/tree-sitter-kotlin/package.json +1 -1
- package/vendor/tree-sitter-proto/package.json +1 -1
- package/vendor/tree-sitter-swift/package.json +1 -1
- package/web/assets/{agent-CKPMqImC.js → agent-ay4LD70X.js} +1 -1
- package/web/assets/{architectureDiagram-UL44E2DR-D1EXI0zA.js → architectureDiagram-UL44E2DR-Dc-viYhd.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-4rpojOyj.js} +1 -1
- package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-BnOy944n.js} +1 -1
- package/web/assets/{classDiagram-KGZ6W3CR-Bv93af_b.js → classDiagram-KGZ6W3CR-Bsgpy98Q.js} +1 -1
- package/web/assets/{classDiagram-v2-72OJOZXJ-CLfEqHUa.js → classDiagram-v2-72OJOZXJ-D5atDGjc.js} +1 -1
- package/web/assets/{diagram-3NCE3AQN-BqAtKUpW.js → diagram-3NCE3AQN-Cz1OEMVi.js} +1 -1
- package/web/assets/{diagram-GF46GFSD-CaBG6n6o.js → diagram-GF46GFSD-CWYwzfP8.js} +1 -1
- package/web/assets/{diagram-QXG6HAR7-CZ-O3rcV.js → diagram-QXG6HAR7-DtBObr8L.js} +1 -1
- package/web/assets/{diagram-WEQXMOUZ-P4lSL4GH.js → diagram-WEQXMOUZ-BUV44Ov_.js} +1 -1
- package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-Ds9s-sRF.js} +1 -1
- package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-CupStHQb.js} +1 -1
- package/web/assets/index-COMMmbxW.css +2 -0
- package/web/assets/{index-CG6q8eTs.js → index-Do8AE5yF.js} +85 -85
- package/web/assets/{infoDiagram-3YFTVSEB-DmLICZx1.js → infoDiagram-3YFTVSEB-DPTXSbgu.js} +1 -1
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CeK-Qhet.js} +1 -1
- package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-D4tx6QQ-.js} +1 -1
- package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-Bcto_kGa.js} +1 -1
- package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-DPIeBTG6.js} +1 -1
- package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-DDGXjVKO.js} +1 -1
- package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-BzDqpcVW.js} +1 -1
- package/web/assets/{stateDiagram-7D4R322I-CB2nABwH.js → stateDiagram-7D4R322I-Bx__als3.js} +1 -1
- package/web/assets/{stateDiagram-v2-36443NZ5-COBGd2RL.js → stateDiagram-v2-36443NZ5-QqOh2yO-.js} +1 -1
- package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-C1eogTOG.js} +1 -1
- package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-D20F4rSW.js} +1 -1
- package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-BeBHg7ST.js} +1 -1
- package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-CdOWeoNI.js} +1 -1
- package/web/index.html +2 -2
- package/scripts/materialize-vendor-grammars.cjs +0 -97
- package/web/assets/index-BKWA-m7o.css +0 -2
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* IMPORTANT: This module must NEVER call process.exit(). The caller (CLI
|
|
9
9
|
* wrapper or server worker) is responsible for process lifecycle.
|
|
10
10
|
*/
|
|
11
|
+
import { type RepoMeta } from '../storage/repo-manager.js';
|
|
11
12
|
export interface AnalyzeCallbacks {
|
|
12
13
|
onProgress: (phase: string, percent: number, message: string) => void;
|
|
13
14
|
onLog?: (message: string) => void;
|
|
@@ -48,6 +49,29 @@ export interface AnalyzeOptions {
|
|
|
48
49
|
noStats?: boolean;
|
|
49
50
|
/** Skip installing standard GitNexus skill files to .claude/skills/gitnexus/. */
|
|
50
51
|
skipSkills?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Build the CFG/PDG substrate (#2081 M1). Forwarded to `PipelineOptions.pdg`,
|
|
54
|
+
* which threads to BOTH the worker (CFG build, via workerData) AND
|
|
55
|
+
* scope-resolution (BasicBlock/CFG emit gate). Off by default.
|
|
56
|
+
*/
|
|
57
|
+
pdg?: boolean;
|
|
58
|
+
/** Per-function source-line cap for worker-side CFG construction (#2081 M1).
|
|
59
|
+
* Forwarded to `PipelineOptions.pdgMaxFunctionLines`. No CLI flag in M1 —
|
|
60
|
+
* programmatic / server analyze-worker path only; the worker applies
|
|
61
|
+
* `DEFAULT_PDG_MAX_FUNCTION_LINES` when unset. */
|
|
62
|
+
pdgMaxFunctionLines?: number;
|
|
63
|
+
/** Per-function CFG edge cap. Forwarded to `PipelineOptions.pdgMaxEdgesPerFunction`. */
|
|
64
|
+
pdgMaxEdgesPerFunction?: number;
|
|
65
|
+
/** Per-function REACHING_DEF edge cap (#2082 M2). Forwarded to
|
|
66
|
+
* `PipelineOptions.pdgMaxReachingDefEdgesPerFunction`. */
|
|
67
|
+
pdgMaxReachingDefEdgesPerFunction?: number;
|
|
68
|
+
/** Per-function taint findings cap (#2083 M3). Forwarded to
|
|
69
|
+
* `PipelineOptions.pdgMaxTaintFindingsPerFunction`. No CLI flag or rc key
|
|
70
|
+
* (KTD8) — programmatic / server path only, like the other pdg caps. */
|
|
71
|
+
pdgMaxTaintFindingsPerFunction?: number;
|
|
72
|
+
/** Per-finding taint hop cap (#2083 M3, KTD6). Forwarded to
|
|
73
|
+
* `PipelineOptions.pdgMaxTaintHops`. No CLI flag or rc key (KTD8). */
|
|
74
|
+
pdgMaxTaintHops?: number;
|
|
51
75
|
/**
|
|
52
76
|
* Default branch threaded into generated AGENTS.md / CLAUDE.md so the
|
|
53
77
|
* regression-compare example uses the configured branch instead of a
|
|
@@ -55,6 +79,16 @@ export interface AnalyzeOptions {
|
|
|
55
79
|
* "main" fallback for non-CLI callers (e.g. the server analyze worker).
|
|
56
80
|
*/
|
|
57
81
|
defaultBranch?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Index-branch selector (#2106). Distinct from `defaultBranch` (which only
|
|
84
|
+
* affects generated AGENTS.md/CLAUDE.md base_ref text). When set, this run is
|
|
85
|
+
* labelled as that branch and routed to a per-branch index slot unless it is
|
|
86
|
+
* the primary branch. When `undefined`, the branch is auto-detected from the
|
|
87
|
+
* checked-out HEAD (the flat/primary slot for the first-indexed branch, a
|
|
88
|
+
* `branches/<slug>/` sub-directory otherwise). Detached HEAD / non-git always
|
|
89
|
+
* maps to the flat slot.
|
|
90
|
+
*/
|
|
91
|
+
branch?: string;
|
|
58
92
|
/**
|
|
59
93
|
* User-provided alias for the registry `name` (#829). When set,
|
|
60
94
|
* forwarded to `registerRepo` so the indexed repo is stored under
|
|
@@ -78,6 +112,13 @@ export interface AnalyzeOptions {
|
|
|
78
112
|
* removed); `undefined` defers to the env / auto-formula fallback.
|
|
79
113
|
*/
|
|
80
114
|
workerPoolSize?: number;
|
|
115
|
+
/**
|
|
116
|
+
* Extra fetch-wrapper function names to treat as HTTP consumers, forwarded to
|
|
117
|
+
* `PipelineOptions.fetchWrappers` (#1589/#1852 residual). Sourced from the CLI
|
|
118
|
+
* `.gitnexusrc` `fetchWrappers` list. `undefined`/empty leaves the route
|
|
119
|
+
* consumer scan unchanged.
|
|
120
|
+
*/
|
|
121
|
+
fetchWrappers?: string[];
|
|
81
122
|
}
|
|
82
123
|
export interface AnalyzeResult {
|
|
83
124
|
repoName: string;
|
|
@@ -102,6 +143,13 @@ export interface AnalyzeResult {
|
|
|
102
143
|
* the persisted meta surface the degraded state instead of reporting healthy.
|
|
103
144
|
*/
|
|
104
145
|
ftsSkipped?: boolean;
|
|
146
|
+
/**
|
|
147
|
+
* True when the index this run produced/validated is the primary/flat slot
|
|
148
|
+
* (#2106 R2). `false` for a non-primary branch index. Lets the CLI skip
|
|
149
|
+
* repo-root AGENTS.md/CLAUDE.md refreshes (e.g. the base_ref fast-path) for a
|
|
150
|
+
* branch analyze, mirroring the in-pipeline `if (!placement.branch)` gate.
|
|
151
|
+
*/
|
|
152
|
+
isPrimaryBranch?: boolean;
|
|
105
153
|
}
|
|
106
154
|
export { deriveEmbeddingMode, DEFAULT_EMBEDDING_NODE_LIMIT } from './embedding-mode.js';
|
|
107
155
|
export type { EmbeddingMode } from './embedding-mode.js';
|
|
@@ -117,4 +165,43 @@ export declare const PHASE_LABELS: Record<string, string>;
|
|
|
117
165
|
* the {@link AnalyzeCallbacks} interface — it never writes to stdout/stderr
|
|
118
166
|
* directly and never calls `process.exit()`.
|
|
119
167
|
*/
|
|
168
|
+
/**
|
|
169
|
+
* Build the primary-inversion warning (#2106 R8), or `undefined` when there is
|
|
170
|
+
* nothing to warn about. Pure + exported for testing. Both inputs are trimmed
|
|
171
|
+
* (a diagnostic — a missed warning is low-harm; a false warning is the thing to
|
|
172
|
+
* avoid). `defaultBranch` is the repo's `origin/HEAD` branch (null when unset,
|
|
173
|
+
* e.g. fresh clones / CI), `flatOwner` is the branch that owns the flat slot.
|
|
174
|
+
*/
|
|
175
|
+
export declare const primaryInversionWarning: (defaultBranch: string | null | undefined, flatOwner: string | null | undefined) => string | undefined;
|
|
176
|
+
/**
|
|
177
|
+
* Collect the recorded parse-cache chunk keys across the flat + every branch
|
|
178
|
+
* meta under a flat `.gitnexus` storage, EXCLUDING `excludeDir` (the current
|
|
179
|
+
* run's own meta dir) so a single-branch repo collects nothing and its prune
|
|
180
|
+
* stays byte-identical to today (#2106 R6). `complete` is false when a sibling
|
|
181
|
+
* meta.json exists but fails to parse — callers then retain the whole shared
|
|
182
|
+
* cache rather than over-evict another branch's still-live shards. Exported for
|
|
183
|
+
* testing.
|
|
184
|
+
*/
|
|
185
|
+
export declare const collectBranchCacheKeys: (storagePath: string, excludeDir?: string) => Promise<{
|
|
186
|
+
keys: Set<string>;
|
|
187
|
+
complete: boolean;
|
|
188
|
+
}>;
|
|
189
|
+
/**
|
|
190
|
+
* Resolve the requested `--pdg` configuration to the shape recorded in
|
|
191
|
+
* `RepoMeta.pdg`, or `undefined` for a pdg-off run. Caps resolve to their
|
|
192
|
+
* defaults so an explicit-default run compares equal to a default run
|
|
193
|
+
* (`0` = unlimited is preserved as `0`). Pure + exported for testing.
|
|
194
|
+
*/
|
|
195
|
+
type PdgOptions = Pick<AnalyzeOptions, 'pdg' | 'pdgMaxFunctionLines' | 'pdgMaxEdgesPerFunction' | 'pdgMaxReachingDefEdgesPerFunction' | 'pdgMaxTaintFindingsPerFunction' | 'pdgMaxTaintHops'>;
|
|
196
|
+
export declare const resolvePdgConfig: (options: PdgOptions) => RepoMeta["pdg"];
|
|
197
|
+
/**
|
|
198
|
+
* Whether the requested `--pdg` configuration differs from the one the
|
|
199
|
+
* existing index's DB rows were built under (#2099 F1). An absent recorded
|
|
200
|
+
* stamp means pdg-off (every legacy meta — `--pdg` shipped opt-in). Any
|
|
201
|
+
* mismatch means the incremental writeback (which only persists changed-file
|
|
202
|
+
* nodes) cannot produce a coherent index: off→on would silently drop the
|
|
203
|
+
* freshly built CFG layer, on→off would strand zombie BasicBlocks — so the
|
|
204
|
+
* caller forces a full writeback. Pure + exported for testing.
|
|
205
|
+
*/
|
|
206
|
+
export declare const pdgModeMismatch: (recorded: RepoMeta["pdg"], options: PdgOptions) => boolean;
|
|
120
207
|
export declare function runFullAnalysis(repoPath: string, options: AnalyzeOptions, callbacks: AnalyzeCallbacks): Promise<AnalyzeResult>;
|
package/dist/core/run-analyze.js
CHANGED
|
@@ -17,14 +17,19 @@ import { initLbug, loadGraphToLbug, getLbugStats, executeQuery, executeWithReuse
|
|
|
17
17
|
import { createSearchFTSIndexes, verifySearchFTSIndexes } from './search/fts-indexes.js';
|
|
18
18
|
import { resolveAnalyzeInstallPolicy } from './lbug/extension-loader.js';
|
|
19
19
|
import { startWalCheckpointDriver, } from './lbug/wal-checkpoint-driver.js';
|
|
20
|
-
import { getStoragePaths, saveMeta, loadMeta, ensureGitNexusIgnored, registerRepo, cleanupOldKuzuFiles, INCREMENTAL_SCHEMA_VERSION, } from '../storage/repo-manager.js';
|
|
20
|
+
import { getStoragePaths, resolveBranchPlacement, saveMeta, loadMeta, ensureGitNexusIgnored, registerRepo, cleanupOldKuzuFiles, INCREMENTAL_SCHEMA_VERSION, } from '../storage/repo-manager.js';
|
|
21
|
+
import { DEFAULT_PDG_MAX_FUNCTION_LINES } from './ingestion/cfg/collect.js';
|
|
22
|
+
import { DEFAULT_MAX_CFG_EDGES_PER_FUNCTION, DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION, } from './ingestion/cfg/emit.js';
|
|
23
|
+
import { DEFAULT_PDG_MAX_TAINT_FINDINGS_PER_FUNCTION, DEFAULT_PDG_MAX_TAINT_HOPS, } from './ingestion/taint/propagate.js';
|
|
24
|
+
import { taintModelVersion } from './ingestion/taint/typescript-model.js';
|
|
21
25
|
import { computeFileHashes, diffFileHashes } from '../storage/file-hash.js';
|
|
22
26
|
import { extractChangedSubgraph, computeEffectiveWriteSet, } from './incremental/subgraph-extract.js';
|
|
23
27
|
import { shadowCandidatesFor } from './incremental/shadow-candidates.js';
|
|
24
28
|
import { loadParseCache, saveParseCache, pruneCache, PARSE_CACHE_VERSION, } from '../storage/parse-cache.js';
|
|
25
29
|
import { getDurableParsedFileDir, pruneAndSaveDurableParsedFileStore, } from '../storage/parsedfile-store.js';
|
|
26
|
-
import { getCurrentCommit, getRemoteUrl, hasGitDir, getInferredRepoName, resolveRepoIdentityRoot, } from '../storage/git.js';
|
|
30
|
+
import { getCurrentCommit, getCurrentBranch, getDefaultBranch, getRemoteUrl, hasGitDir, getInferredRepoName, resolveRepoIdentityRoot, } from '../storage/git.js';
|
|
27
31
|
import { generateAIContextFiles } from '../cli/ai-context.js';
|
|
32
|
+
import { sanitizeDetectedBranch } from '../cli/analyze-config.js';
|
|
28
33
|
import { EMBEDDING_TABLE_NAME } from './lbug/schema.js';
|
|
29
34
|
import { STALE_HASH_SENTINEL } from './lbug/schema.js';
|
|
30
35
|
/**
|
|
@@ -70,6 +75,112 @@ export const PHASE_LABELS = {
|
|
|
70
75
|
* the {@link AnalyzeCallbacks} interface — it never writes to stdout/stderr
|
|
71
76
|
* directly and never calls `process.exit()`.
|
|
72
77
|
*/
|
|
78
|
+
/**
|
|
79
|
+
* Build the primary-inversion warning (#2106 R8), or `undefined` when there is
|
|
80
|
+
* nothing to warn about. Pure + exported for testing. Both inputs are trimmed
|
|
81
|
+
* (a diagnostic — a missed warning is low-harm; a false warning is the thing to
|
|
82
|
+
* avoid). `defaultBranch` is the repo's `origin/HEAD` branch (null when unset,
|
|
83
|
+
* e.g. fresh clones / CI), `flatOwner` is the branch that owns the flat slot.
|
|
84
|
+
*/
|
|
85
|
+
export const primaryInversionWarning = (defaultBranch, flatOwner) => {
|
|
86
|
+
const norm = (s) => s?.trim() || undefined;
|
|
87
|
+
const d = norm(defaultBranch);
|
|
88
|
+
const o = norm(flatOwner);
|
|
89
|
+
if (!d || !o || d === o)
|
|
90
|
+
return undefined;
|
|
91
|
+
return (`Warning: the default branch "${d}" is not the primary index — "${o}" owns the flat slot. ` +
|
|
92
|
+
`Run \`gitnexus clean --branch ${o}\` then re-index on "${d}", or query it explicitly with \`--branch ${d}\`.`);
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Collect the recorded parse-cache chunk keys across the flat + every branch
|
|
96
|
+
* meta under a flat `.gitnexus` storage, EXCLUDING `excludeDir` (the current
|
|
97
|
+
* run's own meta dir) so a single-branch repo collects nothing and its prune
|
|
98
|
+
* stays byte-identical to today (#2106 R6). `complete` is false when a sibling
|
|
99
|
+
* meta.json exists but fails to parse — callers then retain the whole shared
|
|
100
|
+
* cache rather than over-evict another branch's still-live shards. Exported for
|
|
101
|
+
* testing.
|
|
102
|
+
*/
|
|
103
|
+
export const collectBranchCacheKeys = async (storagePath, excludeDir) => {
|
|
104
|
+
const keys = new Set();
|
|
105
|
+
let complete = true;
|
|
106
|
+
const metaDirs = [storagePath];
|
|
107
|
+
const branchesDir = path.join(storagePath, 'branches');
|
|
108
|
+
const slugs = await fs.readdir(branchesDir).catch(() => []);
|
|
109
|
+
for (const slug of slugs)
|
|
110
|
+
metaDirs.push(path.join(branchesDir, slug));
|
|
111
|
+
for (const dir of metaDirs) {
|
|
112
|
+
if (excludeDir && path.resolve(dir) === path.resolve(excludeDir))
|
|
113
|
+
continue;
|
|
114
|
+
let raw;
|
|
115
|
+
try {
|
|
116
|
+
raw = await fs.readFile(path.join(dir, 'meta.json'), 'utf-8');
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
continue; // no meta here — not a branch index, not a failure
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const parsed = JSON.parse(raw);
|
|
123
|
+
if (Array.isArray(parsed.cacheKeys)) {
|
|
124
|
+
for (const k of parsed.cacheKeys)
|
|
125
|
+
if (typeof k === 'string')
|
|
126
|
+
keys.add(k);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
complete = false; // present but corrupt → fail-safe toward retention
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return { keys, complete };
|
|
134
|
+
};
|
|
135
|
+
export const resolvePdgConfig = (options) => options.pdg === true
|
|
136
|
+
? {
|
|
137
|
+
maxFunctionLines: options.pdgMaxFunctionLines ?? DEFAULT_PDG_MAX_FUNCTION_LINES,
|
|
138
|
+
maxEdgesPerFunction: options.pdgMaxEdgesPerFunction ?? DEFAULT_MAX_CFG_EDGES_PER_FUNCTION,
|
|
139
|
+
maxReachingDefEdgesPerFunction: options.pdgMaxReachingDefEdgesPerFunction ??
|
|
140
|
+
DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION,
|
|
141
|
+
// #2083 M3: taint caps + model identity. The key-union comparator in
|
|
142
|
+
// pdgModeMismatch picks these up structurally — an M2-era stamp lacks
|
|
143
|
+
// all three, so the first M3 run over an M2 `--pdg` index trips a full
|
|
144
|
+
// writeback that populates TAINTED/SANITIZES rows without `--force`.
|
|
145
|
+
maxTaintFindingsPerFunction: options.pdgMaxTaintFindingsPerFunction ?? DEFAULT_PDG_MAX_TAINT_FINDINGS_PER_FUNCTION,
|
|
146
|
+
maxTaintHops: options.pdgMaxTaintHops ?? DEFAULT_PDG_MAX_TAINT_HOPS,
|
|
147
|
+
// Built-in model digest (KTD7/R7): persisted findings must never
|
|
148
|
+
// outlive the model that produced them — ANY model-content change
|
|
149
|
+
// ships as a new digest and repopulates the taint edges.
|
|
150
|
+
taintModelVersion,
|
|
151
|
+
}
|
|
152
|
+
: undefined;
|
|
153
|
+
/**
|
|
154
|
+
* Whether the requested `--pdg` configuration differs from the one the
|
|
155
|
+
* existing index's DB rows were built under (#2099 F1). An absent recorded
|
|
156
|
+
* stamp means pdg-off (every legacy meta — `--pdg` shipped opt-in). Any
|
|
157
|
+
* mismatch means the incremental writeback (which only persists changed-file
|
|
158
|
+
* nodes) cannot produce a coherent index: off→on would silently drop the
|
|
159
|
+
* freshly built CFG layer, on→off would strand zombie BasicBlocks — so the
|
|
160
|
+
* caller forces a full writeback. Pure + exported for testing.
|
|
161
|
+
*/
|
|
162
|
+
export const pdgModeMismatch = (recorded, options) => {
|
|
163
|
+
const requested = resolvePdgConfig(options);
|
|
164
|
+
if (!requested && !recorded)
|
|
165
|
+
return false;
|
|
166
|
+
if (!requested || !recorded)
|
|
167
|
+
return true;
|
|
168
|
+
// Structural comparison over the KEY UNION of both resolved records — not a
|
|
169
|
+
// hand-maintained field list. Both sides come fully resolved from
|
|
170
|
+
// resolvePdgConfig, so any new emit-affecting knob added there joins the
|
|
171
|
+
// comparison automatically (M1's hand-extended comparator was the trap this
|
|
172
|
+
// closes: a knob it missed would silently strand a stale projection). It is
|
|
173
|
+
// also what makes the M1→M2 upgrade work with zero extra code: an M1-era
|
|
174
|
+
// stamp lacks maxReachingDefEdgesPerFunction, so `4000 !== undefined` trips
|
|
175
|
+
// a full writeback that populates REACHING_DEF rows without `--force`.
|
|
176
|
+
const reqRecord = requested;
|
|
177
|
+
const recRecord = recorded;
|
|
178
|
+
for (const key of new Set([...Object.keys(reqRecord), ...Object.keys(recRecord)])) {
|
|
179
|
+
if (reqRecord[key] !== recRecord[key])
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
};
|
|
73
184
|
export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
74
185
|
const log = (msg) => callbacks.onLog?.(msg);
|
|
75
186
|
const progress = (phase, percent, message) => callbacks.onProgress(phase, percent, message);
|
|
@@ -80,7 +191,10 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
80
191
|
// counter in its own module instance and is process-scoped, so no separate
|
|
81
192
|
// worker-side reset is needed (see safe-parse.ts ParseTimeoutError contract).
|
|
82
193
|
resetDegradedParseCounter();
|
|
83
|
-
|
|
194
|
+
// `storagePath` is ALWAYS the flat `.gitnexus` — content-addressed caches
|
|
195
|
+
// (parse-cache, parsedfile-store) and the kuzu-migration cleanup live there
|
|
196
|
+
// and are shared across branches (#2106 KTD7).
|
|
197
|
+
const { storagePath } = getStoragePaths(repoPath);
|
|
84
198
|
// Clean up stale KuzuDB files from before the LadybugDB migration.
|
|
85
199
|
const kuzuResult = await cleanupOldKuzuFiles(storagePath);
|
|
86
200
|
if (kuzuResult.found && kuzuResult.needsReindex) {
|
|
@@ -88,7 +202,53 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
88
202
|
}
|
|
89
203
|
const repoHasGit = hasGitDir(repoPath);
|
|
90
204
|
const currentCommit = repoHasGit ? getCurrentCommit(repoPath) : '';
|
|
91
|
-
|
|
205
|
+
// ── #2106: resolve which branch slot this run writes to ───────────────
|
|
206
|
+
// `branchLabel` is the branch identity recorded in meta.json (incl. the
|
|
207
|
+
// primary). `placement.branch` is undefined for the flat/primary slot (the
|
|
208
|
+
// lbug/meta paths stay byte-identical to single-branch behavior) and set for
|
|
209
|
+
// a `branches/<slug>/` sub-directory. Explicit `--branch` is always honored;
|
|
210
|
+
// otherwise auto-detect the checked-out branch (null for detached HEAD /
|
|
211
|
+
// non-git → flat slot).
|
|
212
|
+
// Normalize the auto-detected branch the same way an explicit `--branch` is
|
|
213
|
+
// validated (#2106 R1): a git ref the branch-name rules forbid (backtick,
|
|
214
|
+
// `~ ^ : ? *`, leading `-`, `..`) becomes `null` → the flat slot, matching
|
|
215
|
+
// that a later `--branch <that-ref>` query would also be rejected. A normal
|
|
216
|
+
// ref passes through unchanged so index-time and query-time labels round-trip.
|
|
217
|
+
const checkedOutBranch = repoHasGit
|
|
218
|
+
? (sanitizeDetectedBranch(getCurrentBranch(repoPath)) ?? null)
|
|
219
|
+
: null;
|
|
220
|
+
// Analyze indexes the working tree, not an arbitrary ref. An explicit
|
|
221
|
+
// `--branch X` while a DIFFERENT branch Y is checked out would write Y's
|
|
222
|
+
// content (and Y's commit) into X's index slot, corrupting X (#2106). Refuse
|
|
223
|
+
// the mismatch. Detached HEAD / non-git (checkedOutBranch === null) still
|
|
224
|
+
// allow an explicit label so CI checkouts can name their snapshot.
|
|
225
|
+
if (options.branch && checkedOutBranch && options.branch !== checkedOutBranch) {
|
|
226
|
+
throw new Error(`--branch "${options.branch}" does not match the checked-out branch "${checkedOutBranch}". ` +
|
|
227
|
+
`Check out "${options.branch}" before indexing it, or omit --branch to index the current branch.`);
|
|
228
|
+
}
|
|
229
|
+
const branchLabel = options.branch ?? checkedOutBranch;
|
|
230
|
+
const placement = await resolveBranchPlacement(repoPath, branchLabel);
|
|
231
|
+
const { lbugPath, metaPath } = getStoragePaths(repoPath, placement.branch);
|
|
232
|
+
// Directory that owns this run's meta.json (flat `.gitnexus` for the primary
|
|
233
|
+
// slot, `branches/<slug>/` otherwise). loadMeta/saveMeta operate on it so
|
|
234
|
+
// each branch keeps its own lastCommit / fileHashes / incremental dirty flag.
|
|
235
|
+
const metaDir = path.dirname(metaPath);
|
|
236
|
+
const existingMeta = await loadMeta(metaDir);
|
|
237
|
+
// ── #2106 (R8): warn when the repo's default branch is not the primary ──
|
|
238
|
+
// A non-default branch can own the flat slot (it was indexed first). That
|
|
239
|
+
// index is still fully queryable via `--branch`, so this is an ergonomics
|
|
240
|
+
// wart, not data loss — we only warn (no risky relocation of a live DB).
|
|
241
|
+
if (repoHasGit) {
|
|
242
|
+
// Who owns the flat slot after this run? For a flat/primary run it is this
|
|
243
|
+
// run's resolved label (carrying an existing stamp forward); for a branch
|
|
244
|
+
// run the flat owner is unchanged, so read the flat meta.
|
|
245
|
+
const flatOwner = placement.branch
|
|
246
|
+
? (await loadMeta(storagePath))?.branch
|
|
247
|
+
: (branchLabel ?? existingMeta?.branch);
|
|
248
|
+
const warning = primaryInversionWarning(getDefaultBranch(repoPath), flatOwner);
|
|
249
|
+
if (warning)
|
|
250
|
+
log(warning);
|
|
251
|
+
}
|
|
92
252
|
// ── FTS-only repair path ────────────────────────────────────────────
|
|
93
253
|
if (options.repairFts) {
|
|
94
254
|
if (!existingMeta) {
|
|
@@ -158,13 +318,37 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
158
318
|
// clear it, the on-disk index may be in a half-state. Cheapest path
|
|
159
319
|
// back to a known-good index is to wipe + rebuild from scratch.
|
|
160
320
|
if (existingMeta?.incrementalInProgress) {
|
|
161
|
-
log(
|
|
321
|
+
log(
|
|
322
|
+
// "analyze run", not "incremental run" — since #2099 F1 the flag is a
|
|
323
|
+
// generic dirty marker written by BOTH writeback branches.
|
|
324
|
+
'Previous analyze run did not complete cleanly (incrementalInProgress flag set); ' +
|
|
162
325
|
'forcing full rebuild to restore a known-good index.');
|
|
163
326
|
options = { ...options, force: true };
|
|
164
327
|
// Reload meta after clearing the flag in-memory; we still want fileHashes
|
|
165
328
|
// for the post-rebuild meta carry-over, but force=true ensures the
|
|
166
329
|
// rebuild path executes.
|
|
167
330
|
}
|
|
331
|
+
// ── pdg-mode flip forces full writeback (#2099 F1) ─────────────────
|
|
332
|
+
// The incremental writeback persists only changed-file nodes, so a pdg
|
|
333
|
+
// config differing from the one the DB rows were built under cannot be
|
|
334
|
+
// reconciled incrementally: off→on silently drops the freshly built CFG
|
|
335
|
+
// layer ("Incremental: changed=0", zero BasicBlock rows), on→off strands
|
|
336
|
+
// zombie blocks for unchanged files. MUST sit before the alreadyUpToDate
|
|
337
|
+
// fast path below — a clean-tree flip would otherwise early-return without
|
|
338
|
+
// running the pipeline at all. The notice is deliberately NOT gated on
|
|
339
|
+
// options.force: --skills implies force with no message of its own, and a
|
|
340
|
+
// mode change deserves a diagnostic regardless of why a rebuild happens.
|
|
341
|
+
if (existingMeta && pdgModeMismatch(existingMeta.pdg, options)) {
|
|
342
|
+
const pdgOn = options.pdg === true;
|
|
343
|
+
const capsOnly = !!existingMeta.pdg && pdgOn; // both-on can only mismatch via caps
|
|
344
|
+
const was = existingMeta.pdg ? 'with --pdg' : 'without --pdg';
|
|
345
|
+
const now = pdgOn ? 'with --pdg' : 'without --pdg';
|
|
346
|
+
log(`pdg mode changed (index built ${was}, this run is ${now}` +
|
|
347
|
+
`${capsOnly ? ', but with different caps' : ''}); forcing a full ` +
|
|
348
|
+
`rebuild so the CFG layer is ${pdgOn ? 'fully persisted' : 'fully removed'}. ` +
|
|
349
|
+
`Tip: set \`pdg: ${pdgOn}\` in .gitnexusrc to pin the mode across runs.`);
|
|
350
|
+
options = { ...options, force: true };
|
|
351
|
+
}
|
|
168
352
|
// ── Early-return: already up to date ──────────────────────────────
|
|
169
353
|
if (existingMeta && !options.force && existingMeta.lastCommit === currentCommit) {
|
|
170
354
|
// Non-git folders have currentCommit = '' — always rebuild since we can't detect changes
|
|
@@ -220,6 +404,7 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
220
404
|
repoPath,
|
|
221
405
|
stats: existingMeta.stats ?? {},
|
|
222
406
|
alreadyUpToDate: true,
|
|
407
|
+
isPrimaryBranch: !placement.branch,
|
|
223
408
|
};
|
|
224
409
|
}
|
|
225
410
|
}
|
|
@@ -308,6 +493,15 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
308
493
|
}, {
|
|
309
494
|
parseCache,
|
|
310
495
|
workerPoolSize: options.workerPoolSize,
|
|
496
|
+
// CFG/PDG opt-in (#2081 M1). PipelineOptions.pdg fans out to the worker
|
|
497
|
+
// build gate (workerData.pdg) and the scope-resolution emit gate.
|
|
498
|
+
pdg: options.pdg === true,
|
|
499
|
+
pdgMaxFunctionLines: options.pdgMaxFunctionLines,
|
|
500
|
+
pdgMaxEdgesPerFunction: options.pdgMaxEdgesPerFunction,
|
|
501
|
+
pdgMaxReachingDefEdgesPerFunction: options.pdgMaxReachingDefEdgesPerFunction,
|
|
502
|
+
pdgMaxTaintFindingsPerFunction: options.pdgMaxTaintFindingsPerFunction,
|
|
503
|
+
pdgMaxTaintHops: options.pdgMaxTaintHops,
|
|
504
|
+
fetchWrappers: options.fetchWrappers,
|
|
311
505
|
});
|
|
312
506
|
// ── Phase 2: LadybugDB (60–85%) ──────────────────────────────────
|
|
313
507
|
progress('lbug', 60, 'Loading into LadybugDB...');
|
|
@@ -345,8 +539,8 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
345
539
|
`deleted=${hashDiff.deleted.length} ` +
|
|
346
540
|
`(skipping wipe + ${allFilePaths.length - hashDiff.toWrite.length} unchanged file rows preserved)`);
|
|
347
541
|
// Set the dirty flag BEFORE any destructive DB mutation. Cleared on
|
|
348
|
-
// success at the meta-save step.
|
|
349
|
-
await saveMeta(
|
|
542
|
+
// success at the meta-save step. Scoped to this branch's meta.json.
|
|
543
|
+
await saveMeta(metaDir, {
|
|
350
544
|
...existingMeta,
|
|
351
545
|
incrementalInProgress: {
|
|
352
546
|
startedAt: Date.now(),
|
|
@@ -356,6 +550,19 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
356
550
|
}
|
|
357
551
|
else {
|
|
358
552
|
// Full rebuild path: wipe DB files first.
|
|
553
|
+
// Set the dirty flag BEFORE the wipe whenever a prior meta exists,
|
|
554
|
+
// mirroring the incremental branch above (#2099 F1, KTD2b). Without it a
|
|
555
|
+
// full rebuild crashing between the wipe and the end-of-run saveMeta
|
|
556
|
+
// leaves a meta that vouches for a DB it no longer matches — the next
|
|
557
|
+
// clean-tree run's fast path would certify a destroyed DB (or, after a
|
|
558
|
+
// pdg flip, certify zombie/missing BasicBlock rows indefinitely).
|
|
559
|
+
// toWriteCount: 0 is the full-path sentinel (no incremental write set).
|
|
560
|
+
if (existingMeta) {
|
|
561
|
+
await saveMeta(metaDir, {
|
|
562
|
+
...existingMeta,
|
|
563
|
+
incrementalInProgress: { startedAt: Date.now(), toWriteCount: 0 },
|
|
564
|
+
});
|
|
565
|
+
}
|
|
359
566
|
await closeLbug();
|
|
360
567
|
const lbugFiles = [lbugPath, `${lbugPath}.wal`, `${lbugPath}.lock`];
|
|
361
568
|
for (const f of lbugFiles) {
|
|
@@ -685,6 +892,14 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
685
892
|
repoPath,
|
|
686
893
|
lastCommit: currentCommit,
|
|
687
894
|
indexedAt: new Date().toISOString(),
|
|
895
|
+
// Branch identity this index represents (#2106). Recorded for the flat
|
|
896
|
+
// slot too (so resolveBranchPlacement knows which branch owns it). When
|
|
897
|
+
// the label is null (detached HEAD / non-git re-analyze) we PRESERVE an
|
|
898
|
+
// existing stamp rather than stripping it — otherwise a detached re-index
|
|
899
|
+
// of the primary (e.g. CI's `actions/checkout` default) would un-claim the
|
|
900
|
+
// flat slot and let the next branch analyze overwrite the primary index.
|
|
901
|
+
// Stays absent only when never stamped (fresh detached/non-git repo).
|
|
902
|
+
branch: branchLabel ?? existingMeta?.branch,
|
|
688
903
|
// Captured here (not at registration) so it travels with the
|
|
689
904
|
// on-disk meta.json — sibling-clone fingerprinting works for
|
|
690
905
|
// out-of-tree consumers (group-status, future tooling) without
|
|
@@ -723,9 +938,20 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
723
938
|
// dirty flag (full and incremental success paths converge here).
|
|
724
939
|
schemaVersion: hasGitDir(repoPath) ? INCREMENTAL_SCHEMA_VERSION : undefined,
|
|
725
940
|
fileHashes: hasGitDir(repoPath) ? newFileHashesRecord : undefined,
|
|
941
|
+
// This branch's full live chunk-key set (#2106 R6). `usedKeys` is every
|
|
942
|
+
// chunk hash touched in this scan — cache HITS included (see parse-impl
|
|
943
|
+
// usedKeys.add) — so it's complete even on an incremental run. Persisted
|
|
944
|
+
// so a sibling branch's prune can union it and not evict our shards.
|
|
945
|
+
cacheKeys: [...parseCache.usedKeys],
|
|
726
946
|
incrementalInProgress: undefined,
|
|
947
|
+
// The effective pdg config this run's DB rows were built under
|
|
948
|
+
// (#2099 F1). `undefined` on pdg-off runs — this meta is a fresh
|
|
949
|
+
// literal (no spread of existingMeta), so omission is what CLEARS the
|
|
950
|
+
// stamp after an on→off flip; the next pdgModeMismatch then compares
|
|
951
|
+
// off==off and incremental eligibility is restored.
|
|
952
|
+
pdg: resolvePdgConfig(options),
|
|
727
953
|
};
|
|
728
|
-
await saveMeta(
|
|
954
|
+
await saveMeta(metaDir, meta);
|
|
729
955
|
// Persist the incremental parse cache for the next run. Wraps in
|
|
730
956
|
// try/catch so a cache-write failure never breaks an otherwise
|
|
731
957
|
// successful indexing run. Prune stale chunk-hash entries first so
|
|
@@ -734,6 +960,25 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
734
960
|
// dead weight; the parse phase populates `usedKeys` as it processes
|
|
735
961
|
// chunks).
|
|
736
962
|
try {
|
|
963
|
+
// #2106 R6: the parse cache + durable store are shared across branches.
|
|
964
|
+
// Before pruning to this run's keys, fold in the OTHER branches' recorded
|
|
965
|
+
// chunk keys so a branch switch doesn't evict their still-live shards.
|
|
966
|
+
// Adding to usedKeys makes them survive pruneCache AND land in the saved
|
|
967
|
+
// index (saveParseCache builds the index from usedKeys). Excludes this
|
|
968
|
+
// run's own meta dir, so a single-branch repo folds in nothing → prune
|
|
969
|
+
// set byte-identical to today.
|
|
970
|
+
const { keys: siblingKeys, complete } = await collectBranchCacheKeys(storagePath, metaDir);
|
|
971
|
+
if (complete) {
|
|
972
|
+
for (const k of siblingKeys)
|
|
973
|
+
parseCache.usedKeys.add(k);
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
// Fail-safe toward retention: a sibling meta was unreadable, so keep
|
|
977
|
+
// everything currently loaded rather than evict on incomplete info.
|
|
978
|
+
log('Parse cache: a branch meta was unreadable — retaining all cached chunks (#2106).');
|
|
979
|
+
for (const k of parseCache.entries.keys())
|
|
980
|
+
parseCache.usedKeys.add(k);
|
|
981
|
+
}
|
|
737
982
|
const pruned = pruneCache(parseCache, parseCache.usedKeys);
|
|
738
983
|
if (pruned > 0) {
|
|
739
984
|
log(`Parse cache: pruned ${pruned} stale chunk entries`);
|
|
@@ -763,6 +1008,10 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
763
1008
|
const projectName = await registerRepo(repoPath, meta, {
|
|
764
1009
|
name: options.registryName,
|
|
765
1010
|
allowDuplicateName: options.allowDuplicateName,
|
|
1011
|
+
// Non-primary branch runs upsert into the entry's branches[]; the
|
|
1012
|
+
// primary/flat run (placement.branch === undefined) refreshes the
|
|
1013
|
+
// top-level fields (#2106).
|
|
1014
|
+
branch: placement.branch,
|
|
766
1015
|
});
|
|
767
1016
|
// Keep generated .gitnexus contents ignored without editing the user's root .gitignore.
|
|
768
1017
|
await ensureGitNexusIgnored(repoPath);
|
|
@@ -776,23 +1025,28 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
776
1025
|
}
|
|
777
1026
|
aggregatedClusterCount = Array.from(groups.values()).filter((count) => count >= 5).length;
|
|
778
1027
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1028
|
+
// Only (re)generate the repo-root AI context files (AGENTS.md / CLAUDE.md /
|
|
1029
|
+
// skills) for the primary/flat index (#2106). A non-primary branch analyze
|
|
1030
|
+
// must not churn the repo's committed AGENTS.md with branch-specific stats.
|
|
1031
|
+
if (!placement.branch) {
|
|
1032
|
+
try {
|
|
1033
|
+
await generateAIContextFiles(repoPath, storagePath, projectName, {
|
|
1034
|
+
files: pipelineResult.totalFileCount,
|
|
1035
|
+
nodes: stats.nodes,
|
|
1036
|
+
edges: stats.edges,
|
|
1037
|
+
communities: pipelineResult.communityResult?.stats.totalCommunities,
|
|
1038
|
+
clusters: aggregatedClusterCount,
|
|
1039
|
+
processes: pipelineResult.processResult?.stats.totalProcesses,
|
|
1040
|
+
}, undefined, {
|
|
1041
|
+
skipAgentsMd: options.skipAgentsMd,
|
|
1042
|
+
skipSkills: options.skipSkills,
|
|
1043
|
+
noStats: options.noStats,
|
|
1044
|
+
defaultBranch: options.defaultBranch,
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
catch {
|
|
1048
|
+
// Best-effort — don't fail the entire analysis for context file issues
|
|
1049
|
+
}
|
|
796
1050
|
}
|
|
797
1051
|
// ── Close LadybugDB ──────────────────────────────────────────────
|
|
798
1052
|
// Stop the manual checkpoint driver before closeLbug so its
|
|
@@ -806,6 +1060,7 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
806
1060
|
stats: meta.stats,
|
|
807
1061
|
pipelineResult,
|
|
808
1062
|
ftsSkipped: !ftsAvailable,
|
|
1063
|
+
isPrimaryBranch: !placement.branch,
|
|
809
1064
|
};
|
|
810
1065
|
}
|
|
811
1066
|
catch (err) {
|
|
@@ -2,6 +2,7 @@ import Parser from 'tree-sitter';
|
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { SupportedLanguages } from '../../_shared/index.js';
|
|
4
4
|
import { logger } from '../logger.js';
|
|
5
|
+
import { requireVendoredGrammar } from './vendored-grammars.js';
|
|
5
6
|
const _require = createRequire(import.meta.url);
|
|
6
7
|
const ISSUES_URL = 'https://github.com/abhigyanpatwari/GitNexus/issues';
|
|
7
8
|
const SOURCES = {
|
|
@@ -71,7 +72,7 @@ const SOURCES = {
|
|
|
71
72
|
// user-opt-out grammar like Swift/Dart/Kotlin: a failure here is always an
|
|
72
73
|
// install/platform problem the user needs to see.
|
|
73
74
|
[SupportedLanguages.C]: {
|
|
74
|
-
load: () =>
|
|
75
|
+
load: () => requireVendoredGrammar('tree-sitter-c'),
|
|
75
76
|
optional: true,
|
|
76
77
|
severity: 'error',
|
|
77
78
|
unavailableNote: 'C parsing disabled: vendored `tree-sitter-c` (under ' +
|
|
@@ -85,7 +86,7 @@ const SOURCES = {
|
|
|
85
86
|
// optionalDependencies — may be absent on platforms without prebuilds
|
|
86
87
|
// or when users skip optional installs.
|
|
87
88
|
[SupportedLanguages.Swift]: {
|
|
88
|
-
load: () =>
|
|
89
|
+
load: () => requireVendoredGrammar('tree-sitter-swift'),
|
|
89
90
|
optional: true,
|
|
90
91
|
userSkippable: true,
|
|
91
92
|
unavailableNote: 'Swift parsing disabled: vendored `tree-sitter-swift` (under ' +
|
|
@@ -94,7 +95,7 @@ const SOURCES = {
|
|
|
94
95
|
`See ${ISSUES_URL}/1130.`,
|
|
95
96
|
},
|
|
96
97
|
[SupportedLanguages.Dart]: {
|
|
97
|
-
load: () =>
|
|
98
|
+
load: () => requireVendoredGrammar('tree-sitter-dart'),
|
|
98
99
|
optional: true,
|
|
99
100
|
userSkippable: true,
|
|
100
101
|
unavailableNote: 'Dart parsing disabled: vendored `tree-sitter-dart` (under ' +
|
|
@@ -103,7 +104,7 @@ const SOURCES = {
|
|
|
103
104
|
`See ${ISSUES_URL}/1125.`,
|
|
104
105
|
},
|
|
105
106
|
[SupportedLanguages.Kotlin]: {
|
|
106
|
-
load: () =>
|
|
107
|
+
load: () => requireVendoredGrammar('tree-sitter-kotlin'),
|
|
107
108
|
optional: true,
|
|
108
109
|
userSkippable: true,
|
|
109
110
|
unavailableNote: 'Kotlin parsing disabled: vendored `tree-sitter-kotlin` (under ' +
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Absolute path to the vendored grammar tree (`<pkg>/vendor`).
|
|
3
|
+
*
|
|
4
|
+
* This module compiles to `<pkg>/dist/core/tree-sitter/vendored-grammars.js`
|
|
5
|
+
* and runs from `<pkg>/src/core/tree-sitter/...` under tsx in dev — both sit
|
|
6
|
+
* three directories below the package root, and the build (`tsc`) never bundles,
|
|
7
|
+
* so `import.meta.url` resolves the same way in both. `vendor/` ships in the
|
|
8
|
+
* published package via package.json `files`.
|
|
9
|
+
*/
|
|
10
|
+
export declare const VENDOR_ROOT: string;
|
|
11
|
+
/**
|
|
12
|
+
* The tree-sitter grammars GitNexus vendors inside its own package (NOT npm
|
|
13
|
+
* dependencies). Kept in one place so consumers (runtime loaders, the CLI
|
|
14
|
+
* availability probe, and the test grammar-introspection helper) agree on which
|
|
15
|
+
* grammars resolve from `vendor/` rather than `node_modules`.
|
|
16
|
+
*/
|
|
17
|
+
export declare const VENDORED_GRAMMAR_PACKAGES: ReadonlySet<string>;
|
|
18
|
+
/** Absolute directory of a vendored grammar package under `vendor/`. */
|
|
19
|
+
export declare const vendoredGrammarDir: (packageName: string) => string;
|
|
20
|
+
/**
|
|
21
|
+
* Load a vendored tree-sitter grammar by its absolute path under `vendor/`.
|
|
22
|
+
*
|
|
23
|
+
* GitNexus vendors five grammars (c/dart/proto/swift/kotlin) inside its own
|
|
24
|
+
* package under `vendor/`, each shipping committed per-platform prebuilds. They
|
|
25
|
+
* are deliberately NOT npm dependencies and must NEVER be copied into
|
|
26
|
+
* `node_modules`: an undeclared package under `node_modules` is "extraneous" to
|
|
27
|
+
* every subsequent `npm`/`npx` arborist reify, which prunes or relocates it.
|
|
28
|
+
* That is the root cause of #2111 / #1728 — on Windows the relocation throws
|
|
29
|
+
* `EPERM: operation not permitted, symlink` (errno -4048) during the npx-cache
|
|
30
|
+
* reify Antigravity triggers when it launches the MCP server, and on every OS
|
|
31
|
+
* the second run silently deletes the materialized grammars.
|
|
32
|
+
*
|
|
33
|
+
* Resolving the grammar by absolute path runs its own `bindings/node` entry,
|
|
34
|
+
* which calls `node-gyp-build(<grammarDir>)` and loads
|
|
35
|
+
* `vendor/<name>/prebuilds/<platform>-<arch>/…` directly — no build, no write,
|
|
36
|
+
* no `node_modules` copy. (`node-gyp-build` itself IS an npm dependency and
|
|
37
|
+
* resolves normally from the grammar directory.)
|
|
38
|
+
*/
|
|
39
|
+
export declare const requireVendoredGrammar: (packageName: string) => unknown;
|