@mrciphersmith/keryx 0.3.119 → 0.3.120

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 (30) hide show
  1. package/dist/cli.js +7083 -3896
  2. package/dist/core.js +212 -25
  3. package/package.json +1 -1
  4. package/src/gdskills/bundled/skills/orchestration/feature-dev/SKILL.md +1 -1
  5. package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +36 -39
  6. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +6 -6
  7. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/orchestrator-prompt.md +1 -1
  8. package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.md +12 -18
  9. package/src/gdskills/bundled/skills/orchestration/task-implementer/input-contract.schema.json +8 -0
  10. package/src/gdskills/bundled/skills/orchestration/task-implementer/output-contract.schema.json +19 -0
  11. package/src/gdskills/bundled/skills/review/review-architecture/SKILL.md +1 -1
  12. package/src/gdskills/bundled/skills/review/review-backend/SKILL.md +1 -1
  13. package/src/gdskills/bundled/skills/review/review-clean-code/SKILL.md +1 -1
  14. package/src/gdskills/bundled/skills/review/review-core-boundaries/SKILL.md +1 -1
  15. package/src/gdskills/bundled/skills/review/review-flow-graph/SKILL.md +1 -1
  16. package/src/gdskills/bundled/skills/review/review-frontend/SKILL.md +3 -7
  17. package/src/gdskills/bundled/skills/review/review-frontend-conventions/SKILL.md +4 -6
  18. package/src/gdskills/bundled/skills/review/review-highload/SKILL.md +1 -1
  19. package/src/gdskills/bundled/skills/review/review-logic/SKILL.md +2 -2
  20. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +14 -0
  21. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +16 -49
  22. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +13 -0
  23. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-input.schema.json +38 -0
  24. package/src/gdskills/bundled/skills/review/review-performance/SKILL.md +1 -1
  25. package/src/gdskills/bundled/skills/review/review-pr-feedback/SKILL.md +1 -1
  26. package/src/gdskills/bundled/skills/review/review-regression/SKILL.md +2 -2
  27. package/src/gdskills/bundled/skills/review/review-security-code/SKILL.md +1 -1
  28. package/src/gdskills/bundled/skills/review/review-style/SKILL.md +1 -1
  29. package/src/gdskills/bundled/skills/review/review-testing-practices/SKILL.md +1 -1
  30. package/src/gdskills/contracts/review-finding.schema.json +25 -0
@@ -98,11 +98,13 @@ For a description-based task, omit `issue_number` and issue references in commit
98
98
 
99
99
  ```
100
100
  FIX_CONTEXT:
101
- review_feedback: structured findings from reviewer (file, line, severity, message)
101
+ review_feedback: structured findings from reviewer (id, file, line, severity, message, suggested_fix)
102
102
  original_task_ids: the tasks that introduced the findings (array)
103
103
  iteration: fix iteration number, 1..3 — the same repair bound as 5.4
104
104
  ```
105
105
 
106
+ **A reviewer's `suggested_fix` is a hypothesis, not an instruction.** Per finding: reproduce it first — a test or probe that fails on the current head (red); check the suggested fix against the plan's state × event matrix (`plan.md`, when it has one) and every caller the change reaches; then fix and show the probe green. A finding that does not reproduce is reported, not fixed. Record each divergence in `notes`: `suggested_fix diverged: <id> — <what you did instead and why>` or `did not reproduce: <id>`.
107
+
106
108
  **1.4 Validate the request against the contract:**
107
109
 
108
110
  Write the request you were handed to a file and run:
@@ -143,7 +145,7 @@ git -C "<codebase_path>" rev-parse --abbrev-ref HEAD # must equal <branch>
143
145
 
144
146
  **1.5 TDD Check (if `test_case_specs` is present):**
145
147
 
146
- If the task object contains `test_case_specs` (provided by `tests-creator`):
148
+ If the task object contains `test_case_specs` (provided by `tests-creator`, or in a fix round the reviewers' red probes):
147
149
  1. Read each test file listed in `test_case_specs.test_files`
148
150
  2. Run the tests using `test_case_specs.run_command` — confirm they FAIL
149
151
  3. If tests pass already → report `DONE_WITH_CONCERNS` (tests may not be testing the right thing)
@@ -427,6 +429,7 @@ Write full JSON to `<JOBS_ROOT>/<JOB_NAME>/results/<task_id>.json`:
427
429
  "skill_drift": "<none | stale: <module>/<skill> — <what diverged> | missing: <module> should have a project-skill>",
428
430
  "noticed_not_touched": [{ "what": "<problem>", "where": "<path|symbol>" }],
429
431
  "assumptions": ["<what you assumed where the task did not say>"],
432
+ "behavior_changes": [{ "description": "<what now behaves differently>", "user_visible": true, "needs_operator": true, "alternatives": ["<option you did not take>"] }],
430
433
  "not_touched": [{ "path": "src/path/file.ts", "reason": "<why left alone>" }],
431
434
  "notes": "<any warnings, blockers, or additional context>"
432
435
  }
@@ -434,23 +437,14 @@ Write full JSON to `<JOBS_ROOT>/<JOB_NAME>/results/<task_id>.json`:
434
437
 
435
438
  Set `skill_drift` from Phase 2.0b: if the project-skill you used was not `fresh`, or the code you wrote diverged from what a skill documents, name the skill and the divergence. The orchestrator uses this to decide whether to trigger `skills learn` (do NOT run `learn` yourself — it is a mutating step the orchestrator dispatches; see `rules/core/skill-lifecycle.mdc`).
436
439
 
437
- Three of those fields have a bar, and a field that collects noise trains the
438
- reader to skim all three. Omit one rather than pad it. The trio is adapted (MIT;
439
- see THIRD_PARTY_NOTICES.md) as contract fields rather than a free-text template; the bars below are ours.
440
+ Four of those fields have a bar, and a field that collects noise trains the reader to skim all of them. Omit one rather than pad it. The first three are adapted (MIT; see THIRD_PARTY_NOTICES.md) as contract fields rather than a free-text template; the bars below are ours.
440
441
 
441
- - `assumptions` — what you assumed where the task was silent AND acted on. If
442
- the assumption being wrong would not change a line you wrote, it is a hedge,
443
- not an assumption; leave it out.
444
- - `noticed_not_touched` — a problem in code you actually read, stated so a
445
- follow-up task could be written from it alone, with `where` it lives. Not a
446
- dumping ground for every smell seen in passing, and not style opinion.
447
- - `not_touched` — of the files this task was aimed at (`target_files`), the ones
448
- absent from `files_modified`/`files_created`/`files_deleted`, each with why.
449
- It answers that one question and no other: a reader subtracts those lists and
450
- this one from `target_files`, and flags the remainder as an unexplained omission.
442
+ - `assumptions` — what you assumed where the task was silent AND acted on. If the assumption being wrong would not change a line you wrote, it is a hedge, not an assumption; leave it out.
443
+ - `noticed_not_touched` — a problem in code you actually read, stated so a follow-up task could be written from it alone, with `where` it lives. Not a dumping ground for every smell seen in passing, and not style opinion.
444
+ - `not_touched` — of the files this task was aimed at (`target_files`), the ones absent from `files_modified`/`files_created`/`files_deleted`, each with why. It answers that one question and no other: a reader subtracts those lists and this one from `target_files`, and flags the remainder as an unexplained omission.
445
+ - `behavior_changes` — behavior you chose where the task and the plan's state × event matrix did not decide it. **Every user-visible tradeoff goes here with `user_visible: true, needs_operator: true`** and the `alternatives` you rejected: message wording, where an error appears, which state resets or survives a switch, retry or reload. Never only in `assumptions`, `notes` or a journal line — the orchestrator asks the operator before it accepts a result carrying one, and a tradeoff it cannot see is accepted unasked.
451
446
 
