@rune-kit/rune 2.29.1 → 2.30.1

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 (58) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/README.md +11 -3
  3. package/agents/audit.md +1 -1
  4. package/agents/cook.md +1 -1
  5. package/agents/journal.md +1 -1
  6. package/agents/reviewer.md +23 -1
  7. package/agents/session-bridge.md +1 -1
  8. package/agents/skill-forge.md +1 -1
  9. package/agents/skill-router.md +1 -1
  10. package/agents/trend-scout.md +1 -1
  11. package/agents/worktree.md +1 -1
  12. package/compiler/__tests__/skill-attribution.test.js +109 -0
  13. package/compiler/emitter.js +1 -1
  14. package/hooks/context-watch/index.cjs +5 -8
  15. package/hooks/hooks.json +1 -1
  16. package/hooks/intent-router/index.cjs +9 -6
  17. package/hooks/lib/hook-output.cjs +7 -1
  18. package/hooks/lib/hook-stdin.cjs +52 -0
  19. package/hooks/metrics-collector/index.cjs +5 -8
  20. package/hooks/pre-tool-guard/index.cjs +6 -6
  21. package/hooks/quarantine/index.cjs +12 -21
  22. package/package.json +4 -3
  23. package/skill-index.json +2187 -0
  24. package/skills/audit/SKILL.md +3 -3
  25. package/skills/completion-gate/SKILL.md +1 -1
  26. package/skills/constraint-check/SKILL.md +1 -1
  27. package/skills/context-engine/SKILL.md +2 -2
  28. package/skills/cook/SKILL.md +2 -2
  29. package/skills/cook/references/loop-detection.md +1 -1
  30. package/skills/cook/references/mid-run-signals.md +1 -1
  31. package/skills/debug/SKILL.md +2 -2
  32. package/skills/dependency-doctor/SKILL.md +1 -1
  33. package/skills/design/SKILL.md +1 -1
  34. package/skills/docs/SKILL.md +1 -1
  35. package/skills/docs-seeker/SKILL.md +3 -0
  36. package/skills/hallucination-guard/SKILL.md +2 -2
  37. package/skills/incident/SKILL.md +1 -1
  38. package/skills/integrity-check/SKILL.md +1 -1
  39. package/skills/launch/SKILL.md +1 -1
  40. package/skills/preflight/SKILL.md +35 -9
  41. package/skills/rescue/SKILL.md +1 -1
  42. package/skills/research/SKILL.md +1 -1
  43. package/skills/review/SKILL.md +135 -84
  44. package/skills/review/references/rules/config.md +63 -0
  45. package/skills/review/references/rules/default.md +57 -0
  46. package/skills/review/references/rules/go.md +65 -0
  47. package/skills/review/references/rules/index.md +43 -0
  48. package/skills/review/references/rules/python.md +64 -0
  49. package/skills/review/references/rules/rust.md +65 -0
  50. package/skills/review/references/rules/sql.md +65 -0
  51. package/skills/review/references/rules/ts-js.md +80 -0
  52. package/skills/sast/SKILL.md +1 -1
  53. package/skills/scout/SKILL.md +3 -0
  54. package/skills/sentinel/SKILL.md +29 -6
  55. package/skills/skill-router/SKILL.md +1 -1
  56. package/skills/team/SKILL.md +2 -2
  57. package/skills/verification/SKILL.md +3 -4
  58. package/skills/watchdog/SKILL.md +1 -1
@@ -68,6 +68,16 @@ Every review MUST cite at least one specific concern, suggestion, or explicit ap
68
68
 
69
69
  ## Execution
70
70
 
71
+ ### Review Policy
72
+
73
+ Three rules govern every step below. When a later step seems to conflict with one of these, the policy wins.
74
+
75
+ **1. Precision over recall.** A false alarm costs more reviewer trust than a missed LOW finding. One wrong CRITICAL teaches the developer to skim the next report; a missed style nit costs nothing. Optimise for a report where every line is worth reading — not for coverage.
76
+
77
+ **2. Blocking split.** Correctness and security findings are blocking. Style and idiom findings are non-blocking and never gate a merge on their own. Report both; only the first kind may produce REQUEST CHANGES.
78
+
79
+ **3. Never guess at missing context.** When the surrounding code, caller, or convention needed to judge a line is not in front of you, either go read it (`Read`, `Grep`, `rune:scout`) or stay silent. A finding invented to fill a gap in your own reading is the single most expensive kind of false alarm.
80
+
71
81
  ### Step 1: Scope
72
82
 
73
83
  Determine what to review.
@@ -77,6 +87,23 @@ Determine what to review.
77
87
  - If context is unclear: use `rune:scout` to identify all files touched by the change
78
88
  - List every file in scope before proceeding — do not review files outside the stated scope
79
89
 
