ccsniff 1.1.7 → 1.1.9

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 +10 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccsniff",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
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
@@ -259,6 +259,10 @@ if (opts['bash-discipline']) {
259
259
  // and Monitor docs). Same for `while !curl ...; do sleep N; done`. These are
260
260
  // NOT sleep-poll violations even though they contain `sleep N`.
261
261
  const ENDORSED_POLL = /^\s*(until|while)\s+/;
262
+ // gm-skill SKILL.md prescribes the boot probe `cat .gm/exec-spool/.status.json; date +%s%3N`
263
+ // to compare watcher heartbeat against current epoch. The cat is canonical, not a deviation.
264
+ // Same for reading .watcher.log diagnostics directly.
265
+ const CANONICAL_BOOT_PROBE = /\.gm\/exec-spool\/\.(status\.json|watcher\.log|bootstrap-(status|error)\.json|last-session-start\.json)/;
262
266
  const violations = [];
263
267
  for (const ev of all) {
264
268
  if (!filter(ev)) continue;
@@ -269,6 +273,8 @@ if (opts['bash-discipline']) {
269
273
  if (SPOOL_WRITE.test(cmd) && /^\s*echo\b/.test(cmd)) continue;
270
274
  // `until ...; do sleep N; done` is the harness-endorsed poll pattern.
271
275
  if (ENDORSED_POLL.test(cmd)) continue;
276
+ // Canonical gm-skill boot/diagnostic probes (cat .status.json; date +%s%3N etc.) are prescribed by SKILL.md.
277
+ if (CANONICAL_BOOT_PROBE.test(cmd)) continue;
272
278
  const kind = SLEEP_POLL.test(cmd) ? 'sleep-poll' : (BAD_LEADING.test(cmd) ? 'bad-leading-cmd' : null);
273
279
  if (!kind) continue;
274
280
  violations.push({ ts: ev.timestamp, sid: ev.conversation.id, project: path.basename(ev.conversation.cwd || ''), kind, cmd: cmd.slice(0, 200) });
@@ -316,6 +322,7 @@ if (opts['git-discipline']) {
316
322
  const includeSubagents = opts['include-subagents'];
317
323
  const PUSH = /\bgit\s+push\b/;
318
324
  const PORCELAIN_CLEAN = /\bgit\s+status\s+(--porcelain|-s)\b/;
325
+ const stripQuoted = (s) => s.replace(/"(?:\\.|[^"\\])*"/g, '""').replace(/'(?:\\.|[^'\\])*'/g, "''");
319
326
  const bySid = new Map();
320
327
  for (const ev of all) {
321
328
  if (!filter(ev)) continue;
@@ -331,9 +338,10 @@ if (opts['git-discipline']) {
331
338
  for (let i = 0; i < evs.length; i++) {
332
339
  const ev = evs[i];
333
340
  const cmd = ev.block?.input?.command || '';
334
- if (!PUSH.test(cmd)) continue;
341
+ const cmdStripped = stripQuoted(cmd);
342
+ if (!PUSH.test(cmdStripped)) continue;
335
343
  const lookback = evs.slice(Math.max(0, i - 20), i);
336
- const witnessed = lookback.some(e => PORCELAIN_CLEAN.test(e.block?.input?.command || ''));
344
+ const witnessed = lookback.some(e => PORCELAIN_CLEAN.test(stripQuoted(e.block?.input?.command || '')));
337
345
  if (witnessed) continue;
338
346
  violations.push({ ts: ev.timestamp, sid, project: path.basename(ev.conversation.cwd || ''), kind: 'push-no-porcelain-witness', cmd: cmd.slice(0, 200) });
339
347
  }