gitnexus 1.6.7 → 1.6.8-rc.10
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/cli/analyze-config.js +39 -0
- package/dist/cli/analyze.d.ts +16 -0
- package/dist/cli/analyze.js +34 -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 +6 -0
- package/dist/cli/i18n/en.d.ts +11 -0
- package/dist/cli/i18n/en.js +11 -0
- package/dist/cli/i18n/resources.d.ts +22 -0
- package/dist/cli/i18n/zh-CN.d.ts +11 -0
- package/dist/cli/i18n/zh-CN.js +11 -0
- package/dist/cli/index.js +9 -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/status.js +26 -5
- package/dist/cli/tool.d.ts +5 -0
- package/dist/cli/tool.js +5 -0
- 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/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/language-provider.d.ts +25 -2
- package/dist/core/ingestion/languages/c-cpp.js +11 -4
- 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/parsing-processor.js +21 -0
- package/dist/core/ingestion/pipeline-phases/routes.js +64 -14
- package/dist/core/ingestion/pipeline.d.ts +9 -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/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 +10 -0
- package/dist/core/ingestion/workers/parse-worker.js +27 -52
- 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 +6 -0
- package/dist/core/ingestion/workers/worker-pool.js +28 -14
- 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 +45 -0
- package/dist/core/run-analyze.js +175 -24
- 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 +55 -2
- package/dist/mcp/local/local-backend.js +363 -25
- package/dist/mcp/tools.js +32 -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.js +7 -1
- 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.js +3 -0
- package/dist/storage/repo-manager.d.ts +57 -2
- package/dist/storage/repo-manager.js +132 -18
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +24 -4
- package/hooks/claude/gitnexus-hook.cjs +18 -3
- package/package.json +2 -2
- package/scripts/assert-publish-grammar-coverage.cjs +31 -0
- package/scripts/build-tree-sitter-grammars.cjs +16 -10
- 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/scripts/materialize-vendor-grammars.cjs +0 -97
package/dist/core/run-analyze.js
CHANGED
|
@@ -17,14 +17,15 @@ 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
21
|
import { computeFileHashes, diffFileHashes } from '../storage/file-hash.js';
|
|
22
22
|
import { extractChangedSubgraph, computeEffectiveWriteSet, } from './incremental/subgraph-extract.js';
|
|
23
23
|
import { shadowCandidatesFor } from './incremental/shadow-candidates.js';
|
|
24
24
|
import { loadParseCache, saveParseCache, pruneCache, PARSE_CACHE_VERSION, } from '../storage/parse-cache.js';
|
|
25
25
|
import { getDurableParsedFileDir, pruneAndSaveDurableParsedFileStore, } from '../storage/parsedfile-store.js';
|
|
26
|
-
import { getCurrentCommit, getRemoteUrl, hasGitDir, getInferredRepoName, resolveRepoIdentityRoot, } from '../storage/git.js';
|
|
26
|
+
import { getCurrentCommit, getCurrentBranch, getDefaultBranch, getRemoteUrl, hasGitDir, getInferredRepoName, resolveRepoIdentityRoot, } from '../storage/git.js';
|
|
27
27
|
import { generateAIContextFiles } from '../cli/ai-context.js';
|
|
28
|
+
import { sanitizeDetectedBranch } from '../cli/analyze-config.js';
|
|
28
29
|
import { EMBEDDING_TABLE_NAME } from './lbug/schema.js';
|
|
29
30
|
import { STALE_HASH_SENTINEL } from './lbug/schema.js';
|
|
30
31
|
/**
|
|
@@ -70,6 +71,63 @@ export const PHASE_LABELS = {
|
|
|
70
71
|
* the {@link AnalyzeCallbacks} interface — it never writes to stdout/stderr
|
|
71
72
|
* directly and never calls `process.exit()`.
|
|
72
73
|
*/
|
|
74
|
+
/**
|
|
75
|
+
* Build the primary-inversion warning (#2106 R8), or `undefined` when there is
|
|
76
|
+
* nothing to warn about. Pure + exported for testing. Both inputs are trimmed
|
|
77
|
+
* (a diagnostic — a missed warning is low-harm; a false warning is the thing to
|
|
78
|
+
* avoid). `defaultBranch` is the repo's `origin/HEAD` branch (null when unset,
|
|
79
|
+
* e.g. fresh clones / CI), `flatOwner` is the branch that owns the flat slot.
|
|
80
|
+
*/
|
|
81
|
+
export const primaryInversionWarning = (defaultBranch, flatOwner) => {
|
|
82
|
+
const norm = (s) => s?.trim() || undefined;
|
|
83
|
+
const d = norm(defaultBranch);
|
|
84
|
+
const o = norm(flatOwner);
|
|
85
|
+
if (!d || !o || d === o)
|
|
86
|
+
return undefined;
|
|
87
|
+
return (`Warning: the default branch "${d}" is not the primary index — "${o}" owns the flat slot. ` +
|
|
88
|
+
`Run \`gitnexus clean --branch ${o}\` then re-index on "${d}", or query it explicitly with \`--branch ${d}\`.`);
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Collect the recorded parse-cache chunk keys across the flat + every branch
|
|
92
|
+
* meta under a flat `.gitnexus` storage, EXCLUDING `excludeDir` (the current
|
|
93
|
+
* run's own meta dir) so a single-branch repo collects nothing and its prune
|
|
94
|
+
* stays byte-identical to today (#2106 R6). `complete` is false when a sibling
|
|
95
|
+
* meta.json exists but fails to parse — callers then retain the whole shared
|
|
96
|
+
* cache rather than over-evict another branch's still-live shards. Exported for
|
|
97
|
+
* testing.
|
|
98
|
+
*/
|
|
99
|
+
export const collectBranchCacheKeys = async (storagePath, excludeDir) => {
|
|
100
|
+
const keys = new Set();
|
|
101
|
+
let complete = true;
|
|
102
|
+
const metaDirs = [storagePath];
|
|
103
|
+
const branchesDir = path.join(storagePath, 'branches');
|
|
104
|
+
const slugs = await fs.readdir(branchesDir).catch(() => []);
|
|
105
|
+
for (const slug of slugs)
|
|
106
|
+
metaDirs.push(path.join(branchesDir, slug));
|
|
107
|
+
for (const dir of metaDirs) {
|
|
108
|
+
if (excludeDir && path.resolve(dir) === path.resolve(excludeDir))
|
|
109
|
+
continue;
|
|
110
|
+
let raw;
|
|
111
|
+
try {
|
|
112
|
+
raw = await fs.readFile(path.join(dir, 'meta.json'), 'utf-8');
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
continue; // no meta here — not a branch index, not a failure
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const parsed = JSON.parse(raw);
|
|
119
|
+
if (Array.isArray(parsed.cacheKeys)) {
|
|
120
|
+
for (const k of parsed.cacheKeys)
|
|
121
|
+
if (typeof k === 'string')
|
|
122
|
+
keys.add(k);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
complete = false; // present but corrupt → fail-safe toward retention
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { keys, complete };
|
|
130
|
+
};
|
|
73
131
|
export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
74
132
|
const log = (msg) => callbacks.onLog?.(msg);
|
|
75
133
|
const progress = (phase, percent, message) => callbacks.onProgress(phase, percent, message);
|
|
@@ -80,7 +138,10 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
80
138
|
// counter in its own module instance and is process-scoped, so no separate
|
|
81
139
|
// worker-side reset is needed (see safe-parse.ts ParseTimeoutError contract).
|
|
82
140
|
resetDegradedParseCounter();
|
|
83
|
-
|
|
141
|
+
// `storagePath` is ALWAYS the flat `.gitnexus` — content-addressed caches
|
|
142
|
+
// (parse-cache, parsedfile-store) and the kuzu-migration cleanup live there
|
|
143
|
+
// and are shared across branches (#2106 KTD7).
|
|
144
|
+
const { storagePath } = getStoragePaths(repoPath);
|
|
84
145
|
// Clean up stale KuzuDB files from before the LadybugDB migration.
|
|
85
146
|
const kuzuResult = await cleanupOldKuzuFiles(storagePath);
|
|
86
147
|
if (kuzuResult.found && kuzuResult.needsReindex) {
|
|
@@ -88,7 +149,53 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
88
149
|
}
|
|
89
150
|
const repoHasGit = hasGitDir(repoPath);
|
|
90
151
|
const currentCommit = repoHasGit ? getCurrentCommit(repoPath) : '';
|
|
91
|
-
|
|
152
|
+
// ── #2106: resolve which branch slot this run writes to ───────────────
|
|
153
|
+
// `branchLabel` is the branch identity recorded in meta.json (incl. the
|
|
154
|
+
// primary). `placement.branch` is undefined for the flat/primary slot (the
|
|
155
|
+
// lbug/meta paths stay byte-identical to single-branch behavior) and set for
|
|
156
|
+
// a `branches/<slug>/` sub-directory. Explicit `--branch` is always honored;
|
|
157
|
+
// otherwise auto-detect the checked-out branch (null for detached HEAD /
|
|
158
|
+
// non-git → flat slot).
|
|
159
|
+
// Normalize the auto-detected branch the same way an explicit `--branch` is
|
|
160
|
+
// validated (#2106 R1): a git ref the branch-name rules forbid (backtick,
|
|
161
|
+
// `~ ^ : ? *`, leading `-`, `..`) becomes `null` → the flat slot, matching
|
|
162
|
+
// that a later `--branch <that-ref>` query would also be rejected. A normal
|
|
163
|
+
// ref passes through unchanged so index-time and query-time labels round-trip.
|
|
164
|
+
const checkedOutBranch = repoHasGit
|
|
165
|
+
? (sanitizeDetectedBranch(getCurrentBranch(repoPath)) ?? null)
|
|
166
|
+
: null;
|
|
167
|
+
// Analyze indexes the working tree, not an arbitrary ref. An explicit
|
|
168
|
+
// `--branch X` while a DIFFERENT branch Y is checked out would write Y's
|
|
169
|
+
// content (and Y's commit) into X's index slot, corrupting X (#2106). Refuse
|
|
170
|
+
// the mismatch. Detached HEAD / non-git (checkedOutBranch === null) still
|
|
171
|
+
// allow an explicit label so CI checkouts can name their snapshot.
|
|
172
|
+
if (options.branch && checkedOutBranch && options.branch !== checkedOutBranch) {
|
|
173
|
+
throw new Error(`--branch "${options.branch}" does not match the checked-out branch "${checkedOutBranch}". ` +
|
|
174
|
+
`Check out "${options.branch}" before indexing it, or omit --branch to index the current branch.`);
|
|
175
|
+
}
|
|
176
|
+
const branchLabel = options.branch ?? checkedOutBranch;
|
|
177
|
+
const placement = await resolveBranchPlacement(repoPath, branchLabel);
|
|
178
|
+
const { lbugPath, metaPath } = getStoragePaths(repoPath, placement.branch);
|
|
179
|
+
// Directory that owns this run's meta.json (flat `.gitnexus` for the primary
|
|
180
|
+
// slot, `branches/<slug>/` otherwise). loadMeta/saveMeta operate on it so
|
|
181
|
+
// each branch keeps its own lastCommit / fileHashes / incremental dirty flag.
|
|
182
|
+
const metaDir = path.dirname(metaPath);
|
|
183
|
+
const existingMeta = await loadMeta(metaDir);
|
|
184
|
+
// ── #2106 (R8): warn when the repo's default branch is not the primary ──
|
|
185
|
+
// A non-default branch can own the flat slot (it was indexed first). That
|
|
186
|
+
// index is still fully queryable via `--branch`, so this is an ergonomics
|
|
187
|
+
// wart, not data loss — we only warn (no risky relocation of a live DB).
|
|
188
|
+
if (repoHasGit) {
|
|
189
|
+
// Who owns the flat slot after this run? For a flat/primary run it is this
|
|
190
|
+
// run's resolved label (carrying an existing stamp forward); for a branch
|
|
191
|
+
// run the flat owner is unchanged, so read the flat meta.
|
|
192
|
+
const flatOwner = placement.branch
|
|
193
|
+
? (await loadMeta(storagePath))?.branch
|
|
194
|
+
: (branchLabel ?? existingMeta?.branch);
|
|
195
|
+
const warning = primaryInversionWarning(getDefaultBranch(repoPath), flatOwner);
|
|
196
|
+
if (warning)
|
|
197
|
+
log(warning);
|
|
198
|
+
}
|
|
92
199
|
// ── FTS-only repair path ────────────────────────────────────────────
|
|
93
200
|
if (options.repairFts) {
|
|
94
201
|
if (!existingMeta) {
|
|
@@ -220,6 +327,7 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
220
327
|
repoPath,
|
|
221
328
|
stats: existingMeta.stats ?? {},
|
|
222
329
|
alreadyUpToDate: true,
|
|
330
|
+
isPrimaryBranch: !placement.branch,
|
|
223
331
|
};
|
|
224
332
|
}
|
|
225
333
|
}
|
|
@@ -308,6 +416,7 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
308
416
|
}, {
|
|
309
417
|
parseCache,
|
|
310
418
|
workerPoolSize: options.workerPoolSize,
|
|
419
|
+
fetchWrappers: options.fetchWrappers,
|
|
311
420
|
});
|
|
312
421
|
// ── Phase 2: LadybugDB (60–85%) ──────────────────────────────────
|
|
313
422
|
progress('lbug', 60, 'Loading into LadybugDB...');
|
|
@@ -345,8 +454,8 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
345
454
|
`deleted=${hashDiff.deleted.length} ` +
|
|
346
455
|
`(skipping wipe + ${allFilePaths.length - hashDiff.toWrite.length} unchanged file rows preserved)`);
|
|
347
456
|
// Set the dirty flag BEFORE any destructive DB mutation. Cleared on
|
|
348
|
-
// success at the meta-save step.
|
|
349
|
-
await saveMeta(
|
|
457
|
+
// success at the meta-save step. Scoped to this branch's meta.json.
|
|
458
|
+
await saveMeta(metaDir, {
|
|
350
459
|
...existingMeta,
|
|
351
460
|
incrementalInProgress: {
|
|
352
461
|
startedAt: Date.now(),
|
|
@@ -685,6 +794,14 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
685
794
|
repoPath,
|
|
686
795
|
lastCommit: currentCommit,
|
|
687
796
|
indexedAt: new Date().toISOString(),
|
|
797
|
+
// Branch identity this index represents (#2106). Recorded for the flat
|
|
798
|
+
// slot too (so resolveBranchPlacement knows which branch owns it). When
|
|
799
|
+
// the label is null (detached HEAD / non-git re-analyze) we PRESERVE an
|
|
800
|
+
// existing stamp rather than stripping it — otherwise a detached re-index
|
|
801
|
+
// of the primary (e.g. CI's `actions/checkout` default) would un-claim the
|
|
802
|
+
// flat slot and let the next branch analyze overwrite the primary index.
|
|
803
|
+
// Stays absent only when never stamped (fresh detached/non-git repo).
|
|
804
|
+
branch: branchLabel ?? existingMeta?.branch,
|
|
688
805
|
// Captured here (not at registration) so it travels with the
|
|
689
806
|
// on-disk meta.json — sibling-clone fingerprinting works for
|
|
690
807
|
// out-of-tree consumers (group-status, future tooling) without
|
|
@@ -723,9 +840,14 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
723
840
|
// dirty flag (full and incremental success paths converge here).
|
|
724
841
|
schemaVersion: hasGitDir(repoPath) ? INCREMENTAL_SCHEMA_VERSION : undefined,
|
|
725
842
|
fileHashes: hasGitDir(repoPath) ? newFileHashesRecord : undefined,
|
|
843
|
+
// This branch's full live chunk-key set (#2106 R6). `usedKeys` is every
|
|
844
|
+
// chunk hash touched in this scan — cache HITS included (see parse-impl
|
|
845
|
+
// usedKeys.add) — so it's complete even on an incremental run. Persisted
|
|
846
|
+
// so a sibling branch's prune can union it and not evict our shards.
|
|
847
|
+
cacheKeys: [...parseCache.usedKeys],
|
|
726
848
|
incrementalInProgress: undefined,
|
|
727
849
|
};
|
|
728
|
-
await saveMeta(
|
|
850
|
+
await saveMeta(metaDir, meta);
|
|
729
851
|
// Persist the incremental parse cache for the next run. Wraps in
|
|
730
852
|
// try/catch so a cache-write failure never breaks an otherwise
|
|
731
853
|
// successful indexing run. Prune stale chunk-hash entries first so
|
|
@@ -734,6 +856,25 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
734
856
|
// dead weight; the parse phase populates `usedKeys` as it processes
|
|
735
857
|
// chunks).
|
|
736
858
|
try {
|
|
859
|
+
// #2106 R6: the parse cache + durable store are shared across branches.
|
|
860
|
+
// Before pruning to this run's keys, fold in the OTHER branches' recorded
|
|
861
|
+
// chunk keys so a branch switch doesn't evict their still-live shards.
|
|
862
|
+
// Adding to usedKeys makes them survive pruneCache AND land in the saved
|
|
863
|
+
// index (saveParseCache builds the index from usedKeys). Excludes this
|
|
864
|
+
// run's own meta dir, so a single-branch repo folds in nothing → prune
|
|
865
|
+
// set byte-identical to today.
|
|
866
|
+
const { keys: siblingKeys, complete } = await collectBranchCacheKeys(storagePath, metaDir);
|
|
867
|
+
if (complete) {
|
|
868
|
+
for (const k of siblingKeys)
|
|
869
|
+
parseCache.usedKeys.add(k);
|
|
870
|
+
}
|
|
871
|
+
else {
|
|
872
|
+
// Fail-safe toward retention: a sibling meta was unreadable, so keep
|
|
873
|
+
// everything currently loaded rather than evict on incomplete info.
|
|
874
|
+
log('Parse cache: a branch meta was unreadable — retaining all cached chunks (#2106).');
|
|
875
|
+
for (const k of parseCache.entries.keys())
|
|
876
|
+
parseCache.usedKeys.add(k);
|
|
877
|
+
}
|
|
737
878
|
const pruned = pruneCache(parseCache, parseCache.usedKeys);
|
|
738
879
|
if (pruned > 0) {
|
|
739
880
|
log(`Parse cache: pruned ${pruned} stale chunk entries`);
|
|
@@ -763,6 +904,10 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
763
904
|
const projectName = await registerRepo(repoPath, meta, {
|
|
764
905
|
name: options.registryName,
|
|
765
906
|
allowDuplicateName: options.allowDuplicateName,
|
|
907
|
+
// Non-primary branch runs upsert into the entry's branches[]; the
|
|
908
|
+
// primary/flat run (placement.branch === undefined) refreshes the
|
|
909
|
+
// top-level fields (#2106).
|
|
910
|
+
branch: placement.branch,
|
|
766
911
|
});
|
|
767
912
|
// Keep generated .gitnexus contents ignored without editing the user's root .gitignore.
|
|
768
913
|
await ensureGitNexusIgnored(repoPath);
|
|
@@ -776,23 +921,28 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
776
921
|
}
|
|
777
922
|
aggregatedClusterCount = Array.from(groups.values()).filter((count) => count >= 5).length;
|
|
778
923
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
924
|
+
// Only (re)generate the repo-root AI context files (AGENTS.md / CLAUDE.md /
|
|
925
|
+
// skills) for the primary/flat index (#2106). A non-primary branch analyze
|
|
926
|
+
// must not churn the repo's committed AGENTS.md with branch-specific stats.
|
|
927
|
+
if (!placement.branch) {
|
|
928
|
+
try {
|
|
929
|
+
await generateAIContextFiles(repoPath, storagePath, projectName, {
|
|
930
|
+
files: pipelineResult.totalFileCount,
|
|
931
|
+
nodes: stats.nodes,
|
|
932
|
+
edges: stats.edges,
|
|
933
|
+
communities: pipelineResult.communityResult?.stats.totalCommunities,
|
|
934
|
+
clusters: aggregatedClusterCount,
|
|
935
|
+
processes: pipelineResult.processResult?.stats.totalProcesses,
|
|
936
|
+
}, undefined, {
|
|
937
|
+
skipAgentsMd: options.skipAgentsMd,
|
|
938
|
+
skipSkills: options.skipSkills,
|
|
939
|
+
noStats: options.noStats,
|
|
940
|
+
defaultBranch: options.defaultBranch,
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
catch {
|
|
944
|
+
// Best-effort — don't fail the entire analysis for context file issues
|
|
945
|
+
}
|
|
796
946
|
}
|
|
797
947
|
// ── Close LadybugDB ──────────────────────────────────────────────
|
|
798
948
|
// Stop the manual checkpoint driver before closeLbug so its
|
|
@@ -806,6 +956,7 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
806
956
|
stats: meta.stats,
|
|
807
957
|
pipelineResult,
|
|
808
958
|
ftsSkipped: !ftsAvailable,
|
|
959
|
+
isPrimaryBranch: !placement.branch,
|
|
809
960
|
};
|
|
810
961
|
}
|
|
811
962
|
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;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const _require = createRequire(import.meta.url);
|
|
5
|
+
/**
|
|
6
|
+
* Absolute path to the vendored grammar tree (`<pkg>/vendor`).
|
|
7
|
+
*
|
|
8
|
+
* This module compiles to `<pkg>/dist/core/tree-sitter/vendored-grammars.js`
|
|
9
|
+
* and runs from `<pkg>/src/core/tree-sitter/...` under tsx in dev — both sit
|
|
10
|
+
* three directories below the package root, and the build (`tsc`) never bundles,
|
|
11
|
+
* so `import.meta.url` resolves the same way in both. `vendor/` ships in the
|
|
12
|
+
* published package via package.json `files`.
|
|
13
|
+
*/
|
|
14
|
+
export const VENDOR_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'vendor');
|
|
15
|
+
/**
|
|
16
|
+
* The tree-sitter grammars GitNexus vendors inside its own package (NOT npm
|
|
17
|
+
* dependencies). Kept in one place so consumers (runtime loaders, the CLI
|
|
18
|
+
* availability probe, and the test grammar-introspection helper) agree on which
|
|
19
|
+
* grammars resolve from `vendor/` rather than `node_modules`.
|
|
20
|
+
*/
|
|
21
|
+
export const VENDORED_GRAMMAR_PACKAGES = new Set([
|
|
22
|
+
'tree-sitter-c',
|
|
23
|
+
'tree-sitter-dart',
|
|
24
|
+
'tree-sitter-proto',
|
|
25
|
+
'tree-sitter-swift',
|
|
26
|
+
'tree-sitter-kotlin',
|
|
27
|
+
]);
|
|
28
|
+
/** Absolute directory of a vendored grammar package under `vendor/`. */
|
|
29
|
+
export const vendoredGrammarDir = (packageName) => path.join(VENDOR_ROOT, packageName);
|
|
30
|
+
/**
|
|
31
|
+
* Load a vendored tree-sitter grammar by its absolute path under `vendor/`.
|
|
32
|
+
*
|
|
33
|
+
* GitNexus vendors five grammars (c/dart/proto/swift/kotlin) inside its own
|
|
34
|
+
* package under `vendor/`, each shipping committed per-platform prebuilds. They
|
|
35
|
+
* are deliberately NOT npm dependencies and must NEVER be copied into
|
|
36
|
+
* `node_modules`: an undeclared package under `node_modules` is "extraneous" to
|
|
37
|
+
* every subsequent `npm`/`npx` arborist reify, which prunes or relocates it.
|
|
38
|
+
* That is the root cause of #2111 / #1728 — on Windows the relocation throws
|
|
39
|
+
* `EPERM: operation not permitted, symlink` (errno -4048) during the npx-cache
|
|
40
|
+
* reify Antigravity triggers when it launches the MCP server, and on every OS
|
|
41
|
+
* the second run silently deletes the materialized grammars.
|
|
42
|
+
*
|
|
43
|
+
* Resolving the grammar by absolute path runs its own `bindings/node` entry,
|
|
44
|
+
* which calls `node-gyp-build(<grammarDir>)` and loads
|
|
45
|
+
* `vendor/<name>/prebuilds/<platform>-<arch>/…` directly — no build, no write,
|
|
46
|
+
* no `node_modules` copy. (`node-gyp-build` itself IS an npm dependency and
|
|
47
|
+
* resolves normally from the grammar directory.)
|
|
48
|
+
*/
|
|
49
|
+
export const requireVendoredGrammar = (packageName) => {
|
|
50
|
+
// Fail loudly on a name that isn't actually vendored — a typo or a list that
|
|
51
|
+
// drifted out of sync (VENDORED_GRAMMAR_PACKAGES vs the CLI probe vs the build
|
|
52
|
+
// registry) would otherwise surface as a confusing absolute-path require miss.
|
|
53
|
+
if (!VENDORED_GRAMMAR_PACKAGES.has(packageName)) {
|
|
54
|
+
throw new Error(`'${packageName}' is not a vendored grammar (expected one of: ${[...VENDORED_GRAMMAR_PACKAGES].join(', ')}).`);
|
|
55
|
+
}
|
|
56
|
+
return _require(vendoredGrammarDir(packageName));
|
|
57
|
+
};
|
|
@@ -8,6 +8,7 @@ import { isHttpMode, getHttpDimensions, httpEmbedQuery, } from '../../core/embed
|
|
|
8
8
|
import { resolveEmbeddingConfig } from '../../core/embeddings/config.js';
|
|
9
9
|
import { applyHfEnvOverrides, isHfDownloadFailure, withHfDownloadRetry, } from '../../core/embeddings/hf-env.js';
|
|
10
10
|
import { getLocalEmbeddingRuntimeBlocker } from '../../core/embeddings/runtime-support.js';
|
|
11
|
+
import { ensureOnnxRuntimeCommonResolvable } from '../../core/embeddings/onnxruntime-common-resolver.js';
|
|
11
12
|
import { silenceStdout, restoreStdout, realStderrWrite } from '../../core/lbug/pool-adapter.js';
|
|
12
13
|
import { logger } from '../../core/logger.js';
|
|
13
14
|
// Model config
|
|
@@ -43,6 +44,9 @@ export const initEmbedder = async () => {
|
|
|
43
44
|
try {
|
|
44
45
|
// Lazy-load transformers.js only after the runtime guard has passed, so
|
|
45
46
|
// unsupported platforms never reach the native ONNX import (#1515).
|
|
47
|
+
// Under pnpm-strict / `pnpm dlx`, transformers' phantom `onnxruntime-common`
|
|
48
|
+
// import is unresolvable; register the fallback resolver first (#307).
|
|
49
|
+
ensureOnnxRuntimeCommonResolvable();
|
|
46
50
|
const { pipeline, env } = await import('@huggingface/transformers');
|
|
47
51
|
env.allowLocalModels = false;
|
|
48
52
|
// Bridge user-controlled env vars to transformers.js: HF_HOME →
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Supports multiple indexed repositories via a global registry.
|
|
6
6
|
* LadybugDB connections are opened lazily per repo on first query.
|
|
7
7
|
*/
|
|
8
|
-
import { type RegistryEntry } from '../../storage/repo-manager.js';
|
|
8
|
+
import { type RegistryEntry, type BranchSummary } from '../../storage/repo-manager.js';
|
|
9
9
|
import { GroupService } from '../../core/group/service.js';
|
|
10
10
|
/**
|
|
11
11
|
* Quick test-file detection for filtering impact results.
|
|
@@ -16,6 +16,15 @@ export declare function isTestFilePath(filePath: string): boolean;
|
|
|
16
16
|
export declare const VALID_NODE_LABELS: Set<string>;
|
|
17
17
|
/** Valid relation types for impact analysis filtering */
|
|
18
18
|
export declare const VALID_RELATION_TYPES: Set<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Relation types the #1858 epistemic-boundary probe keys on. Kept as
|
|
21
|
+
* module-level `readonly` arrays (not Sets) because computeEpistemicBoundary
|
|
22
|
+
* binds them as Cypher query params (`r.type IN $heritage` / `IN $types`).
|
|
23
|
+
* The heritage set is exactly the IMPACT_RELATION_CONFIDENCE 0.85 tier —
|
|
24
|
+
* "statically verifiable, but the concrete binding past it is not".
|
|
25
|
+
*/
|
|
26
|
+
export declare const EPISTEMIC_HERITAGE_RELATION_TYPES: readonly string[];
|
|
27
|
+
export declare const EPISTEMIC_CONSUMER_RELATION_TYPES: readonly string[];
|
|
19
28
|
/**
|
|
20
29
|
* Per-relation-type confidence floor for impact analysis.
|
|
21
30
|
*
|
|
@@ -56,6 +65,10 @@ interface RepoHandle {
|
|
|
56
65
|
lastCommit: string;
|
|
57
66
|
remoteUrl?: string;
|
|
58
67
|
stats?: RegistryEntry['stats'];
|
|
68
|
+
/** Primary/flat branch name, when known (#2106). */
|
|
69
|
+
branch?: string;
|
|
70
|
+
/** Non-primary branch indexes available for this repo (#2106). */
|
|
71
|
+
branches?: BranchSummary[];
|
|
59
72
|
}
|
|
60
73
|
/**
|
|
61
74
|
* Resolve the git diff cwd for detect_changes, auto-detecting linked worktrees.
|
|
@@ -107,6 +120,10 @@ export interface RepoListing {
|
|
|
107
120
|
path: string;
|
|
108
121
|
lastCommit: string;
|
|
109
122
|
}>;
|
|
123
|
+
/** Primary/flat branch name, when known (#2106). */
|
|
124
|
+
branch?: string;
|
|
125
|
+
/** Non-primary branch indexes available for this repo (#2106). */
|
|
126
|
+
branches?: Array<Omit<BranchSummary, 'stats'>>;
|
|
110
127
|
}
|
|
111
128
|
/** Continuation metadata for the paginated `list_repos` MCP tool (#2119). */
|
|
112
129
|
export interface ListReposPagination {
|
|
@@ -153,6 +170,7 @@ export declare class LocalBackend {
|
|
|
153
170
|
private initializedRepos;
|
|
154
171
|
private reinitPromises;
|
|
155
172
|
private lastStalenessCheck;
|
|
173
|
+
private lastObservedIndexedAt;
|
|
156
174
|
private groupToolSvc;
|
|
157
175
|
/**
|
|
158
176
|
* One-shot stderr warnings for sibling-clone drift, keyed by
|
|
@@ -214,7 +232,20 @@ export declare class LocalBackend {
|
|
|
214
232
|
* On a miss, re-reads the registry once in case a new repo was indexed
|
|
215
233
|
* while the MCP server was running.
|
|
216
234
|
*/
|
|
217
|
-
resolveRepo(repoParam?: string): Promise<RepoHandle>;
|
|
235
|
+
resolveRepo(repoParam?: string, branch?: string): Promise<RepoHandle>;
|
|
236
|
+
/**
|
|
237
|
+
* Re-point a resolved repo handle at a specific branch index (#2106).
|
|
238
|
+
*
|
|
239
|
+
* - No `branch` (default) → the primary/flat handle, unchanged (backward
|
|
240
|
+
* compatible: every existing caller passes no branch).
|
|
241
|
+
* - `branch` equal to the known primary → the flat handle.
|
|
242
|
+
* - `branch` matching an indexed non-primary branch → a handle whose
|
|
243
|
+
* `lbugPath` points at `branches/<slug>/lbug`; the connection pool keys by
|
|
244
|
+
* `lbugPath`, so this is the only change needed to scope every tool.
|
|
245
|
+
* - `branch` that was never indexed → a clear error (never a silently-empty
|
|
246
|
+
* result against the wrong DB).
|
|
247
|
+
*/
|
|
248
|
+
private applyBranchScope;
|
|
218
249
|
/**
|
|
219
250
|
* Try to resolve a repo from the in-memory cache. Returns null on miss.
|
|
220
251
|
* Throws {@link RegistryAmbiguousTargetError} when `repoParam` matches
|
|
@@ -409,6 +440,28 @@ export declare class LocalBackend {
|
|
|
409
440
|
private rename;
|
|
410
441
|
private impact;
|
|
411
442
|
private _impactImpl;
|
|
443
|
+
/**
|
|
444
|
+
* #1858 — epistemic lower-bound detection.
|
|
445
|
+
*
|
|
446
|
+
* impact()/context() traverse only edges materialized in the graph. When the
|
|
447
|
+
* queried symbol sits on an interface / abstract boundary, callers that bind
|
|
448
|
+
* to the interface via DI, a container, or dynamic dispatch — rather than
|
|
449
|
+
* naming the concrete symbol — are not traced. The reported count is then a
|
|
450
|
+
* lower bound, not an exact figure. Instead of returning a confident count
|
|
451
|
+
* that silently omits those callers, annotate the result with
|
|
452
|
+
* `epistemic: 'lower-bound'` plus a human-readable boundary note. A fully
|
|
453
|
+
* resolved leaf with no indirection stays `epistemic: 'exact'`.
|
|
454
|
+
*
|
|
455
|
+
* Aligns with the numeric confidence model rather than the long-deleted
|
|
456
|
+
* TIER_CONFIDENCE enum: the heritage/indirection edges this keys on
|
|
457
|
+
* (IMPLEMENTS / METHOD_IMPLEMENTS / EXTENDS) carry the 0.85
|
|
458
|
+
* `IMPACT_RELATION_CONFIDENCE` floor — "statically verifiable, but the
|
|
459
|
+
* concrete binding past it is not".
|
|
460
|
+
*
|
|
461
|
+
* Never throws: on query error it returns 'exact', so it can only add signal,
|
|
462
|
+
* never suppress a result.
|
|
463
|
+
*/
|
|
464
|
+
private computeEpistemicBoundary;
|
|
412
465
|
/**
|
|
413
466
|
* Shared BFS traversal for impact analysis (name-resolved or UID-resolved symbol).
|
|
414
467
|
*/
|