90
+ **Strict Focus Rule** — the scope list you just wrote is the only set of files this review may produce findings about.
91
+
92
+ - Reads outside that list are for **understanding only**. Open any file you need to judge the diff correctly (callers, types, config, conventions) — that is encouraged, not scope creep.
93
+ - A finding whose subject is a file outside scope is **dropped, not reported** — not downgraded to LOW, not filed under "while I was in there". Dropped.
94
+ - If you spot a genuine issue elsewhere while gathering context, record it as a **one-line follow-up in the report footer**, never as a review finding. It carries no severity and does not affect the verdict.
95
+
96
+ The reason is trust, not bureaucracy: a review that wanders produces findings the author cannot act on in this change, and trains them to skim the ones they can.
97
+
98
+ **Rule Loading** — pick the checklists this diff actually needs, then stop.
99
+
100
+ 1. Collect the distinct file extensions across the scope list you just wrote.
101
+ 2. `Read` `references/rules/index.md` and follow its mapping to the matching rule files — at most one per language present in the diff.
102
+ 3. Always read `references/rules/default.md`. It carries the five dimensions every file is judged on.
103
+ 4. Read nothing further. **Never read all rule files** — a Go rule cannot produce a true finding about a diff with no Go in it, only a plausible-looking one, and that is the noise this split exists to remove.
104
+
105
+ Every rule in those files ends with a `Do not report when…` clause. That clause is as binding as the rule above it: a rule that only says when to fire will fire on everything. Rules do not restate the Review Policy, the Evidence Contract, or the claim types — they inherit all three from this file, and a rule never invents a severity or a gate of its own.
106
+
80
107
  ### Step 1.5: Blast Radius Assessment
81
108
 
82
109
  For each modified function/class, estimate its blast radius before reviewing.
@@ -133,33 +160,35 @@ Read each changed file. Prioritize bugs that **pass CI but break production**
133
160
  - **Data loss paths**: write operations without confirmation, delete without soft-delete, truncation without backup
134
161
  - **Edge cases**: empty input, null/undefined, zero, negative numbers, empty arrays, Unicode, timezone boundaries
135
162
  - Check for: logic errors, off-by-one errors, incorrect conditionals, broken async/await patterns
136
- - Flag each finding with file path, line number, and severity
163
+ - Flag each finding using the **Evidence Contract** below — a snippet you copied, not a line number you remembered
137
164
 
138
- **Common patterns to flag:**
165
+ #### Evidence Contract
139
166
 
140
- ```typescript
141
- // BAD — missing await causes race condition
142
- async function saveUser(data) {
143
- db.users.create(data); // caller proceeds before save completes
144
- return { success: true };
145
- }
146
- // GOOD
147
- async function saveUser(data) {
148
- await db.users.create(data);
149
- return { success: true };
150
- }
151
- ```
167
+ Every finding carries these five fields. The line number is the one field you do **not** produce yourself.
152
168
 
153
- ```typescript
154
- // BAD — null deref crash
155
- function getUsername(user) {
156
- return user.profile.name.toUpperCase(); // crashes if profile or name is null
157
- }
158
- // GOOD safe access
159
- function getUsername(user) {
160
- return user?.profile?.name?.toUpperCase() ?? 'Anonymous';
161
- }
162
- ```
169
+ | Field | Required | Source |
170
+ |-------|----------|--------|
171
+ | `path` | yes | the Step 1 scope list |
172
+ | `evidence` | yes | verbatim snippet copied out of the file |
173
+ | `line` | resolved | `Grep` on `evidence` at report time — never model recall |
174
+ | `severity` | yes | your judgement |
175
+ | `claim` | yes | `OBSERVED` / `DERIVED` / `ASSUMED` (Step 6) |
176
+
177
+ A line number recalled from a long context drifts, and a correct finding pointing at the wrong line is unactionable — the reader looks, sees nothing, and stops trusting the report. A snippet can be checked against the file; a remembered number cannot. So produce what you can copy, and let a tool resolve the rest (Step 6, Anchor Pass).
178
+
179
+ **Evidence rules:**
180
+
181
+ - Copy the lines **verbatim** — no rewriting, reformatting, re-indenting, or tidying
182
+ - Strip diff markers (`+`, `-`, and the leading space on context lines) before recording
183
+ - Include only the lines directly involved — no surrounding context padding
184
+ - Cap at **5 lines**. A finding that needs more than 5 lines to show is a design comment, not a defect — report it without evidence at MEDIUM or below
185
+ - Multiple disjoint locations → pick the single most relevant one and file the rest as separate findings
186
+
187
+ Evidence blocks are **substance, not shape**. Under `context-engine`'s `caveman` output mode the prose around a finding compresses; the evidence block does not (`../context-engine/references/output-modes.md` — "shape is negotiable, substance is not").
188
+
189
+ **Strict Focus applies here** (restated from Step 1, because this is the step that breaks it): reading a caller or a helper outside the diff to decide whether a changed line is correct is *expected*. Reporting a bug you noticed in that caller is not — that finding is dropped, or goes to the report footer as a one-line follow-up. The question this step answers is only ever "is the **changed** code correct?"
190
+
191
+ **Language-specific patterns** for the files in scope come from the rule files loaded in Step 1 — each carries the concrete triggers *and* the conditions under which they must not be reported.
163
192
 
164
193
  ### Step 3: Pattern Check
165
194
 
@@ -172,30 +201,7 @@ Check consistency with project conventions.
172
201
  - Check TypeScript: no `any`, full type coverage, no non-null assertions without justification
173
202
  - Flag inconsistencies as MEDIUM or LOW depending on impact
174
203
 
