rulereceipt 0.1.39 → 0.1.40

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/README.md CHANGED
@@ -184,7 +184,7 @@ survives that by saying UNCLEAR. A gate cannot — so you mark it:
184
184
  rulereceipt rules --forbid <handle> --literal "git push --force"
185
185
  ```
186
186
 
187
- Handles come from `rulereceipt check --show-skipped`. The mark is stored
187
+ Handles come from `rulereceipt rules --handles`. The mark is stored
188
188
  against the rule's content hash, and the guard blocks on that literal and no
189
189
  other. Three things it deliberately will not do:
190
190
 
package/dist/cli.js CHANGED
@@ -535,6 +535,33 @@ async function runRules(opts) {
535
535
  console.log(`your judgment onto words you never read.`);
536
536
  return;
537
537
  }
538
+ /**
539
+ * Every rule with its handle.
540
+ *
541
+ * --forbid needs a handle, and before this the only place handles were
542
+ * printed was `check --show-skipped`, which lists the items the classifier
543
+ * DISCARDED. A rule that is actually being checked had no handle anywhere,
544
+ * so the marking feature shipped in 0.1.39 could not be reached for any
545
+ * rule a user would want to mark. Found by trying to use it.
546
+ */
547
+ if (opts.handles) {
548
+ if (rules.length === 0) {
549
+ console.log("No CLAUDE.md or AGENTS.md rules found in this project.");
550
+ return;
551
+ }
552
+ console.log(`${rules.length} rule${rules.length === 1 ? "" : "s"} in this project:\n`);
553
+ for (const r of rules) {
554
+ const h = ruleFingerprint(r);
555
+ const marked = overrides.get(h)?.forbids;
556
+ console.log(` ${h} ${r.title.replace(/\s+/g, " ").trim().slice(0, 72)}`);
557
+ if (marked?.length)
558
+ console.log(` blocks on: ${marked.map((f) => `\`${f}\``).join(", ")}`);
559
+ }
560
+ console.log(`\nMark which clause of a rule is the prohibition:`);
561
+ console.log(` rulereceipt rules --forbid <handle> --literal "<the banned command>"`);
562
+ console.log(`Only a marked clause can ever refuse a command, and only via \`rulereceipt guard\`.`);
563
+ return;
564
+ }
538
565
  if (opts.clear) {
539
566
  console.log(clearOverride(cwd, opts.clear) ? `Removed the correction for ${opts.clear}.` : `No correction stored for ${opts.clear}.`);
540
567
  return;
@@ -651,6 +678,7 @@ program
651
678
  .description("correct what the classifier treats as a rule. Handles come from `check --show-skipped`.")
652
679
  .option("--include <handle>", "treat this item as a real rule and check it from now on")
653
680
  .option("--exclude <handle>", "treat this item as documentation and stop reporting it")
681
+ .option("--handles", "list every rule with its handle, for use with --forbid")
654
682
  .option("--forbid <handle>", "mark which clause of this rule is the prohibition, so the guard may block on it")
655
683
  .option("--literal <text>", "the exact banned command, used with --forbid; must appear in the rule")
656
684
  .option("--clear <handle>", "remove a stored correction")
package/dist/guard.js CHANGED
@@ -80,13 +80,22 @@ function structuredBlocks(cwd, event) {
80
80
  function ratifiedLiteralBlocks(cwd, command) {
81
81
  const overrides = loadOverrides(cwd);
82
82
  const blocks = [];
83
- for (const c of forbidRules(cwd)) {
84
- if (c.kind !== "deterministic")
83
+ // Read from the RULES, not from the classification. A human mark
84
+ // supersedes the classifier, including its refusal to classify — and that
85
+ // refusal is the common case here. "Never use `git push --force`; prefer
86
+ // `git push --force-with-lease`" goes to judgment via hasMixedPolarity,
87
+ // for a correct reason: literal matching cannot tell which half owns which
88
+ // token. A rule naming both the ban and the alternative is the canonical
89
+ // reason to have someone say which is which, so gating the mark behind the
90
+ // classifier made the feature unavailable in exactly the case that
91
+ // motivated it. Shipped that way in 0.1.39 and caught by running it.
92
+ for (const rule of loadRules(cwd)) {
93
+ if (overrides.get(ruleFingerprint(rule))?.decision === "notARule")
85
94
  continue;
86
- for (const literal of ratifiedForbids(overrides, c.rule)) {
95
+ for (const literal of ratifiedForbids(overrides, rule)) {
87
96
  if (!commandRunsLiteral(command, literal))
88
97
  continue;
89
- blocks.push({ rule: c.rule, why: `the command about to run does \`${literal}\`, which this rule forbids (marked by you, not inferred)` });
98
+ blocks.push({ rule, why: `the command about to run does \`${literal}\`, which this rule forbids (marked by you, not inferred)` });
90
99
  break;
91
100
  }
92
101
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rulereceipt",
3
- "version": "0.1.39",
3
+ "version": "0.1.40",
4
4
  "description": "Checks whether a Claude Code session actually followed your CLAUDE.md / AGENTS.md rules, with evidence.",
5
5
  "repository": {
6
6
  "type": "git",