@mrciphersmith/keryx 0.3.121 → 0.3.123

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 (21) hide show
  1. package/README.md +21 -1
  2. package/dist/cli.js +8829 -4479
  3. package/dist/core.js +1914 -1236
  4. package/package.json +2 -1
  5. package/src/gdgraph/staleness.ts +20 -7
  6. package/src/gdskills/bundled/rules/core/skill-lifecycle.mdc +9 -0
  7. package/src/gdskills/bundled/rules/core/skills-storage-workflow.mdc +6 -0
  8. package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +12 -12
  9. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +21 -22
  10. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/orchestrator-prompt.md +12 -4
  11. package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.md +11 -12
  12. package/src/gdskills/bundled/skills/orchestration/task-implementer/input-contract.schema.json +5 -0
  13. package/src/gdskills/bundled/skills/orchestration/task-implementer/output-contract.schema.json +16 -0
  14. package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.md +9 -9
  15. package/src/gdskills/bundled/skills/quality/tests-creator/input-contract.schema.json +4 -0
  16. package/src/gdskills/bundled/skills/quality/tests-creator/output-contract.schema.json +17 -1
  17. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +3 -2
  18. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +4 -4
  19. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +10 -1
  20. package/src/gdskills/bundled/skills/review/review-premise/SKILL.md +1 -1
  21. package/src/gdskills/contracts/review-finding.schema.json +14 -2
@@ -42,7 +42,7 @@ license: "MIT"
42
42
  | Wave bytes | `--host keryx-shell` (60000: a child dies at 5 min) | `--host claude-code` (150000) | `--host codex` / `--host other` (150000) | its own |
43
43
 
44
44
  - **Every host keeps the same files, per package** (`<package>` is the directory `keryx review start` created): slices in `<package>/slices/`, one reply per dispatch in `<package>/raw/<reviewer>.txt` (reviewer replies only), one driver per dispatch in `<package>/drivers/<reviewer>.json`, and `research.json` from `keryx review ledger build --package <package>` — never by hand. `retry-plan --package`, `verdict` and `ledger build` read `<package>/raw` by default; the shared `.metaproject/data/review/raw` holds other rounds' replies and is read only when named, so never write there.
45
- - **The result is the file, never the spawn tool's output.** keryx shell cuts a child's summary at 16000 characters and Claude Code returns only the last message: parse `<package>/raw/<reviewer>.txt`. A missing file is no result (`BLOCKED`).
45
+ - **The result is the file, never the spawn tool's output.** keryx shell cuts a child's summary at 16000 characters and Claude Code returns only the last message: parse `<package>/raw/<reviewer>.txt`. A missing file is no result (`BLOCKED`). Token spend: keryx shell appends `KERYX_USAGE {"tokens_in":…,"tokens_out":…,"model":…,"by":"host"}` to that file itself; on another host, when your spawn tool's result reports the subagent's tokens, append that one line, with `"by":"host"`, to the file yourself (a dispatch without it is counted unknown, never 0; a line without the mark is counted as stated by the reviewer, not measured).
46
46
  - **Caller token.** `keryx review start` prints `export KERYX_REVIEW_CALLER_TOKEN=…`; outside keryx shell export it on this session's keryx calls, or readiness item 10 reads "not observable on this host".
47
47
  - **Operator decisions.** In keryx shell the operator answers a card: `review_confirm_plan`, `review_waive_readiness`, `review_waive_coverage`, `review_waive_mutation`, `review_override_reviewers`, `review_chunks_answer`, `review_publish`, `review_comments_reply`, `review_decide_premise`. Elsewhere give the operator the exact command to run in their own terminal — `keryx review confirm-plan --package <package>`, `keryx review complete <package> --waive <item> "<their words>"` or `--waive-coverage "<their words>"`, `keryx review waive-mutation --package <package> --reason "<why>"`, `keryx review override-reviewers --package <package> --reason "<why>"`, `keryx review chunks estimate --package <package> --answer yes --quote "<their words>"`, `keryx review publish <package>`, `keryx review comments reply … --final`, `keryx review decide-premise --package <package> --finding <id> --answer uphold|dismiss --reason "<why>"` — and wait. You never run `--mode automatic` or `--yes`, and never write an operator quote yourself; unattended, the item is Not run.
48
48
 
