ccsniff 1.1.1 → 1.1.3
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 +63 -1
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -29,7 +29,7 @@ const FLAGS = {
|
|
|
29
29
|
string: ['since', 'until', 'before', 'after', 'grep', 'igrep', 'cwd', 'project', 'role', 'type', 'tool', 'session', 'sid', 'parent', 'rollup', 'format', 'sort', 'unsloth', 'unsloth-format'],
|
|
30
30
|
multi: ['grep', 'igrep', 'role', 'type', 'tool', 'session', 'sid', 'project', 'cwd'],
|
|
31
31
|
number: ['limit', 'head', 'tail-n', 'ctx', 'truncate'],
|
|
32
|
-
bool: ['json', 'ndjson', 'tail', 'f', 'full', 'reverse', 'invert', 'no-subagents', 'only-subagents', 'no-meta', 'only-meta', 'list-sessions', 'list-projects', 'list-tools', 'stats', 'count', 'help', 'h'],
|
|
32
|
+
bool: ['json', 'ndjson', 'tail', 'f', 'full', 'reverse', 'invert', 'no-subagents', 'only-subagents', 'no-meta', 'only-meta', 'list-sessions', 'list-projects', 'list-tools', 'bash-discipline', 'include-subagents', 'stats', 'count', 'help', 'h'],
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
function parseArgs(argv) {
|
|
@@ -62,6 +62,9 @@ USAGE
|
|
|
62
62
|
ccsniff --list-sessions [filters]
|
|
63
63
|
ccsniff --list-projects
|
|
64
64
|
ccsniff --list-tools
|
|
65
|
+
ccsniff --bash-discipline [--stats] Bash calls that should have used Read/Glob/Grep
|
|
66
|
+
(excludes subagents by default — --include-subagents to opt in;
|
|
67
|
+
excludes 'echo > .gm/exec-spool/in/...' as canonical spool-write)
|
|
65
68
|
ccsniff --stats [filters]
|
|
66
69
|
|
|
67
70
|
TIME (any ISO date, epoch ms, or relative Ns/Nm/Nh/Nd/Nw)
|
|
@@ -237,6 +240,65 @@ if (opts['list-projects']) {
|
|
|
237
240
|
process.exit(0);
|
|
238
241
|
}
|
|
239
242
|
|
|
243
|
+
// ---------- bash-discipline (flag Bash calls that should have been Read/Glob/Grep/dispatch)
|
|
244
|
+
if (opts['bash-discipline']) {
|
|
245
|
+
// discipline is about MY tool routing, not subagents — they have separate prompts/contexts.
|
|
246
|
+
// Default: exclude subagents. --include-subagents opts them back in.
|
|
247
|
+
const includeSubagents = opts['include-subagents'];
|
|
248
|
+
const BAD_LEADING = /^\s*(cat|head|tail|ls|grep|find|sed|awk)\b/;
|
|
249
|
+
const SLEEP_POLL = /\bsleep\s+\d+\s*;.*(cat|ls|grep|find|head|tail)/;
|
|
250
|
+
const SPOOL_WRITE = /\.gm\/exec-spool\/in\//;
|
|
251
|
+
const violations = [];
|
|
252
|
+
for (const ev of all) {
|
|
253
|
+
if (!filter(ev)) continue;
|
|
254
|
+
if (ev.block?.type !== 'tool_use' || ev.block?.name !== 'Bash') continue;
|
|
255
|
+
if (!includeSubagents && ev.conversation?.isSubagent) continue;
|
|
256
|
+
const cmd = ev.block?.input?.command || '';
|
|
257
|
+
// `echo > .gm/exec-spool/in/<verb>/N.txt` is the canonical spool-write pattern, not a deviation.
|
|
258
|
+
if (SPOOL_WRITE.test(cmd) && /^\s*echo\b/.test(cmd)) continue;
|
|
259
|
+
const kind = SLEEP_POLL.test(cmd) ? 'sleep-poll' : (BAD_LEADING.test(cmd) ? 'bad-leading-cmd' : null);
|
|
260
|
+
if (!kind) continue;
|
|
261
|
+
violations.push({ ts: ev.timestamp, sid: ev.conversation.id, project: path.basename(ev.conversation.cwd || ''), kind, cmd: cmd.slice(0, 200) });
|
|
262
|
+
}
|
|
263
|
+
const byKind = new Map();
|
|
264
|
+
for (const v of violations) byKind.set(v.kind, (byKind.get(v.kind) || 0) + 1);
|
|
265
|
+
if (opts.stats || opts.count) {
|
|
266
|
+
if (opts.count) { process.stdout.write(`${violations.length}\n`); process.exit(0); }
|
|
267
|
+
const subagentNote = includeSubagents ? '' : ' (subagents excluded — pass --include-subagents to include)';
|
|
268
|
+
process.stdout.write(`# ${violations.length} bash-discipline violations${subagentNote}\n`);
|
|
269
|
+
for (const [k, c] of [...byKind.entries()].sort((a, b) => b[1] - a[1])) process.stdout.write(` ${String(c).padStart(6)} ${k}\n`);
|
|
270
|
+
const byProj = new Map();
|
|
271
|
+
for (const v of violations) byProj.set(v.project, (byProj.get(v.project) || 0) + 1);
|
|
272
|
+
process.stdout.write(`# by project\n`);
|
|
273
|
+
for (const [p, c] of [...byProj.entries()].sort((a, b) => b[1] - a[1])) process.stdout.write(` ${String(c).padStart(6)} ${p}\n`);
|
|
274
|
+
const byDay = new Map();
|
|
275
|
+
for (const v of violations) {
|
|
276
|
+
const day = new Date(v.ts).toISOString().slice(0, 10);
|
|
277
|
+
byDay.set(day, (byDay.get(day) || 0) + 1);
|
|
278
|
+
}
|
|
279
|
+
if (byDay.size > 1) {
|
|
280
|
+
process.stdout.write(`# by day\n`);
|
|
281
|
+
for (const [d, c] of [...byDay.entries()].sort((a, b) => a[0].localeCompare(b[0]))) process.stdout.write(` ${String(c).padStart(6)} ${d}\n`);
|
|
282
|
+
}
|
|
283
|
+
const byHour = new Map();
|
|
284
|
+
for (const v of violations) {
|
|
285
|
+
const hour = new Date(v.ts).toISOString().slice(0, 13);
|
|
286
|
+
byHour.set(hour, (byHour.get(hour) || 0) + 1);
|
|
287
|
+
}
|
|
288
|
+
if (byHour.size > 1) {
|
|
289
|
+
process.stdout.write(`# by hour (last 12)\n`);
|
|
290
|
+
const sorted = [...byHour.entries()].sort((a, b) => a[0].localeCompare(b[0])).slice(-12);
|
|
291
|
+
for (const [h, c] of sorted) process.stdout.write(` ${String(c).padStart(6)} ${h}:00\n`);
|
|
292
|
+
}
|
|
293
|
+
process.exit(0);
|
|
294
|
+
}
|
|
295
|
+
for (const v of violations) {
|
|
296
|
+
process.stdout.write(`${new Date(v.ts).toISOString().slice(0, 19)} ${v.sid.slice(0, 8)} ${v.kind.padEnd(15)} [${v.project}] ${v.cmd}\n`);
|
|
297
|
+
}
|
|
298
|
+
process.stderr.write(`# ${violations.length} violations (${[...byKind.entries()].map(([k, c]) => `${k}:${c}`).join(' ')})\n`);
|
|
299
|
+
process.exit(0);
|
|
300
|
+
}
|
|
301
|
+
|
|
240
302
|
// ---------- list-tools
|
|
241
303
|
if (opts['list-tools']) {
|
|
242
304
|
const tools = new Map();
|