175
- **Common patterns to flag:**
176
-
177
- ```typescript
178
- // BAD — mutation
179
- function addItem(cart, item) {
180
- cart.items.push(item); // mutates in place
181
- return cart;
182
- }
183
- // GOOD — immutable
184
- function addItem(cart, item) {
185
- return { ...cart, items: [...cart.items, item] };
186
- }
187
- ```
188
-
189
- ```typescript
190
- // BAD — any defeats TypeScript's purpose
191
- function process(data: any): any {
192
- return data.items.map((i: any) => i.value);
193
- }
194
- // GOOD — typed
195
- function process(data: { items: Array<{ value: string }> }): string[] {
196
- return data.items.map(i => i.value);
197
- }
198
- ```
204
+ Mutation, type-escape, and idiom triggers are language-specific — take them from the Step 1 rule files rather than applying one language's conventions to another's.
199
205
 
200
206
  ### Step 4: Security Check
201
207
 
@@ -345,20 +351,59 @@ End with the only metric that matters: `net: -<N> lines, -<M> deps possible.` No
345
351
 
346
352
  Produce a structured severity-ranked report.
347
353
 
348
- **Before reporting, apply confidence filter:**
349
- - Only report findings with >80% confidence it is a real issue
354
+ #### Falsification Pass (run before writing the report)
355
+
356
+ **Falsify, not verify.** Do not ask "am I confident enough to report this?" — a model cannot calibrate its own confidence to a number, so that question filters nothing and quietly drops true findings. Ask the answerable question instead: **"did I read something that disproves this?"**
357
+
358
+ | | Condition | Action |
359
+ |---|---|---|
360
+ | **DROP** | The code you read contains **direct counter-evidence** against the finding's key claim — the null check exists three lines up, the `await` is there, the input is validated by the caller you opened | Discard it |
361
+ | **KEEP** | The finding depends on context **outside the diff** that you did read via tools — that context is evidence, not a disqualification | Report it |
362
+ | **KEEP** | You can neither verify nor disprove it | Report it, typed honestly (below) |
363
+
364
+ **"Unsure" is not grounds to drop.** Only counter-evidence is. A finding you could not confirm is still a finding — it is reported at the severity its claim type allows, not deleted to keep the report tidy.
365
+
366
+ Dropped findings are **discarded silently** — never listed as "considered and dismissed". A disproven finding is noise whether or not you label it as such.
367
+
368
+ **Type every surviving finding** with a claim type from `../completion-gate/references/claim-discipline.md`:
369
+
370
+ | Type | Means | Ceiling |
371
+ |------|-------|---------|
372
+ | `OBSERVED` | You read the code path this session and saw the defect | Any severity |
373
+ | `DERIVED` | Follows from what you read through a mechanism you can state in the finding | Any severity |
374
+ | `ASSUMED` | Requires an unverified premise (a caller you did not open, a runtime condition you cannot see) | **Never CRITICAL** — state the premise in the finding |
375
+
376
+ An `ASSUMED` finding capped below CRITICAL is the honest form of "this looks wrong but I could not confirm the call path". Promotion happens by reading the code, never by rephrasing the finding more confidently.
377
+
378
+ #### Anchor Pass (run per surviving finding)
379
+
380
+ Resolve every line number now, with a tool. Climb the ladder and stop at the first rung that hits:
381
+
382
+ 1. `Grep` the exact `evidence` string in `path`. A hit → the finding is **anchored**; use the line number `Grep` returned.
383
+ 2. No hit → retry once with whitespace normalised (collapse runs of spaces) and the first and last lines of the snippet dropped. A hit → anchored on the remaining core.
384
+ 3. Still no hit → the finding is **`UNANCHORED`**.
385
+
386
+ **`UNANCHORED` handling** — advisory, never blocking:
387
+
388
+ - Downgrade severity by one level: CRITICAL → HIGH → MEDIUM → LOW. LOW stays LOW.
389
+ - Report as `path (unanchored)` in place of `path:line`, with the evidence snippet shown inline
390
+ - **Never silently drop it.** A failed anchor means the snippet does not match the file as you recorded it — usually a transcription slip, occasionally a file that moved while you read. Both deserve the reader's attention; neither is counter-evidence, so neither disproves the finding.
391
+
392
+ Anchoring resolves a finding; it does not filter one. Only the Falsification Pass drops findings — a survivor of that pass always reaches the report, anchored or not.
393
+
394
+ **Then, before reporting:**
350
395
  - Consolidate similar issues: "8 functions missing error handling in src/services/" — not 8 separate findings
351
396
  - Skip stylistic preferences unless they violate conventions found in `.eslintrc`, `CLAUDE.md`, or `CONTRIBUTING.md`
352
397
  - Adapt to project type: a `console.log` in a CLI tool is fine; in a production API handler it is not
353
398
 
354
399
  - Group findings by severity: CRITICAL → HIGH → MEDIUM → LOW
355
- - Include file path and line number for every finding
400
+ - Give every finding its `path:line` from the Anchor Pass — or `path (unanchored)` — plus the evidence block underneath
356
401
  - Include a Positive Notes section (good patterns observed)
357
402
  - Include a Verdict: APPROVE | REQUEST CHANGES | NEEDS DISCUSSION
358
403
 
359
404
  ### Step 6.5: Fix-First Triage
360
405
 
361
- > From gstack (garrytan/gstack, 50.9k★): "Reviews that produce 20 findings and delegate all to the user waste everyone's time."
406
+ A review that produces 20 findings and delegates every one of them back to the user has moved work, not done it.
362
407
 
