@sdsrs/code-graph 0.155.0 → 0.156.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.155.0",
7
+ "version": "0.156.0",
8
8
  "keywords": [
9
9
  "code-graph",
10
10
  "ast",
@@ -129,6 +129,11 @@ function firstShellClause(cmd) {
129
129
  if (c === quote) quote = null;
130
130
  continue;
131
131
  }
132
+ // Outside quotes a backslash escapes the next character: `\"` opens no
133
+ // quote and `\;` / `\|` are literal. quotedSpans reads patterns by the same
134
+ // rule; a splitter that disagreed let `grep \"X\" src/ && echo "Y"` hand the
135
+ // echo's `Y` to the pattern reader (pre-ship review F1).
136
+ if (c === '\\') { i++; continue; }
132
137
  if (c === '"' || c === "'") { quote = c; continue; }
133
138
  // Control operators END the grep's argument list → truncate. NOT redirects
134
139
  // (`>` `<`): `2>&1`, `>out`, and process substitution `-f <(cat pats) src/`
@@ -227,8 +232,47 @@ function extractPatterns(cmd) {
227
232
  // Collect every quoted argument — first one is the pattern in standard grep
228
233
  // usage; subsequent ones (e.g. `-e "second"`) are also patterns or filter
229
234
  // expressions and worth screening too.
230
- const matches = [...stripped.matchAll(/"([^"]+)"|'([^']+)'/g)];
231
- return matches.map(m => m[1] !== undefined ? m[1] : m[2]);
235
+ return quotedSpans(stripped).map(s => s.body).filter(Boolean);
236
+ }
237
+
238
+ /**
239
+ * The quoted spans of a shell clause, read the way the shell reads them — the
240
+ * rules firstShellClause, splitTopLevelSegments and extractUnansweredTail also
241
+ * follow (lesson #9656 inside quotes; pre-ship review F1 outside), which a span regex (`"([^"]+)"|'([^']+)'`) did not: it closed a
242
+ * double-quoted argument at the first `\"`, so
243
+ * `grep "if:\s*'\|\"if\"\|statusMessage"` yielded `\|statusMessage`, and its
244
+ * translation `|statusMessage` matches every line (observed 2026-09-25).
245
+ * - `'…'` is literal to the next `'`.
246
+ * - `"…"`: a backslash escapes `"` `\` `$` and backtick (the backslash is
247
+ * dropped) and, before a newline, removes both characters (a line
248
+ * continuation); before anything else it is literal (`"a\|b"`).
249
+ * - Outside quotes a backslash escapes the next character, so `\"` opens
250
+ * nothing — the shell hands grep the quote character itself.
251
+ * An unterminated quote ends the scan: what follows is not a span we can read.
252
+ * @returns {{start: number, end: number, body: string}[]} `end` is exclusive,
253
+ * covering both quote characters.
254
+ */
255
+ function quotedSpans(s) {
256
+ const spans = [];
257
+ if (typeof s !== 'string') return spans;
258
+ for (let i = 0; i < s.length; i++) {
259
+ const c = s[i];
260
+ if (c === '\\') { i++; continue; }
261
+ if (c !== '"' && c !== "'") continue;
262
+ let body = '';
263
+ let j = i + 1;
264
+ for (; j < s.length && s[j] !== c; j++) {
265
+ if (c === '"' && s[j] === '\\' && j + 1 < s.length && '"\\$`\n'.includes(s[j + 1])) {
266
+ j++;
267
+ if (s[j] === '\n') continue; // backslash-newline is a line continuation: both go
268
+ }
269
+ body += s[j];
270
+ }
271
+ if (j >= s.length) break;
272
+ spans.push({ start: i, end: j + 1, body });
273
+ i = j;
274
+ }
275
+ return spans;
232
276
  }
233
277
 
234
278
  // Declaration anchors inside a pattern (`def cascade_failure|class TaskState`)
@@ -349,7 +393,16 @@ function isAgFilenameSearch(clause) {
349
393
  // argument can contain whitespace, so `ag "some_symbol -g x" src/` splits into
350
394
  // three tokens and the middle one looks exactly like a flag. Blanking leaves
351
395
  // an attached value's flag behind (`-g"x"` → `-g`), which is what we want.
352
- const scan = clause.replace(VERB_STRIP, '').replace(/"[^"]*"|'[^']*'/g, ' ');
396
+ // Spans come from quotedSpans, not a span regex: `"a\" -g x"` is ONE
397
+ // argument, and a regex that closed it at the `\"` left `-g` bare.
398
+ const unverbed = clause.replace(VERB_STRIP, '');
399
+ let scan = '';
400
+ let at = 0;
401
+ for (const sp of quotedSpans(unverbed)) {
402
+ scan += unverbed.slice(at, sp.start) + ' ';
403
+ at = sp.end;
404
+ }
405
+ scan += unverbed.slice(at);
353
406
  for (const tok of scan.trim().split(/\s+/)) {
354
407
  if (!tok || tok[0] !== '-') continue;
355
408
  if (tok.startsWith('--')) {
@@ -602,6 +655,7 @@ function extractUnansweredTail(cmd) {
602
655
  if (c === quote) quote = null;
603
656
  continue;
604
657
  }
658
+ if (c === '\\') { i++; continue; } // outside quotes: escapes the next char (see firstShellClause)
605
659
  if (c === '"' || c === "'") { quote = c; continue; }
606
660
  if (c === ';' || (c === '&' && cmd[i + 1] === '&')) {
607
661
  const tail = cmd.slice(i + (c === ';' ? 1 : 2)).trim();
@@ -695,6 +749,15 @@ function splitTopLevelSegments(cmd) {
695
749
  if (c === quote) quote = null;
696
750
  continue;
697
751
  }
752
+ // Outside quotes: escapes the next char, kept verbatim (see firstShellClause)
753
+ // — except a newline, where the pair is a line continuation and the shell
754
+ // removes both. Keeping them left `&& \⏎ grep …` a segment that starts
755
+ // with `\`, which GREP_HEAD rejects (pre-ship review round 2 F1).
756
+ if (c === '\\' && i + 1 < cmd.length) {
757
+ if (cmd[i + 1] !== '\n') cur += c + cmd[i + 1];
758
+ i++;
759
+ continue;
760
+ }
698
761
  if (c === '"' || c === "'") { quote = c; cur += c; continue; }
699
762
  // `&&` and `||` (a single `&`/`|` is NOT a split — `|` is an output-filter
700
763
  // pipe, lone `&` is background and rare in tool calls).
@@ -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.155.0 code-graph-mcp snapshot create --out snapshot.db
38
+ npx -y -p @sdsrs/code-graph@0.156.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.155.0",
3
+ "version": "0.156.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.155.0",
39
- "@sdsrs/code-graph-linux-arm64": "0.155.0",
40
- "@sdsrs/code-graph-darwin-x64": "0.155.0",
41
- "@sdsrs/code-graph-darwin-arm64": "0.155.0",
42
- "@sdsrs/code-graph-win32-x64": "0.155.0"
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"
43
43
  }
44
44
  }