rulereceipt 0.1.51 → 0.1.52

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.
@@ -567,6 +567,13 @@ export function approvalGateActions(rule) {
567
567
  function isApprovalGateRule(rule) {
568
568
  return approvalGateActions(rule).length > 0;
569
569
  }
570
+ /**
571
+ * A subordinating condition that scopes when a prohibition applies. A literal
572
+ * matcher cannot read it, so a forbid carrying one is routed to judgment
573
+ * rather than flagged on every occurrence. Kept to clear scoping words so a
574
+ * flat prohibition that merely contains "if" in passing is not swept in.
575
+ */
576
+ const CONDITIONAL_SCOPE = /\b(?:when(?:ever)?|unless|except\s+when|only\s+(?:if|when)|as\s+long\s+as|provided\s+that|in\s+cases?\s+where)\b|\bif\s+(?:you|the|it|they|we|a|an|on|in|running|building|committing|pushing|deploying)\b/i;
570
577
  export function classifyRule(rule) {
571
578
  // Checked first: if this isn't a rule at all, no check of any kind
572
579
  // should run against it — not a keyword match, not an LLM call.
@@ -634,6 +641,19 @@ export function classifyRule(rule) {
634
641
  if (BRANCH_WORD.test(text) && branchName !== undefined) {
635
642
  return { kind: "gitBranchPolicy", rule, branchName, polarity, polarityInferred };
636
643
  }
644
+ // A forbid scoped by a condition the literal checkers cannot evaluate.
645
+ //
646
+ // Raised by etoryoki on anthropics/claude-code#2544: "Never run `terraform
647
+ // apply` when you're on main" read as "never run it" flags every correct run
648
+ // elsewhere. A deterministic literal match sees only the command, not the
649
+ // "when/unless/if" that scopes it, so it accuses every occurrence. Routing
650
+ // these to judgment lets the condition actually be weighed — the same choice
651
+ // etoryoki made ("treat conditional or hedged sentences as not checkable
652
+ // instead of guessing"). Placed AFTER the branch check on purpose:
653
+ // gitBranchPolicy evaluates its own branch condition and must keep those.
654
+ if (polarity === "forbid" && CONDITIONAL_SCOPE.test(text)) {
655
+ return { kind: "judgment", rule };
656
+ }
637
657
  // Route on ANY code-shaped literal, but check ONLY the code-shaped ones.
638
658
  // This used to pass every literal through once one of them looked like
639
659
  // code, so a rule mentioning `foo(` and `name` searched written files for
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rulereceipt",
3
- "version": "0.1.51",
3
+ "version": "0.1.52",
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",