@sdsrs/code-graph 0.75.0 → 0.75.2
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.
|
@@ -1359,6 +1359,63 @@ test('resolveProjectRoot: no index up to $HOME → null (home itself still check
|
|
|
1359
1359
|
} finally { fsE2e.rmSync(base, { recursive: true, force: true }); }
|
|
1360
1360
|
});
|
|
1361
1361
|
|
|
1362
|
+
test('resolveProjectRoot: skips a STRAY nested subdir index, prefers the .git root', () => {
|
|
1363
|
+
// monorepo (daagu shape): root has .git + index; a subdir carries a stray
|
|
1364
|
+
// index relic but no .git. Resolving from the subdir must climb to the root,
|
|
1365
|
+
// not pin the stray nested index (the statusline "oscillation" root cause).
|
|
1366
|
+
const base = fsE2e.mkdtempSync(pathE2e.join(osE2e.tmpdir(), 'cg-root-'));
|
|
1367
|
+
try {
|
|
1368
|
+
const proj = pathE2e.join(base, 'proj');
|
|
1369
|
+
fsE2e.mkdirSync(pathE2e.join(proj, '.git'), { recursive: true });
|
|
1370
|
+
fsE2e.mkdirSync(pathE2e.join(proj, '.code-graph'), { recursive: true });
|
|
1371
|
+
fsE2e.writeFileSync(pathE2e.join(proj, '.code-graph', 'index.db'), '');
|
|
1372
|
+
const sub = pathE2e.join(proj, 'backend');
|
|
1373
|
+
fsE2e.mkdirSync(pathE2e.join(sub, '.code-graph'), { recursive: true });
|
|
1374
|
+
fsE2e.writeFileSync(pathE2e.join(sub, '.code-graph', 'index.db'), '');
|
|
1375
|
+
assert.equal(resolveProjectRoot(sub, { home: base }), proj);
|
|
1376
|
+
} finally { fsE2e.rmSync(base, { recursive: true, force: true }); }
|
|
1377
|
+
});
|
|
1378
|
+
|
|
1379
|
+
test('resolveProjectRoot: a nested index with its OWN .git (submodule) still wins', () => {
|
|
1380
|
+
const base = fsE2e.mkdtempSync(pathE2e.join(osE2e.tmpdir(), 'cg-root-'));
|
|
1381
|
+
try {
|
|
1382
|
+
const proj = pathE2e.join(base, 'proj');
|
|
1383
|
+
fsE2e.mkdirSync(pathE2e.join(proj, '.git'), { recursive: true });
|
|
1384
|
+
fsE2e.mkdirSync(pathE2e.join(proj, '.code-graph'), { recursive: true });
|
|
1385
|
+
fsE2e.writeFileSync(pathE2e.join(proj, '.code-graph', 'index.db'), '');
|
|
1386
|
+
const sub = pathE2e.join(proj, 'vendored');
|
|
1387
|
+
fsE2e.mkdirSync(pathE2e.join(sub, '.git'), { recursive: true });
|
|
1388
|
+
fsE2e.mkdirSync(pathE2e.join(sub, '.code-graph'), { recursive: true });
|
|
1389
|
+
fsE2e.writeFileSync(pathE2e.join(sub, '.code-graph', 'index.db'), '');
|
|
1390
|
+
assert.equal(resolveProjectRoot(sub, { home: base }), sub);
|
|
1391
|
+
} finally { fsE2e.rmSync(base, { recursive: true, force: true }); }
|
|
1392
|
+
});
|
|
1393
|
+
|
|
1394
|
+
test('resolveProjectRoot: start with its OWN .git but no index → null (boundary, no escape)', () => {
|
|
1395
|
+
const base = fsE2e.mkdtempSync(pathE2e.join(osE2e.tmpdir(), 'cg-root-'));
|
|
1396
|
+
try {
|
|
1397
|
+
const proj = pathE2e.join(base, 'proj'); // indexed parent
|
|
1398
|
+
fsE2e.mkdirSync(pathE2e.join(proj, '.code-graph'), { recursive: true });
|
|
1399
|
+
fsE2e.writeFileSync(pathE2e.join(proj, '.code-graph', 'index.db'), '');
|
|
1400
|
+
const sub = pathE2e.join(proj, 'sub'); // own .git, no index
|
|
1401
|
+
fsE2e.mkdirSync(pathE2e.join(sub, '.git'), { recursive: true });
|
|
1402
|
+
assert.equal(resolveProjectRoot(sub, { home: base }), null);
|
|
1403
|
+
} finally { fsE2e.rmSync(base, { recursive: true, force: true }); }
|
|
1404
|
+
});
|
|
1405
|
+
|
|
1406
|
+
test('resolveProjectRoot: non-git monorepo — stray subdir index resolves to indexed ancestor', () => {
|
|
1407
|
+
const base = fsE2e.mkdtempSync(pathE2e.join(osE2e.tmpdir(), 'cg-root-'));
|
|
1408
|
+
try {
|
|
1409
|
+
const root = pathE2e.join(base, 'mono'); // indexed, NO .git
|
|
1410
|
+
fsE2e.mkdirSync(pathE2e.join(root, '.code-graph'), { recursive: true });
|
|
1411
|
+
fsE2e.writeFileSync(pathE2e.join(root, '.code-graph', 'index.db'), '');
|
|
1412
|
+
const sub = pathE2e.join(root, 'backend'); // stray index, no .git
|
|
1413
|
+
fsE2e.mkdirSync(pathE2e.join(sub, '.code-graph'), { recursive: true });
|
|
1414
|
+
fsE2e.writeFileSync(pathE2e.join(sub, '.code-graph', 'index.db'), '');
|
|
1415
|
+
assert.equal(resolveProjectRoot(sub, { home: base }), root);
|
|
1416
|
+
} finally { fsE2e.rmSync(base, { recursive: true, force: true }); }
|
|
1417
|
+
});
|
|
1418
|
+
|
|
1362
1419
|
test('rebaseRelativePaths: daagu shape — bare `app` from backend/ cwd', () => {
|
|
1363
1420
|
const exists = (p) => p.endsWith(pathE2e.join('backend', 'app'));
|
|
1364
1421
|
const cmd = 'grep -rn "rr_source\\|max_retries" app --include=*.py';
|
|
@@ -14,16 +14,61 @@ const fs = require('fs');
|
|
|
14
14
|
const os = require('os');
|
|
15
15
|
const path = require('path');
|
|
16
16
|
|
|
17
|
+
// Resolves to the project's CANONICAL index dir, skipping STRAY nested indexes.
|
|
18
|
+
// A monorepo subdir (`daagu/backend`, `daagu/frontend`) can carry its own
|
|
19
|
+
// `.code-graph/index.db` — a relic an older binary created — nested under the
|
|
20
|
+
// real root's index. Returning the nearest such index made every consumer
|
|
21
|
+
// (statusline gate, hooks) read a different DB per cwd (statusline "oscillation",
|
|
22
|
+
// `✗ 0 nodes` in an empty subdir index). Mirror the Rust resolver: the start's
|
|
23
|
+
// own index wins only if it is NOT a stray nested index (no indexed ancestor) OR
|
|
24
|
+
// start is itself a project boundary (`.git`, i.e. a real submodule). Otherwise
|
|
25
|
+
// prefer the project root: the nearest indexed `.git` root, else the outermost
|
|
26
|
+
// indexed dir on the chain. `null` when nothing on start→…→home is indexed.
|
|
17
27
|
function resolveProjectRoot(startDir, opts = {}) {
|
|
18
28
|
const home = opts.home !== undefined ? opts.home : os.homedir();
|
|
19
29
|
const exists = opts.exists || fs.existsSync;
|
|
20
|
-
|
|
30
|
+
const hasIndex = (d) => exists(path.join(d, '.code-graph', 'index.db'));
|
|
31
|
+
const hasGit = (d) => exists(path.join(d, '.git'));
|
|
32
|
+
const start = path.resolve(startDir || '.');
|
|
33
|
+
|
|
34
|
+
// start's own `.git` is a hard project boundary (a real submodule / distinct
|
|
35
|
+
// repo): use its index if present, else `null` — never escape to an ancestor's
|
|
36
|
+
// index. Mirrors the Rust resolver's rule 1 (which returns cwd even without an
|
|
37
|
+
// index because it CREATES one; the JS reader has nothing to read → null).
|
|
38
|
+
if (hasGit(start)) return hasIndex(start) ? start : null;
|
|
39
|
+
|
|
40
|
+
// Detect whether `start` is a STRAY nested index: walk STRICT ancestors up to
|
|
41
|
+
// the nearest `.git` root (project boundary), bounded at home. An indexed
|
|
42
|
+
// ancestor within that boundary means start's own index is a monorepo-subdir
|
|
43
|
+
// relic. Stop AT the git root — an index above it (e.g. `~/.code-graph`) is an
|
|
44
|
+
// unrelated outer project and must not poison this one.
|
|
45
|
+
let gitRootIndexed = null;
|
|
46
|
+
let ancestorIndexed = false;
|
|
47
|
+
let dir = start;
|
|
21
48
|
for (;;) {
|
|
22
|
-
if (exists(path.join(dir, '.code-graph', 'index.db'))) return dir;
|
|
23
|
-
if (dir === home) return null;
|
|
24
49
|
const parent = path.dirname(dir);
|
|
25
|
-
|
|
50
|
+
// Stop BEFORE home (and fs root): an index at/above home (e.g. ~/.code-graph
|
|
51
|
+
// from indexing a home dir) is an unrelated outer project, never a parent
|
|
52
|
+
// that makes `start` stray.
|
|
53
|
+
if (parent === dir || parent === home) break;
|
|
26
54
|
dir = parent;
|
|
55
|
+
if (hasIndex(dir)) ancestorIndexed = true;
|
|
56
|
+
if (hasGit(dir)) { if (hasIndex(dir)) gitRootIndexed = dir; break; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// start's own index wins unless it is stray (an indexed ancestor within the
|
|
60
|
+
// git boundary). start's own `.git` was already handled above.
|
|
61
|
+
if (hasIndex(start) && !ancestorIndexed) return start;
|
|
62
|
+
if (gitRootIndexed) return gitRootIndexed;
|
|
63
|
+
// Otherwise the nearest indexed ancestor (skipping a stray start), bounded at
|
|
64
|
+
// home; null if nothing on the chain is indexed. Mirrors the original walk.
|
|
65
|
+
let d = hasIndex(start) ? path.dirname(start) : start;
|
|
66
|
+
for (;;) {
|
|
67
|
+
if (hasIndex(d)) return d;
|
|
68
|
+
if (d === home) return null;
|
|
69
|
+
const parent = path.dirname(d);
|
|
70
|
+
if (parent === d) return null;
|
|
71
|
+
d = parent;
|
|
27
72
|
}
|
|
28
73
|
}
|
|
29
74
|
|
|
@@ -5,6 +5,7 @@ const fs = require('fs');
|
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { findBinary } = require('./find-binary');
|
|
8
|
+
const { resolveProjectRoot } = require('./project-root');
|
|
8
9
|
const lifecycle = require('./lifecycle');
|
|
9
10
|
const cleanupDisabledStatusline = lifecycle.cleanupDisabledStatusline || (() => ({ cleaned: false }));
|
|
10
11
|
|
|
@@ -24,14 +25,19 @@ function updatePending() {
|
|
|
24
25
|
const disabledCleanup = cleanupDisabledStatusline();
|
|
25
26
|
if (disabledCleanup.cleaned) process.exit(0);
|
|
26
27
|
|
|
27
|
-
// Only show status in projects that have a code-graph directory.
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
// Only show status in projects that have a code-graph directory. The statusLine
|
|
29
|
+
// config is global, so we must exit silently for non-code-graph directories.
|
|
30
|
+
// Walk UP to the canonical project root (resolveProjectRoot) rather than keying
|
|
31
|
+
// on the bare process.cwd(): when the shell sits in a subdir, the bare-cwd gate
|
|
32
|
+
// either showed a STRAY nested subdir index (monorepo relic — the statusline
|
|
33
|
+
// "oscillating" between root/backend/frontend node counts) or, in a clean subdir
|
|
34
|
+
// with no local index, showed nothing at all. The resolver skips stray nested
|
|
35
|
+
// indexes, so the statusline tracks one DB — the project root — from any subdir.
|
|
36
|
+
const root = resolveProjectRoot(process.cwd());
|
|
37
|
+
if (!root) {
|
|
33
38
|
process.exit(0);
|
|
34
39
|
}
|
|
40
|
+
const codeGraphDir = path.join(root, '.code-graph');
|
|
35
41
|
|
|
36
42
|
// Check for background indexing progress file first
|
|
37
43
|
const progressFile = path.join(codeGraphDir, 'indexing-status.json');
|
|
@@ -108,7 +114,11 @@ let errText = '';
|
|
|
108
114
|
try {
|
|
109
115
|
report = parseReport(execFileSync(bin, ['health-check', '--format', 'json'], {
|
|
110
116
|
timeout: 3000,
|
|
111
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
117
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
118
|
+
// Run the binary FROM the resolved root so its own project-root resolution
|
|
119
|
+
// lands on the same DB the gate above picked (a subdir cwd would otherwise
|
|
120
|
+
// re-resolve to a stray nested index inside the binary).
|
|
121
|
+
cwd: root
|
|
112
122
|
}).toString());
|
|
113
123
|
} catch (e) {
|
|
114
124
|
// health-check exits NON-ZERO on an unhealthy/empty index but still writes the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.75.
|
|
3
|
+
"version": "0.75.2",
|
|
4
4
|
"description": "MCP server that indexes codebases into an AST knowledge graph with semantic search, call graph traversal, and HTTP route tracing",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"node": ">=16"
|
|
36
36
|
},
|
|
37
37
|
"optionalDependencies": {
|
|
38
|
-
"@sdsrs/code-graph-linux-x64": "0.75.
|
|
39
|
-
"@sdsrs/code-graph-linux-arm64": "0.75.
|
|
40
|
-
"@sdsrs/code-graph-darwin-x64": "0.75.
|
|
41
|
-
"@sdsrs/code-graph-darwin-arm64": "0.75.
|
|
42
|
-
"@sdsrs/code-graph-win32-x64": "0.75.
|
|
38
|
+
"@sdsrs/code-graph-linux-x64": "0.75.2",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.75.2",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.75.2",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.75.2",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.75.2"
|
|
43
43
|
}
|
|
44
44
|
}
|