452
- Then check the shape and record the file — two commands, both of which refuse
453
- rather than warn:
447
+ Then check the shape and record the file — two commands, both of which refuse rather than warn:
454
448
 
455
449
  ```bash
456
450
  keryx skills contracts validate <JOBS_ROOT>/<JOB_NAME>/results/<task_id>.json \
@@ -489,7 +483,7 @@ The step's own status is the orchestrator's to write (`keryx job step <JOB_NAME>
489
483
 
490
484
  Return a compact STATUS response following `rules/core/subagent-status-protocol.md`.
491
485
  **Do NOT include the full JSON block inline** — the orchestrator reads the result file when it needs details.
492
- The inline response must contain only: STATUS line + Completed bullets + Files changed + Verification summary.
486
+ The inline response must contain only: STATUS line + Completed bullets + Files changed + Verification summary + a `## Behavior changes` list (each entry's description, `user_visible`, `needs_operator`, alternatives) whenever `behavior_changes` is non-empty — a flow dispatch writes no result file, so this list is the only copy the orchestrator reads. Any `needs_operator: true` entry makes the status at best `DONE_WITH_CONCERNS`, never `DONE`.
493
487
 
494
488
  **Status classification:**
495
489
  - `success` → `STATUS: DONE`, or `DONE_WITH_CONCERNS` when work that cleared the
@@ -149,6 +149,14 @@
149
149
  "additionalProperties": false,
150
150
  "required": ["file", "severity", "message"],
151
151
  "properties": {
152
+ "id": {
153
+ "type": "string",
154
+ "description": "The finding's id in the review package, cited back in the result's `notes` when the fix diverged from `suggested_fix` or the finding did not reproduce"
155
+ },
156
+ "suggested_fix": {
157
+ "type": "string",
158
+ "description": "The reviewer's proposed fix. A hypothesis, not an instruction: SKILL.md 1.3 reproduces the finding first and checks this against the plan's state × event matrix before applying anything"
159
+ },
152
160
  "file": {
153
161
  "type": "string",
154
162
  "description": "File path where the issue was found"
@@ -65,6 +65,25 @@
65
65
  "description": "What was assumed where the task did not say. A wrong assumption is the usual cause of a rejected result, so these are the first thing a reviewer should check. Read only by a human — nothing in the result or the request can confirm an assumption, so this is deliberately an array of sentences rather than a structure that would imply a check nobody performs.",
66
66
  "items": { "type": "string", "minLength": 1 }
67
67
  },
68
+ "behavior_changes": {
69
+ "type": "array",
70
+ "description": "Behavior the implementer chose where the task and the plan's state × event matrix did not decide it. Every user-visible tradeoff (message wording, error placement, which state resets or survives) is listed with `user_visible: true, needs_operator: true`; flow-orchestrator Phase 2 asks the operator before accepting a result carrying such an entry, so a tradeoff recorded only in `assumptions` or `notes` is one nobody decided.",
71
+ "items": {
72
+ "type": "object",
73
+ "additionalProperties": false,
74
+ "required": ["description", "user_visible", "needs_operator"],
75
+ "properties": {
76
+ "description": { "type": "string", "minLength": 1, "description": "What now behaves differently, stated as the user or caller would observe it" },
77
+ "user_visible": { "type": "boolean", "description": "A user of the product can see the difference" },
78
+ "needs_operator": { "type": "boolean", "description": "The choice is a product decision the operator must confirm before the result is accepted" },
79
+ "alternatives": {
80
+ "type": "array",
81
+ "description": "The options not taken, so the operator chooses between real alternatives rather than approving the one shown",
82
+ "items": { "type": "string", "minLength": 1 }
83
+ }
84
+ }
85
+ }
86
+ },
68
87
  "not_touched": {
69
88
  "type": "array",
70
89
  "description": "Files a reader would expect in `files_modified` and will not find there, each with the reason. `path` is a separate field because this is machine-checkable: a reader holding the request can subtract `files_modified`/`files_created`/`files_deleted` and this list from `task.target_files` and flag whatever is left as an unexplained omission.",
@@ -26,7 +26,7 @@ license: "MIT"
26
26
 
27
27
  Specialized reviewer for **architectural patterns and structural decisions**.
28
28
  Reviews only changes introduced in the current branch (merge-base to HEAD).
29
- Focuses exclusively on verifiable structural violations — not style opinions or hypothetical concerns. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs.
29
+ Focuses exclusively on verifiable structural violations — not style opinions or hypothetical concerns. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs. It runs no command unless its payload's `allowed_tools` says `run-tests`.
30
30
 
31
31
  ---
32
32
 
@@ -26,7 +26,7 @@ license: "MIT"
26
26
 
27
27
  Focused backend reviewer covering NestJS patterns, REST API design, database access patterns,
28
28
  and TypeScript correctness for service-layer code. This skill does NOT duplicate security or
29
- performance checks — those belong to `review-security-code` and `review-performance`. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs.
29
+ performance checks — those belong to `review-security-code` and `review-performance`. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs. It runs no command unless its payload's `allowed_tools` says `run-tests`.
30
30
 
31
31
  ---
32
32
 
@@ -27,7 +27,7 @@ license: "MIT"
27
27
  # Review: Clean Code + SOLID
28
28
 
29
29
  Specialized reviewer for **Clean Code principles** (Robert C. Martin) and **SOLID** at the
30
- function and class level. Focuses on code in the current branch diff only. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs.
30
+ function and class level. Focuses on code in the current branch diff only. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs. It runs no command unless its payload's `allowed_tools` says `run-tests`.
31
31
 
32
32
  The goal is not stylistic nitpicking — every finding here points to a concrete
33
33
  maintainability, readability, or extensibility problem that will cause friction
@@ -24,7 +24,7 @@ license: "MIT"
24
24
  # Review — Core Boundaries
25
25
 
26
26
  Reviewer for shared infrastructure modules. A core module should provide stable foundations
27
- used by feature modules; it should not accumulate feature-specific behaviour. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs.
27
+ used by feature modules; it should not accumulate feature-specific behaviour. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs. It runs no command unless its payload's `allowed_tools` says `run-tests`.
28
28
 
29
29
  ---
30
30
 
@@ -24,7 +24,7 @@ license: "MIT"
24
24
  # Review — Flow Graph Abstractions
25
25
 
26
26
  Reviewer for reusable ReactFlow/graph integration layers. Use it when a repository has a shared
27
- graph surface consumed by domain modules. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs.
27
+ graph surface consumed by domain modules. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs. It runs no command unless its payload's `allowed_tools` says `run-tests`.
28
28
 
29
29
  ---
30
30
 
@@ -2,18 +2,14 @@
2
2
  name: review-frontend
3
3
  model_tier: standard
4
4
  description: "Use when a frontend review is requested, checking React component patterns, MobX state management (observer, actions, computed, reactions, lifecycle), View-Store boundaries, and TypeScript safety in changed frontend code. NOT for backend patterns, security vulnerabilities, performance bottlenecks, or cross-layer architecture."
5
- triggers:
6
- - "frontend review"
7
- - "component review"
8
- - "ui review"
9
- - "review React"
10
- - "review components"
5
+ triggers: ["frontend review", "component review", "ui review", "review React", "review components"]
11
6
  metadata:
12
7
  author: "MrCipherSmith"
13
8
  version: "1.1.0"
14
9
  category: "review"
15
10
  compatible_harnesses: "cursor,codex,zed,opencode,claude"
16
11
  stack_requires: "react,mobx"
12
+ overlap_group: "frontend"
17
13
  license: "MIT"
18
14
  ---
19
15
 
@@ -21,7 +17,7 @@ license: "MIT"
21
17
 
22
18
  ## Purpose
23
19
 
24
- Validates frontend-specific patterns in the changed code of the current branch. Covers React component correctness (observer wrapping, MVVM boundary, useEffect misuse, accessibility basics, key props), full MobX store patterns (structure, member ordering, accessibility modifiers, observable collections, reactions, async actions, bidirectional sync, lifecycle, API placement), and TypeScript safety. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs.
20
+ Validates frontend-specific patterns in the changed code of the current branch. Covers React component correctness (observer wrapping, MVVM boundary, useEffect misuse, accessibility basics, key props), full MobX store patterns (structure, member ordering, accessibility modifiers, observable collections, reactions, async actions, bidirectional sync, lifecycle, API placement), and TypeScript safety. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs. It runs no command unless its payload's `allowed_tools` says `run-tests`.
25
21
 
26
22
  This skill consolidates what was previously split between `code-style-review` (frontend parts) and `code-mobx-store-review`. It supersedes both for frontend changes.
27
23
 
@@ -11,16 +11,14 @@ description: |
11
11
  NOT for: generic React and MobX correctness with no local rule behind it
12
12
  (review-frontend), what the CSS actually renders (review-layout), or naming and
13
13
  readability as a matter of taste (review-style).
14
- triggers:
15
- - "frontend conventions"
16
- - "local frontend rules"
17
- - "CLAUDE frontend"
18
- - "review frontend conventions"
14
+ triggers: ["frontend conventions", "local frontend rules", "CLAUDE frontend", "review frontend conventions"]
19
15
  metadata:
20
16
  author: "MrCipherSmith"
21
17
  version: "1.0.0"
22
18
  category: "review"
23
19
  stack_requires: "react,mobx"
20
+ overlap_group: "frontend"
21
+ overlap_rank: "generic"
24
22
  license: "MIT"
25
23
  ---
26
24
 
@@ -28,7 +26,7 @@ license: "MIT"
28
26
 
29
27
  Reviewer for frontend repository conventions that are more specific than generic React/MobX
30
28
  correctness. Use it alongside `review-frontend`, `review-style`, `review-performance`, and
31
- `review-logic`. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs.
29
+ `review-logic`. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs. It runs no command unless its payload's `allowed_tools` says `run-tests`.
32
30
 
33
31
  Before reviewing, read the nearest project guide files if they exist: root `CLAUDE.md`,
34
32
  `AGENTS.md`, `.junie/guidelines.md`, `ARCHITECTURE.md`, and module-level `CLAUDE.md` files.
@@ -28,7 +28,7 @@ license: "MIT"
28
28
 
29
29
  Specialized reviewer for **code that will run under significant concurrent load**.
30
30
  Focuses on correctness under concurrency, resource contention, back-pressure, failure
31
- modes at scale, and distributed systems invariants — not general performance profiling. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs.
31
+ modes at scale, and distributed systems invariants — not general performance profiling. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs. It runs no command unless its payload's `allowed_tools` says `run-tests`.
32
32
 
33
33
  Reviews only changes introduced in the current branch (merge-base to HEAD).
34
34
 
@@ -27,7 +27,7 @@ license: "MIT"
27
27
 
28
28
  Specialized reviewer for **logic correctness and algorithmic soundness**.
29
29
  Inherits from the original `code-ai-review` + `code-learned-review` correctness phases
30
- and unifies them into a single focused pass. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs.
30
+ and unifies them into a single focused pass. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs. It runs no command unless its payload's `allowed_tools` says `run-tests`.
31
31
 
32
32
  This reviewer does not care about formatting, naming, or architecture opinions.
33
33
  Every finding must describe an observable, reproducible defect or a spec gap.
@@ -193,7 +193,7 @@ earlier review round, because there the finding is recorded as closed and is not
193
193
 
194
194
  ## Orchestrated Review Contract
195
195
 
196
- When dispatched by `review-orchestrator`, follow the provided `reviewer-input.schema.json` payload. Return a `REVIEW_RESULT` object compatible with `.metaproject/skills/gdskills/review/review-orchestrator/reviewer-finding.schema.json`, then a concise markdown summary. Keep findings evidence-based, include concrete `suggested_fix` for every blocker/major, and return `NEEDS_CONTEXT` instead of guessing when required context is missing.
196
+ When dispatched by `review-orchestrator`, follow the provided `reviewer-input.schema.json` payload. Return a `REVIEW_RESULT` object compatible with `.metaproject/skills/gdskills/review/review-orchestrator/reviewer-finding.schema.json`, then a concise markdown summary. Keep findings evidence-based, include concrete `suggested_fix` for every blocker/major, and return `NEEDS_CONTEXT` instead of guessing when required context is missing. On a FIX slice, also return `prior_dispositions: [{id, disposition}]` for every attached finding id.
197
197
 
198
198
  ---
199
199
 
@@ -687,6 +687,20 @@ sync as abandoned"* is a claim; the disposition is whether the branch it renamed
687
687
  is reachable and pinned. On a recorded round, two such commits asserted behaviour
688
688
  on lines no test could reach.
689
689
 
690
+ ### Delta rounds — what `review start` hands you
691
+
692
+ `keryx review start` detects the round and writes `<package>/round.json` and `<package>/prior_findings.json`. It searches every source, because the earlier round may have run on another machine: the local package, git history (tracked package files), the PR's `keryx round` block, PR comments, and the flow's packages. `round.json` carries `round`, `source`, `sourcesTried`, `previous`, `mode`, `range`, `fixHunks`, `newHunks` and `carried`.
693
+
694
+ | `mode` | `range` | Review |
695
+ |---|---|---|
696
+ | `first` | merge-base..head | A first round. `sourcesTried` records what was searched when nothing was found. |
697
+ | `delta` | previous head..head | `fix` slices (a hunk that answers a prior finding): `review-logic`, `review-regression`, the authors of the attached findings and the verifier, each with its `prior_findings`. `new` slices: round 1, with the roles the slice names. Unchanged code: not re-reviewed. `carried` findings keep their dispositions. |
698
+ | `fallback-first` | merge-base..head | The previous head is not an ancestor (force-push, rebase). Review the whole range with the prior findings attached. |
699
+
700
+ A fix hunk still has a radius: enumerate what names the thing it changed (`review_context.scope.files`), as in any fix round. The attached findings are the contract: each leaves the round with a disposition, below.
701
+
702
+ A reviewer on a FIX slice reports those dispositions itself: its `REVIEW_RESULT` carries `prior_dispositions: [{id, disposition}]` with one entry for every attached finding id (`disposition` is `closed`, `open`, `partial`, `regressed` or `withdrawn`), judged against the code. `keryx review ingest` reads the array into the ledger; an attached id with no entry stays undisposed.
703
+
690
704
  ### A fix is a change, and changes get reviewed
691
705
 
692
706
  The most expensive class in a multi-round review is **the defect the fix
@@ -27,7 +27,7 @@ license: "MIT"
27
27
  1. **Can it spawn?** The round needs a subagent-spawn tool (`Agent`, `spawn_subagent` or the host's equivalent) and depth budget left: Claude Code nests subagents at most 3 deep, and at the limit `Agent` is withheld. A host with no spawn tool at all (Codex and other AGENTS.md hosts) does not stop here and does not review alone: it runs the round through the autonomous runner, `keryx review run --autonomous --target pr --ref <pull request URL>` (no pull request: `--target branch --ref <base> [--head <commit>]`), and relays its verdict and exit code (see "Hosts"). Only a session that has spent the depth budget (a nested subagent) stops; its whole output is this line, with no tool calls and no review of your own:
28
28
  `STATUS: BLOCKED nested_dispatch_unavailable — run this skill in the main session`
29
29
  2. **Is a round needed?** List the files with `git diff --name-status --no-renames <base>...HEAD`, so a rename shows both paths. A diff where EVERY file is prose or machine-written data ends here with one line, `round not needed: <reason>`, and no reviewer. Prose is exactly: README or CHANGELOG at any depth; CONTRIBUTING, LICENSE, SECURITY or CODE_OF_CONDUCT at the top level; and under `docs/`, `.metaproject/wiki`, `.metaproject/reviews` or a flow's `journal.md`, a file with extension md, rst or adoc whose name holds a lowercase letter (an all-uppercase name such as AGENT or WARP is steering by convention). Machine-written data is a file under `coverage/` or `__generated__/` with extension lcov, info or map only; json, txt, html and md there are code. Steering is code wherever it sits, `docs/` included: a directory (singular or plural) named agent, skill, command, rule, prompt, instruction, persona, steering or hook, any path segment starting with a dot (`.claude`, `.cursorrules`, `.mcp.json`), a file name starting with SKILL, AGENT, CLAUDE, GEMINI, QWEN, CODEX, WARP, CRUSH, JULES, KNOWLEDGE, GUIDELINES, CONVENTIONS or copilot-instructions, ending in .local, .override, .instructions, .prompt, .agent or .chatmode before the extension, or containing prompt, instruction or system. A flow's `description.md`, `acceptance-criteria.md` and `tasks/` are gate inputs, so they are code. Every other file counts as code too, including lockfiles, committed `dist/` output, CI, auth, migrations, config JSON, and any path with `..`, a backslash, a leading `./` or `//`. `classifyRoundNeed` in `src/review/round-need.ts` is the tested reference of this rule; the host executes this text, and nothing in the CLI calls the function yet. `--all`, an explicit reviewer list or a gating round forces a round.
30
- 3. **Round ceiling.** 5 rounds (`REVIEW_ROUND_CAP`) over the same `merge-base..HEAD`. Past it: interactive, ask the operator; unattended, `STATUS: BLOCKED round_cap`.
30
+ 3. **Round ceiling.** 5 rounds (`REVIEW_ROUND_CAP`) over the same change. Past it: interactive, ask the operator; unattended, `STATUS: BLOCKED round_cap`.
31
31
  4. **PR state, shown before dispatch.** On a PR target read its state (merged, approved, CI) and tell the operator the risk that it merges before the round ends: a verdict ready at minute 55 and held to minute 119 lost the race to the merge. Depth and the estimate need the package and the slice manifest, so they come later: after `review scope`, `review start` and `review slice`, before the first reviewer starts ("Round plan" steps 5-6; `SKILL.detail.md` § "Preflight 4").
32
32
 
33
33
  ## Hosts — the same round, different tools
@@ -48,13 +48,13 @@ license: "MIT"
48
48
 
49
49
  ## Round plan — the order on every host
50
50
 
51
- 1. `keryx review scope --ref <base> --json > .metaproject/data/review/scope.json` (Step 3).
52
- 2. `keryx review start --target <kind> --ref <ref> --scope .metaproject/data/review/scope.json --memory-query "<query>" --host <host>`, plus `--unattended` when nobody is at the keyboard.
51
+ 1. `keryx review plan --target <pr-url|branch> [--ref <base>] [--head <sha>] --host <host> [--memory-query "<query>"] [--unattended]` is the one command: it runs start, scope (Step 3), blast-radius (Step 3b), slice, required and estimate, writes `<package>/plan.json` and dispatches nothing. Read the round first (Step 1a), then dispatch from step 7.
52
+ 2. Without `plan`, run those stages one by one in this order: `keryx review scope --ref <base> --json > .metaproject/data/review/scope.json`, then `keryx review start --target <kind> --ref <ref> --scope .metaproject/data/review/scope.json --memory-query "<query>" --host <host>` (plus `--unattended` when nobody is at the keyboard); `start` searches every prior-round source (local package, git history, the PR's keryx round block, PR comments, flow) and writes `round.json`.
53
53
  3. `keryx review blast-radius --ref <base> --package <package> --json > blast-radius.json` (Step 3b). Reasoned omissions (`unrelated-symbol`, `hub-damped`, `text-hub`, `test-consumer`) are context, not scope; only cap drops become chunks. Run the tests in `<package>/tests-to-run.json` with the command the radius prints, instead of reading them, and hand the output to the reviewers as context.
54
54
  4. `keryx review slice --ref <base> --package <package> --host <host>`: each slice carries `roles`, `depth` (deep or light) and `audit`.
55
55
  5. `keryx review required --mode all --scope .metaproject/data/review/scope.json --depth auto --package <package>`.
56
56
  6. `keryx review estimate --package <package>`. Over threshold, the operator confirms (card `review_confirm_plan`, or `keryx review confirm-plan --package <package>` in their terminal); `dispatch-check` and `ledger build` refuse an unconfirmed plan, so never dispatch one. Unattended: report the estimate and stop with `STATUS: BLOCKED plan_over_threshold`.
57
- 7. Dispatch by slice roles: each role gets the slices that name it; a deep slice names every roster role its path gates allow, an `audit` one the whole core set. A major or worse on a light slice comes back from `retry-plan` as an escalation: dispatch the core roles on it.
57
+ 7. Dispatch by slice roles: each role gets the slices that name it; a deep slice names every roster role its path gates allow, an `audit` one the whole core set. A major or worse on a light slice comes back from `review next` as an `escalated` dispatch: dispatch the core roles on it.
58
58
  8. Verifier on the first blocker or major (Step 10). It runs no command itself: run `keryx review verify-run --package <package> --finding <id> -- <command>` and hand it the saved `research-evidence/verify-<id>-<n>.txt` as context.
59
59
  9. `keryx review verdict <package>` to the operator, then the tail: mutation pass, `ledger build`, `ingest`, `readiness`, `complete`.
60
60
 
@@ -106,6 +106,7 @@ Publish this checklist with `plan_set` under the ids `step-0`…`step-14` once,
106
106
  ## How to spawn (Steps 8 and 10)
107
107
 
108
108
  - One `Agent` call per reviewer, all calls of a wave in a single message, so the wave runs in parallel.
109
+ - Reviewers are read-only: they read the diff and the files and run no command. Only you (through `keryx review verify-run --timeout <seconds>`), the verifier and at most one testing reviewer run commands, inside a time budget. Each payload carries `allowed_tools` (`read-only` or `run-tests`) and `exec_budget_s`.
109
110
  - Use the reviewer's exact `subagent_type`. If the runtime has no such type, use `general-purpose` and name the reviewer's `SKILL.md` path in the prompt. Neither available: that reviewer is `BLOCKED`.
110
111
  - Prompts carry pointers, not pasted files — the diff, the SHAs, the PR, the schema paths — and never paraphrase a contract. On keryx shell the slices travel in `context_files` instead (below). Run `keryx review dispatch-check --payload <file> --manifest <package>/slices/manifest.json --package <package>` on every payload before a mass dispatch; it fails `REVIEWER_INPUT_PATH_MISSING` on a missing `skill_path` or `context_doc`.
111
112
  - A reply is a result only if it starts with a `STATUS:` line and carries a `REVIEW_RESULT` block matching `reviewer-finding.schema.json`; anything else is `BLOCKED`. The one exception: a reply or dispatch error of `STATUS: RATE_LIMITED` is handled by the Rate limits rule under Control, never marked `BLOCKED`.
@@ -119,7 +120,7 @@ A reviewer starts in the project root and reads project files; a path outside th
119
120
  1. **Slice first.** `keryx review slice --ref <base> --package <package> --host <host>` after `keryx review start`. It writes `<package>/slices/slice-NN.diff` files of at most 150000 bytes each and a `manifest.json` carrying a wave budget (`--wave-bytes`, never below the slice ceiling; default by `--host`: 60000 on keryx-shell, 150000 elsewhere): what ONE reviewer dispatch may total, so a role owning many slices is dispatched in slice waves that each fit the child deadline. On keryx shell with a large diff pass a small `--max-bytes` (for example 18000) and `--wave-bytes` (for example 54000) so no wave outruns a reviewer's five minutes; ledgers (csv, lockfiles, snapshots, generated files) are listed under `omissions` with a reason — list them in the report as not reviewed, never as clean.
120
121
  2. **Slices travel by path; on keryx shell, only by `context_files`.** The slices sit under the project (`<package>/slices`). On keryx shell pass each reviewer its slice paths in `spawn_subagent`'s `context_files`: the host copies the text into that reviewer's task, so you never paste or retype a slice. Elsewhere name the slice paths in the prompt. Put the `slices` ids in its reviewer-input. A dispatch that names a git range but no slice leaves the reviewer with nothing to read — that is `BLOCKED`, not a review.
121
122
  3. **Check before dispatch, every payload.** `keryx review dispatch-check --payload <file> --manifest <package>/slices/manifest.json --package <package>` for the wave; exit 1 means a payload names no slice, is over the wave ceiling (dispatch fewer slices, then the next wave), names a `skill_path` or `context_doc` that does not exist (`REVIEWER_INPUT_PATH_MISSING`), belongs to an unconfirmed over-threshold plan (`REVIEWER_INPUT_PLAN_UNCONFIRMED`), or breaks `reviewer-input.schema.json`. Fix it; do not dispatch around it.
122
- 4. **One batch.** Dispatch the whole wave — up to 10 reviewers — in one message, not one at a time. On keryx shell every round reviewer's `spawn_subagent` carries `review_wave: {"package": "<package>", "pass": "round"}` (a wave outside it counts against the turn's 32 children) and a `timeout_ms` that fits its wave: the default 300000 is sized for a wave of 60000 bytes, so a larger `--wave-bytes` needs a larger `timeout_ms`. Give each reviewer `max_rounds` of at least 40 (the default); a reviewer that spends its rounds reading the skill and the schema returns nothing. **Incomplete means the next wave while it makes progress, Not run when it stalls.** Every reviewer result lists the `slices` it covered, in its `REVIEW_RESULT` object, as ids (`"slices": ["slice-01", "slice-02"]`); an object with an `id` and an optional `coverage` note (`{"id": "slice-01", "coverage": "read in full"}`) is read the same way. An object with a `status` counts only when it is DONE, COMPLETE, COVERED or OK (any case); a slice marked NOT_READ, INCOMPLETE, SKIPPED or PARTIAL is not covered, so give a slice you did not read no `status` of that kind and leave it out. An entry with no id counts for nothing. **One driver file per dispatch.** Write `<package>/drivers/<reviewer>.json` with `{"reviewer": "<name>", "slices": [<ids dispatched>], "result": "raw/<reviewer>.txt", "definition": "<path of the reviewer's SKILL.md>"}`: `ledger build` takes the reviewer's rule file from `definition` (a `skill_path` there, or inside a `payload` object, is read too) and the slices it was handed from `slices`. Only reviewer replies belong in `raw/`; a verification payload or a note filed there is reported as set aside and left in place, but it still costs a step. On `INCOMPLETE`, `BLOCKED` or `NEEDS_CONTEXT` (never accepted as a finished scope), and on a `DONE` that left slices open, `keryx review retry-plan --manifest <package>/slices/manifest.json --result <package>/raw/<reviewer>.txt --package <package>` decides and hands out the next wave of only the slices still uncovered (`--next-wave` plans one without a result). A result that covered a new slice spends no attempt; two results in a row with nothing new, or six failed rounds, print the `- **Not run:** …` line to paste into the report, and a result with nothing new first has its open slices cut in half. Dispatch each wave, save its reply, and run `retry-plan` again until it says `accepted`; after a crash or timeout run `keryx review retry-plan --manifest <package>/slices/manifest.json --package <package> --reviewer <id> --next-wave` first, which collects late results from `<package>/raw/` (per reviewer) and never re-hands a finished slice. Only the operator can accept a smaller set than that (`review_override_reviewers`, or `keryx review override-reviewers` in their terminal). An unfinished reviewer is never counted as a clean pass. **Persist every result, on every host.** On keryx shell pass each reviewer `save_result_to: <package>/raw/<reviewer>.txt`: the host writes its full final text there. Elsewhere the prompt tells the reviewer to write its whole reply to `<package>/raw/<reviewer>.txt` before it answers. Either way `review complete` has raw evidence after the transcript is cut, and you parse that file, never the tool output. Build `research.json` with `keryx review ledger build --package <package>` — never by hand: it reads the saved results, `scope-files.json` and the retry state, marks a reviewer complete only when its result is finished and its payloads cover the scope, and prints each gap with the exact next command and the waves left (a `retry-plan --next-wave` call, or a `--dispositions` entry for a raw finding). Run `keryx review ingest --research <package>/research.json` once it prints READY (exit 0). A refused `review complete` names what is missing: fix that with a command and run it again; it is never `blocked`. `--head` wants a SHA (`git rev-parse HEAD`), not `HEAD`.
123
+ 4. **One batch.** Dispatch the whole wave — up to 10 reviewers — in one message, not one at a time. On keryx shell every round reviewer's `spawn_subagent` carries `review_wave: {"package": "<package>", "pass": "round"}` (a wave outside it counts against the turn's 32 children) and a `timeout_ms` that fits its wave: the default 300000 is sized for a wave of 60000 bytes, so a larger `--wave-bytes` needs a larger `timeout_ms`. Give each reviewer `max_rounds` of at least 40 (the default); a reviewer that spends its rounds reading the skill and the schema returns nothing. **Incomplete means the next wave while it makes progress, Not run when it stalls.** Every reviewer result lists the `slices` it covered, in its `REVIEW_RESULT` object, as ids (`"slices": ["slice-01", "slice-02"]`); an object with an `id` and an optional `coverage` note (`{"id": "slice-01", "coverage": "read in full"}`) is read the same way. An object with a `status` counts only when it is DONE, COMPLETE, COVERED or OK (any case); a slice marked NOT_READ, INCOMPLETE, SKIPPED or PARTIAL is not covered, so give a slice you did not read no `status` of that kind and leave it out. An entry with no id counts for nothing. **One driver file per dispatch.** Write `<package>/drivers/<reviewer>.json` with `{"reviewer": "<name>", "slices": [<ids dispatched>], "result": "raw/<reviewer>.txt", "definition": "<path of the reviewer's SKILL.md>"}`: `ledger build` takes the reviewer's rule file from `definition` (a `skill_path` there, or inside a `payload` object, is read too) and the slices it was handed from `slices`. Only reviewer replies belong in `raw/`; a verification payload or a note filed there is reported as set aside and left in place, but it still costs a step. On `INCOMPLETE`, `BLOCKED` or `NEEDS_CONTEXT` (never accepted as a finished scope), and on a `DONE` that left slices open, `keryx review retry-plan --manifest <package>/slices/manifest.json --result <package>/raw/<reviewer>.txt --package <package>` decides and hands out the next wave of only the slices still uncovered (`--next-wave` plans one without a result). A result that covered a new slice spends no attempt; two results in a row with nothing new, or six failed rounds, print the `- **Not run:** …` line to paste into the report, and a result with nothing new first has its open slices cut in half. `keryx review slice --package <package>` already wrote one driver per reviewer x wave (`drivers/<reviewer>-w<n>.json`: slice ids, absolute slice paths, `result` path; the dispatcher adds `model`). Dispatch each wave in one batch, save each reply to its driver's `result`, and after each batch run `keryx review next --package <package>` (never `retry-plan` once per reviewer): it returns every reviewer's next wave at once, with reason `missing`, `retry`, `stalled` or `escalated`, or `done`. After a crash or timeout run it first: it collects late results from `<package>/raw/` and never re-hands a finished slice. Only the operator can accept a smaller set than that (`review_override_reviewers`, or `keryx review override-reviewers` in their terminal). An unfinished reviewer is never counted as a clean pass. **Persist every result, on every host.** On keryx shell pass each reviewer `save_result_to: <package>/raw/<reviewer>.txt`: the host writes its full final text there. Elsewhere the prompt tells the reviewer to write its whole reply to `<package>/raw/<reviewer>.txt` before it answers. Either way `review complete` has raw evidence after the transcript is cut, and you parse that file, never the tool output. Build `research.json` with `keryx review ledger build --package <package>` — never by hand: it reads the saved results, `scope-files.json` and the retry state, marks a reviewer complete only when its result is finished and its payloads cover the scope, and prints each gap with the exact next command and the waves left (a `retry-plan --next-wave` call, or a `--dispositions` entry for a raw finding). Run `keryx review ingest --research <package>/research.json` once it prints READY (exit 0). A refused `review complete` names what is missing: fix that with a command and run it again; it is never `blocked`. `--head` wants a SHA (`git rev-parse HEAD`), not `HEAD`.
123
124
  5. **Never print a scope or a slice.** The tool output of one call is cut at about 20 KB and `keryx review scope` of a large PR prints hundreds of KB. Run it as `keryx review scope --ref <base> --json > .metaproject/data/review/scope.json` and read the counts from the file (`keryx ctx read`, or `jq`); take `<base>` from `git symbolic-ref refs/remotes/origin/HEAD`, not a guessed `origin/main`. Create `.metaproject/data/review/` first (`mkdir -p`) — a missing directory is a command to run, not a blocker. **Machine artifacts by direct write.** Save slices, payloads and results by redirection or `keryx ctx run --raw -- <command>`; the summarized form of `keryx ctx run` drops the bytes a later step parses.
124
125
  6. **Write mutants whole.** A testing reviewer's `mutant_candidates` cover every changed branch, code downstream of the diff included. Write them to `research-evidence/mutants.json` unchanged; you may not narrow them, and a candidate no pass has run still blocks the coverage gate (`SKILL.detail.md`, mutation pass).
125
126
  7. **Measure before `review-layout`.** Run the browser measurements (Playwright/Storybook) yourself and pass them in its first dispatch (`context_files` on keryx shell, by path elsewhere); they count as evidence when the command and viewport are named. A second `NEEDS_CONTEXT` from one role is Not run (`layout: no engine`), not another refill (`SKILL.detail.md`).
@@ -130,7 +131,7 @@ A reviewer starts in the project root and reads project files; a path outside th
130
131
  - **Waves.** Split by diff domain (`core` = logic, security, architecture; `domain` = frontend, backend, conventions; `support` = style, tests), in that order; A/B/C below keep their dependency order, except Wave C, which starts early and overlaps A and B (see Wave C). A full or `--all` round runs at most 10 reviewers per wave (`MAX_REVIEWERS_PER_WAVE_FULL`).
131
132
  - **Rate limits.** On a reviewer dispatch that fails with a rate-limit or overloaded error (reported as `STATUS: RATE_LIMITED`, a harness signal, not a schema status), halve the wave, requeue the rest and honour `retry-after`, at most 300 seconds (`MAX_RETRY_AFTER_SECONDS`). After at most two halvings (`MAX_RATE_HALVINGS`) stop with `STATUS: BLOCKED rate_limited`, naming the queue left.
132
133
  - **Early verdict.** When every reviewer and the verifier are done, save every verifier pass to `research-evidence/verifications.json`, then run `keryx review verdict <package>` (it reads `<package>/raw`; the shared `.metaproject/data/review/raw` only when passed as `--raw`, which mixes in other rounds) and give the operator the verdict then; refuted findings are excluded only once `verifications.json` exists. The mutation pass, ledger and `keryx review complete` continue as the tail, and every managed round finishes them. The verdict is interim: the round is not done until `complete` (`SKILL.detail.md` § "Early verdict").
133
- - **Recovery.** After a timeout or crash: collect, validate, ingest, then dispatch only what is missing with `keryx review retry-plan --manifest <package>/slices/manifest.json --package <package> --reviewer <id> --next-wave` (it prints `collected late: N`). Never re-dispatch a wave whose results are on disk; after a second timeout of the same reviewer change the task (fewer slices, narrower focus), not only the diff size (`SKILL.detail.md` § "Recovery").
134
+ - **Recovery.** After a timeout or crash: collect, validate, ingest, then dispatch only what is missing with `keryx review next --package <package>` (all reviewers at once). Never re-dispatch a wave whose results are on disk; after a second timeout of the same reviewer change the task (fewer slices, narrower focus), not only the diff size (`SKILL.detail.md` § "Recovery").
134
135
  - **Done when** the consolidated report and the `keryx:findings` block are emitted and the stage counts and **Not run** are stated. A managed round (not `lightweight`, which is report-only) is also done only when every dispatched reviewer's result is recorded with `keryx review ingest` and `keryx review complete` has run. `completed` means `keryx review readiness` passed, or each failing item is waived by the operator: in keryx shell call `review_waive_readiness` (a card, never a terminal), then run `keryx review complete`; elsewhere the operator runs `keryx review complete <package> --waive <item> "<their words>"` in their terminal; unattended, record Not run; publishing to GitHub is held by the same checklist, and a not-ready package goes out only through `keryx review publish --partial` (in keryx shell: `review_publish` with `partial: true`).
135
136
 
136
137
  ---
@@ -696,49 +697,15 @@ record `model_assignment: adaptive` with the model you chose.
696
697
 
697
698
  ### Fix round check (Step 1a)
698
699
 
699
- A **fix round** is any review of work produced to answer earlier findings. Set
700
- `is_fix_round: true` on every reviewer input, and populate `prior_findings` with
701
- the earlier findings and the disposition the fix claimed for each.
702
-
703
- **The Jev edit guard is a FIX-phase tool, not a review-round tool.** `keryx review jev-edit-guard` (a separate feature) is the recommended Jev step during the FIX phase itself — run by the author's agent as it applies fixes, before this round re-reviews them. This skill never runs it; it is not part of review-orchestrator's own dispatch.
704
-
705
- **Nothing refuses a dispatch that omits them.** `reviewer-input.schema.json`
706
- states the rule and no production TypeScript loads that schema; reviewer
707
- dispatch is an action the host agent takes, not a `keryx` invocation, so there
708
- is no point at which a malformed dispatch could be rejected. `reviewer-input` is
709
- also absent from the `CONTRACTS` registry (`src/gdskills/contracts.ts`), so
710
- `keryx skills contracts validate` cannot be pointed at it either.
711
-
712
- So this is a requirement on you, unenforced, and the only evidence it was met is
713
- the `prior_findings` array the reviewer actually receives. A reviewer that cannot
714
- see what the fix was answering cannot tell whether the fix is complete.
715
-
716
- Two scope rules apply, and they exist because breaking them is what produced
717
- seven rounds on PR #215 and four on PR #216:
718
-
719
- 1. **Review `merge-base..HEAD`, never the fix commit alone.** Narrowing to the
720
- newest commit is the intuitive move and it is wrong: it hides the blast
721
- radius. A fix that changes a guard, an instruction, a refusal or a helper
722
- makes every *other* site that names it wrong, and those sites are outside the
723
- fix commit by construction.
724
-
725
- 2. **Enumerate what NAMES the thing the fix changed.** For each guard,
726
- instruction, message, refusal or helper the fix touched, grep for its callers
727
- and for the text that recommends it, and record the result in
728
- `review_context.scope.files`. On PR #216 a round corrected one operator
729
- instruction of four; the correction silently broke the other three, and
730
- nothing looked for them because nothing was asked to.
731
-
732
- Recording the enumeration matters as much as doing it: a round that searched and
733
- found nothing is a different fact from a round that never searched, and only the
734
- recorded list distinguishes them.
735
-
736
- Every prior finding leaves the round with a disposition — `closed`, `open`, `partial`,
737
- `regressed` or `withdrawn` — one line each in the report before the new findings, checked against
738
- the code and not the commit message. A fix is new code: scope A includes it, and the report carries
739
- a `## Regressions the fixes introduced` section. A finding this round disproves is withdrawn at the
740
- top of the report with its evidence. The table, recorded shapes and rationale:
741
- `SKILL.detail.md` § "Fix rounds — dispositions, regressions, withdrawal".
700
+ `keryx review start` (or `keryx review plan`) decides the round; you never pick a range. `<package>/round.json` says round N, the mode (`first`, `delta`, `fallback-first`), the source that found the previous round and the `range`; `<package>/prior_findings.json` holds the earlier findings. "Round 1 — no previous round found (sources tried …)" is a valid outcome: review as a first round and say which sources were tried.
701
+
702
+ - **`delta` round.** A FIX slice (`kind: fix`, a hunk that answers a prior finding) gets `review-logic`, `review-regression`, the authors of the findings attached to it and the verifier. Set `is_fix_round: true` and attach those findings as `prior_findings` on each of those reviewer inputs. A NEW slice (`kind: new`) is reviewed as round 1, with the roles its slice names. Code of `merge-base..head` that did not change since the previous round gets one cheap second look: SECOND-LOOK slices (`kind: second-look`, listed in `round.json` `secondLookHunks`) are read once per round by `review-logic` and `review-security-code` only, and their findings are ordinary findings. When round.json says "previous round weak: …" (the previous round did not complete, left uncovered slices, skipped required reviewers, had "Not run" lines, or reviewed a narrower scope) that code is `kind: new` instead and gets the full round-1 treatment. `review plan` prints "second-look: N slices / M dispatches". Carried findings keep their dispositions. An empty range (base equals head: a merged pull request read against its own target branch) is refused; pass the recorded base with `review start --base` / `review plan --ref`. A reviewer on a FIX slice returns `prior_dispositions: [{id, disposition}]` in its `REVIEW_RESULT`, one entry for every attached finding id.
703
+ - **`fallback-first` round** (force-push or rebase: the previous head is not an ancestor). Review `merge-base..head` in full, with the prior findings attached.
704
+ - **Every attached prior finding leaves the round with a disposition** — `closed`, `open`, `partial`, `regressed` or `withdrawn` — one line each in the report before the new findings, checked against the code and not the commit message. A fix is new code: the report carries a `## Regressions the fixes introduced` section. A finding this round disproves is withdrawn at the top of the report with its evidence. Table, recorded shapes and rationale: `SKILL.detail.md` § "Fix rounds — dispositions, regressions, withdrawal".
705
+ - **Duplicates.** `keryx review ingest` merges duplicate findings; later waves receive `known_findings` and do not report them again.
706
+ - **Enumerate what NAMES the thing the fix changed.** For each guard, instruction, message, refusal or helper a fix hunk touched, find its callers and the text that recommends it, and record the result in `review_context.scope.files`. On PR #216 a round corrected one operator instruction of four and silently broke the other three, because nothing looked. A round that searched and found nothing is a different fact from one that never searched; only the recorded list tells them apart.
707
+
708
+ **The Jev edit guard is a FIX-phase tool.** `keryx review jev-edit-guard` runs in the author's agent while it applies fixes; this skill never runs it. Nothing refuses a dispatch that omits them (the `prior_findings`): `reviewer-input.schema.json` states the rule, no production code loads it, and the `prior_findings` array a reviewer receives is the only evidence it was met.
742
709
 
743
710
  ### Step 2: Determine Review Mode
744
711
 
@@ -193,6 +193,19 @@
193
193
  "minLength": 1
194
194
  }
195
195
  },
196
+ "prior_dispositions": {
197
+ "title": "Dispositions of prior findings (FIX slices)",
198
+ "description": "Returned by a reviewer on a FIX slice: one entry for every attached prior finding id, giving its disposition checked against the code. The ledger reads it.",
199
+ "type": "array",
200
+ "items": {
201
+ "type": "object",
202
+ "required": ["id", "disposition"],
203
+ "properties": {
204
+ "id": {"type": "string", "minLength": 1},
205
+ "disposition": {"type": "string", "enum": ["closed", "open", "partial", "regressed", "withdrawn"]}
206
+ }
207
+ }
208
+ },
196
209
  "slices": {
197
210
  "title": "Slices this result covered",
198
211
  "description": "The ids of the dispatched slices the reviewer read. An entry is an id string, or an object with an `id`, an optional `coverage` note and an optional `status`. `keryx review retry-plan` and `ledger build` read both. An object counts as covered only when its `status` is absent or one of DONE, COMPLETE, COMPLETED, COVERED, OK (any case); any other status (NOT_READ, INCOMPLETE, SKIPPED, PARTIAL, …) leaves the slice uncovered and is named in the plan reason or the ledger gap. An entry with no id counts for nothing.",
@@ -181,6 +181,25 @@
181
181
  "default": false,
182
182
  "description": "True when the diff under review was produced to answer earlier findings. A fix round without the earlier findings is the failure this field exists to stop: the reviewer cannot tell whether a fix is complete when it does not know what the fix was answering."
183
183
  },
184
+ "allowed_tools": {
185
+ "type": "string",
186
+ "enum": ["read-only", "run-tests"],
187
+ "default": "read-only",
188
+ "description": "What the reviewer may do beyond reading. `read-only` (the default): read the diff and files, run no command. `run-tests`: at most one testing reviewer per round may run the tests, inside `exec_budget_s`. Verification commands go through the parent's `keryx review verify-run --timeout`."
189
+ },
190
+ "exec_budget_s": {
191
+ "type": "integer",
192
+ "minimum": 0,
193
+ "description": "Seconds of command time the reviewer may spend in total. 0 or absent with `allowed_tools: read-only`; a positive budget with `run-tests`."
194
+ },
195
+ "known_findings": {
196
+ "type": "array",
197
+ "description": "Findings already recorded by earlier waves of this round. Do not report them again; `keryx review ingest` merges duplicates.",
198
+ "items": {
199
+ "type": "object",
200
+ "additionalProperties": true
201
+ }
202
+ },
184
203
  "prior_findings": {
185
204
  "type": "array",
186
205
  "description": "Every finding from earlier rounds on this change, with what the fix claimed about each. Eleven rounds across two flows were dispatched without this, and twice in a row the fix repaired the site the finding named while leaving its siblings — which the NEXT round then found. A reviewer that can see the earlier finding and the claimed disposition can check the claim.",
@@ -227,6 +246,25 @@
227
246
  "additionalProperties": true
228
247
  }
229
248
  },
