rulereceipt 0.1.86 → 0.1.87

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/dist/guard.d.ts CHANGED
@@ -74,5 +74,16 @@ export interface GuardDecision {
74
74
  export declare function guardDecision(cwd: string, toolName: string, toolInput: {
75
75
  command?: unknown;
76
76
  } & Record<string, unknown>, events?: TranscriptEvent[], permissionMode?: string): GuardDecision;
77
+ /** One line for the guard receipt log — every block (or would-block) is recorded. */
78
+ export declare function guardReceiptLine(decision: GuardDecision, command: string, shadow: boolean): {
79
+ ts: string;
80
+ mode: "shadow" | "enforce";
81
+ action: "deny" | "would-deny" | "ask" | "would-ask";
82
+ command: string;
83
+ rules: {
84
+ id: string;
85
+ title: string;
86
+ }[];
87
+ };
77
88
  export declare function runGuard(): Promise<void>;
78
89
  export {};
package/dist/guard.js CHANGED
@@ -25,9 +25,9 @@ function gitCurrentBranch(cwd) {
25
25
  return undefined;
26
26
  }
27
27
  }
28
- import { readFileSync } from "node:fs";
28
+ import { readFileSync, appendFileSync, mkdirSync } from "node:fs";
29
29
  import { homedir } from "node:os";
30
- import { join } from "node:path";
30
+ import { join, dirname } from "node:path";
31
31
  /**
32
32
  * Modes where Claude Code shows NO permission prompt, so a hook's "ask" is
33
33
  * ignored and the call just runs (Claude Code #89561; "ask" also drops bypass
@@ -301,6 +301,38 @@ export function guardDecision(cwd, toolName, toolInput, events = [], permissionM
301
301
  }
302
302
  return allow;
303
303
  }
304
+ /**
305
+ * Shadow mode (live-blocking rollout, vs Failproof): set RULERECEIPT_GUARD_SHADOW=1
306
+ * and the guard never blocks — it LOGS what it WOULD have blocked and allows the
307
+ * call. The honest way to turn on enforcement: run shadow first, read the log,
308
+ * then drop the flag. Enforcement is the default (no env needed).
309
+ */
310
+ function guardShadowEnabled() {
311
+ const v = process.env.RULERECEIPT_GUARD_SHADOW;
312
+ return v === "1" || v === "true" || v === "yes";
313
+ }
314
+ /** One line for the guard receipt log — every block (or would-block) is recorded. */
315
+ export function guardReceiptLine(decision, command, shadow) {
316
+ const base = decision.deny ? "deny" : "ask";
317
+ return {
318
+ ts: new Date().toISOString(),
319
+ mode: shadow ? "shadow" : "enforce",
320
+ action: (shadow ? `would-${base}` : base),
321
+ command: command.replace(/\s+/g, " ").trim().slice(0, 120),
322
+ rules: decision.blocks.map((b) => ({ id: b.rule.id, title: b.rule.title })),
323
+ };
324
+ }
325
+ /** Append a receipt line to .rulereceipt/guard-log.jsonl. Fail-open: never breaks the guard. */
326
+ function writeGuardReceipt(cwd, line) {
327
+ try {
328
+ const p = join(cwd, ".rulereceipt", "guard-log.jsonl");
329
+ mkdirSync(dirname(p), { recursive: true });
330
+ appendFileSync(p, `${JSON.stringify(line)}\n`);
331
+ }
332
+ catch {
333
+ /* logging must never stop (or crash) the guard */
334
+ }
335
+ }
304
336
  export async function runGuard() {
305
337
  const allow = () => {
306
338
  process.stdout.write(JSON.stringify({}));
@@ -321,6 +353,15 @@ export async function runGuard() {
321
353
  }
322
354
  }
323
355
  const decision = guardDecision(cwd, tool, toolInput, events, input.permission_mode);
356
+ const shadow = guardShadowEnabled();
357
+ const commandStr = typeof toolInput.command === "string" ? toolInput.command : tool;
358
+ // Every block (or would-block) writes a receipt line — the audit trail of
359
+ // what the guard stopped, in both shadow and enforce mode.
360
+ if (decision.deny || decision.ask)
361
+ writeGuardReceipt(cwd, guardReceiptLine(decision, commandStr, shadow));
362
+ // Shadow mode: log above, then ALLOW — never block, never ask.
363
+ if (shadow)
364
+ return allow();
324
365
  if (!decision.deny && decision.ask) {
325
366
  // "ask" is not a refusal: no exit 2. Claude Code shows its permission
326
367
  // prompt with this reason; the user's click decides.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rulereceipt",
3
- "version": "0.1.86",
3
+ "version": "0.1.87",
4
4
  "description": "Checks whether your AI coding agent followed your rules, with evidence. Works with Claude Code (Codex in testing); reads CLAUDE.md, AGENTS.md, Cursor, Copilot and Windsurf rules.",
5
5
  "repository": {
6
6
  "type": "git",