@mrciphersmith/keryx 0.3.119 → 0.3.121

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 (42) hide show
  1. package/dist/cli.js +9730 -5314
  2. package/dist/core.js +1162 -812
  3. package/package.json +1 -1
  4. package/src/gdskills/bundled/install-manifest.json +2 -1
  5. package/src/gdskills/bundled/rules/core/session-plan-bridge.mdc +1 -1
  6. package/src/gdskills/bundled/skills/orchestration/feature-dev/SKILL.md +4 -4
  7. package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +47 -59
  8. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.detail.md +70 -0
  9. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +42 -79
  10. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/orchestrator-prompt.md +1 -1
  11. package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.md +12 -18
  12. package/src/gdskills/bundled/skills/orchestration/task-implementer/input-contract.schema.json +25 -2
  13. package/src/gdskills/bundled/skills/orchestration/task-implementer/output-contract.schema.json +19 -0
  14. package/src/gdskills/bundled/skills/review/review-architecture/SKILL.md +6 -11
  15. package/src/gdskills/bundled/skills/review/review-backend/SKILL.md +6 -11
  16. package/src/gdskills/bundled/skills/review/review-clean-code/SKILL.md +6 -11
  17. package/src/gdskills/bundled/skills/review/review-core-boundaries/SKILL.md +5 -10
  18. package/src/gdskills/bundled/skills/review/review-flow-graph/SKILL.md +5 -10
  19. package/src/gdskills/bundled/skills/review/review-frontend/SKILL.md +8 -17
  20. package/src/gdskills/bundled/skills/review/review-frontend-conventions/SKILL.md +8 -15
  21. package/src/gdskills/bundled/skills/review/review-highload/SKILL.md +6 -11
  22. package/src/gdskills/bundled/skills/review/review-jev-comments/SKILL.md +4 -9
  23. package/src/gdskills/bundled/skills/review/review-jev-contract/SKILL.md +4 -9
  24. package/src/gdskills/bundled/skills/review/review-jev-docs/SKILL.md +4 -9
  25. package/src/gdskills/bundled/skills/review/review-jev-risk/SKILL.md +4 -9
  26. package/src/gdskills/bundled/skills/review/review-jev-rules/SKILL.md +4 -9
  27. package/src/gdskills/bundled/skills/review/review-jev-scenarios/SKILL.md +4 -9
  28. package/src/gdskills/bundled/skills/review/review-layout/SKILL.md +8 -9
  29. package/src/gdskills/bundled/skills/review/review-logic/SKILL.md +7 -12
  30. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +39 -2
  31. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +49 -92
  32. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +13 -0
  33. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-input.schema.json +38 -0
  34. package/src/gdskills/bundled/skills/review/review-performance/SKILL.md +6 -11
  35. package/src/gdskills/bundled/skills/review/review-pr-contract/SKILL.md +2 -2
  36. package/src/gdskills/bundled/skills/review/review-pr-feedback/SKILL.md +8 -10
  37. package/src/gdskills/bundled/skills/review/review-premise/SKILL.md +180 -0
  38. package/src/gdskills/bundled/skills/review/review-regression/SKILL.md +4 -4
  39. package/src/gdskills/bundled/skills/review/review-security-code/SKILL.md +6 -11
  40. package/src/gdskills/bundled/skills/review/review-style/SKILL.md +6 -11
  41. package/src/gdskills/bundled/skills/review/review-testing-practices/SKILL.md +2 -2
  42. package/src/gdskills/contracts/review-finding.schema.json +88 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.3.119",
3
+ "version": "0.3.121",
4
4
  "description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -455,7 +455,7 @@
455
455
  },