249
+ "known_findings": {
250
+ "type": "array",
251
+ "description": "Findings an EARLIER WAVE of this same round already reported, from `knownFindingsForWave(<package>)` in src/review/dedupe.ts: id plus the file/line/quote anchor, nothing else. A reviewer that would raise the same defect at the same site cites the known id in its evidence instead of re-reporting it; ingest merges whatever duplicates still arrive (same file, line within 3 or same quote, same mechanism) into one finding with the highest severity and `reported_by`. Optional; absent on the first wave.",
252
+ "items": {
253
+ "type": "object",
254
+ "required": ["id"],
255
+ "properties": {
256
+ "id": { "type": "string", "minLength": 1 },
257
+ "global_id": { "type": ["string", "null"] },
258
+ "reviewer": { "type": "string" },
259
+ "severity": { "type": "string", "enum": ["blocker", "major", "minor", "info"] },
260
+ "file": { "type": ["string", "null"] },
261
+ "line": { "type": ["integer", "null"], "minimum": 1 },
262
+ "quote": { "type": ["string", "null"] },
263
+ "problem": { "type": "string" }
264
+ },
265
+ "additionalProperties": false
266
+ }
267
+ },
230
268
  "layers": {
231
269
  "type": "object",
232
270
  "description": "The bounded layers block for this reviewer's files, built from the package's own graph, the working-copy wiki and the memory (`keryx review layers block --package <dir> --files <a,b>`). Optional. `truncated` names what was cut and the command that fetches the rest.",
@@ -21,7 +21,7 @@ license: "MIT"
21
21
 
22
22
  ## Purpose
23
23
 
24
- Finds real, code-level performance problems introduced in the changed code of the current branch. Covers N+1 database queries, unnecessary React re-renders, missing memoization in hot paths, memory leaks, large bundle imports, synchronous blocking in async contexts, missing pagination, and redundant API calls. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs.
24
+ Finds real, code-level performance problems introduced in the changed code of the current branch. Covers N+1 database queries, unnecessary React re-renders, missing memoization in hot paths, memory leaks, large bundle imports, synchronous blocking in async contexts, missing pagination, and redundant API calls. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs. It runs no command unless its payload's `allowed_tools` says `run-tests`.
25
25
 
26
26
  This skill focuses on **measurable or high-likelihood bottlenecks** only. Premature optimization, hypothetical slowness, and style preferences are out of scope.
27
27
 
@@ -524,7 +524,7 @@ branch on purpose.
524
524
  "operator_confirmed": { "confirmed_by": "<who>", "confirmed_at": "<iso8601>", "plan_digest": "<digest of the plan shown>" },
525
525
  "constraints": [
526
526
  "pr: open it as a draft, titled 'fix(review): address feedback on #<n>', body linking #<n> and listing which plan item answers which comment.",
527
- "review: run review-orchestrator with --all on every round. The loop's exit threshold is the one your own PR review/fix loop defines; do not take it from this string.",
527
+ "review: run review-orchestrator on every round, starting each round with `keryx review start` (or `keryx review plan`) on the new head: the round, its range and the prior findings come from `round.json`. No own range, no `--all` per round, no whole-branch re-review. The loop's exit threshold is the one your own PR review/fix loop defines; do not take it from this string.",
528
528
  "review: the fix PR is its own conversation — collect and reply on IT as normal. The round MUST NOT run a reply pass against #<n>: a reply there writes the durable record, so the post-merge answer citing the merge SHA is skipped as already-handled and the reviewer is left holding a mid-loop answer that has since stopped being true.",
529
529
  "attempt budget: at most <max_fix_rounds> review/fix attempts. This LOWERS your bound and never raises it; absent the value, your own bound stands. The `keryx review loop` repetition check applies either way. Do not raise anything to reach a clean round; escalate instead.",
530
530
  "scope: the plan items only. A finding outside them is recorded as follow-up, not fixed in this flow."
@@ -22,7 +22,7 @@ license: "MIT"
22
22
  # Review: Regression
23
23
 
24
24
  You are reviewing **scope B** of a deep round. The files you are given are not
25
- under review. They are under **regression check**. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs.
25
+ under review. They are under **regression check**. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs. It runs no command unless its payload's `allowed_tools` says `run-tests`.
26
26
 
27
27
  ## The one question
28
28
 
@@ -164,7 +164,7 @@ rejected anyway, so in practice every finding you report carries one.
164
164
  ```
165
165
 
166
166
  Severity comes from **Severity (canonical)** in `review-orchestrator/SKILL.md`; no table here. When dispatched, follow the `reviewer-input.schema.json` payload and return a `REVIEW_RESULT` object compatible with
167
- `.metaproject/skills/gdskills/review/review-orchestrator/reviewer-finding.schema.json`, then a short summary; `NEEDS_CONTEXT` rather than a guess.
167
+ `.metaproject/skills/gdskills/review/review-orchestrator/reviewer-finding.schema.json`, then a short summary; `NEEDS_CONTEXT` rather than a guess. On a FIX slice, also return `prior_dispositions: [{id, disposition}]` for every attached finding id.
168
168
 
169
169
  ### Shared laws (every reviewer)
170
170
 
@@ -23,7 +23,7 @@ license: "MIT"
23
23
 
24
24
  ## Purpose
25
25
 
26
- Finds exploitable security vulnerabilities introduced in the changed code of the current branch. Covers injection, auth/authz gaps, IDOR, secrets, insecure crypto, CSRF, path traversal, and framework-specific patterns (NestJS, React). Produces findings with explicit attack vectors and concrete fixes. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs.
26
+ Finds exploitable security vulnerabilities introduced in the changed code of the current branch. Covers injection, auth/authz gaps, IDOR, secrets, insecure crypto, CSRF, path traversal, and framework-specific patterns (NestJS, React). Produces findings with explicit attack vectors and concrete fixes. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs. It runs no command unless its payload's `allowed_tools` says `run-tests`.
27
27
 
28
28
  This skill covers **code-level security only**. It does NOT audit npm/bun dependency trees, Docker configurations, or deployment surfaces — use `security-audit` for those.
29
29
 
@@ -25,7 +25,7 @@ license: "MIT"
25
25
 
26
26
  Focused style reviewer. Covers naming conventions, dead code, readability, DRY violations,
27
27
  import organization, comment quality, file length, cyclomatic complexity, and console noise.
28
- Never touches logic correctness, architecture, security, or performance. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs.
28
+ Never touches logic correctness, architecture, security, or performance. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs. It runs no command unless its payload's `allowed_tools` says `run-tests`.
29
29
 
30
30
  ---
31
31
 
@@ -29,7 +29,7 @@ license: "MIT"
29
29
  # Review — Testing Practices
30
30
 
31
31
  Reviewer for local testing discipline. Read project test guides first when present, then apply
32
- the neutral baseline below. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs.
32
+ the neutral baseline below. Its inputs are the reviewer payload only; session memory and earlier conversation are not inputs. It runs the tests only when its payload's `allowed_tools` says `run-tests`, within `exec_budget_s`; otherwise it runs no command.
33
33
 
34
34
  ---
35
35