363
408
  Classify each finding as **AUTO-FIX** or **ASK** before reporting:
364
409
 
@@ -376,11 +421,13 @@ Classify each finding as **AUTO-FIX** or **ASK** before reporting:
376
421
  - Collect ASK findings into ONE `AskUserQuestion` — not 5 separate questions
377
422
  - Report both: "Auto-fixed 4 issues. 2 findings need your input: [...]"
378
423
 
379
- **Rationalization prevention**: "This looks fine" is NOT acceptable without evidence. If you can't cite a specific file:line or convention that justifies the code, flag it as UNVERIFIED don't rationalize away uncertainty.
424
+ **Rationalization prevention**: "This looks fine" is NOT acceptable without evidence. If you cannot cite a specific file:line or convention that justifies the code, do not wave it through — report it as an `ASSUMED` finding naming the premise you could not check.
425
+
426
+ This is the same asymmetry as the Falsification Pass, applied in the other direction: uncertainty never justifies **dropping** a finding, and it never justifies **clearing** code either. Both resolve by reading, or by saying plainly what was not read.
380
427
 
381
428
  ### Step 6.6: Scope Drift Detection
382
429
 
383
- > From gstack (garrytan/gstack, 50.9k★): "Intent vs diff catches scope creep that plan-based guards miss."
430
+ Comparing stated intent against the actual diff catches scope creep that plan-based guards miss — the plan was right, the diff simply grew past it.
384
431
 
385
432
  After reviewing code, compare **stated intent** vs **actual diff**:
386
433
 
@@ -403,26 +450,6 @@ After reporting:
403
450
  - If untested code: call `rune:test` with specific coverage gaps identified
404
451
  - Call `neural-memory` (Capture Mode) to save any novel code quality patterns or recurring issues found.
405
452
 
406
- ## Framework-Specific Checks
407
-
408
- Apply **only** if the framework is detected in the changed files. Skip if not relevant.
409
-
410
- **React / Next.js** (detect: `import React` or `.tsx` files)
411
- - `useEffect` with missing dependencies (stale closure) → flag HIGH
412
- - List items using index as key on reorderable lists: `key={i}` → flag MEDIUM
413
- - Props drilled through 3+ levels without Context or composition → flag MEDIUM
414
- - Client-side hooks (`useState`, `useEffect`) in Server Components (Next.js App Router) → flag HIGH
415
-
416
- **Node.js / Express** (detect: `import express` or `require('express')`)
417
- - Missing rate limiting on public endpoints → flag MEDIUM
418
- - `req.body` passed directly to DB without validation schema → flag HIGH
419
- - Synchronous operations blocking the event loop inside async handlers → flag HIGH
420
-
421
- **Python** (detect: `.py` files with `django`, `flask`, or `fastapi` imports)
422
- - `except:` bare catch without specific exception type → flag MEDIUM
423
- - Mutable default arguments: `def func(items=[])` → flag HIGH
424
- - Missing type hints on public functions (if project uses mypy/pyright) → flag LOW
425
-
426
453
  ## UI/UX Anti-Pattern Checks
427
454
 
428
455
  Apply **only** when `.tsx`, `.jsx`, `.svelte`, `.vue`, or `.html` files are in the diff. Skip for backend-only changes.
@@ -622,25 +649,36 @@ LOW — style inconsistency, naming suggestion, minor refactor opportunity
622
649
 
623
650
  ## Output Format
624
651
 
625
- ```
652
+ ````
626
653
  ## Code Review Report
627
654
  - **Files Reviewed**: [count]
628
655
  - **Findings**: [count by severity]
629
656
  - **Review Commit**: [git hash at time of review]
630
657
  - **Council**: [not invoked | MULTI_FAMILY (N families) | NO_DECORRELATION — same-family subagents only]
658
+ - **Unanchored**: [count — findings whose evidence did not resolve to a line]
631
659
  - **Overall**: APPROVE | REQUEST CHANGES | NEEDS DISCUSSION
632
660
 
633
661
  ### Spec Compliance
634
662
  - [PASS/FAIL]: [acceptance criteria coverage]
635
663
 
636
664
  ### CRITICAL
637
- - `path/to/file.ts:42` — [description of critical issue]
665
+ - `src/auth/session.ts:42` — [OBSERVED] loose equality on the session token: a request with no token and a session with no token are both `undefined`, and `undefined == undefined` is true, so an anonymous caller is granted that session's user
666
+ ```ts
667
+ if (req.token == session.token) return grant(session.user);
668
+ ```
638
669
 
639
670
  ### HIGH
640
- - `path/to/file.ts:85` — [description of high-severity issue]
671
+ - `src/db/users.ts:85` — [DERIVED] create is not awaited, so the handler returns success before the write lands; a failed insert is reported to the client as 201
672
+ ```ts
673
+ db.users.create(data);
674
+ return { success: true };
675
+ ```
641
676
 
642
677
  ### MEDIUM
643
- - `path/to/file.ts:120` — [description of medium issue]
678
+ - `src/cache/store.ts (unanchored)` — [ASSUMED: caller in worker.ts not read] cache write has no TTL; if the worker path also writes this key the entry never expires
679
+ ```ts
680
+ cache.set(key, value)
681
+ ```
644
682
 
645
683
  ### Blast Radius
646
684
  - [High-impact symbols with caller counts]
@@ -650,7 +688,14 @@ LOW — style inconsistency, naming suggestion, minor refactor opportunity
650
688
 
651
689
  ### Verdict
652
690
  [Summary and recommendation]
653
- ```
691
+
692
+ ### Follow-ups (outside this scope)
693
+ - `other/file.ts` — [one line, no severity, does not affect the verdict; omit section if none]
694
+ ````
695
+
696
+ Read the MEDIUM entry above as the shape of an honest weak finding: unanchored (so `path (unanchored)`, already downgraded one level), `ASSUMED` with its unchecked premise named, and still reported — because none of that is counter-evidence.
697
+
698
+ Every finding carries its claim type from the Falsification Pass and its evidence block from the Evidence Contract. `ASSUMED` findings name the premise that was not checked and never appear under CRITICAL. A finding over the 5-line evidence cap is a design comment — it ships without a block, at MEDIUM or below. The Follow-ups section is the only place an out-of-scope observation may appear.
654
699
 
