rulereceipt 0.1.88 → 0.1.89

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/dist/guard.js +27 -2
  2. package/package.json +1 -1
package/dist/guard.js CHANGED
@@ -8,6 +8,7 @@ import { loadOverrides, ruleFingerprint, ratifiedForbids } from "./overrides.js"
8
8
  import { commandRunsLiteral } from "./checks/proposedAction.js";
9
9
  import { approvalOccurrences, allowListed, approvalCommandShort, approvalScopedBranch } from "./checks/approvalGate.js";
10
10
  import { readTranscriptFromFile } from "./parsers/transcriptParser.js";
11
+ import { touchedPaths, ruleWasLoaded } from "./checks/pathScope.js";
11
12
  import { execFileSync } from "node:child_process";
12
13
  /**
13
14
  * The current git branch in `cwd`, or undefined if it can't be determined —
@@ -92,8 +93,28 @@ function forbidRules(cwd) {
92
93
  * is why the guard cannot drift from the report: same checkers, same
93
94
  * verdicts, different tense.
94
95
  */
96
+ /**
97
+ * A path-scoped rule (a subfolder CLAUDE.md, or `paths:`/`globs:` frontmatter)
98
+ * only governs its own subtree. The guard must honour that: enforcing it on an
99
+ * action OUTSIDE the subtree is a false-block — the live-blocking equivalent of
100
+ * a false accusation. Found 2026-10-03: subfolder-rule discovery (0.1.88) made
101
+ * loadRules return those scoped rules, and the guard applied every forbid
102
+ * globally, so a demo rule scoped to one subtree blocked an edit elsewhere.
103
+ *
104
+ * Direction of error, deliberately toward NOT blocking: an unscoped rule always
105
+ * applies; a scoped rule applies only when the proposed action's touched path is
106
+ * inside the scope. A Bash command exposes no file path to match, so a scoped
107
+ * rule never fires on one here — under-enforcing a subtree is safe, a false-block
108
+ * is not. (`check` already path-scopes via this same machinery.)
109
+ */
110
+ function inScope(rule, touched) {
111
+ if (!rule.paths || rule.paths.length === 0)
112
+ return true;
113
+ return ruleWasLoaded(rule.paths, touched);
114
+ }
95
115
  function structuredBlocks(cwd, event) {
96
- const cls = forbidRules(cwd);
116
+ const touched = touchedPaths([event]);
117
+ const cls = forbidRules(cwd).filter((c) => inScope(c.rule, touched));
97
118
  const of = (k) => cls.filter((c) => c.kind === k);
98
119
  const results = [
99
120
  ...runCodeContentChecks(of("codeContent"), [event]),
@@ -218,7 +239,11 @@ function reason(blocks) {
218
239
  * decide — by the current permission mode — whether to ask or to deny.
219
240
  */
220
241
  function unapprovedGate(cwd, command, events) {
221
- const gates = classifyRules(loadRules(cwd)).filter((c) => c.kind === "approvalGate");
242
+ // A Bash command exposes no file path, so a path-scoped gate (a subfolder rule)
243
+ // can't be confirmed in-scope and is not enforced here — same no-false-block
244
+ // reasoning as structuredBlocks' inScope.
245
+ const gates = classifyRules(loadRules(cwd)).filter((c) => c.kind === "approvalGate")
246
+ .filter((c) => inScope(c.rule, touchedPaths([{ role: "assistant", kind: "tool_use", toolName: "Bash", input: { command }, timestamp: "" }])));
222
247
  for (const { rule, actions } of gates) {
223
248
  const proposed = { role: "assistant", kind: "tool_use", toolName: "Bash", input: { command }, timestamp: "", permissionMode: "dontAsk" };
224
249
  const occ = approvalOccurrences([...events, proposed], actions, { scopedBranch: approvalScopedBranch(rule), currentBranch: gitCurrentBranch(cwd) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rulereceipt",
3
- "version": "0.1.88",
3
+ "version": "0.1.89",
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",