@sdsrs/code-graph 0.156.0 → 0.158.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.156.0",
7
+ "version": "0.158.0",
8
8
  "keywords": [
9
9
  "code-graph",
10
10
  "ast",
@@ -46,6 +46,8 @@ const {
46
46
  extractCgFlags,
47
47
  cgFlagSet,
48
48
  extractSearchPath,
49
+ bareSourceTarget,
50
+ firstShellClause,
49
51
  normalizeCommandPaths,
50
52
  rebaseRelativePaths,
51
53
  resolveProjectRoot,
@@ -263,6 +265,14 @@ function runMain() {
263
265
  markCooldown(rawCmd, root);
264
266
 
265
267
  const { segment, block } = found;
268
+ // D#73 — a bare dir names `<shell cwd>/src`. From a subdirectory shell (same
269
+ // guard as pre-grep-guide's runMain), or after anything earlier in the same
270
+ // command, that is not provably the root's src: round 2 found `cd x && grep`,
271
+ // round 3 reproduced 19 more forms that move the shell (`builtin cd`, `if cd`,
272
+ // `{ cd …; }`, `\cd`, `popd`, `eval`, a function). A denylist of those never
273
+ // closes, so a bare dir is answered only when the grep is the FIRST segment.
274
+ if (bareSourceTarget(firstShellClause(segment))
275
+ && (relPrefix || splitTopLevelSegments(cmd)[0] !== segment)) return;
266
276
  // Run the answer exactly like the deny path.
267
277
  const rawPattern = pickBlockPattern(segment);
268
278
  // Grep-response gate (2026-07-03 audit: 18/18 injects were 0 CONSUMED because they
@@ -89,6 +89,30 @@ const SRC_PREFIXES =
89
89
  const SRC_PATH = new RegExp(`(?:^|\\s|["'])(${SRC_PREFIXES})/`);
90
90
  // Anchored variant for whole-token matching in extractSearchPath.
91
91
  const SRC_PATH_TOKEN = new RegExp(`^(?:\\./)?(${SRC_PREFIXES})/`);
92
+ // A path operand the rewrite grammar proved: a bare prefix word (`src`), or a
93
+ // `./`-rooted one with any subpath (`./src`, `./src/x/`), which SRC_PATH's
94
+ // lookbehind never matched.
95
+ const SRC_BARE_TOKEN = new RegExp(`^(?:(?:${SRC_PREFIXES})|\\./(?:${SRC_PREFIXES})(?:/.*)?)$`);
96
+
97
+ // D#73 — a source dir written as a bare word (`grep -rn X src`, `rg X tests`,
98
+ // `./src`): the shape models write when a prompt says "under src/", and it
99
+ // matched nothing before. Recognized ONLY where rewritePlan's grammar proves the
100
+ // word is the command's path operand. A regex over the text cannot tell: these
101
+ // prefixes are English words (`server`, `tasks`) and import-path fragments
102
+ // inside patterns, flag values (`rg -t cmd`, `ag --ignore tests`), halves of a
103
+ // two-path or `$(…)` search, or the pattern itself (`grep -n tasks f.py`) —
104
+ // pre-ship review round 1 reproduced each as a wrong hint or inject. Callers
105
+ // in a subdirectory shell must not use it on an un-rebased word: there a bare
106
+ // `src` is `<cwd>/src`, not the root's (see runMain). `.` and no path at all
107
+ // stay out: they reach non-source files, and grep -r ignores .gitignore where
108
+ // cg does not.
109
+ function bareSourceTarget(clause) {
110
+ const plan = rewritePlan(clause);
111
+ return plan && plan.target !== undefined && SRC_BARE_TOKEN.test(plan.target) ? plan.target : null;
112
+ }
113
+ function namesSourcePath(clause) {
114
+ return SRC_PATH.test(clause) || bareSourceTarget(clause) !== null;
115
+ }
92
116
  const PIPE_INTO_GREP = new RegExp(`\\|\\s*(?:${GREP_VERB})\\b`);
93
117
  const CG_INVOKED = /\bcode-graph-mcp\b/;
94
118
  // File argument(s) that end in a config/lockfile/data extension. If, after removing
@@ -184,11 +208,11 @@ function shouldHint(cmd) {
184
208
  // v0.96 — the source-path gate must see ONLY the grep's own args, not a path in
185
209
  // a non-grep tail (`grep X skills/a.py; wc scripts/b` must not fire on scripts/b).
186
210
  const clause = firstShellClause(cmd);
187
- if (!SRC_PATH.test(clause)) return false; // not against indexed source tree
211
+ if (!namesSourcePath(clause)) return false; // not against indexed source tree
188
212
  // If a config file appears AND no source path remains after stripping it, skip.
189
213
  if (CONFIG_TARGET_ONLY.test(clause)) {
190
214
  const stripped = clause.replace(CONFIG_TARGET_STRIP, ' ');
191
- if (!SRC_PATH.test(stripped)) return false;
215
+ if (!namesSourcePath(stripped)) return false;
192
216
  }
193
217
  return true;
194
218
  }
@@ -679,6 +703,13 @@ function extractSearchPath(cmd) {
679
703
  // v0.96 — scope to the grep's own clause so the answer is never scoped to a
680
704
  // path in a non-grep tail (the file the user actually grepped is the only one
681
705
  // the "already ran for you" answer may claim to have searched).
706
+ // D#73 — when the grammar proves a bare/`./` source operand, it is the scope.
707
+ // It must win over the text scan below, which takes the first path-shaped
708
+ // token and so can take a PATTERN (`grep -rn "./src/x" tests`) or cut a
709
+ // quoted operand at its space (pre-ship review round 2 B1, B2). The grammar
710
+ // rejects `..`, so no traversal reaches this return.
711
+ const bare = bareSourceTarget(firstShellClause(cmd));
712
+ if (bare) return bare;
682
713
  for (const raw of firstShellClause(cmd).split(/\s+/)) {
683
714
  const token = raw.replace(/^["']|["']$/g, '');
684
715
  if (!token || token.startsWith('-')) continue;
@@ -711,6 +742,11 @@ function countNamedPaths(cmd, patterns) {
711
742
  if (pats.has(tok)) continue; // the search pattern, not a path
712
743
  if (tok.includes('/') || /\.[A-Za-z0-9]{1,6}$/.test(tok)) n++; // dir-sep or file extension
713
744
  }
745
+ // D#73 — the grammar's bare-dir operand is a named path too, counted like
746
+ // its `src/` spelling (whose loop pass counts it). The grammar admits one
747
+ // path operand, so this adds at most one.
748
+ const bare = bareSourceTarget(firstShellClause(cmd));
749
+ if (bare && !bare.includes('/') && !/\.[A-Za-z0-9]{1,6}$/.test(bare)) n++;
714
750
  return n;
715
751
  }
716
752
 
@@ -1183,6 +1219,9 @@ function runMain() {
1183
1219
  let cmd = normalizeCommandPaths(rawCmd, root);
1184
1220
  const relPrefix = path.relative(root, shellCwd);
1185
1221
  if (relPrefix) cmd = rebaseRelativePaths(cmd, relPrefix, root);
1222
+ // D#73 — from a subdirectory, a bare dir the rebase left alone is `<cwd>/src`,
1223
+ // which the root-relative answer would not search (pre-ship review round 1 H1).
1224
+ if (relPrefix && bareSourceTarget(firstShellClause(cmd))) return;
1186
1225
  if (!shouldHint(cmd)) return;
1187
1226
 
1188
1227
  // v0.64 — fingerprint the grep's pattern once, shared by the emit points below.
@@ -1393,6 +1432,7 @@ module.exports = {
1393
1432
  extractUnansweredTail, // v0.50 — compound-tail honesty in answered denies
1394
1433
  extractPatterns, // v0.32.1 — exposed for tests
1395
1434
  countNamedPaths, // v0.70 — multi-path deny→hint downgrade
1435
+ bareSourceTarget, // D#73 — a bare source dir the rewrite grammar proves is the path operand
1396
1436
  isRevisionScopedGitGrep, // v0.71 — git grep --cached/treeish exclusion
1397
1437
  extractSearchPath, // v0.47.0 — deny-with-answer
1398
1438
  normalizeCommandPaths, // v0.47.1 — abs-path matcher fix
@@ -35,7 +35,7 @@ jobs:
35
35
  node-version: '20'
36
36
  - name: Build snapshot
37
37
  run: |
38
- npx -y -p @sdsrs/code-graph@0.156.0 code-graph-mcp snapshot create --out snapshot.db
38
+ npx -y -p @sdsrs/code-graph@0.158.0 code-graph-mcp snapshot create --out snapshot.db
39
39
  zstd -9 snapshot.db -o snapshot.db.zst
40
40
  mv snapshot.db.zst "code-graph-snapshot-${GITHUB_SHA:0:7}.db.zst"
41
41
  - name: Upload to release
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdsrs/code-graph",
3
- "version": "0.156.0",
3
+ "version": "0.158.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.156.0",
39
- "@sdsrs/code-graph-linux-arm64": "0.156.0",
40
- "@sdsrs/code-graph-darwin-x64": "0.156.0",
41
- "@sdsrs/code-graph-darwin-arm64": "0.156.0",
42
- "@sdsrs/code-graph-win32-x64": "0.156.0"
38
+ "@sdsrs/code-graph-linux-x64": "0.158.0",
39
+ "@sdsrs/code-graph-linux-arm64": "0.158.0",
40
+ "@sdsrs/code-graph-darwin-x64": "0.158.0",
41
+ "@sdsrs/code-graph-darwin-arm64": "0.158.0",
42
+ "@sdsrs/code-graph-win32-x64": "0.158.0"
43
43
  }
44
44
  }