rulereceipt 0.1.67 → 0.1.68

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.
@@ -83,7 +83,9 @@ const ACTION_CLAIMS = [
83
83
  // Idioms that borrow "pushed" but aren't a git push. "pushed the fix/code/
84
84
  // changes/branch to <remote>" stays a claim; effort/figurative senses do
85
85
  // not (finding #6, 2026-09-26).
86
- exclude: /\bpushed\s+(?:back|for|through|forward|ahead|past|hard|on|myself|ourselves|yourself|themselves|the\s+(?:boundar|button|envelope|limit|deadline|pace)|to\s+(?:get|finish|complete|ship|meet|hit|make|wrap|move))/i,
86
+ // "pushed nothing/none" is a statement that NO push happened — the opposite
87
+ // of a push claim (finding 2026-09-29, false-accusation corpus run).
88
+ exclude: /\bpushed\s+(?:back|for|through|forward|ahead|past|hard|on|nothing|none|myself|ourselves|yourself|themselves|the\s+(?:boundar|button|envelope|limit|deadline|pace)|to\s+(?:get|finish|complete|ship|meet|hit|make|wrap|move))/i,
87
89
  command: /\bgit\s+push\b/i,
88
90
  },
89
91
  {
@@ -312,6 +312,28 @@ function isContentToken(literal) {
312
312
  return false;
313
313
  return (literal.match(/[A-Za-z0-9]/g) ?? []).length >= 3;
314
314
  }
315
+ /**
316
+ * A file/directory/route token, as opposed to a code construct or an import
317
+ * specifier. It names a PATH, so searching file CONTENT for it (codeContent's
318
+ * job) matches every import, changelog line, package.json entry or prose that
319
+ * merely MENTIONS the path — a "found `dist/` written into a file" false
320
+ * accusation. Found in the false-accusation corpus run 2026-09-29: ~30 distinct
321
+ * FAIL texts were exactly this (`dist/`, `src/`, `README.md`, `AGENTS.md`,
322
+ * `./types`, `/status`, …). These belong to fileLifecycle (a path mutation) or,
323
+ * absent a mutation verb, to the deterministic checker, which reports UNCLEAR
324
+ * rather than fabricating a FAIL.
325
+ *
326
+ * Deliberately NARROW so real import specifiers stay checkable: `lucide-react`
327
+ * and `@scope/pkg` are NOT file tokens (no leading `./` or `/`, no trailing
328
+ * `/`, no file extension), so a "never import X" rule keeps working.
329
+ */
330
+ const FILE_EXTENSION = /\.(json|ya?ml|toml|md|mdx|env|lock|ini|cfg|conf|xml|txt|js|jsx|mjs|cjs|ts|tsx|py|rb|go|rs|sh|sql|css|scss|html)$/i;
331
+ function looksLikeFilePathToken(literal) {
332
+ return (/\/$/.test(literal) || // trailing slash: a directory ("dist/", "src/")
333
+ /^(?:\.{1,2}\/|\/)/.test(literal) || // leading ./ ../ or /: a path or route
334
+ FILE_EXTENSION.test(literal) // a bare filename with a known extension
335
+ );
336
+ }
315
337
  // Catches rules like "add tests for every change" or "every new function
316
338
  // needs a test" - no literal backtick token to pattern-match, so without
317
339
  // this they'd fall all the way through to judgment (an LLM call) even
@@ -743,7 +765,11 @@ export function classifyRule(rule) {
743
765
  // only, so this is an action not a mention. Plain words and shell commands
744
766
  // are excluded by isContentToken; protected files already routed above.
745
767
  if (polarity === "forbid") {
746
- const contentTokens = [...patterns].filter(isContentToken);
768
+ // A file/dir/route token is a path reference, not code to search for inside
769
+ // file content — routing it to codeContent produces "found `dist/` written
770
+ // into a file" on any mention. Excluded here; it falls through to the
771
+ // deterministic checker (UNCLEAR, never a fabricated FAIL).
772
+ const contentTokens = [...patterns].filter((p) => isContentToken(p) && !looksLikeFilePathToken(p));
747
773
  if (contentTokens.length > 0) {
748
774
  return { kind: "codeContent", rule, patterns: contentTokens, polarity, polarityInferred };
749
775
  }
@@ -47,8 +47,28 @@ function editedContentFromEvent(event) {
47
47
  */
48
48
  function containsCall(content, pattern) {
49
49
  const leadsWithIdentifier = /^[A-Za-z0-9_$]/.test(pattern);
50
- if (!leadsWithIdentifier)
51
- return content.includes(pattern);
50
+ if (!leadsWithIdentifier) {
51
+ // A CALL like `.forEach(` legitimately follows an object (`arr.forEach()`),
52
+ // so member access before it is fine — bare containment.
53
+ if (pattern.endsWith("("))
54
+ return content.includes(pattern);
55
+ // A punct-leading NON-call token (a dotfile/extension like `.env`, `.log`)
56
+ // sitting right after an identifier is a property access or the tail of a
57
+ // longer token, not the token itself: `.env` inside `process.env` is not
58
+ // the .env file. Require a non-identifier char (or the start) before it.
59
+ // Found in the false-accusation corpus run 2026-09-29 — a `.env` rule
60
+ // FAILed every file using `process.env`.
61
+ let fromPunct = 0;
62
+ for (;;) {
63
+ const at = content.indexOf(pattern, fromPunct);
64
+ if (at === -1)
65
+ return false;
66
+ const before = at === 0 ? "" : content[at - 1];
67
+ if (!/[A-Za-z0-9_$]/.test(before))
68
+ return true;
69
+ fromPunct = at + 1;
70
+ }
71
+ }
52
72
  let from = 0;
53
73
  for (;;) {
54
74
  const at = content.indexOf(pattern, from);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rulereceipt",
3
- "version": "0.1.67",
3
+ "version": "0.1.68",
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",