@@ -291,7 +291,7 @@ overwritten by a later close; record a correction as a new round.
291
291
 
292
292
  `review complete` on a PR-bound package refuses a merged PR or a moved head unless `report.md`'s first line names the reviewed SHA and says so; a package with no PR binding is unaffected.
293
293
  `review complete` refuses a report with no `Evidence level:` line (`static only — did not run: <what>` or `ran — <what>`); a package ingested before the field existed only warns.
294
- `review ingest` refuses an internal finding whose `quote` is not at its `path` at the reviewed SHA, and adds a `citation not found` line, never a deletion, to a rebuttal citing a rule or document that is not in the tree.
294
+ `review ingest` keeps an internal finding whose `quote` is not at its `path` at the reviewed SHA (absent, or in several places) with no line, flagged `quote_unlocated`, instead of refusing the round (readiness item `quotes-located` lists it until it has a disposition); it still refuses, naming the finding, a quote it could not check at all (commit not in the checkout, no file named, or a size bound), and adds a `citation not found` line, never a deletion, to a rebuttal citing a rule or document that is not in the tree.
295
295
  A finding may close as `narrowed` (research obligation `"status":"narrowed","remains":"<what remains>"`); it counts as closed only with that text, and the report keeps it under "Refuted and narrowed".
296
296
 
297
297
  **Closing a fix round without dispositions leaves every finding reading `unknown`.** That is not "the reviewers were right"; it is "nobody wrote down what happened", and it is the single reason the precision figure cannot be read.
@@ -682,7 +682,7 @@ record `model_assignment: adaptive` with the model you chose.
682
682
 
683
683
  `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.
684
684
 
685
- - **`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.
685
+ - **`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, `review-security-code` when one of them is a security finding, the roles its own changed lines route by content (e.g. `review-security-code` for a fix that spawns a process or builds an argv), 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.
686
686
  - **`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.
687
687
  - **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".
688
688
  - **Duplicates.** `keryx review ingest` merges duplicate findings; later waves receive `known_findings` and do not report them again.
@@ -1223,7 +1223,7 @@ Before consolidation, validate every reviewer result:
1223
1223
  - Every blocker must include evidence and a concrete suggested fix.
1224
1224
  - Findings without evidence are downgraded to `info` or returned to the reviewer for clarification.
1225
1225
  - Duplicate findings are merged by `dedupe_key` or by `(file, quote, problem)`.
1226
- - **A reply that is not a result is `BLOCKED`.** That covers an empty reply (`(subagent produced no text)`), a reply without a valid `REVIEW_RESULT` block, and a `spawn_subagent` output whose first line is `status: BudgetExhausted (…)` or `status: NoProgress …` — the reviewer was cut short, so whatever fragment it left is not its review. Re-dispatch it exactly once with a larger budget, telling it to return its report before the budget ends (and the same `cwd` when the round reviews a worktree). If that also fails, the pass is **not run**: list it under **Not run** with the reason, and never write its findings yourself — a review nobody ran reported as run is worse than a gap the operator can see.
1226
+ - **A reply that is not a result is `BLOCKED`.** That covers an empty reply (`(subagent produced no text)`), a reply without a valid `REVIEW_RESULT` block, and a `spawn_subagent` output whose first line is `status: BudgetExhausted (…)` or `status: NoProgress …` — the reviewer was cut short, so whatever fragment it left is not its review — and a `DONE` result whose `inputs_unreadable` lists a mandatory input it could not read. Re-dispatch it exactly once with a larger budget, telling it to return its report before the budget ends (and the same `cwd` when the round reviews a worktree). For unreadable inputs a larger budget changes nothing: first fix the path or payload that `inputs_unreadable` names, then re-dispatch once. If that also fails, the pass is **not run**: list it under **Not run** with the reason, and never write its findings yourself — a review nobody ran reported as run is worse than a gap the operator can see.
1227
1227
  - **The verifier is held to the same rule.** A finding without an applied verifier verdict is `unverified`; the orchestrator never verifies one itself, and a verifier that returned nothing leaves every finding `unverified`, stated in the stage counts.
1228
1228
  - If a reviewer exceeds `max_findings`, keep blockers/majors first and summarize lower severity findings.
1229
1229
 
@@ -82,7 +82,7 @@
82
82
  "minimum": 1
83
83
  },
