ccsniff 1.1.15 → 1.1.17

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +32 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccsniff",
3
- "version": "1.1.15",
3
+ "version": "1.1.17",
4
4
  "description": "Watch Claude Code JSONL output files and emit structured events as a Node.js EventEmitter",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/cli.js CHANGED
@@ -380,6 +380,26 @@ if (opts['git-discipline']) {
380
380
  if (opts['search-discipline']) {
381
381
  const includeSubagents = opts['include-subagents'];
382
382
  const BASH_SEARCH = /(^|[|&;]|\s)(rg|grep|find|ag|ack|fd|fgrep|egrep)\s/;
383
+ // A search-tool token inside a quoted string (echo/printf/node -e payloads) is text, not a shell
384
+ // invocation; blank quoted bodies before matching, like git-discipline strips commit-message bodies.
385
+ const stripQuoted = (s) => s.replace(/"(?:\\.|[^"\\])*"/g, '""').replace(/'(?:\\.|[^'\\])*'/g, "''");
386
+ // codesearch indexes ONLY the conversation's own cwd (the gm repo). A search whose target is a
387
+ // sibling repo outside cwd has NO codesearch index to route through, so the agent is forced to
388
+ // native search and flagging it is a false positive. Exempt a line that targets an absolute path
389
+ // or cd's into a directory that is not under the conversation cwd.
390
+ const normPath = (p) => String(p || '').replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase();
391
+ const targetsOutsideCwd = (line, cwd) => {
392
+ const cwdN = normPath(cwd);
393
+ if (!cwdN) return false;
394
+ const stripped = stripQuoted(line);
395
+ // explicit `cd <dir>` to a path outside cwd
396
+ const cdM = stripped.match(/(?:^|[|&;]\s*)cd\s+([^\s|&;]+)/i);
397
+ if (cdM) { const d = normPath(cdM[1]); if (d.startsWith('/') || /^[a-z]:/.test(d)) { if (!d.startsWith(cwdN)) return true; } }
398
+ // absolute path argument to the search tool that is outside cwd
399
+ const absArgs = stripped.match(/(?:^|\s)((?:[a-z]:)?\/[^\s|&;"']+)/gi) || [];
400
+ for (const a of absArgs) { const d = normPath(a.trim()); if ((d.startsWith('/') || /^[a-z]:/.test(d)) && !d.startsWith(cwdN)) return true; }
401
+ return false;
402
+ };
383
403
  const violations = [];
384
404
  for (const ev of all) {
385
405
  if (!filter(ev)) continue;
@@ -390,8 +410,14 @@ if (opts['search-discipline']) {
390
410
  const ts = ev.timestamp, sid = ev.conversation?.id || '';
391
411
  let kind = null, detail = '';
392
412
  if (name === 'Grep' || name === 'Glob') {
393
- kind = `native-search-${name.toLowerCase()}`;
394
- detail = (ev.block?.input?.pattern || ev.block?.input?.query || '').slice(0, 120);
413
+ // A Grep/Glob whose path points outside the cwd targets a sibling repo with no codesearch
414
+ // index exempt it, same as a cross-repo bash search.
415
+ const gp = ev.block?.input?.path;
416
+ if (gp && targetsOutsideCwd(gp, ev.conversation?.cwd)) { /* cross-repo, exempt */ }
417
+ else {
418
+ kind = `native-search-${name.toLowerCase()}`;
419
+ detail = (ev.block?.input?.pattern || ev.block?.input?.query || '').slice(0, 120);
420
+ }
395
421
  } else if (name === 'Task' || name === 'Agent') {
396
422
  const sub = (ev.block?.input?.subagent_type || ev.block?.input?.description || '').toLowerCase();
397
423
  if (/explore|search|general-purpose/.test(sub)) {
@@ -404,9 +430,11 @@ if (opts['search-discipline']) {
404
430
  // not searching the codebase tree — codesearch has no equivalent for that and it is not the
405
431
  // bypass the rule targets. Flag only a search tool that STARTS a pipeline segment (reads the
406
432
  // tree directly), never one immediately downstream of a pipe.
407
- const isTreeSearchLine = (line) => BASH_SEARCH.test(line.split('|')[0]);
433
+ const isTreeSearchLine = (line) => BASH_SEARCH.test(stripQuoted(line).split('|')[0]);
408
434
  const hitLine = cmd.split('\n').find(isTreeSearchLine);
409
- if (hitLine) {
435
+ // Exempt a tree-search line that targets a sibling repo outside cwd (no codesearch index exists
436
+ // for it). Each command may `cd` first, so evaluate the cd context on the same line.
437
+ if (hitLine && !targetsOutsideCwd(hitLine, ev.conversation?.cwd)) {
410
438
  kind = 'native-search-bash';
411
439
  detail = (hitLine.split('|')[0]).trim().slice(0, 120);
412
440
  }