655
700
  ### Review Staleness Detection
656
701
 
@@ -666,7 +711,7 @@ When `cook` or `ship` checks review status: compare review commit hash with curr
666
711
  ## Constraints
667
712
 
668
713
  1. MUST read the full diff — not just the files the user pointed at
669
- 2. MUST reference specific file:line for every finding
714
+ 2. MUST give every finding a verbatim evidence snippet, and resolve its line via the Anchor Pass rather than recall — an unresolved finding is reported `(unanchored)` and downgraded, never renumbered by guess
670
715
  3. MUST NOT rubber-stamp with generic praise ("well-structured", "clean code") without evidence
671
716
  4. MUST check: correctness, security, performance, conventions, test coverage
672
717
  5. MUST categorize findings: CRITICAL (blocks commit) / HIGH / MEDIUM / LOW
@@ -697,7 +742,7 @@ chain_metadata:
697
742
  exports:
698
743
  findings_count: { critical: [N], high: [N], medium: [N], low: [N] }
699
744
  findings:
700
- - { severity: "[level]", file: "[path]", line: [N], message: "[issue]" }
745
+ - { severity: "[level]", file: "[path]", line: [N or null when unanchored], anchored: [true | false], evidence: "[verbatim snippet, ≤5 lines]", message: "[issue]", claim_type: "[OBSERVED | DERIVED | ASSUMED]" }
701
746
  verdict: "[APPROVE | REQUEST_CHANGES | NEEDS_DISCUSSION]"
702
747
  quality_score: [0-100] # when mode: "scored"
703
748
  suggested_next:
@@ -710,9 +755,14 @@ chain_metadata:
710
755
 
711
756
  | Failure Mode | Severity | Mitigation |
712
757
  |---|---|---|
713
- | Finding flood — 20+ findings overwhelm developer | MEDIUM | Confidence filter: only >80% confidence, consolidate similar issues per file |
758
+ | Finding flood — 20+ findings overwhelm developer | MEDIUM | Falsification Pass drops disproven findings; consolidate similar issues per file |
714
759
  | "LGTM" without file:line evidence | HIGH | HARD-GATE blocks this — cite at least one specific item per changed file |
715
- | Expanding review scope beyond the diff | MEDIUM | Limit to `git diff` scopedo not creep into adjacent unchanged files |
760
+ | Expanding review scope beyond the diff | MEDIUM | Strict Focus Rule (Step 1)read anything for context, but findings about out-of-scope files are dropped or become footer follow-ups |
761
+ | Dropping a true finding because it could not be confirmed | HIGH | Falsification Pass — only counter-evidence drops a finding; "unsure" reports it as `ASSUMED` |
762
+ | Line number recalled from context instead of resolved — points at the wrong line, reader finds nothing, stops trusting the report | HIGH | Evidence Contract (Step 2) produces a copyable snippet; the Anchor Pass (Step 6) resolves the number with `Grep`. Never write a line number you did not get back from a tool |
763
+ | Dropping a finding because its evidence would not anchor | MEDIUM | Anchoring resolves, it never filters — `UNANCHORED` downgrades one level and reports with the snippet inline. A failed `Grep` is not counter-evidence |
764
+ | Reading every rule file regardless of the diff's languages | MEDIUM | Step 1 Rule Loading — load only the files matching extensions in scope, plus `default.md`. A rule for a language absent from the diff can only produce a plausible-looking false finding |
765
+ | Applying a rule's trigger while ignoring its `Do not report when…` clause | HIGH | The negative clause is part of the rule, not commentary on it. A rule cited without checking its exclusion is the single most common source of confident false alarms |
716
766
  | Security finding without sentinel escalation | HIGH | Any auth/crypto/payment code touched → MUST call rune:sentinel |
717
767
  | Skipping UI anti-pattern checks for frontend changes | MEDIUM | Any .tsx/.jsx/.svelte/.vue in diff → MUST run UI/UX Anti-Pattern Checks section |
718
768
  | Skipping spec compliance check (Step 5.5 Stage 1) | HIGH | Code quality without spec check ships clean code that does the wrong thing — always load the plan/ticket before reviewing quality |
@@ -729,7 +779,8 @@ chain_metadata:
729
779
  ## Done When
730
780
 
731
781
  - All changed files in the diff read and analyzed