84
84
  "quote": {
85
- "description": "The code this finding is about, copied verbatim out of `file`. A few lines is enough — it is matched against the file at the round's head, exactly first and then with whitespace collapsed. A quote that appears in more than one place is recorded as `unlocatable` rather than anchored to the first hit, so quote enough to be unambiguous.",
85
+ "description": "The code this finding is about, copied verbatim out of `file`. A few lines is enough — it is matched against the file at the round's head, exactly first, then with whitespace collapsed, then (a one-line quote of at least 12 characters) as an exact substring of exactly one line; a shorter fragment must be a whole line. A quote that appears in more than one place is recorded as `unlocatable` rather than anchored to the first hit, so quote enough to be unambiguous.",
86
86
  "type": [
87
87
  "string",
88
88
  "null"
@@ -184,6 +184,15 @@
184
184
  "type": "string"
185
185
  }
186
186
  },
187
+ "inputs_unreadable": {
188
+ "title": "Mandatory inputs the reviewer could not read",
189
+ "description": "Name each mandatory input of the dispatch the reviewer could not read: pr_context.json, this schema, a context file, the payload, the slice manifest. Empty or absent when it read them all. A DONE or DONE_WITH_CONCERNS result that names any is not a finished run for `ledger build` or `retry-plan`. Only this field is read for it, never the summary's wording.",
190
+ "type": "array",
191
+ "items": {
192
+ "type": "string",
193
+ "minLength": 1
194
+ }
195
+ },
187
196
  "follow_ups": {
188
197
  "title": "Follow-ups handed back to the parent",
189
198
  "description": "Work the reviewer could not do itself and leaves to the parent. Each entry becomes a research obligation `<raw>:fu<N>` (N counts from 1, in this order; a `Parent follow-up:` line after the REVIEW_RESULT block is appended) with status `unresolved`. It closes only through a disposition (`finding`, `refuted`, `out-of-scope` or `narrowed` with a \"what remains\" text); until then the ledger is not reconciled and the report lists it as not verified.",
@@ -60,7 +60,7 @@ judgement on it; the claim and the code read first let you see what the reviewer
60
60
 
61
61
  | Field | Type | Required | Description |
62
62
  |-------|------|----------|-------------|
63
- | `findings` | array | yes | Canonical findings (`review-finding.schema.json`), each with its display `id`, unique in the package (the caller renumbers a collision, a new finding sharing a carried one's id included, in the saved reviewer results before you run): your claim's `finding_id` (a claim may name an id the duplicate merge absorbed; it then targets the surviving finding). Only a finding carried from an earlier round also has a `global_id` (ingest mints the rest after you run); a claim may name a carried finding by it. Each has `reviewer`, `severity`, `source` (`external` marks a PR comment), `file`, `quote`, `problem`, `suggested_fix`. In a delta round, only findings with no carried premise. |
63
+ | `findings` | array | yes | Canonical findings (`review-finding.schema.json`), each with its display `id`, unique in the package (the caller renumbers a collision, a new finding sharing a carried one's id included, in the saved reviewer results before you run): your claim's `finding_id` (a claim may name an id the duplicate merge absorbed; it then targets the surviving finding). A display `id` holds no `#`, the global_id separator (`<reviewId>#<id>`): ingest refuses one, so the caller names a finding of several waves `<raw stem>:<id>` (`review-logic-w1:F-001`), never `review-logic-w1#F-001`. Several judge dispatches each return one claims file; `review ingest --premises` takes them all. Only a finding carried from an earlier round also has a `global_id` (ingest mints the rest after you run); a claim may name a carried finding by it. Each has `reviewer`, `severity`, `source` (`external` marks a PR comment), `file`, `quote`, `problem`, `suggested_fix`. In a delta round, only findings with no carried premise. |
64
64
  | `judge` | string | yes | Your own name. Recorded on the claims file. |
65
65
  | `source` | string | no | `round` (default), `follow-up-issue` or `external-comment`: where the findings came from. The last two have no review package: each finding is the issue or comment text as `problem`, its `file:line` refs as `file`/`line` with the code there as `quote`, the comment's `C-NNN` (review-pr-feedback) or the issue's number as `id` with its URL as `url`, its author as `reviewer` and the severity it states (else `minor`). The caller saves your claims beside its own record (a flow: `<flow-dir>/premise/<n>.json`) and journals the operator's answer; no `ingest` or `decide-premise` runs, so the caller applies every refusal of the merge (`PREMISE_REJECTION_REASONS`, `src/review/premise.ts`) by hand. For one finding those are: any claim on a finding below `minor` (`below-minor`); an uncited `challenged` or `downgraded`; a `downgraded` with no `severity`, or with the finding's own severity (`not-a-downgrade`) or a higher one (`raise-severity`); a `severity` on any other verdict (`severity-without-downgrade`); a downgrade of an external or `review-security-code` finding, of a `minor`, or below `minor`; a hold without a `question`; an `owner-decision` with fewer than two options; a judge that raised the finding. |
66
66
  | `branch` / `base_sha` | string | no | So `git log` and `git blame` read the change under review, where your host gives you git; on keryx shell the caller passes their output in `context_files`. |
@@ -192,7 +192,7 @@
192
192
  },
193
193
  "quote": {
194
194
  "title": "The code this finding is about, copied verbatim out of `file`",
195
- "description": "What replaces asking the model for a line number: a model is poor at counting lines and good at repeating text it has just read. The quote is matched against `file` at the round's head — exactly first, then with whitespace collapsed, then not at all. A quote that matches in more than one place is `unlocatable` rather than anchored to the first hit, because choosing among them would be a guess wearing a line number. Absent for a finding that is about the round rather than a site.",
195
+ "description": "What replaces asking the model for a line number: a model is poor at counting lines and good at repeating text it has just read. The quote is matched against `file` at the round's head — exactly first, then with whitespace collapsed, then (a one-line quote of at least 12 characters, MIN_SUBSTRING_QUOTE_CHARS) as an exact substring of exactly one line, then not at all; a shorter fragment must be a whole line. A quote that matches in more than one place is `unlocatable` rather than anchored to the first hit, because choosing among them would be a guess wearing a line number. Absent for a finding that is about the round rather than a site.",
196
196
  "type": ["string", "null"]
197
197
  },
198
198
  "locator": {
@@ -203,7 +203,7 @@
203
203
  "required": ["state"],
204
204
  "properties": {
205
205
  "state": { "type": "string", "enum": ["derived", "unlocatable"] },
206
- "method": { "type": "string", "enum": ["exact", "whitespace-normalised"] },
206
+ "method": { "type": "string", "enum": ["exact", "whitespace-normalised", "substring"] },
207
207
  "reason": { "type": ["string", "null"] },
208
208
  "reported_line": { "type": ["integer", "null"], "minimum": 1 }
209
209
  },
@@ -222,6 +222,18 @@
222
222
  "type": "boolean",
223
223
  "description": "Written, as true, only by `review ingest` when `file` does not exist at the round's reviewed head (a cat-file existence check of `<head>:<file>`). The finding is kept, never dropped, and is listed in scope.md: a finding that cites a path nobody can open is the reviewer's claim, not a located site."
224
224
  },
225
+ "quote_unlocated": {
226
+ "type": "object",
227
+ "additionalProperties": false,
228
+ "required": ["reason"],
229
+ "description": "Written only by `review ingest` when `quote` was checked against `file` at the round's reviewed head and is absent from it (`absent`) or matches more than one place (`ambiguous`, with the matched lines in `candidate_lines`, bounded by the scan). The finding is kept with `line` null, never relocated by similarity or proximity, listed in scope.md and by readiness item `quotes-located`; the round is recorded rather than refused. It is the machine-readable outcome of the same check that writes `locator` (`state: unlocatable`, its `reason` the prose of it): ingest writes both from one result and recomputes both on every ingest, so they never disagree. Gates and readers branch on this flag; `locator` is the record of how the line was or was not derived.",
230
+ "properties": {
231
+ "reason": { "type": "string", "enum": ["absent", "ambiguous"] },
232
+ "candidate_lines": { "type": "array", "maxItems": 50000, "items": { "type": "integer", "minimum": 1 } }
233
+ },
234
+ "if": { "properties": { "reason": { "const": "absent" } }, "required": ["reason"] },
235
+ "then": { "not": { "required": ["candidate_lines"] } }
236
+ },
225
237
  "symbol": { "type": ["string", "null"] },
226
238
  "problem": { "type": "string" },
227
239
  "impact": { "type": "string" },