@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.
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "sdsrs"
6
6
  },
7
- "version": "0.79.0",
7
+ "version": "0.80.0",
8
8
  "keywords": [
9
9
  "code-graph",
10
10
  "ast",
@@ -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
- // A cwd is "non-project" when it carries none of the recognized project
37
- // markers. The plugin's activation gates short-circuit there: no MCP tools,
38
- // no index creation, no SessionStart map injection, no auto-adoption.
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 !isProjectRoot(cwd);
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(os.tmpdir(), `.code-graph-ctx-${type}`);
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(os.tmpdir(), `.code-graph-ctx-${type}`), '');
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(os.tmpdir(), '.code-graph-mcp-restart-notice');
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.79.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.79.0",
39
- "@sdsrs/code-graph-linux-arm64": "0.79.0",
40
- "@sdsrs/code-graph-darwin-x64": "0.79.0",
41
- "@sdsrs/code-graph-darwin-arm64": "0.79.0",
42
- "@sdsrs/code-graph-win32-x64": "0.79.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
  }