ccsniff 1.1.20 → 1.1.21
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.
- package/package.json +1 -1
- package/src/cli.js +10 -23
- package/src/discipline-helpers.js +29 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { JsonlReplayer, rollup, vault } from './index.js';
|
|
3
3
|
import { toUnslothMessages, toShareGPT } from './unsloth.js';
|
|
4
4
|
import { parseTime, compileRegexes, buildFilter } from './filters.js';
|
|
5
|
+
import { stripQuoted, targetsOutsideCwd, targetsSingleFile } from './discipline-helpers.js';
|
|
5
6
|
import fs from 'fs';
|
|
6
7
|
import path from 'path';
|
|
7
8
|
import os from 'os';
|
|
@@ -383,26 +384,10 @@ if (opts['git-discipline']) {
|
|
|
383
384
|
if (opts['search-discipline']) {
|
|
384
385
|
const includeSubagents = opts['include-subagents'];
|
|
385
386
|
const BASH_SEARCH = /(^|[|&;]|\s)(rg|grep|find|ag|ack|fd|fgrep|egrep)\s/;
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
//
|
|
390
|
-
// sibling repo outside cwd has NO codesearch index to route through, so the agent is forced to
|
|
391
|
-
// native search and flagging it is a false positive. Exempt a line that targets an absolute path
|
|
392
|
-
// or cd's into a directory that is not under the conversation cwd.
|
|
393
|
-
const normPath = (p) => String(p || '').replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase();
|
|
394
|
-
const targetsOutsideCwd = (line, cwd) => {
|
|
395
|
-
const cwdN = normPath(cwd);
|
|
396
|
-
if (!cwdN) return false;
|
|
397
|
-
const stripped = stripQuoted(line);
|
|
398
|
-
// explicit `cd <dir>` to a path outside cwd
|
|
399
|
-
const cdM = stripped.match(/(?:^|[|&;]\s*)cd\s+([^\s|&;]+)/i);
|
|
400
|
-
if (cdM) { const d = normPath(cdM[1]); if (d.startsWith('/') || /^[a-z]:/.test(d)) { if (!d.startsWith(cwdN)) return true; } }
|
|
401
|
-
// absolute path argument to the search tool that is outside cwd
|
|
402
|
-
const absArgs = stripped.match(/(?:^|\s)((?:[a-z]:)?\/[^\s|&;"']+)/gi) || [];
|
|
403
|
-
for (const a of absArgs) { const d = normPath(a.trim()); if ((d.startsWith('/') || /^[a-z]:/.test(d)) && !d.startsWith(cwdN)) return true; }
|
|
404
|
-
return false;
|
|
405
|
-
};
|
|
387
|
+
// stripQuoted, targetsOutsideCwd (cwd-override + cross-repo exemption), and targetsSingleFile
|
|
388
|
+
// (single-file read-filter exemption) live in discipline-helpers.js so they are unit-testable
|
|
389
|
+
// without running the CLI. codesearch indexes only the conversation cwd, so a cross-repo or
|
|
390
|
+
// single-file grep has no index to route through and flagging it is a false positive.
|
|
406
391
|
const violations = [];
|
|
407
392
|
for (const ev of all) {
|
|
408
393
|
if (!filter(ev)) continue;
|
|
@@ -433,11 +418,13 @@ if (opts['search-discipline']) {
|
|
|
433
418
|
// not searching the codebase tree — codesearch has no equivalent for that and it is not the
|
|
434
419
|
// bypass the rule targets. Flag only a search tool that STARTS a pipeline segment (reads the
|
|
435
420
|
// tree directly), never one immediately downstream of a pipe.
|
|
436
|
-
|
|
421
|
+
// A line whose first non-space token is `#` is a shell comment, not a command — never a search.
|
|
422
|
+
const isTreeSearchLine = (line) => !/^\s*#/.test(line) && BASH_SEARCH.test(stripQuoted(line).split('|')[0]);
|
|
437
423
|
const hitLine = cmd.split('\n').find(isTreeSearchLine);
|
|
438
424
|
// Exempt a tree-search line that targets a sibling repo outside cwd (no codesearch index exists
|
|
439
|
-
// for it)
|
|
440
|
-
|
|
425
|
+
// for it), or that greps ONE explicit file (a read-filter codesearch cannot serve). Each
|
|
426
|
+
// command may cd/git -C first, so evaluate the context on the same line.
|
|
427
|
+
if (hitLine && !targetsOutsideCwd(hitLine, ev.conversation?.cwd) && !targetsSingleFile(hitLine)) {
|
|
441
428
|
kind = 'native-search-bash';
|
|
442
429
|
detail = (hitLine.split('|')[0]).trim().slice(0, 120);
|
|
443
430
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const normPath = (p) => String(p || '').replace(/\\/g, '/').replace(/^\/([a-z])\//i, '$1:/').replace(/\/+$/, '').toLowerCase();
|
|
2
|
+
|
|
3
|
+
export const stripQuoted = (s) => s.replace(/"(?:\\.|[^"\\])*"/g, '""').replace(/'(?:\\.|[^'\\])*'/g, "''");
|
|
4
|
+
|
|
5
|
+
const isAbs = (d) => d.startsWith('/') || /^[a-z]:/.test(d);
|
|
6
|
+
|
|
7
|
+
export function targetsOutsideCwd(line, cwd) {
|
|
8
|
+
const cwdN = normPath(cwd);
|
|
9
|
+
if (!cwdN) return false;
|
|
10
|
+
const stripped = stripQuoted(line);
|
|
11
|
+
const ctxM = stripped.match(/(?:^|[|&;]\s*)(?:cd|pushd)\s+([^\s|&;]+)/i) || stripped.match(/\bgit\s+-C\s+([^\s|&;]+)/i);
|
|
12
|
+
if (ctxM) { const d = normPath(ctxM[1]); if (isAbs(d) && !d.startsWith(cwdN)) return true; }
|
|
13
|
+
const absArgs = stripped.match(/(?:^|\s)((?:[a-z]:)?\/[^\s|&;"']+)/gi) || [];
|
|
14
|
+
for (const a of absArgs) { const d = normPath(a.trim()); if (isAbs(d) && !d.startsWith(cwdN)) return true; }
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function targetsSingleFile(line) {
|
|
19
|
+
let s = stripQuoted(line).split('|')[0];
|
|
20
|
+
s = s.replace(/\d*>>?\s*&?\s*\S+/g, ' ').replace(/<\s*\S+/g, ' ');
|
|
21
|
+
if (!/\b(grep|egrep|fgrep|rg|ag|ack)\b/.test(s)) return false;
|
|
22
|
+
if (/\s-[a-z]*[rR]\b|--recursive/.test(s)) return false;
|
|
23
|
+
const toks = s.trim().split(/\s+/);
|
|
24
|
+
const last = toks[toks.length - 1];
|
|
25
|
+
if (!last || last.startsWith('-')) return false;
|
|
26
|
+
if (/[*?{}\[\]]/.test(last)) return false;
|
|
27
|
+
if (last.endsWith('/')) return false;
|
|
28
|
+
return /\.[a-z0-9]{1,6}$/i.test(last) && !last.includes('|');
|
|
29
|
+
}
|