732
- - Every finding references specific file:line with severity label
782
+ - Rule files matching the diff's extensions loaded (plus `default.md`), and no others
783
+ - Every finding carries a verbatim evidence snippet and a severity label, with its line resolved by the Anchor Pass or marked `(unanchored)` and downgraded
733
784
  - Security-critical code escalated to sentinel (or confirmed not present)
734
785
  - Test coverage gaps identified and documented
735
786
  - UI anti-pattern checks ran for any frontend files in diff (or confirmed not applicable)
@@ -0,0 +1,63 @@
1
+ # Config, CI, and infrastructure files
2
+
3
+ Blocking here: credentials in the tree, a CI workflow that lets untrusted code reach a secret, and a
4
+ production default that is wide open. Formatting, key order, and comment density are not findings —
5
+ config files attract style opinions, and a report full of them buries the one line that matters.
6
+
7
+ ## Secrets
8
+
9
+ - A token, key, password, or connection string with a literal value
10
+ - A `.env` file committed with real values rather than placeholders
11
+ - A secret passed as a build argument or baked into an image layer — it stays in history
12
+ - A credential echoed into logs by a debug or verbose setting
13
+
14
+ Do not report an obvious placeholder (`changeme`, `xxx`, `example.com`, a documented dummy) or a
15
+ value in a file the repo's ignore rules already exclude — check before reporting.
16
+
17
+ ## CI workflow security
18
+
19
+ - `pull_request_target` combined with a checkout of the PR's head — untrusted code runs with secrets
20
+ - A third-party action pinned to a tag or branch rather than a commit SHA
21
+ - A workflow token granted write scope where the job only reads
22
+ - A secret referenced in a job that a fork's PR can trigger
23
+ - User-controlled text (title, branch name, body) interpolated straight into a `run:` block
24
+
25
+ Do not report an unpinned action published by the same organization as the repository, where the
26
+ project's other workflows follow the same convention.
27
+
28
+ ## Runtime and container defaults
29
+
30
+ - A container running as root, or with `privileged`, host network, or a docker socket mounted
31
+ - An image tagged `latest` in anything that is deployed
32
+ - A debug, verbose, or development flag set in a production-facing config
33
+ - A health check or resource limit absent on a deployed service
34
+
35
+ Do not report root or `latest` in a local development compose file, a test fixture, or a
36
+ documentation example — the risk is a property of where it is deployed, not of the string.
37
+
38
+ ## Network exposure
39
+
40
+ - An ingress, security group, or firewall rule open to `0.0.0.0/0` on a non-public port
41
+ - CORS allowing `*` together with credentials — the browser rejects it, and the intent is unclear
42
+ - TLS verification disabled, or a minimum protocol version below the project's stated baseline
43
+ - A management, metrics, or admin endpoint bound to a public interface
44
+
45
+ Do not report a wildcard CORS origin on an endpoint that serves only public, unauthenticated data.
46
+
47
+ ## File semantics
48
+
49
+ - YAML where an unquoted `yes`, `no`, `on`, `off`, or a country code like `NO` is read as a boolean
50
+ - Duplicate keys in YAML or JSON — the last one silently wins
51
+ - A number that must stay a string (a version, a zip code, an account id) left unquoted
52
+ - A tab character used for indentation in YAML — a parse error
53
+
54
+ Do not report quoting style on values where no type coercion is possible.
55
+
56
+ ## Versions and dependencies
57
+
58
+ - A floating range (`*`, `latest`, an unbounded caret) in a manifest with no lockfile committed
59
+ - A lockfile changed with no corresponding manifest change, or the reverse
60
+ - A pinned dependency with a known advisory where the fix is a patch release
61
+
62
+ Do not report a floating range in a devDependency of a library whose lockfile is deliberately not
63
+ committed — that is the standard convention for published packages.
@@ -0,0 +1,57 @@
1
+ # Default rules
2
+
3
+ Loaded for every review, alongside any language file that matches. These are the five dimensions
4
+ every changed file is judged on, regardless of language. Deliberately small — a language rule file
5
+ is more specific and therefore more trustworthy, so where the two overlap, prefer the language file.
6
+
7
+ ## Correctness
8
+
9
+ - A condition inverted, off by one, or testing a different value than the one it guards
10
+ - An edge case the change introduces and does not handle: empty input, zero, a negative number, a
11
+ single-element collection, a boundary date
12
+ - A failure path that leaves state half-written, with no rollback or compensating action
13
+ - A default or fallback that silently produces a plausible-but-wrong value
14
+
15
+ Do not report an unhandled edge case when the caller you read already excludes it — and if you did
16
+ not read the caller, that is an `ASSUMED` finding naming the caller, not an `OBSERVED` one.
17
+
18
+ ## Security
19
+
20
+ - Input from outside the trust boundary reaching a query, path, command, template, or redirect
21
+ - A new entry point with no authorization check where comparable entry points have one
22
+ - A credential, token, or key with a literal value
23
+ - An error message or log line carrying a secret, a token, or personally identifying data
24
+
25
+ Do not report on code whose only inputs are internal constants or values already validated at a
26
+ boundary you read. Auth, crypto, and payment code still escalates to `rune:sentinel` regardless of
27
+ whether this rule fires.
28
+
29
+ ## Performance
30
+
31
+ - Work inside a loop that does not depend on the loop: a repeated query, a re-read file, a
32
+ recompiled pattern
33
+ - An operation whose cost grows with a collection that grows without bound
34
+ - A synchronous or blocking call on a latency-sensitive path
35
+
36
+ Do not report performance on a path that runs once at startup, in a script, or in a test — and do
37
+ not report a complexity concern without naming the input that makes it matter. An unmeasured
38
+ micro-optimization is a LOW finding at most.
39
+
40
+ ## Maintainability
41
+
42
+ - A magic value repeated across call sites that should be a named constant
43
+ - A function doing several unrelated things, where the change made it worse rather than found it so
44
+ - Naming that contradicts what the code does — a `get` that writes, an `is` that returns a value
45
+ - Copy-pasted logic that now has to be corrected in more than one place
46
+
47
+ Do not report a maintainability concern that predates the diff. Pre-existing structure is a
48
+ footer follow-up, never a finding against this change — the author cannot act on it here.
49
+
50
+ ## Test coverage
51
+
52
+ - New branching logic with no test exercising the new branch
53
+ - A bug fix with no test that fails without the fix
54
+ - A test changed to match new behaviour where the behaviour change itself was not requested
55
+
56
+ Do not report missing tests for pure config, generated files, or a change whose behaviour is
57
+ already covered by an existing test you located.
@@ -0,0 +1,65 @@
1
+ # Go
2
+
3
+ Blocking here: discarded errors, goroutine and resource leaks, data races, and nil semantics that
4
+ panic at runtime. Naming, comment style, and receiver conventions are non-blocking — `gofmt` and
5
+ `go vet` already cover the ground a reviewer would otherwise spend the report on.
6
+
7
+ ## Error handling
8
+
9
+ - An error assigned to `_`, or a call whose error return is dropped entirely
10
+ - An error returned unwrapped where the caller needs to distinguish causes — no `%w`, no sentinel
11
+ - `err` shadowed by `:=` in an inner scope, so the outer check tests a stale value
12
+ - `panic` on a recoverable condition in library code
13
+
14
+ Do not report a discarded error on a call that cannot meaningfully fail in context — `fmt.Fprintf`
15
+ to a `strings.Builder`, a `Close` on a read-only handle already drained — provided the discard is
16
+ deliberate and local.
17
+
18
+ ## Concurrency
19
+
20
+ - A goroutine with no exit path: it blocks forever on a channel nobody closes or writes
21
+ - `sync.WaitGroup` whose `Add` is inside the goroutine rather than before it starts
22
+ - A map or slice written from multiple goroutines without a mutex or channel discipline
23
+ - A mutex locked on a path that can `return` before `Unlock`, with no `defer`
24
+ - Sending on a channel that another path may have closed
25
+
26
+ Do not report loop-variable capture in a module whose `go.mod` declares `go 1.22` or later — the
27
+ per-iteration variable semantics make the classic capture bug impossible there.
28
+
29
+ ## Context and timeouts
30
+
31
+ - `context.Background()` or `context.TODO()` inside a request path that already has a context
32
+ - An outbound HTTP, RPC, or DB call with no deadline and no context propagation
33
+ - A context cancel function from `WithCancel` / `WithTimeout` not deferred — a leak
34
+ - A context stored in a struct field rather than passed as the first parameter
35
+
36
+ Do not report a missing deadline on a call inside `main`, an init path, or a long-running worker
37
+ that is meant to run until cancelled.
38
+
39
+ ## Resource lifecycle
40
+
41
+ - `defer resp.Body.Close()` missing after a successful HTTP call, or placed before the error check
42
+ - `defer` inside a loop where the resource must be released each iteration, not at function exit
43
+ - A file, rows handle, or connection opened on one branch and closed on only some of them
44
+ - `rows.Err()` never checked after a `sql.Rows` iteration
45
+
46
+ Do not report a missing `Close` when the value's lifetime is the process itself.
47
+
48
+ ## Nil and slice semantics
49
+
50
+ - A write to a nil map — reads are fine, writes panic
51
+ - A nil pointer dereference on a path where the constructor can return `(nil, err)`
52
+ - `append` on a slice sharing a backing array with a caller's slice, silently overwriting
53
+ - A typed nil returned as an `error` interface — non-nil interface holding a nil pointer
54
+
55
+ Do not report slice aliasing when the source slice is built inside the same function and never
56
+ escapes it.
57
+
58
+ ## Untrusted input
59
+
60
+ - A query assembled with `fmt.Sprintf` rather than placeholders
61
+ - `exec.Command` whose arguments derive from input, especially through a shell
62
+ - A path joined from input without `filepath.Clean` plus a base-directory check
63
+
64
+ Do not report `fmt.Sprintf` in a query when every interpolated value is a constant or comes from a
65
+ fixed allowlist in the same file.
@@ -0,0 +1,43 @@
1
+ # Review Rules — Index
2
+
3
+ Rule files scoped by file type. Step 1 of `review` loads **only** the entries matching extensions
4
+ actually present in the scope list, plus `default.md`. Loading all of them reintroduces exactly the
5
+ noise this split exists to remove.
6
+
7
+ ## Mapping
8
+
9
+ | Pattern | Rule file |
10
+ |---------|-----------|
11
+ | `*.ts`, `*.tsx`, `*.js`, `*.jsx`, `*.mjs`, `*.cjs` | `ts-js.md` |
12
+ | `*.py` | `python.md` |
13
+ | `*.go` | `go.md` |
14
+ | `*.rs` | `rust.md` |
15
+ | `*.sql`, migration files | `sql.md` |
16
+ | `*.yml`, `*.yaml`, `*.json`, `*.toml`, CI workflows, Dockerfile | `config.md` |
17
+ | everything else | `default.md` |
18
+
19
+ ## Loading protocol
20
+
21
+ 1. Collect the distinct extensions across the Step 1 scope list.
22
+ 2. Read the matching rule files — at most one per language present.
23
+ 3. Always read `default.md`; it carries the dimensions every file is judged on.
24
+ 4. Stop there. A rule file for a language absent from the diff cannot produce a finding about it.
25
+
26
+ ## What a rule file is, and is not
27
+
28
+ Every section ends with a `Do not report when…` line. That line is the point of the file — a rule
29
+ that only says when to fire will fire on everything, and a review that fires on everything gets
30
+ skimmed. The negative clause binds as hard as the positive one.
31
+
32
+ Rule files do **not** restate what `SKILL.md` already owns:
33
+
34
+ - **Review Policy** — precision over recall, the blocking split, never guessing at missing
35
+ context. Rules inherit it rather than repeating it.
36
+ - **Evidence Contract** — verbatim snippets and line numbers resolved by the Anchor Pass.
37
+ - **Claim types** — `OBSERVED` / `DERIVED` / `ASSUMED`, and the rule that `ASSUMED` never reaches
38
+ CRITICAL.
39
+ - **Severity meanings** — defined once. A rule file may say a finding is *typically* HIGH; it never
40
+ invents a severity, a gate, or a verdict of its own.
41
+
42
+ A rule fires a finding. Whether that finding survives is the Falsification Pass's call, not the
43
+ rule's.
@@ -0,0 +1,64 @@
1
+ # Python
2
+
3
+ Blocking here: exceptions that hide failure, blocking calls inside `async def`, untrusted input
4
+ reaching an interpreter or a shell, and resources never released. Type hints and naming are
5
+ non-blocking — report them once, at LOW, or not at all.
6
+
7
+ ## Exception handling
8
+
9
+ - `except:` or `except BaseException:` — swallows `KeyboardInterrupt` and `SystemExit` too
10
+ - A handler that logs and continues where the caller then acts on state the failed block never set
11
+ - `raise NewError(...)` inside a handler without `from e` — the original traceback is lost
12
+ - `contextlib.suppress` or a bare `pass` handler over a block that performs a write
13
+
14
+ Do not report a broad `except Exception` at a genuine top-level boundary — a task runner, a request
15
+ middleware, a CLI entrypoint — where the handler logs with traceback and the process must survive.
16
+
17
+ ## Async correctness
18
+
19
+ - A blocking call inside `async def`: `time.sleep`, `requests`, a sync DB driver, `open().read()`
20
+ on a large file, or a CPU-bound loop
21
+ - A coroutine called without `await`, so it is created and discarded
22
+ - `asyncio.gather` over operations that must not partially apply
23
+ - Tasks created with `create_task` and never awaited or stored — they can be garbage collected mid-flight
24
+
25
+ Do not report a blocking call when it runs through `run_in_executor`, `asyncio.to_thread`, or an
26
+ equivalent offload — and do not report sync code in a project with no async entrypoints at all.
27
+
28
+ ## Mutable state
29
+
30
+ - A mutable default argument: `def f(items=[])` / `={}` — shared across every call
31
+ - A module-level mutable used as a cache without a lock in a threaded or async server
32
+ - Mutating a list or dict while iterating it
33
+ - A dataclass field defaulting to a mutable without `field(default_factory=...)`
34
+
35
+ Do not report a module-level mutable that is written once at import and only read afterwards.
36
+
37
+ ## Untrusted input
38
+
39
+ - SQL built by f-string, `%`, `.format`, or `+` instead of parameters
40
+ - `subprocess` with `shell=True` on any value derived from input
41
+ - `eval`, `exec`, `pickle.loads`, or `yaml.load` without `SafeLoader` on external data
42
+ - A path joined from user input without confining it to a base directory
43
+ - Secrets read from a literal in source rather than the environment or a secret store
44
+
45
+ Do not report a parameterized query whose only interpolation is a table or column name drawn from a
46
+ fixed allowlist visible in the same module.
47
+
48
+ ## Resource lifecycle
49
+
50
+ - `open`, a socket, a DB connection, or a session acquired without a context manager or `finally`
51
+ - A lock acquired on a path that can raise before it is released
52
+ - A thread or process pool created per call rather than reused
53
+
54
+ Do not report a short-lived handle in a script or test where the interpreter exits immediately after.
55
+
56
+ ## Typing and correctness
57
+
58
+ - `is` / `is not` comparing values rather than identity (`is "text"`, `is 0`)
59
+ - Equality against `None`, `True`, or `False` with `==` where identity is meant
60
+ - Missing type hints on a public function **only** if the project runs mypy or pyright
61
+ - Integer division or float equality where exactness matters (money, counters)
62
+
63
+ Do not report missing hints in a project with no type checker configured — that is a preference,
64
+ and reporting it as a defect is precisely the noise the blocking split exists to prevent.