@sdsrs/code-graph 0.79.0 → 0.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
// this tool, so counting it would let a once-polluted /tmp self-certify as a
|
|
23
23
|
// project on the next session (circular).
|
|
24
24
|
const fs = require('fs');
|
|
25
|
+
const os = require('os');
|
|
25
26
|
const path = require('path');
|
|
26
27
|
|
|
27
28
|
const PROJECT_MARKERS = [
|
|
@@ -33,11 +34,32 @@ function isProjectRoot(cwd) {
|
|
|
33
34
|
return PROJECT_MARKERS.some(m => fs.existsSync(path.join(cwd, m)));
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
37
|
+
// Walk up from `cwd` to the nearest ancestor carrying a project marker, bounded
|
|
38
|
+
// by $HOME (exclusive) and the filesystem root. Returns the marker dir, or null
|
|
39
|
+
// if none. Mirrors the Rust binary's `resolve_project_root_bounded` so the JS
|
|
40
|
+
// activation gate and the binary AGREE: before this, the gate checked ONLY the
|
|
41
|
+
// literal cwd, so launching from a marker-less monorepo SUBDIR (e.g.
|
|
42
|
+
// `repo/backend/` with `.git` only at `repo/`) classified it non-project and
|
|
43
|
+
// served the 0-tool stub — even though the binary would have resolved `repo/`
|
|
44
|
+
// and answered queries. The $HOME-exclusive bound keeps every /tmp / headless
|
|
45
|
+
// cwd non-project (a stray marker in $HOME must not certify them).
|
|
46
|
+
function findProjectRoot(cwd = process.cwd()) {
|
|
47
|
+
const home = os.homedir();
|
|
48
|
+
let dir = path.resolve(cwd);
|
|
49
|
+
for (;;) {
|
|
50
|
+
if (dir === home) return null; // $HOME-exclusive: don't certify $HOME or above
|
|
51
|
+
if (isProjectRoot(dir)) return dir;
|
|
52
|
+
const parent = path.dirname(dir);
|
|
53
|
+
if (parent === dir) return null; // filesystem root
|
|
54
|
+
dir = parent;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// A cwd is "non-project" when neither it NOR any ancestor (up to $HOME) carries a
|
|
59
|
+
// recognized project marker. The plugin's activation gates short-circuit there:
|
|
60
|
+
// no MCP tools, no index creation, no SessionStart map injection, no auto-adoption.
|
|
39
61
|
function isNonProjectCwd(cwd = process.cwd()) {
|
|
40
|
-
return
|
|
62
|
+
return findProjectRoot(cwd) === null;
|
|
41
63
|
}
|
|
42
64
|
|
|
43
|
-
module.exports = { PROJECT_MARKERS, isProjectRoot, isNonProjectCwd };
|
|
65
|
+
module.exports = { PROJECT_MARKERS, isProjectRoot, findProjectRoot, isNonProjectCwd };
|
|
@@ -7,7 +7,7 @@ const fs = require('fs');
|
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const os = require('os');
|
|
9
9
|
|
|
10
|
-
const { PROJECT_MARKERS, isProjectRoot, isNonProjectCwd } = require('./project-detect');
|
|
10
|
+
const { PROJECT_MARKERS, isProjectRoot, findProjectRoot, isNonProjectCwd } = require('./project-detect');
|
|
11
11
|
|
|
12
12
|
function mkTmp(t) {
|
|
13
13
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-pd-'));
|
|
@@ -71,3 +71,25 @@ test('isProjectRoot detects each marker', (t) => {
|
|
|
71
71
|
assert.equal(isProjectRoot(dir), true, `${marker} should make cwd a project`);
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
|
+
|
|
75
|
+
test('isNonProjectCwd: a marker-less SUBDIR of a project resolves to the project (walk-up, monorepo fix)', (t) => {
|
|
76
|
+
// Regression (v0.79.1 audit #7): the gate checked ONLY the literal cwd, so a
|
|
77
|
+
// monorepo subdir (`.git` only at the repo root) served the 0-tool stub even
|
|
78
|
+
// though the Rust binary's resolver walks up and would answer queries. The
|
|
79
|
+
// gate now walks up too.
|
|
80
|
+
const root = mkTmp(t);
|
|
81
|
+
fs.mkdirSync(path.join(root, '.git'));
|
|
82
|
+
const sub = path.join(root, 'backend', 'src');
|
|
83
|
+
fs.mkdirSync(sub, { recursive: true });
|
|
84
|
+
assert.equal(isProjectRoot(sub), false, 'the subdir itself has no marker');
|
|
85
|
+
assert.equal(isNonProjectCwd(sub), false, 'but it is INSIDE a project → not non-project');
|
|
86
|
+
assert.equal(findProjectRoot(sub), root, 'walk-up returns the repo root');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('findProjectRoot: a marker-less tree with no ancestor marker → null (tmp/headless stays gated)', (t) => {
|
|
90
|
+
const dir = mkTmp(t);
|
|
91
|
+
const sub = path.join(dir, 'a', 'b');
|
|
92
|
+
fs.mkdirSync(sub, { recursive: true });
|
|
93
|
+
assert.equal(findProjectRoot(sub), null);
|
|
94
|
+
assert.equal(isNonProjectCwd(sub), true);
|
|
95
|
+
});
|
|
@@ -7,6 +7,12 @@ const { execFileSync } = require('child_process');
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
9
|
const os = require('os');
|
|
10
|
+
// Hook cooldown flags + the restart notice go under cgTmpDir() (a code-graph-mcp/
|
|
11
|
+
// subdir of os.tmpdir()), NOT bare os.tmpdir(): Claude Code overrides $TMPDIR to
|
|
12
|
+
// ~/.claude/tmp/, so bare-tmp flags interleave with transcript captures —
|
|
13
|
+
// diagnostic blindness + the §8 recursive-grep footgun (see tmp-dir.js). The
|
|
14
|
+
// other hook scripts already route through here; this one was the lone holdout.
|
|
15
|
+
const { cgTmpDir } = require('./tmp-dir');
|
|
10
16
|
|
|
11
17
|
// Mid-session install detection: hook fires but no manifest yet.
|
|
12
18
|
const MANIFEST_PATH = path.join(os.homedir(), '.cache', 'code-graph', 'install-manifest.json');
|
|
@@ -22,7 +28,7 @@ const COOLDOWNS = {
|
|
|
22
28
|
|
|
23
29
|
function isCoolingDown(type) {
|
|
24
30
|
try {
|
|
25
|
-
const flag = path.join(
|
|
31
|
+
const flag = path.join(cgTmpDir(), `.code-graph-ctx-${type}`);
|
|
26
32
|
const stat = fs.statSync(flag);
|
|
27
33
|
return Date.now() - stat.mtimeMs < (COOLDOWNS[type] || 60000);
|
|
28
34
|
} catch { return false; }
|
|
@@ -30,7 +36,7 @@ function isCoolingDown(type) {
|
|
|
30
36
|
|
|
31
37
|
function markCooldown(type) {
|
|
32
38
|
try {
|
|
33
|
-
fs.writeFileSync(path.join(
|
|
39
|
+
fs.writeFileSync(path.join(cgTmpDir(), `.code-graph-ctx-${type}`), '');
|
|
34
40
|
} catch { /* ok */ }
|
|
35
41
|
}
|
|
36
42
|
|
|
@@ -398,7 +404,7 @@ function runMain() {
|
|
|
398
404
|
// Mid-session install: lifecycle.js install() hasn't run yet (no manifest).
|
|
399
405
|
// MCP server only starts at session startup — tell the user to restart.
|
|
400
406
|
if (!fs.existsSync(MANIFEST_PATH)) {
|
|
401
|
-
const noticeFile = path.join(
|
|
407
|
+
const noticeFile = path.join(cgTmpDir(), '.code-graph-mcp-restart-notice');
|
|
402
408
|
try {
|
|
403
409
|
// Show once per hour to avoid spam
|
|
404
410
|
if (Date.now() - fs.statSync(noticeFile).mtimeMs < 3600000) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.80.0",
|
|
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.
|
|
39
|
-
"@sdsrs/code-graph-linux-arm64": "0.
|
|
40
|
-
"@sdsrs/code-graph-darwin-x64": "0.
|
|
41
|
-
"@sdsrs/code-graph-darwin-arm64": "0.
|
|
42
|
-
"@sdsrs/code-graph-win32-x64": "0.
|
|
38
|
+
"@sdsrs/code-graph-linux-x64": "0.80.0",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.80.0",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.80.0",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.80.0",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.80.0"
|
|
43
43
|
}
|
|
44
44
|
}
|