@rune-kit/rune 2.29.0 → 2.30.0
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/.codex-plugin/plugin.json +1 -1
- package/README.md +49 -2
- package/agents/audit.md +1 -1
- package/agents/cook.md +1 -1
- package/agents/journal.md +1 -1
- package/agents/reviewer.md +23 -1
- package/agents/session-bridge.md +1 -1
- package/agents/skill-forge.md +1 -1
- package/agents/skill-router.md +1 -1
- package/agents/trend-scout.md +1 -1
- package/agents/worktree.md +1 -1
- package/compiler/__tests__/skill-attribution.test.js +109 -0
- package/compiler/__tests__/update.test.js +416 -0
- package/compiler/bin/rune.js +19 -0
- package/compiler/commands/update.js +354 -0
- package/package.json +1 -1
- package/skills/audit/SKILL.md +3 -3
- package/skills/completion-gate/SKILL.md +1 -1
- package/skills/constraint-check/SKILL.md +1 -1
- package/skills/context-engine/SKILL.md +2 -2
- package/skills/cook/SKILL.md +2 -2
- package/skills/cook/references/loop-detection.md +1 -1
- package/skills/cook/references/mid-run-signals.md +1 -1
- package/skills/debug/SKILL.md +2 -2
- package/skills/dependency-doctor/SKILL.md +1 -1
- package/skills/design/SKILL.md +1 -1
- package/skills/docs/SKILL.md +1 -1
- package/skills/docs-seeker/SKILL.md +3 -0
- package/skills/hallucination-guard/SKILL.md +2 -2
- package/skills/incident/SKILL.md +1 -1
- package/skills/integrity-check/SKILL.md +1 -1
- package/skills/launch/SKILL.md +1 -1
- package/skills/preflight/SKILL.md +35 -9
- package/skills/rescue/SKILL.md +1 -1
- package/skills/research/SKILL.md +1 -1
- package/skills/review/SKILL.md +135 -84
- package/skills/review/references/rules/config.md +63 -0
- package/skills/review/references/rules/default.md +57 -0
- package/skills/review/references/rules/go.md +65 -0
- package/skills/review/references/rules/index.md +43 -0
- package/skills/review/references/rules/python.md +64 -0
- package/skills/review/references/rules/rust.md +65 -0
- package/skills/review/references/rules/sql.md +65 -0
- package/skills/review/references/rules/ts-js.md +80 -0
- package/skills/sast/SKILL.md +1 -1
- package/skills/scout/SKILL.md +3 -0
- package/skills/sentinel/SKILL.md +29 -6
- package/skills/skill-router/SKILL.md +1 -1
- package/skills/team/SKILL.md +2 -2
- package/skills/verification/SKILL.md +3 -4
- package/skills/watchdog/SKILL.md +1 -1
|
@@ -103,7 +103,7 @@ const permissions = await fetchPermissions(id); // waits unnecessarily
|
|
|
103
103
|
const [user, permissions] = await Promise.all([fetchUser(id), fetchPermissions(id)]);
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
Flag each issue with: file path,
|
|
106
|
+
Flag each issue with: file path, a **verbatim evidence snippet** copied from the file, category (null-deref | missing-await | off-by-one | type-coerce), and a one-line description. Record the snippet, not a line number — Step 6's Anchor Pass resolves the line with `Grep`.
|
|
107
107
|
|
|
108
108
|
### Step 2 — Error Handling
|
|
109
109
|
For every changed file, verify:
|
|
@@ -132,7 +132,7 @@ app.use((err, req, res, next) => {
|
|
|
132
132
|
});
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
Flag each violation with: file path,
|
|
135
|
+
Flag each violation with: file path, a **verbatim evidence snippet**, category (bare-catch | missing-status-check | raw-error-exposure), and description. Same rule as Step 1 — the snippet is what you produce; the line number comes from Step 6's Anchor Pass.
|
|
136
136
|
|
|
137
137
|
### Step 3 — Regression Check
|
|
138
138
|
Use `rune:scout` to identify all files that import or depend on the changed files/functions.
|
|
@@ -337,7 +337,21 @@ Score is appended to the Preflight Report footer. Useful for tracking quality tr
|
|
|
337
337
|
Invoke `rune:sentinel` on the changed files. Attach sentinel's output verbatim under the "Security" section of the preflight report. If sentinel returns BLOCK, preflight verdict is also BLOCK.
|
|
338
338
|
|
|
339
339
|
### Step 6 — Generate Verdict
|
|
340
|
-
|
|
340
|
+
|
|
341
|
+
**Falsification Pass first.** Before aggregating, filter findings by disproof, not by confidence — the same rule `review` applies (`../review/SKILL.md` → Step 6):
|
|
342
|
+
|
|
343
|
+
- **DROP** a finding only when the code you read contains **direct counter-evidence** against its key claim (the null check exists, the `await` is present, the caller validates the input).
|
|
344
|
+
- **KEEP** a finding that depends on context outside the diff which you did read via tools — that context is evidence.
|
|
345
|
+
- **KEEP** a finding you can neither verify nor disprove. "Unsure" is not grounds to drop; only counter-evidence is.
|
|
346
|
+
- Dropped findings are discarded silently, never listed as considered-and-dismissed.
|
|
347
|
+
|
|
348
|
+
Type each surviving finding `OBSERVED | DERIVED | ASSUMED` per `../completion-gate/references/claim-discipline.md`. An `ASSUMED` finding — one resting on a premise you could not check — names that premise and **never escalates the verdict to BLOCK on its own**. It reports as WARN with the premise stated.
|
|
349
|
+
|
|
350
|
+
**Anchor Pass second.** Every finding you collected in Steps 1-4 carries an evidence snippet, not a line number. Resolve each one now via the Anchor Ladder defined in `../review/SKILL.md` → Step 6: `Grep` the exact snippet, retry once whitespace-normalised with the outer lines dropped, and on a second miss mark the finding `UNANCHORED`.
|
|
351
|
+
|
|
352
|
+
`UNANCHORED` behaves here exactly as it does in `review` — downgrade one level (BLOCK → WARN → INFO), report as `path (unanchored)` with the snippet inline, never drop. A finding that will not anchor **cannot carry the BLOCK verdict on its own**, for the same reason an `ASSUMED` one cannot: halting a pipeline on a claim nobody can locate spends the developer's trust faster than the bug would have.
|
|
353
|
+
|
|
354
|
+
Then aggregate all surviving findings:
|
|
341
355
|
- Any BLOCK from sentinel OR a logic issue that would cause data corruption or security bypass OR a dead interactive element (Step 4 cross-layer pairing / Step 4.5 dead-interactive check) OR a BLOCK from any domain hook → overall **BLOCK**
|
|
342
356
|
- Any missing error handling, regression risk with no tests, or incomplete feature (other than the BLOCK cases above) → **WARN**
|
|
343
357
|
- Only style or best-practice suggestions → **PASS**
|
|
@@ -346,18 +360,28 @@ Report PASS, WARN, or BLOCK. For WARN, list each item the developer must acknowl
|
|
|
346
360
|
|
|
347
361
|
## Output Format
|
|
348
362
|
|
|
349
|
-
|
|
363
|
+
````
|
|
350
364
|
## Preflight Report
|
|
351
365
|
- **Status**: PASS | WARN | BLOCK
|
|
352
366
|
- **Files Checked**: [count]
|
|
353
367
|
- **Changes**: +[added] -[removed] lines across [files] files
|
|
368
|
+
- **Unanchored**: [count — findings whose evidence did not resolve to a line]
|
|
354
369
|
|
|
355
370
|
### Logic Issues
|
|
356
|
-
- `path/to/file.ts:42` — null-deref: `user.name` accessed without null check
|
|
357
|
-
|
|
371
|
+
- `path/to/file.ts:42` — [OBSERVED] null-deref: `user.name` accessed without null check
|
|
372
|
+
```ts
|
|
373
|
+
return user.name.trim();
|
|
374
|
+
```
|
|
375
|
+
- `path/to/api.ts (unanchored)` — [ASSUMED: caller in job.ts not read] missing-await: async database call not awaited
|
|
376
|
+
```ts
|
|
377
|
+
db.orders.insert(row);
|
|
378
|
+
```
|
|
358
379
|
|
|
359
380
|
### Error Handling
|
|
360
381
|
- `path/to/handler.ts:20` — bare-catch: error swallowed silently
|
|
382
|
+
```ts
|
|
383
|
+
} catch (e) {}
|
|
384
|
+
```
|
|
361
385
|
|
|
362
386
|
### Regression Risk
|
|
363
387
|
- `utils/format.ts` — changed function used by 5 modules, 2 have tests, 3 untested (WARN)
|
|
@@ -379,12 +403,14 @@ Report PASS, WARN, or BLOCK. For WARN, list each item the developer must acknowl
|
|
|
379
403
|
|
|
380
404
|
### Verdict
|
|
381
405
|
WARN — 3 issues found (0 blocking, 3 must-acknowledge). Resolve before commit or explicitly acknowledge each WARN.
|
|
382
|
-
|
|
406
|
+
````
|
|
407
|
+
|
|
408
|
+
Findings under Logic Issues and Error Handling show their evidence block; the Regression, Completeness, and Coherence sections describe file-level or structural gaps that have no single line to anchor, so they carry a path only.
|
|
383
409
|
|
|
384
410
|
## Constraints
|
|
385
411
|
|
|
386
412
|
1. MUST check: logic errors, error handling, edge cases, type safety, naming conventions
|
|
387
|
-
2. MUST
|
|
413
|
+
2. MUST anchor every line-level finding to a verbatim evidence snippet, resolving the line via the Anchor Pass rather than recall — unresolved findings report `(unanchored)`, downgraded one level, and cannot carry BLOCK alone
|
|
388
414
|
3. MUST NOT skip edge case analysis — "happy path works" is insufficient
|
|
389
415
|
4. MUST verify error messages are user-friendly and don't leak internal details
|
|
390
416
|
5. MUST check that async operations have proper error handling and cleanup
|
|
@@ -424,7 +450,7 @@ WARN — 3 issues found (0 blocking, 3 must-acknowledge). Resolve before commit
|
|
|
424
450
|
- Cross-layer pairing checked (Step 4): every new interactive component's handler chain reaches a real endpoint/service or has an explicit UI-only scope
|
|
425
451
|
- Dead-interactive scan done (Step 4.5 UI hook) on all UI files in the diff
|
|
426
452
|
- Sentinel invoked and its output attached in Security section
|
|
427
|
-
- Structured report emitted with PASS / WARN / BLOCK verdict and
|
|
453
|
+
- Structured report emitted with PASS / WARN / BLOCK verdict, and every line-level finding anchored to a verbatim snippet (or reported `(unanchored)` and downgraded)
|
|
428
454
|
|
|
429
455
|
## Cost Profile
|
|
430
456
|
|
package/skills/rescue/SKILL.md
CHANGED
package/skills/research/SKILL.md
CHANGED
package/skills/review/SKILL.md
CHANGED
|
@@ -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
|
|
163
|
+
- Flag each finding using the **Evidence Contract** below — a snippet you copied, not a line number you remembered
|
|
137
164
|
|
|
138
|
-
|
|
165
|
+
#### Evidence Contract
|
|
139
166
|
|
|
140
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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
|
-
|
|
349
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
- `
|
|
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
|
-
- `
|
|
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
|
-
- `
|
|
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
|
|
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 |
|
|
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 |
|
|
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
|
-
-
|
|
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.
|