456
456
  "review-core-skills": {
457
457
  "kind": "skill",
458
- "description": "Generic (non-stack-specific) review skills: orchestrator, logic, architecture, security, performance, clean-code, highload, regression, core-boundaries, flow-graph, layout, style, testing-practices, verifier, pr-feedback, and the legacy AI/learned/style review profiles.",
458
+ "description": "Generic (non-stack-specific) review skills: orchestrator, logic, architecture, security, performance, clean-code, highload, regression, core-boundaries, flow-graph, layout, style, testing-practices, verifier, premise, pr-feedback, and the legacy AI/learned/style review profiles.",
459
459
  "paths": [
460
460
  "src/gdskills/bundled/skills/review/review-orchestrator/**",
461
461
  "src/gdskills/bundled/skills/review/review-logic/**",
@@ -472,6 +472,7 @@
472
472
  "src/gdskills/bundled/skills/review/review-style/**",
473
473
  "src/gdskills/bundled/skills/review/review-testing-practices/**",
474
474
  "src/gdskills/bundled/skills/review/review-verifier/**",
475
+ "src/gdskills/bundled/skills/review/review-premise/**",
475
476
  "src/gdskills/bundled/skills/review/review-pr-feedback/**",
476
477
  "src/gdskills/bundled/skills/review/code-ai-review/**",
477
478
  "src/gdskills/bundled/skills/review/code-learned-review/**",
@@ -36,7 +36,7 @@ in progress, so it never makes the agent continue on its own, which is what make
36
36
  |---|---|---|
37
37
  | `job-orchestrator` | the step ids `keryx job status <name> --json` reports — `analyze`, `context`, `prepare`, `tests-creator`, `implement`, `sanity-check`, `verify`, `review`, `security`, `fix`, `verify-post-fix`, `perf-check`, `report`, `pr`, `deploy` | `keryx job status <name>` (read it, never retype it) |
38
38
  | `flow-orchestrator` | the Flow's task ids `T1..Tn` | `keryx flow status <id>` / the Flow's `tasks.md` |
39
- | `review-orchestrator` | `step-0` … `step-14`, from its own numbered checklist | its Workflow section |
39
+ | `review-orchestrator` | `step-0` … `step-14` and `step-10b`, from its own numbered checklist | its Workflow section |
40
40
 
41
41
  An id that exists only in the session plan is drift by construction: the operator
42
42
  cannot reconcile it with the CLI, and a resumed run cannot match it. If a
@@ -39,7 +39,7 @@ End-to-end feature development workflow from idea to merge-ready PR.
39
39
 
40
40
  ### Phase 1: REQUIREMENTS + SPEC
41
41
 
42
- 1. Parse input (description or GitHub issue via `gh issue view`)
42
+ 1. Parse input (description or GitHub issue via `gh issue view`). An issue filed from a review finding, or an external review comment, is a premise to check: dispatch `review-premise` on it first, shaped and recorded as `flow-orchestrator` Phase 1 says ("A flow sourced from a finding": no `decide-premise`; claims in `<flow-dir>/premise/<n>.json` if a flow exists; with none, its claims object goes into the Implementation Spec under **Premise**), and on `challenged` or `owner-decision` ask the operator its question with its options and journal their answer before writing the spec
43
43
  2. Clarify ambiguities — ask the user up to 3 questions max
44
44
  3. Produce the **Implementation Spec** (per `implementation-doc-mandate.mdc`):
45
45
  - **What**: feature description in 2-3 sentences
@@ -48,8 +48,8 @@ End-to-end feature development workflow from idea to merge-ready PR.
48
48
  - **Acceptance criteria**: testable bullet points
49
49
  - **Approach**: which files will change, key design decisions
50
50
  - **Test strategy**: framework, which scenarios will be covered
51
- 4. **Save spec** to `.feature-spec.md` in project root (add to .gitignore if not already)
52
- 5. **Get user confirmation before proceeding**
51
+ - **Premise** (only for an input sourced from a finding, with no flow): the review-premise claims object and the operator's answer, in their words
52
+ 4. **Save spec** to `.feature-spec.md` in project root (add to .gitignore if not already), then **get user confirmation before proceeding**
53
53
 
54
54
  ### Phase 2: DESIGN
55
55
 
@@ -113,7 +113,7 @@ Input: codebase_path=<project_root>, scope=changed, base_branch=<base>
113
113
  - `git diff main...HEAD` — review the full diff
114
114
  - Check for: TODOs left behind, console.logs, hardcoded values
115
115
  - Verify all acceptance criteria from Phase 1 spec
116
- 3. Fix any findings (max 2 review-fix cycles)
116
+ 3. Fix any findings (max 2 review-fix cycles), never one `review-premise` holds: a held finding is neither fixable nor clean and blocks the cycle until answered; ask the operator about it as `review-orchestrator` "Wave D" says, and only an upheld one is fixed, carrying `premise_verdict` and `premise_answer` as task-implementer's `FIX_CONTEXT` defines them (unattended: stop and report the held list). Each next round is `keryx review start` or `keryx review plan` on the new head: the round comes from `round.json`; no own range, no whole-branch re-review (the `git diff main...HEAD` above is round 1 only).
117
117
  4. Re-run `code-verifier` after any fixes
118
118
 
119
119
  ### Phase 8: DELIVER + CHANGE REPORT
@@ -77,11 +77,12 @@ flowchart TD
77
77
  C --> D["flow-init context enrichment"]
78
78
  D --> E["freeze AC + start flow"]
79
79
  E --> F["dispatch tests-creator / task-implementer"]
80
- F --> G["code-verifier + review-orchestrator"]
80
+ F --> FG{"first green: ask operator - open PR or harden first"}
81
+ FG --> G["code-verifier + review-orchestrator"]
81
82
  G --> H{"All tasks, checks, review OK?"}
82
83
  H -- "no" --> F
83
84
  H -- "yes" --> I{"Ask user how to finish"}
84
- I -- "create PR" --> J["create PR and run review/fix loop"]
85
+ I -- "create PR" --> J["create PR; each round = review start on the new head (delta)"]
85
86
  J --> K{"review clean, PR mergeable?"}
86
87
  K -- "no, attempts < 5" --> J
87
88
  K -- "no, attempts = 5 or repetition detected" --> R["enrich context and change fix strategy"]
@@ -177,9 +178,7 @@ Record attempts as they happen, not only on resume:
177
178
  keryx flow task attempt <id> <Tn> --outcome started|failed|blocked [--detail "<what happened>"]
178
179
  ```
179
180
 
180
- `attempts.count` is append-only and lives in `flow.json`. A counter that lives
181
- only in the orchestrator's context resets to zero exactly when the loop bound
182
- matters most, which makes it not a counter.
181
+ `attempts.count` is append-only and lives in `flow.json`. A counter that lives only in the orchestrator's context resets to zero exactly when the loop bound matters most, which makes it not a counter.
183
182
 
184
183
  ### 0.1 Route
185
184
 
@@ -244,39 +243,29 @@ Write or update:
244
243
  - `tasks.md` - task definitions grouped by context, test, implement, review, docs;
245
244
  - `acceptance-criteria.md` - verifiable `ACn` criteria, each ending in one marker: [verify: exec `<command>`] or [verify: invariant `<command>`] (a backticked command), [verify: judged] (a human checks: live run, operator decision, text quality), or [verify: none — <reason>]. If a kind is unclear, ask the operator ONE question listing the criteria with proposed kinds, before `flow freeze`. Behaviour with an external dependency or a failure path gets its own failure-scenario criterion with an observable result (saved, error visible, retry creates no duplicate); a check claimed to catch a regression carries `· sensitive: revert|mutation|failure-scenario` and the confirm note carries `evidence: <ref>` (revert/mutation run or failure-scenario reference).
246
245
 
246
+ ### UI or state scope: a state × event matrix in plan.md
247
+
248
+ When the scope touches UI, a store, or other code that holds state across events, `plan.md` carries a state × event matrix before `flow freeze`. Rows are the states the surface can be in (for example loading, loaded, empty, missing, first-load failed, refresh failed); columns are the events that reach it (for example the user switches selection, unmount, workspace or context change, retry). Each cell is one line: what the user sees and which state survives, citing the project's styling/UX rules where they decide it. Every non-trivial cell gets an `ACn` row, or one AC per group of cells with the same result; a cell that is genuinely "no change" needs none. List only the states and events the change reaches:
249
+
250
+ | state \ event | user switches selection | unmount | retry |
251
+ |---|---|---|---|
252
+ | loading | drop the stale response; loading shows for the new one | no state write after unmount | — |
253
+ | refresh failed | keep loaded data; the error does not follow to the new selection | — | re-fetch; error stays inline until success |
254
+
255
+ It does not apply when the scope holds no UI or state (CLI output, pure functions, docs, build config): write `State × event matrix: not applicable — <why>` in `plan.md`. The matrix is what workers and fix rounds check behavior against; a choice no cell decides is a `behavior_changes` entry for the operator (Phase 2).
256
+
247
257
  ### A verification step in the plan is a task, not a sentence
248
258
 
249
- Every check the plan says must happen before the work is accepted - run the
250
- thing end to end, confirm two components agree, exercise the path no test
251
- covers - is added with `keryx flow task add` and closed with
252
- `keryx flow task done`. Prose in `plan.md` blocks nothing, and an
253
- orchestrator that wrote the step is the same one deciding whether to run it.
254
-
255
- The failure this prevents is specific and has happened: a plan listed
256
- "confirm both components agree on the same input" as step 5, the
257
- implementation shipped without it, and review found that the two did not
258
- agree at all - the change could not work in production. The check had been
259
- identified correctly and then skipped, because nothing made skipping it
260
- visible.
261
-
262
- Tasks are the mechanism for this. `keryx flow complete` runs a `tasks` gate
263
- over them, so an unrun verification step keeps the flow open instead of being
264
- quietly dropped.
265
-
266
- Know the gate's exact scope, because for years this file claimed a gate that
267
- did not exist and 24 completed flows shipped with an open task:
268
-
269
- - the gate is **opt-in per flow package**, keyed on `gates.tasks` in
270
- `flow.json`, which `keryx flow init` writes for every flow it creates. A
271
- package created before the gate landed does not carry the flag, and for it
272
- the gate reports `skipped` and blocks nothing;
273
- - a task fails the gate when its status is not `done`; when its disposition is
274
- `failed`; when its disposition is `blocked` (terminal, but the work did not
275
- happen — and the harness emits this disposition on its own for a run that
276
- ended blocked); when its disposition is `skipped` with no recorded reason; or
277
- when its disposition is a value this build does not recognise. An
278
- unrecognised disposition FAILS rather than falling through: a gate whose
279
- default for the unknown case is "pass" is not a gate;
259
+ Every check the plan says must happen before the work is accepted - run the thing end to end, confirm two components agree, exercise the path no test covers - is added with `keryx flow task add` and closed with `keryx flow task done`. Prose in `plan.md` blocks nothing, and an orchestrator that wrote the step is the same one deciding whether to run it.
260
+
261
+ The failure this prevents is specific and has happened: a plan listed "confirm both components agree on the same input" as step 5, the implementation shipped without it, and review found that the two did not agree at all - the change could not work in production. The check had been identified correctly and then skipped, because nothing made skipping it visible.
262
+
263
+ Tasks are the mechanism for this. `keryx flow complete` runs a `tasks` gate over them, so an unrun verification step keeps the flow open instead of being quietly dropped.
264
+
265
+ Know the gate's exact scope, because for years this file claimed a gate that did not exist and 24 completed flows shipped with an open task:
266
+
267
+ - the gate is **opt-in per flow package**, keyed on `gates.tasks` in `flow.json`, which `keryx flow init` writes for every flow it creates. A package created before the gate landed does not carry the flag, and for it the gate reports `skipped` and blocks nothing;
268
+ - a task fails the gate when its status is not `done`; when its disposition is `failed`; when its disposition is `blocked` (terminal, but the work did not happen — and the harness emits this disposition on its own for a run that ended blocked); when its disposition is `skipped` with no recorded reason; or when its disposition is a value this build does not recognise. An unrecognised disposition FAILS rather than falling through: a gate whose default for the unknown case is "pass" is not a gate;
280
269
  - to close a task as deliberately not needed, record why:
281
270
 
282
271
  ```bash
@@ -285,6 +274,8 @@ did not exist and 24 completed flows shipped with an open task:
285
274
 
286
275
  Read the `tasks` line in the `flow complete` output. If it says `skipped`, the gate did not run and the task list is yours to verify by hand.
287
276
 
277
+ **A flow sourced from a finding: check its premise before freeze.** A follow-up issue filed from a review, or an external review comment, is a premise to check, not a settled requirement. Before `flow freeze`, dispatch `review-premise` with `source: follow-up-issue` or `external-comment` and the finding shaped as its Input Contract `source` row says (the issue number or the comment's `C-NNN` as `id`, its URL as `url`). Skip it when the dispatch says the premise is already recorded (`review-pr-feedback` rule 7 sends a `premise:` constraint). No review package exists, so no `ingest` or `decide-premise` runs: save its claims as `<flow-dir>/premise/<n>.json`, and apply every refusal of the merge by hand, as that `source` row lists them (`PREMISE_REJECTION_REASONS`, `src/review/premise.ts`): a refused claim leaves the finding upheld. On `challenged` or `owner-decision` ask the operator its question with its options (`ask_user` in keryx shell, the host's question tool elsewhere), journal their answer, and plan only what the operator upholds.
278
+
288
279
  Then freeze and start (`--yes`: the operator already answered the kinds question, so the freeze must not prompt):
289
280
 
290
281
  ```bash
@@ -389,11 +380,7 @@ fi
389
380
  keryx flow task done <id> <Tn>
390
381
  ```
391
382
 
392
- Why each path is checked on its own: `git status --porcelain --` with no path
393
- lists the whole tree, `git add` fails on a path that is neither on disk nor
394
- tracked, and `git commit` with nothing staged exits 1. The trailing
395
- `-- "${PENDING[@]}"` commits only these paths, so a file another lane staged is
396
- not swept into this commit.
383
+ Why each path is checked on its own: `git status --porcelain --` with no path lists the whole tree, `git add` fails on a path that is neither on disk nor tracked, and `git commit` with nothing staged exits 1. The trailing `-- "${PENDING[@]}"` commits only these paths, so a file another lane staged is not swept into this commit.
397
384
 
398
385
  If new work is discovered:
399
386
 
@@ -415,6 +402,8 @@ properly formatted `subagent-result`.
415
402
  | `BLOCKED` | `keryx flow block <id> --reason "<worker reason>"`; resolve or escalate one concise question, then `flow unblock` and re-dispatch. |
416
403
  | `FAILED` | Emitted by harness **child** workers (`src/harness/child/contract.ts`), never by skill workers — `task-implementer` maps its own `failed` onto `BLOCKED`. Retry once with the same dispatch. If it fails again, block the flow and surface the error to the user. |
417
404
 
405
+ **A `behavior_changes` entry with `needs_operator: true` is a question, not a concern to journal.** task-implementer lists every user-visible tradeoff it made (message wording, error placement, state reset) there. Before accepting that result, ask the operator with the question tool — one question per entry, the implemented choice and each of its `alternatives` as options — and record the answer in `journal.md`. An answer other than the implemented one is a fix task on the same `Tn` before the boundary commit, not a review finding later.
406
+
418
407
  Carry `run_id`/`dispatch_id` across retries so the flow journal stays traceable,
419
408
  and record every dispatch against the task's persisted counter so a session
420
409
  restart does not reset the budget:
@@ -432,7 +421,8 @@ Before accepting implementation:
432
421
  1. Run focused tests for touched scope.
433
422
  2. Run `code-verifier`.
434
423
  3. Run `keryx health run` when Code Health is enabled.
435
- 4. Check the bounds, then run `review-orchestrator` with relevant domains — in this session,
424
+ 4. **First-green gate, once per flow.** The first time every implement/test task is `done` and steps 1–3 pass, ask the operator one question (question tool, options) before any review round: `issue AC met — open the PR with the standard review`, or `harden first` with each extra matrix cell or failure path you would add as its own option. Record the answer in `journal.md`; picked hardening becomes `keryx flow task add` tasks (plus `keryx flow ac update` when it adds criteria) before review. A dispatched run has no operator: it takes the standard review and journals that the dispatch answered the gate.
425
+ 5. Check the bounds, then run `review-orchestrator` with relevant domains — in this session,
436
426
  never handed whole to one subagent: one without a dispatch tool returns `STATUS: BLOCKED`.
437
427
  ```bash
438
428
  keryx review budget --spent <usd-so-far> --full --outstanding <subagents still running, 0 if none>
@@ -440,16 +430,16 @@ Before accepting implementation:
440
430
  A non-zero exit means the spend ceiling (3 USD by default) has been reached:
441
431
  **stop and ask the user** rather than dispatching another fan-out — never shrink the fan-out to fit.
442
432
  Dispatch prompts point at sources (files, SHAs, the PR) and never paraphrase a contract.
433
+ Every round opens with `keryx review start` on the current head, never a hand-built range (Phase 4 loop, step 1).
443
434
 
444
- `--outstanding` is the part that matters here. `review-orchestrator` runs inline
445
- in this session and dispatches reviewers in parallel; keryx cannot observe other
446
- subagents. Passing the count still running is the only thing that makes the
447
- concurrency cap mean anything; omit it and the cap bounds the reviewer fan-out
448
- alone, which the review record then states plainly. Add `--full` for `--all`.
435
+ `--outstanding` is the part that matters here. `review-orchestrator` runs inline in this session and dispatches reviewers in parallel; keryx cannot observe other subagents. Passing the count still running is the only thing that makes the concurrency cap mean anything; omit it and the cap bounds the reviewer fan-out alone, which the review record then states plainly. Add `--full` for `--all`.
449
436
 
450
- 5. If findings require code changes, dispatch fix work through `task-implementer`
451
- and record the fix task in the flow.
452
- 6. Close the skill-learning loop (see `rules/core/skill-lifecycle.mdc`). Collect
437
+ 6. If findings require code changes, run a fix round through `task-implementer`, recorded as a flow fix task:
438
+ - **Scope freeze.** A fix round fixes the round's findings and nothing else. Hardening, refactors or new cells a fix suggests go to the operator as a question (as in step 4); never grow scope inside a fix round.
439
+ - **Red probes → ONE implementer.** When reviewers left red probes (failing tests or commands on the findings), pass them as `test_case_specs` to one `task-implementer` dispatch — no separate `tests-creator` task. `tests-creator` runs only for a new contract no probe covers.
440
+ - **A held finding is not fixed.** A finding `review-premise` held (`challenged` or `owner-decision`) is neither fixable nor clean: it never goes into `fix_context.review_feedback` and it blocks the loop until answered. Ask the operator about it as `review-orchestrator` "Wave D" says (the card in keryx shell; elsewhere the question tool, then the exact `keryx review decide-premise` command for them to run in their terminal) and journal the recorded answer; only an upheld finding enters a fix round, carrying `premise_verdict` and `premise_answer` as task-implementer's `FIX_CONTEXT` defines them. A dispatched run leaves it held and journals it.
441
+ - **`suggested_fix` is a hypothesis.** Pass each finding's `id` and `suggested_fix` in `fix_context.review_feedback`; the implementer reproduces the finding (red), checks the suggestion against the state × event matrix, then fixes (task-implementer 1.3). Journal every `suggested_fix diverged` / `did not reproduce` note it returns.
442
+ 7. Close the skill-learning loop (see `rules/core/skill-lifecycle.mdc`). Collect
453
443
  the `skill_drift` fields from task-implementer results and the
454
444
  `## Skill Learning` block from review-orchestrator. For each flagged
455
445
  project-skill, dispatch a subagent — on a cheaper / non-flagship model if one
@@ -536,18 +526,12 @@ the bound plus an escalation — never an unbounded loop.
536
526
 
537
527
  ### PR review/fix loop
538
528
 
539
- 1. Run the relevant `review-orchestrator` checks against the PR and current
540
- branch state.
541
- 2. If findings or required check failures remain, create or update a flow fix
542
- task, dispatch `task-implementer`, push the fix, and run review again.
529
+ 1. Each round — the first and every one after a fix — is `keryx review start` on the PR's current head, then `review-orchestrator` from that package. `review start` finds the previous round itself (local package, git history, the PR's round block, the flow package) and, when the previous reviewed head is an ancestor, reviews the delta: fix changes with a small roster (logic, regression, the findings' authors, the verifier) and the prior findings attached, new changes as round 1, unchanged code carried with its dispositions. Never pass a whole-branch range or `--all` per round; `<package>/round.json` records the round, its source and its mode.
530
+ 2. If findings (held ones aside: Phase 3 step 6) or required check failures remain, run a fix round under Phase 3 step 6 (flow fix task, `task-implementer`), push the fix, and run the next round as in step 1.
543
531
 
544
532
  **A required check failure is not automatically a fix task.** When `review.jev.ci_triage` is on, run `keryx review ci-triage --run <id> --json` on each failed run first: `top: flaky` (or a `deterministic` override) → rerun once (`gh run rerun --failed <id>`), record it in `journal.md`, and treat a second failure as `real-regression` regardless; `top: real-regression` → investigate and fix as usual; `top: infra` → report it in the completion notes and leave the code alone. The verdict is advisory only — never the sole reason to skip a fix task.
545
533
 
546
- **The threshold is `minor`.** The loop exits when the round reports zero
547
- findings at `blocker`, `major` or `minor`; `info` does not hold it. State the
548
- remaining `info` findings in the completion report rather than fixing them
549
- under a loop that was not opened for them. A caller may lower the threshold in
550
- `constraints`; it cannot raise it to merge over a `minor`.
534
+ **The threshold is `minor`.** The loop exits when the round reports zero findings at `blocker`, `major` or `minor` that are not held, and no held finding awaits the operator; `info` does not hold it. A held finding counts as neither fixable nor clean: while one is unanswered, run no fix round for it and stop to ask (unattended: stop and report the held list). State the remaining `info` findings in the completion report rather than fixing them under a loop that was not opened for them. A caller may lower the threshold in `constraints`; it cannot raise it to merge over a `minor`.
551
535
  3. Allow at most **five** review/fix attempts for the current approach. Count
552
536
  an attempt when review/check results are available, including a clean result,
553
537
  and record it with `keryx flow task attempt <id> <Tn> --outcome ...` so the
@@ -691,4 +675,8 @@ Stop and re-read this skill if you are thinking:
691
675
  | "The worker returned BLOCKED twice — faster if I implement this task myself." | The implementer never self-accepts and the orchestrator never implements. Block the flow, escalate one concise question, then unblock and re-dispatch. Doing the work here erases the boundary the whole flow model rests on. |
692
676
  | "Verification is described in the plan, so it will happen." | A verification step in the plan is a task, not a sentence. If it is not a task with a status, nothing records whether it ran, and the flow reaches `implemented` with an unrun gate. |
693
677
  | "The review fan-out is cheap, so the budget check can wait." | `keryx review budget --spent … --outstanding …` gates the fan-out, and `review-orchestrator` runs inline in this same main session, where keryx cannot see the in-flight subagents; declare them with `--outstanding`. Skipping the check means the cap bounds nothing. |
678
+ | "The reviewer wrote the fix; applying it is the fastest close." | A `suggested_fix` is a hypothesis. Most majors in a long fix loop are introduced by fixes: reproduce the finding red, check the suggestion against the state × event matrix, then fix (Phase 3 step 6). |
679
+ | "The finding came from a review, so its premise is settled." | A finding can reproduce and still not be a defect. Never pass a held finding in `fix_context.review_feedback`; ask the operator as `review-orchestrator` "Wave D" says (Phase 3 step 6); a flow sourced from a finding runs `review-premise` before freeze (Phase 1). |
680
+ | "While I'm in this code, the fix round can also harden the empty state." | Scope freeze: a fix round fixes findings. New hardening is an operator decision at the first-green gate or as an explicit question, never silent growth. |
681
+ | "The implementer journaled its UX choice, so it is decided." | A `behavior_changes` entry with `needs_operator: true` is decided by the operator, asked with options before the result is accepted (Phase 2). A journal line decides nothing. |
694
682
  | "I'll set the verification kind later." | An `unclassified` criterion cannot become data afterwards without a recorded rewrite: `keryx flow ac update <id> --criterion ACn --text "…" --reason "<why>"`. Put the `[verify: …]` marker on every criterion before `flow freeze`. |
@@ -0,0 +1,70 @@
1
+ # Job Orchestrator - Reference Detail
2
+
3
+ Reference material moved out of `SKILL.md` to keep it within its length ceiling:
4
+ rationale, evidence and long examples. Nothing here is an instruction the agent must
5
+ follow at a step; each section is linked from the spot in `SKILL.md` that used it.
6
+
7
+ ## Dry-run plan output example
8
+
9
+ ```
10
+ Dry-run plan for: issue-4141--pipeline-validation
11
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
12
+ Step 1: analyze [issue-analyzer] → input: issue #4141
13
+ Step 2: context [context-collector] → input: analysis result, project_dir
14
+ Step 3: prepare [orchestrator] → creates: feature/pipeline-validation worktree
15
+ Step 4: tests-creator [tests-creator × 3] → RED stubs, one per task
16
+ Step 5: implement [task-implementer × 3] → wave-parallel, 3 tasks
17
+ Step 6: sanity-check [orchestrator] → verifies commits exist
18
+ Step 7: verify [code-verifier] → lint + type-check + test + imports
19
+ Step 8: review [review-orchestrator] → managed round, ingested
20
+ Step 9: security [security-audit] → conditional: auth/API/DB/env files
21
+ Step 10: fix [task-implementer] → conditional: if NEEDS_FIX
22
+ Step 11: verify-post-fix [code-verifier] → conditional: after fix
23
+ Step 12: perf-check [perf-check] → conditional: frontend/bundle files
24
+ Step 13: report [orchestrator] → aggregates all results
25
+ Step 14: pr [orchestrator + gh CLI] → conditional: if create_pr
26
+ Step 15: deploy [deploy] → conditional: if user asks
27
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
28
+ Estimated sub-agent calls: 11-14 (varies with tasks and review findings)
29
+ No changes will be made. Use without --dry-run to execute.
30
+ ```
31
+
32
+ ## Post-mortem document template
33
+
34
+ ```markdown
35
+ # Post-Mortem: <job-name>
36
+
37
+ ## Timeline
38
+ (from .metaproject/jobs/<job-name>/journal.md — every line as recorded)
39
+ - <ISO timestamp> - created
40
+ - <ISO timestamp> - step: implement in-progress (retries 0)
41
+ - <ISO timestamp> - step: implement in-progress (retries 1)
42
+ - <ISO timestamp> - step: implement failed (retries 1) — <reason>
43
+
44
+ ## What Went Wrong
45
+ - <Step name> failed with: <error class> — <error message>
46
+ - Recorded retries: <metrics.steps[].retries>
47
+ - Root cause hypothesis: <analysis>
48
+
49
+ ## What Worked
50
+ - <N> tasks completed successfully
51
+ - Context collection was accurate
52
+
53
+ ## Recommendations for Retry
54
+ - Fix <specific issue> before re-running
55
+ - Consider splitting task-3 into smaller subtasks
56
+ ```
57
+
58
+ ## Metrics table in the report
59
+
60
+ **Aggregated in the report:**
61
+ ```markdown
62
+ ## Metrics
63
+ | Step | Duration | Retries |
64
+ |------|----------|---------|
65
+ | Analyze | 45s | 0 |
66
+ | Context | 30s | 0 |
67
+ | Implement | 5m 22s | 1 |
68
+ | Review | 1m 10s | 0 |
69
+ | **Total** | **7m 47s** | **1** |
70
+ ```
@@ -989,7 +989,7 @@ transcript and nothing else, which is precisely the failure the managed pipeline
989
989
  replaced.
990
990
 
991
991
  A pull request driven by this orchestrator has to pass the completion gate shipped in
992
- 0.2.71. Its five conditions are what 2.6 and 2.7 are built to satisfy:
992
+ 0.2.71. Its conditions are what 2.6 and 2.7 are built to satisfy:
993
993
 
994
994
  | Gate condition | Satisfied by |
995
995
  |---|---|
@@ -998,6 +998,7 @@ A pull request driven by this orchestrator has to pass the completion gate shipp
998
998
  | scope B is recorded when a scope-B reviewer ran | `keryx review blast-radius --json` → `review ingest --blast-radius` (2.6.1) |
999
999
  | no inbound PR comment is unanswered | `keryx review comments collect` every round, `… reply --final` once (2.6.2) |
1000
1000
  | verification stats exist | `review-verifier` dispatched, passed as `--verifications` (2.6.1) |
1001
+ | no held finding lacks an operator decision | `review-premise` (Wave D) claims passed as `--premises`; each held finding asked as `review-orchestrator` "Wave D" says (2.6.1) |
1001
1002
 
1002
1003
  #### 2.6.0 Review Scope Selection
1003
1004
 
@@ -1125,24 +1126,26 @@ runs on the session model. That is a correct answer, not a failure.
1125
1126
 
1126
1127
  ```
1127
1128
  flags: <selected flags, e.g. --backend --security --testing-practices>
1128
- commit_range: <BASE_SHA>..HEAD
1129
+ commit_range: <BASE_SHA>..HEAD # round 1 only; a later round takes its range from round.json
1129
1130
  issue_url: <issue URL, when the job has one — enables the Stage 1 spec gate>
1130
1131
  context_doc: <JOBS_ROOT>/<job-name>/ai/context.md
1131
1132
  verification_mode: annotate
1132
1133
  managed_review: { mode: "review-flow", target: "branch", target_ref: "<feature-branch>" }
1133
- is_fix_round: <true on any round after the first>
1134
+ is_fix_round: <true on any round after the first; the round, its range and prior_findings come from `review start`/`review plan`, never from your own range>
1134
1135
  pr_comments: { enabled: <true when a PR exists> }
1135
1136
  ```
1136
1137
 
1137
- It emits the unified report AND the fenced ```json keryx:findings``` block, and runs
1138
- `review-verifier` (Wave C) over the consolidated findings, returning its verification
1139
- claims as a file path.
1138
+ It emits the unified report AND the fenced ```json keryx:findings``` block, runs
1139
+ `review-verifier` (Wave C) and then `review-premise` (Wave D) over the consolidated
1140
+ findings, and returns the verification claims and the premise claims as file paths.
1140
1141
 
1141
- **Step 7 — verification is part of the round, not an extra.** `review-orchestrator`
1142
- dispatches `review-verifier` in Wave C over the consolidated findings. The verifier
1143
- **runs something** and can only delete — it never raises a severity, adds a finding,
1144
- or rewrites one, and it never verifies a finding raised by the same reviewer. Its
1145
- claims are merged by the CLI, not by hand.
1142
+ **Step 7 — verification and the premise check are part of the round, not extras.**
1143
+ `review-orchestrator` dispatches `review-verifier` in Wave C over the consolidated
1144
+ findings. The verifier **runs something** and can only delete — it never raises a
1145
+ severity, adds a finding, or rewrites one, and it never verifies a finding raised by the
1146
+ same reviewer. After it, `review-premise` (Wave D) judges whether each finding is a
1147
+ defect at all: it can lower a severity or hold a finding for the operator, never add
1148
+ one. Both claim sets are merged by the CLI, not by hand.
1146
1149
 
1147
1150
  **Step 8 — ingest the round.** This is what makes it citable.
1148
1151
 
@@ -1152,13 +1155,14 @@ keryx review ingest --report <path/to/review-report.md> --ref <feature-branch> \
1152
1155
  --scope <JOBS_ROOT>/<job-name>/scope.json \
1153
1156
  --blast-radius <JOBS_ROOT>/<job-name>/blast-radius.json \
1154
1157
  --verifications <path/to/verifications.json> --verification-mode annotate \
1155
- --refuted <path/to/refuted.json> \
1158
+ --premises <path/to/premises.json> --refuted <path/to/refuted.json> \
1156
1159
  --spent <usd-so-far> --outstanding <subagents in flight>
1157
1160
  ```
1158
1161
 
1159
1162
  An unrecognised option is **refused, not ignored** — a silently dropped flag writes
1160
1163
  nothing and still reports success. `--refuted` carries findings this round raised and
1161
1164
  then dismissed; without it the package keeps only the survivors of an unlogged triage.
1165
+ A first ingest without `--premises` holds nothing; a re-ingest carries earlier holds.
1162
1166
 
1163
1167
  **Findings are the canonical shape.** One vocabulary, everywhere in this skill:
1164
1168
 
@@ -1174,9 +1178,18 @@ then dismissed; without it the package keeps only the survivors of an unlogged t
1174
1178
 
1175
1179
  **Classify:**
1176
1180
  ```
1177
- NEEDS_FIX = count(blocker) > 0 OR count(major) > 0
1181
+ HELD = findings review-premise holds (challenged / owner-decision) with no operator decision
1182
+ OPEN = blocker + major findings not in HELD
1183
+ NEEDS_FIX = count(OPEN) > 0
1178
1184
  ```
1179
1185
 
1186
+ A HELD finding is neither fixable nor clean: it never enters a fix prompt, and it blocks
1187
+ the job until answered. Ask the operator about each one as `review-orchestrator` "Wave
1188
+ D" says; an upheld one rejoins OPEN at its severity (carrying `premise_verdict` and
1189
+ `premise_answer` as task-implementer's `FIX_CONTEXT` defines them), a dismissed one
1190
+ carries `dismissed-incorrect` from the operator's decision. Unattended: stop and report
1191
+ the HELD list; never let the loop end reading clean while HELD is non-empty.
1192
+
1180
1193
  **Document:** record the report in the job package too, so the job and the review
1181
1194
  record point at each other:
1182
1195
 
@@ -1276,7 +1289,7 @@ still reported — so a finding the next reviewer simply did not look at was rec
1276
1289
  fixed. That is absence-as-evidence, and the completion gate refuses it.
1277
1290
 
1278
1291
  ```
1279
- UNRESOLVED_FINDINGS = all blocker + major findings from step 2.6
1292
+ UNRESOLVED_FINDINGS = OPEN from step 2.6 # never a HELD finding (Classify, 2.6.1)
1280
1293
  PREVIOUS_REVIEW_OUTPUT = <the ingested report from step 2.6>
1281
1294
 
1282
1295
  FOR iteration in [1, 2, 3]:
@@ -1298,15 +1311,15 @@ FOR iteration in [1, 2, 3]:
1298
1311
  4. Run the sanity check (step 2.5.2 logic) — verify commits were made
1299
1312
  5. Run the next managed round — the FULL 2.6.1 sequence, not a bare re-dispatch:
1300
1313
  keryx review budget --spent <usd> --outstanding <n>
1301
- keryx review start --target branch --ref <feature-branch> --head <new-head>
1314
+ keryx review plan --target branch --ref <feature-branch> --head <new-head>
1315
+ # start + scope + blast-radius + slice + required + estimate on the new head;
1316
+ # round.json names the delta, scope.json and blast-radius.json land in the package
1302
1317
  keryx review comments collect --repo <r> --pr <n> --sha <new-head> --round <N+1> --out <file>
1303
- keryx review scope --ref "$BASE_SHA" --json > scope.json
1304
- keryx review blast-radius --ref "$BASE_SHA" --previous blast-radius.json --json > blast-radius.json
1305
- <run review-orchestrator inline, in this session, with is_fix_round: true>
1318
+ <run review-orchestrator inline, in this session; it takes the round from round.json>
1306
1319
  keryx review ingest --report <new-report> --ref <feature-branch> --head <new-head> \
1307
1320
  --scope scope.json --blast-radius blast-radius.json \
1308
- --verifications <file> --refuted <file> --outstanding <n>
1309
- 6. Recompute NEEDS_FIX from the ingested findings
1321
+ --verifications <file> --premises <file> --refuted <file> --outstanding <n>
1322
+ 6. Recompute HELD, OPEN and NEEDS_FIX from the ingested findings; a new HELD finding is asked about as in Classify
1310
1323
  7. Record what became of each finding raised in the PREVIOUS round — every one of
1311
1324
  them, before the next iteration starts:
1312
1325
 
@@ -1320,9 +1333,8 @@ FOR iteration in [1, 2, 3]:
1320
1333
  must cite where the outcome is written down. A recorded state and its citation
1321
1334
  cannot be overwritten by a later close — record a correction as a new round.
1322
1335
  Closing with no dispositions leaves every finding reading `unknown`, which means
1323
- "nobody wrote down what happened".
1324
- 8. UNRESOLVED_FINDINGS = the blocker + major findings of the NEW round that are
1325
- still without a terminal disposition
1336
+ "nobody wrote down what happened". A HELD finding gets no disposition here: it waits for the operator.
1337
+ 8. UNRESOLVED_FINDINGS = the OPEN findings of the NEW round still without a terminal disposition
1326
1338
 
1327
1339
  9. STUCK CHECK — runs before the next iteration and ignores the budget:
1328
1340
  IF any finding identity is in UNRESOLVED_FINDINGS for the SECOND iteration
@@ -1351,6 +1363,8 @@ IF still NEEDS_FIX after max iterations, or the stuck check broke the loop:
1351
1363
  steps. Give every surviving finding a disposition (dismissed-wont-fix or
1352
1364
  dismissed-deprioritised, with evidence) rather than leaving it `unknown`
1353
1365
  → continue to VERIFY (2.8)
1366
+
1367
+ IF HELD is non-empty when the loop ends: ask the operator (Classify); unattended, stop and report the HELD list
1354
1368
  ```
1355
1369
 
1356
1370
  `comments reply` **refuses without `--final`**: replying per round turns one review
@@ -1936,28 +1950,8 @@ When `dry_run: true` is set (or `--dry-run` is passed):
1936
1950
  3. **Phase 2 is skipped entirely** — no sub-agents dispatched, no git operations
1937
1951
  4. **Output:** Full plan tree with agent names, input data shapes, dependencies:
1938
1952
 
1939
- ```
1940
- Dry-run plan for: issue-4141--pipeline-validation
1941
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1942
- Step 1: analyze [issue-analyzer] → input: issue #4141
1943
- Step 2: context [context-collector] → input: analysis result, project_dir
1944
- Step 3: prepare [orchestrator] → creates: feature/pipeline-validation worktree
1945
- Step 4: tests-creator [tests-creator × 3] → RED stubs, one per task
1946
- Step 5: implement [task-implementer × 3] → wave-parallel, 3 tasks
1947
- Step 6: sanity-check [orchestrator] → verifies commits exist
1948
- Step 7: verify [code-verifier] → lint + type-check + test + imports
1949
- Step 8: review [review-orchestrator] → managed round, ingested
1950
- Step 9: security [security-audit] → conditional: auth/API/DB/env files
1951
- Step 10: fix [task-implementer] → conditional: if NEEDS_FIX
1952
- Step 11: verify-post-fix [code-verifier] → conditional: after fix
1953
- Step 12: perf-check [perf-check] → conditional: frontend/bundle files
1954
- Step 13: report [orchestrator] → aggregates all results
1955
- Step 14: pr [orchestrator + gh CLI] → conditional: if create_pr
1956
- Step 15: deploy [deploy] → conditional: if user asks
1957
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1958
- Estimated sub-agent calls: 11-14 (varies with tasks and review findings)
1959
- No changes will be made. Use without --dry-run to execute.
1960
- ```
1953
+ The full plan tree (agent names, input shapes, dependencies, no changes made):
1954
+ `SKILL.detail.md`, "Dry-run plan output example".
1961
1955
 
1962
1956
  5. Ask user: "Execute this plan? (yes / adjust / abort)"
1963
1957
 
@@ -2158,29 +2152,7 @@ When a job ends with a step recorded `failed`, or with unresolved blocker findin
2158
2152
  off `journal.md`, which `keryx job` timestamped as the job ran, and the retry counts
2159
2153
  come from `keryx job status <job-name> --json`:
2160
2154
 
2161
- ```markdown
2162
- # Post-Mortem: <job-name>
2163
-
2164
- ## Timeline
2165
- (from .metaproject/jobs/<job-name>/journal.md — every line as recorded)
2166
- - <ISO timestamp> - created
2167
- - <ISO timestamp> - step: implement in-progress (retries 0)
2168
- - <ISO timestamp> - step: implement in-progress (retries 1)
2169
- - <ISO timestamp> - step: implement failed (retries 1) — <reason>
2170
-
2171
- ## What Went Wrong
2172
- - <Step name> failed with: <error class> — <error message>
2173
- - Recorded retries: <metrics.steps[].retries>
2174
- - Root cause hypothesis: <analysis>
2175
-
2176
- ## What Worked
2177
- - <N> tasks completed successfully
2178
- - Context collection was accurate
2179
-
2180
- ## Recommendations for Retry
2181
- - Fix <specific issue> before re-running
2182
- - Consider splitting task-3 into smaller subtasks
2183
- ```
2155
+ Use the template in `SKILL.detail.md`, "Post-mortem document template".
2184
2156
 
2185
2157
  2. Save to `.metaproject/jobs/<job-name>/post-mortem.md`
2186
2158
  3. Include in final user message: "Post-mortem saved to `.metaproject/jobs/<job-name>/post-mortem.md`"
@@ -2220,16 +2192,7 @@ terminal disposition.
2220
2192
  keryx job status <job-name> --json # `retries` per step, plus phase and next_step
2221
2193
  ```
2222
2194
 
2223
- **Aggregated in the report:**
2224
- ```markdown
2225
- ## Metrics
2226
- | Step | Duration | Retries |
2227
- |------|----------|---------|
2228
- | Analyze | 45s | 0 |
2229
- | Context | 30s | 0 |
2230
- | Implement | 5m 22s | 1 |
2231
- | Review | 1m 10s | 0 |
2232
- | **Total** | **7m 47s** | **1** |
2233
- ```
2195
+ **Aggregated in the report:** a per-step Duration/Retries table with a total row
2196
+ (`SKILL.detail.md`, "Metrics table in the report").
2234
2197
 
2235
2198
  This data identifies which steps are bottlenecks and which ones needed a second attempt.
@@ -179,7 +179,7 @@ If `blocker` or `major` findings exist:
179
179
  1. Group by file
180
180
  2. Launch task-implementer (`task_type: "fix"`) with the findings
181
181
  3. Run the sanity check — verify commits were made
182
- 4. Run the NEXT managed round (the whole REVIEW sequence above, `is_fix_round: true`)
182
+ 4. Run the NEXT managed round: `keryx review plan --target branch --ref <feature-branch> --head <new-head>` (or `review start`) on the new head, then review-orchestrator. The round, range and prior findings come from `round.json`: no own range, no `--all` per round, no whole-branch re-review.
183
183
  5. Disposition every finding of the previous round:
184
184
  ```bash
185
185
  keryx review complete <review-id> --finding F-001 --disposition acted-on --evidence "<commit>"