@mrciphersmith/keryx 0.3.123 → 0.3.125
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/cli.js +1549 -556
- package/dist/core.js +85 -17
- package/package.json +1 -1
- package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +5 -5
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +11 -11
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +25 -26
- package/src/gdskills/bundled/skills/review/review-premise/SKILL.md +2 -2
- package/src/gdskills/contracts/review-finding.schema.json +14 -1
package/dist/core.js
CHANGED
|
@@ -9745,7 +9745,7 @@ var init_skill_length_ceilings = __esm(() => {
|
|
|
9745
9745
|
"review/review-jev-scenarios": 182,
|
|
9746
9746
|
"review/review-layout": 237,
|
|
9747
9747
|
"review/review-logic": 365,
|
|
9748
|
-
"review/review-orchestrator":
|
|
9748
|
+
"review/review-orchestrator": 1571,
|
|
9749
9749
|
"review/review-performance": 354,
|
|
9750
9750
|
"review/review-pr-contract": 191,
|
|
9751
9751
|
"review/review-pr-feedback": 893,
|
|
@@ -23431,6 +23431,42 @@ function checkPageInvariants(before, after) {
|
|
|
23431
23431
|
function describeViolations(violations) {
|
|
23432
23432
|
return violations.map((violation) => `${violation.kind}: ${violation.detail}`).join("; ");
|
|
23433
23433
|
}
|
|
23434
|
+
function unwrapFencedReply(reply) {
|
|
23435
|
+
const trimmed = toLf(reply).trim();
|
|
23436
|
+
const open = /^```(?:markdown|md)?[ \t]*\n/i.exec(trimmed);
|
|
23437
|
+
if (!open || !/\n```[ \t]*$/.test(trimmed))
|
|
23438
|
+
return reply;
|
|
23439
|
+
const inner = trimmed.slice(open[0].length, trimmed.lastIndexOf("\n```"));
|
|
23440
|
+
const unbalanced = inner.split(`
|
|
23441
|
+
`).filter((line) => /^```/.test(line.trim())).length % 2 !== 0;
|
|
23442
|
+
return unbalanced ? reply : `${inner}
|
|
23443
|
+
`;
|
|
23444
|
+
}
|
|
23445
|
+
function leadingYamlBlock(markdown) {
|
|
23446
|
+
if (!markdown.startsWith(`---
|
|
23447
|
+
`))
|
|
23448
|
+
return null;
|
|
23449
|
+
const close = markdown.indexOf(`
|
|
23450
|
+
---`, 3);
|
|
23451
|
+
if (close < 0)
|
|
23452
|
+
return null;
|
|
23453
|
+
const lineEnd = markdown.indexOf(`
|
|
23454
|
+
`, close + 4);
|
|
23455
|
+
return markdown.slice(0, lineEnd < 0 ? markdown.length : lineEnd + 1);
|
|
23456
|
+
}
|
|
23457
|
+
function unwrapFencedPage(markdown) {
|
|
23458
|
+
const source = toLf(markdown);
|
|
23459
|
+
const meta = leadingYamlBlock(source);
|
|
23460
|
+
if (meta === null)
|
|
23461
|
+
return markdown;
|
|
23462
|
+
const body = source.slice(meta.length);
|
|
23463
|
+
const unwrapped = unwrapFencedReply(body);
|
|
23464
|
+
if (unwrapped === body)
|
|
23465
|
+
return markdown;
|
|
23466
|
+
const innerMeta = leadingYamlBlock(unwrapped);
|
|
23467
|
+
const inner = innerMeta === null ? unwrapped : unwrapped.slice(innerMeta.length);
|
|
23468
|
+
return inEolOf(markdown, `${meta}${inner.replace(/^\n+/, "")}`);
|
|
23469
|
+
}
|
|
23434
23470
|
function generatedSectionHeadings(generated) {
|
|
23435
23471
|
return sectionsOf(generated).filter((section) => !/^(summary|changelog)$/i.test(section.heading)).filter((section) => !/^_Draft\b/.test(section.lines.slice(1).find((line) => line.trim() !== "")?.trim() ?? "")).map((section) => section.heading);
|
|
23436
23472
|
}
|
|
@@ -23456,7 +23492,8 @@ function isDeclaredGenerated(heading) {
|
|
|
23456
23492
|
return isReferenceHeading(heading) || /^related wiki$/i.test(heading);
|
|
23457
23493
|
}
|
|
23458
23494
|
function mergeGeneratedSections(existing, generated, entryNote) {
|
|
23459
|
-
const
|
|
23495
|
+
const original = toLf(existing);
|
|
23496
|
+
const source = toLf(unwrapFencedPage(original));
|
|
23460
23497
|
const generatedLf = toLf(generated);
|
|
23461
23498
|
const fresh = new Map(sectionsOf(generatedLf).map((section) => [section.heading.toLowerCase(), section]));
|
|
23462
23499
|
const enriched = sectionsOf(source).some((section) => !fresh.has(section.heading.toLowerCase()) && !/^changelog$/i.test(section.heading));
|
|
@@ -23507,8 +23544,8 @@ ${replacement.lines.join(`
|
|
|
23507
23544
|
}
|
|
23508
23545
|
}
|
|
23509
23546
|
const repaired = repairVersionAndOrder(next);
|
|
23510
|
-
if (next ===
|
|
23511
|
-
return repaired ===
|
|
23547
|
+
if (next === original)
|
|
23548
|
+
return repaired === original ? existing : inEolOf(existing, repaired);
|
|
23512
23549
|
const version = nextPageVersion(repaired);
|
|
23513
23550
|
return inEolOf(existing, prependChangelogEntry(setPageVersion(repaired, version), `- ${version} - ${entryNote}`));
|
|
23514
23551
|
}
|
|
@@ -27472,6 +27509,20 @@ function buildReviewScope(diff, config) {
|
|
|
27472
27509
|
}
|
|
27473
27510
|
|
|
27474
27511
|
// src/review/types.ts
|
|
27512
|
+
function cardPressFields(source) {
|
|
27513
|
+
return {
|
|
27514
|
+
...source?.pressedBy === undefined ? {} : { pressedBy: source.pressedBy },
|
|
27515
|
+
...source?.pressedAt === undefined ? {} : { pressedAt: source.pressedAt }
|
|
27516
|
+
};
|
|
27517
|
+
}
|
|
27518
|
+
var HOST_QUESTION_SOURCES = ["claude-code", "codex", "keryx-shell", "helyx-poll", "other"];
|
|
27519
|
+
function isHostQuestion(value) {
|
|
27520
|
+
if (value === null || typeof value !== "object")
|
|
27521
|
+
return false;
|
|
27522
|
+
const hq = value;
|
|
27523
|
+
const filled = (field) => typeof field === "string" && field.trim() !== "";
|
|
27524
|
+
return HOST_QUESTION_SOURCES.includes(hq["source"]) && filled(hq["ref"]) && filled(hq["questionText"]) && filled(hq["answerText"]) && filled(hq["time"]);
|
|
27525
|
+
}
|
|
27475
27526
|
var FINDING_DISPOSITION_STATES = [
|
|
27476
27527
|
"unknown",
|
|
27477
27528
|
"acted-on",
|
|
@@ -28932,7 +28983,8 @@ var DECISION_SOURCES = {
|
|
|
28932
28983
|
roundLimit: "round-limit",
|
|
28933
28984
|
wikiEnrich: "tui-wiki-enrich",
|
|
28934
28985
|
queueRoute: "tui-queue-route",
|
|
28935
|
-
sessionLease: "tui-session-lease"
|
|
28986
|
+
sessionLease: "tui-session-lease",
|
|
28987
|
+
hostQuestion: "host-question"
|
|
28936
28988
|
};
|
|
28937
28989
|
var WORK_DECISION_SOURCES = Object.values(DECISION_SOURCES);
|
|
28938
28990
|
|
|
@@ -29079,7 +29131,7 @@ import path49 from "path";
|
|
|
29079
29131
|
var OPERATOR_DECISIONS_FILE = "operator-decisions.json";
|
|
29080
29132
|
var OPERATOR_DECISION_KINDS = ["mutation-waiver", "reviewer-override"];
|
|
29081
29133
|
var PREMISE_DECISION_KIND = "premise-question";
|
|
29082
|
-
var VIAS = ["tui-card", "remote-card", "terminal"];
|
|
29134
|
+
var VIAS = ["tui-card", "remote-card", "terminal", "host-question"];
|
|
29083
29135
|
var text = (value) => typeof value === "string" && value.trim().length > 0;
|
|
29084
29136
|
function validPremiseSubject(value) {
|
|
29085
29137
|
if (value === null || typeof value !== "object")
|
|
@@ -29087,17 +29139,20 @@ function validPremiseSubject(value) {
|
|
|
29087
29139
|
const subject = value;
|
|
29088
29140
|
return text(subject.severity) && text(subject.verdict) && text(subject.problemSha256);
|
|
29089
29141
|
}
|
|
29142
|
+
function viaConsistent(d) {
|
|
29143
|
+
return d.confirmedVia === "host-question" ? isHostQuestion(d.hostQuestion) : true;
|
|
29144
|
+
}
|
|
29090
29145
|
function validPremise(entry) {
|
|
29091
29146
|
if (entry === null || typeof entry !== "object")
|
|
29092
29147
|
return false;
|
|
29093
29148
|
const d = entry;
|
|
29094
|
-
return d.kind === PREMISE_DECISION_KIND && text(d.finding) && (d.answer === "uphold" || d.answer === "dismiss") && text(d.reason) && text(d.quote) && text(d.at) && text(d.actor) && VIAS.includes(d.confirmedVia) && validPremiseSubject(d.subject);
|
|
29149
|
+
return d.kind === PREMISE_DECISION_KIND && text(d.finding) && (d.answer === "uphold" || d.answer === "dismiss") && text(d.reason) && text(d.quote) && text(d.at) && text(d.actor) && VIAS.includes(d.confirmedVia) && viaConsistent(d) && validPremiseSubject(d.subject);
|
|
29095
29150
|
}
|
|
29096
29151
|
function validGateDecision(entry) {
|
|
29097
29152
|
if (entry === null || typeof entry !== "object")
|
|
29098
29153
|
return false;
|
|
29099
29154
|
const d = entry;
|
|
29100
|
-
return OPERATOR_DECISION_KINDS.includes(d.kind) && text(d.quote) && text(d.reason) && VIAS.includes(d.confirmedVia) && text(d.at) && (d.subject === undefined || validSubject(d.subject));
|
|
29155
|
+
return OPERATOR_DECISION_KINDS.includes(d.kind) && text(d.quote) && text(d.reason) && VIAS.includes(d.confirmedVia) && viaConsistent(d) && text(d.at) && (d.subject === undefined || validSubject(d.subject));
|
|
29101
29156
|
}
|
|
29102
29157
|
function foldFileName(name) {
|
|
29103
29158
|
return name.normalize("NFKC").toUpperCase().toLowerCase().normalize("NFKC");
|
|
@@ -29146,9 +29201,9 @@ async function confirmedDecision(packageDir, kind, now) {
|
|
|
29146
29201
|
}
|
|
29147
29202
|
function decisionWriters(kind, packageArg = "<review-dir>") {
|
|
29148
29203
|
if (kind === PREMISE_DECISION_KIND) {
|
|
29149
|
-
return `in keryx shell, call the review_decide_premise tool (the operator answers a card); in a terminal, run \`keryx review decide-premise --package ${packageArg} --finding <id> --answer uphold|dismiss --reason "<why>"
|
|
29204
|
+
return `in keryx shell, call the review_decide_premise tool (the operator answers a card); elsewhere ask the operator with the host's question tool, or in a terminal, run \`keryx review decide-premise --package ${packageArg} --finding <id> --answer uphold|dismiss --reason "<why>"\` (from a host question add --answered-by <source>:<ref> --answer-text "<their words>" --question-text "<the question>")`;
|
|
29150
29205
|
}
|
|
29151
|
-
return kind === "mutation-waiver" ? `in keryx shell, call the review_waive_mutation tool (the operator answers a card); in a terminal, run \`keryx review waive-mutation --package ${packageArg} --reason "<why>"
|
|
29206
|
+
return kind === "mutation-waiver" ? `in keryx shell, call the review_waive_mutation tool (the operator answers a card); elsewhere ask the operator with the host's question tool, or in a terminal, run \`keryx review waive-mutation --package ${packageArg} --reason "<why>"\` (from a host question add --answered-by <source>:<ref> --answer-text "<their words>" --question-text "<the question>")` : `in keryx shell, call the review_override_reviewers tool (the operator answers a card); elsewhere ask the operator with the host's question tool, or in a terminal, run \`keryx review override-reviewers --package ${packageArg} --reason "<why>"\` (from a host question add --answered-by <source>:<ref> --answer-text "<their words>" --question-text "<the question>")`;
|
|
29152
29207
|
}
|
|
29153
29208
|
|
|
29154
29209
|
// src/review/mutate.ts
|
|
@@ -30167,7 +30222,9 @@ function readCoverageWaiver(dispatch) {
|
|
|
30167
30222
|
return;
|
|
30168
30223
|
const files = Array.isArray(waiver.files) ? waiver.files.filter(text2) : [];
|
|
30169
30224
|
const via = waiver.confirmedVia === "tui-card" || waiver.confirmedVia === "remote-card" ? waiver.confirmedVia : undefined;
|
|
30170
|
-
|
|
30225
|
+
const pressedBy = typeof waiver.pressedBy === "number" && Number.isSafeInteger(waiver.pressedBy) ? waiver.pressedBy : undefined;
|
|
30226
|
+
const pressedAt = text2(waiver.pressedAt) ? waiver.pressedAt : undefined;
|
|
30227
|
+
return { operatorQuote: waiver.operatorQuote, time: waiver.time, files, ...via === undefined ? {} : { confirmedVia: via }, ...cardPressFields({ pressedBy, pressedAt }) };
|
|
30171
30228
|
}
|
|
30172
30229
|
async function computeCoverage(packageDir, options = {}) {
|
|
30173
30230
|
const research = options.research ?? await readJson2(path53.join(packageDir, "research.json"));
|
|
@@ -32156,7 +32213,9 @@ function flowStateSchema() {
|
|
|
32156
32213
|
description: "The completion target the token was minted for: `pr:<url>` or `merged`."
|
|
32157
32214
|
}
|
|
32158
32215
|
}
|
|
32159
|
-
}
|
|
32216
|
+
},
|
|
32217
|
+
pressedBy: { type: "integer", description: "Flow 464: on a remote-card token, the Telegram user id that pressed the card." },
|
|
32218
|
+
pressedAt: { type: "string", description: "Flow 464: on a remote-card token, the shell's clock when the press arrived." }
|
|
32160
32219
|
}
|
|
32161
32220
|
}
|
|
32162
32221
|
}
|
|
@@ -32463,7 +32522,9 @@ async function mintConfirmationToken(cwd, dir, binding, deps) {
|
|
|
32463
32522
|
mintedAt: mintedAt.toISOString(),
|
|
32464
32523
|
expiresAt: new Date(mintedAt.getTime() + CONFIRMATION_TOKEN_TTL_MS).toISOString(),
|
|
32465
32524
|
...binding.confirmedVia !== undefined ? { confirmedVia: binding.confirmedVia } : {},
|
|
32466
|
-
...binding.confirmationQuote !== undefined ? { confirmationQuote: binding.confirmationQuote } : {}
|
|
32525
|
+
...binding.confirmationQuote !== undefined ? { confirmationQuote: binding.confirmationQuote } : {},
|
|
32526
|
+
...binding.pressedBy !== undefined ? { pressedBy: binding.pressedBy } : {},
|
|
32527
|
+
...binding.pressedAt !== undefined ? { pressedAt: binding.pressedAt } : {}
|
|
32467
32528
|
};
|
|
32468
32529
|
await writeFileAtomic(confirmTokenPath(cwd, dir), `${JSON.stringify(stored, null, 2)}
|
|
32469
32530
|
`);
|
|
@@ -32523,7 +32584,9 @@ async function consumeConfirmationToken(cwd, dir, stored, now) {
|
|
|
32523
32584
|
tokenRef: stored.tokenRef,
|
|
32524
32585
|
mintedAt: stored.mintedAt,
|
|
32525
32586
|
consumedAt,
|
|
32526
|
-
boundTo: { kind: stored.kind, acChecksum: stored.acChecksum, target: stored.target }
|
|
32587
|
+
boundTo: { kind: stored.kind, acChecksum: stored.acChecksum, target: stored.target },
|
|
32588
|
+
...stored.pressedBy !== undefined ? { pressedBy: stored.pressedBy } : {},
|
|
32589
|
+
...stored.pressedAt !== undefined ? { pressedAt: stored.pressedAt } : {}
|
|
32527
32590
|
};
|
|
32528
32591
|
}
|
|
32529
32592
|
function describeConfirmationFailure(reason, flowId) {
|
|
@@ -34882,7 +34945,12 @@ function createFlowService(deps) {
|
|
|
34882
34945
|
flowId: flow.id,
|
|
34883
34946
|
acChecksum: flow.acChecksum,
|
|
34884
34947
|
target: completionTarget({ merged: merged === true, prUrl: flow.pr.url }),
|
|
34885
|
-
...confirmation !== undefined ? {
|
|
34948
|
+
...confirmation !== undefined ? {
|
|
34949
|
+
confirmedVia: confirmation.confirmedVia,
|
|
34950
|
+
confirmationQuote: confirmation.quote,
|
|
34951
|
+
...confirmation.pressedBy !== undefined ? { pressedBy: confirmation.pressedBy } : {},
|
|
34952
|
+
...confirmation.pressedAt !== undefined ? { pressedAt: confirmation.pressedAt } : {}
|
|
34953
|
+
} : {}
|
|
34886
34954
|
}, { now: deps.now });
|
|
34887
34955
|
flow = await save(cwd, dir, flow, "confirmation-minted", `token ${stored.tokenRef}, expires ${stored.expiresAt}`);
|
|
34888
34956
|
return {
|
|
@@ -47091,7 +47159,7 @@ var HELP_GROUPS = [
|
|
|
47091
47159
|
kind: "slash",
|
|
47092
47160
|
name: "/decisions",
|
|
47093
47161
|
group: "Automation",
|
|
47094
|
-
summary: "Recommendation journal report: match share by mode and stage, the deviations with their reasons, and your own answers and typed reasons in full (marked \u270D in the sidebar row)."
|
|
47162
|
+
summary: "Recommendation journal report: match share by mode and stage, the deviations with their reasons, and your own answers and typed reasons in full (marked \u270D in the sidebar row), and the Telegram review cards waiting or answered (review_confirm_plan, review_decide_premise, review_waive_readiness, review_waive_coverage, review_complete)."
|
|
47095
47163
|
},
|
|
47096
47164
|
{
|
|
47097
47165
|
kind: "slash",
|
|
@@ -47109,7 +47177,7 @@ var HELP_GROUPS = [
|
|
|
47109
47177
|
kind: "slash",
|
|
47110
47178
|
name: "/remote-control",
|
|
47111
47179
|
group: "Automation",
|
|
47112
|
-
summary: 'Drive this session from a Telegram topic: send lines, get replies, answer approvals and questions (an option button, or "\u270D \u0421\u0432\u043E\u0439 \u043E\u0442\u0432\u0435\u0442" and a reply with your own text). Off by default; [name|off|status].'
|
|
47180
|
+
summary: 'Drive this session from a Telegram topic: send lines, get replies, answer approvals and questions (an option button, or "\u270D \u0421\u0432\u043E\u0439 \u043E\u0442\u0432\u0435\u0442" and a reply with your own text). Off by default; [name|off|status]. Also takes the review cards (review_confirm_plan, review_decide_premise, review_waive_readiness, review_waive_coverage, review_complete): each pending one shows with its age and the time left of its 1-hour token, and who pressed.'
|
|
47113
47181
|
},
|
|
47114
47182
|
{
|
|
47115
47183
|
kind: "slash",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.125",
|
|
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": {
|
|
@@ -437,7 +437,7 @@ Before accepting implementation:
|
|
|
437
437
|
6. If findings require code changes, run a fix round through `task-implementer`, recorded as a flow fix task:
|
|
438
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
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,
|
|
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 host's question tool, naming the finding id, then `keryx review decide-premise` with `--answered-by <source>:<ref> --answer-text … --question-text …` to record the answer) 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
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
442
|
7. Close the skill-learning loop (see `rules/core/skill-lifecycle.mdc`). Collect
|
|
443
443
|
the `skill_drift` fields from task-implementer results and the
|
|
@@ -1186,11 +1186,11 @@ NEEDS_FIX = count(OPEN) > 0
|
|
|
1186
1186
|
```
|
|
1187
1187
|
|
|
1188
1188
|
A HELD finding is neither fixable nor clean: it never enters a fix prompt, and it blocks
|
|
1189
|
-
the job until answered. Ask the operator about each one
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
the HELD list; never let the loop end reading clean while HELD is non-empty.
|
|
1189
|
+
the job until answered. Ask the operator about each one with the host's question tool and
|
|
1190
|
+
record the answer with `keryx review decide-premise --answered-by …` as `review-orchestrator`
|
|
1191
|
+
"Wave D" says (never send them to a terminal); an upheld one rejoins OPEN at its severity
|
|
1192
|
+
(carrying `premise_verdict` and `premise_answer` as task-implementer's `FIX_CONTEXT` defines them), a dismissed one carries `dismissed-incorrect` from the
|
|
1193
|
+
operator's decision. Unattended: stop and report the HELD list; never let the loop end reading clean while HELD is non-empty.
|
|
1194
1194
|
|
|
1195
1195
|
**Document:** record the report in the job package too, so the job and the review
|
|
1196
1196
|
record point at each other:
|
|
@@ -383,7 +383,7 @@ A review must be reproducible from its package: the same inputs give the same co
|
|
|
383
383
|
|
|
384
384
|
**One memory read, and the log.** The stage query is `keryx memory search "<changed modules and the concepts they touch>" --status accepted`, once. Pass the same text to `keryx review start … --memory-query "<query>"` so the manifest's `cleanContext` block holds it next to the start time. keryx writes the orchestrator log, `<package>/tool-calls/orchestrator.jsonl`, on its own: `keryx review start` and `keryx review attach` mark the package active, and every `keryx memory` command and every MCP memory tool (`memory_search`, `memory_handoff`, `memory_propose`) appends one line naming its caller. You do not write that file and must not edit it; a hand-written line is a forgery the readiness item cannot tell apart from a record. A call by a `spawn_subagent` child is recorded under the child's reviewer id and is not judged by item 10. A call from a host that names no caller is recorded as `unattributed`: it warns in the item's detail line and never blocks. Reviewer logs stay in their own files.
|
|
385
385
|
|
|
386
|
-
Item 10, `memory-outside-stage`, reads that log. Outside the stage is: a memory search with a different query, a search without `--status accepted`, any other memory command or MCP memory tool, and a stage call stamped before the round started. The same query repeated is the stage. Repair is to rerun from a session whose only memory read is the stage; only the operator's waiver lifts it: in keryx shell call `review_waive_readiness` (the operator answers a card, never a terminal), then
|
|
386
|
+
Item 10, `memory-outside-stage`, reads that log. Outside the stage is: a memory search with a different query, a search without `--status accepted`, any other memory command or MCP memory tool, and a stage call stamped before the round started. The same query repeated is the stage. Repair is to rerun from a session whose only memory read is the stage; only the operator's waiver lifts it: in keryx shell call `review_waive_readiness` (the operator answers a card, never a terminal), then call `review_complete`; outside keryx shell `keryx review complete <package> --waive memory-outside-stage "<operator quote>"` in a terminal; unattended, record Not run. With no orchestrator log the item passes and its evidence says it could not see your reads; with a log that holds no memory call it passes and says so. Outside keryx shell, `keryx review start` prints a caller token (`export KERYX_REVIEW_CALLER_TOKEN=…`): export it on this session's keryx calls so they are attributed to you; without it the item reads "not observable on this host" rather than a clean pass. Only calls that go through keryx are visible: a read through a tool keryx does not wrap is not in the log.
|
|
387
387
|
|
|
388
388
|
**Open questions.** One general rule, replacing per-fork wording: the answer is in this skill or `SKILL.detail.md`, or it is the user's. Interactive: one `AskUserQuestion`. Unattended: the whole output is one line, `STATUS: BLOCKED question — <the question>`, and you stop. A guess and a recollection are both wrong answers.
|
|
389
389
|
|
|
@@ -663,10 +663,10 @@ certifies the flow is the one that checked the least.
|
|
|
663
663
|
8. `report-names` — pending mutation candidates and parity warnings are named in the report.
|
|
664
664
|
9. `pr-files` — the changed-file count equals the PR's own file list. A recount more than 3x the recorded scope is refused: it came from the wrong base. Recount with `keryx review blast-radius --package <ref> --changed <the PR's files>`.
|
|
665
665
|
10. `memory-outside-stage` — the orchestrator read memory only in its memory stage ("Clean context" above). Outside keryx shell it reads "not observable on this host" unless this session's keryx calls carry the caller token `keryx review start` printed.
|
|
666
|
-
11. `premise-decisions` — every finding `review-premise` held (`challenged` or `owner-decision`) has an operator decision: in keryx shell the card `review_decide_premise`; elsewhere the operator
|
|
666
|
+
11. `premise-decisions` — every finding `review-premise` held (`challenged` or `owner-decision`) has an operator decision: in keryx shell the card `review_decide_premise`; elsewhere ask the operator with the host's question tool and record the answer with `keryx review decide-premise --package <dir> --finding <global_id> --answer uphold|dismiss --reason "<why>" --answered-by <source>:<ref> --answer-text "<their words>" --question-text "<the question>"` (SKILL.md "Wave D"; the record reads `confirmedVia: "host-question"`). Not waivable: each held finding needs its own answer, and `review_waive_readiness` / `--waive` refuse this item.
|
|
667
667
|
12. `quotes-located` — every open finding whose quote `review ingest` could not place (absent from the file at the reviewed head, or in it more than once) has a disposition. Ingest keeps such a finding with no line, flagged `quote_unlocated` and listed in scope.md, instead of refusing the round. Re-quote one distinct line and re-ingest, record a disposition, or the operator waives the item.
|
|
668
668
|
|
|
669
|
-
`keryx review complete` refuses status `completed` while any item fails. A failing item other than `premise-decisions` is waived only by the operator. In keryx shell call `review_waive_readiness` for the item: the operator answers it as a card, the waiver is recorded with the card text and the time, and then you
|
|
669
|
+
`keryx review complete` refuses status `completed` while any item fails. A failing item other than `premise-decisions` is waived only by the operator. In keryx shell call `review_waive_readiness` for the item: the operator answers it as a card, the waiver is recorded with the card text and the time, and then you call the `review_complete` card (the operator answers it in the shell or in the Telegram topic). An agent completes a review only through that card; `keryx review complete` is for a human in a terminal. Never send the operator to a terminal. Outside keryx shell the operator runs `keryx review complete --waive <item> "<their own words>"` in a terminal, repeatable. Unattended, record the item as Not run. `keryx review publish` is held by the same checklist. Publishing is the operator's decision too: in keryx shell call `review_publish` (a card shows the target and the body; never send the operator to a terminal). A not-ready package goes to GitHub only as a partial review (`review_publish` with `partial: true`, or `keryx review publish --partial`), which puts a "Partial review" header on the post and never approves. The shell guard stops a publish command that runs through the keryx shell; a publisher outside it (CI, another agent, a script file) is not stopped, and that limit is named, not hidden.
|
|
670
670
|
|
|
671
671
|
## Fix rounds — dispositions, regressions, withdrawal
|
|
672
672
|
|
|
@@ -776,17 +776,17 @@ behaviour), `timeout`, or `invalid` (anchor missing or not unique, file outside
|
|
|
776
776
|
A survivor is evidence for the finding that names it.
|
|
777
777
|
|
|
778
778
|
`keryx review complete` refuses while proposed mutants are unrun on an attended round, unless the
|
|
779
|
-
operator waived the pass themselves: the `review_waive_mutation` card in keryx shell, or
|
|
780
|
-
waive-mutation --package <review-dir> --reason "<why>"
|
|
781
|
-
`operator-decisions.json`; a `dispatch.mutationWaiver` quote you write is not enough on a package
|
|
779
|
+
operator waived the pass themselves: the `review_waive_mutation` card in keryx shell, or their answer to
|
|
780
|
+
the host question recorded with `keryx review waive-mutation --package <review-dir> --reason "<why>"
|
|
781
|
+
--answered-by … --answer-text … --question-text …` (it writes `operator-decisions.json`; a `dispatch.mutationWaiver` quote you write is not enough on a package
|
|
782
782
|
`keryx review start` created). Attended is what the start recorded in `run-profile.json`: every round
|
|
783
783
|
except `review start --unattended` and `keryx review run --autonomous`, which record `Not run: mutation
|
|
784
784
|
pass` without asking.
|
|
785
785
|
|
|
786
|
-
These operator gates (cards, terminal
|
|
786
|
+
These operator gates (cards, terminal commands, host questions, plan confirmation, decision records) keep a
|
|
787
787
|
cooperating agent from cutting corners and make every decision visible and attributable; they do not
|
|
788
|
-
stop a hostile same-user agent, which can fake a pty
|
|
789
|
-
boundary). The autonomous runner's mutation-gate exemption predates flow 457 and is kept on purpose.
|
|
788
|
+
stop a hostile same-user agent, which can fake a pty, invent a host-question answer or write the JSON
|
|
789
|
+
records itself (the TM-03/TM-04 boundary). The autonomous runner's mutation-gate exemption predates flow 457 and is kept on purpose.
|
|
790
790
|
|
|
791
791
|
`keryx review mutate --cap <n>` runs the first `n` candidates and records `cap` and `not_reached`
|
|
792
792
|
in `mutation-<n>.json`. The table then prints `Not run: N candidates` (and the report must carry
|
|
@@ -857,13 +857,13 @@ minute 119, and the PR merged first; the other re-dispatched a finished wave and
|
|
|
857
857
|
1. On a PR target read merged / approved / CI (`gh pr view <n> --json state,reviewDecision,statusCheckRollup`). Say in one line what the state means for the round: an approved PR with green CI can merge any minute, and a merged PR makes the rest of the round a post-merge audit.
|
|
858
858
|
2. **Later, once the package and slices exist** (after `review scope`, `review start` and `review slice --package`, before the first reviewer starts): `keryx review required --mode all --scope .metaproject/data/review/scope.json --depth auto --package <review-dir>` prints the reviewer set and the depth it chose (see "Depth floor"); auto needs the file list and a line count, else the round is full. `keryx review estimate --package <review-dir> [--manifest <slices/manifest.json>]` prints assignments, waves and a duration range; the manifest defaults to `<package>/slices/manifest.json`, then `.metaproject/data/review/slices/manifest.json`.
|
|
859
859
|
3. Show the operator depth + estimate and the merge risk before the first dispatch. Attended: let them choose `full` over a light default. Unattended: record both in the report and continue.
|
|
860
|
-
4. **An over-threshold plan waits for the operator.** `estimate` flags a plan over `reviewPlan.maxDispatches` (default 40) or `reviewPlan.maxMinutes` (default 90) in `routing.config.json`; `dispatch-check --package` and `ledger build` refuse it (`REVIEWER_INPUT_PLAN_UNCONFIRMED`) until `<review-dir>/plan-confirmation.json` exists. Only the operator writes it: the `review_confirm_plan` card in keryx shell, or `keryx review confirm-plan --package <review-dir
|
|
860
|
+
4. **An over-threshold plan waits for the operator.** `estimate` flags a plan over `reviewPlan.maxDispatches` (default 40) or `reviewPlan.maxMinutes` (default 90) in `routing.config.json`; `dispatch-check --package` and `ledger build` refuse it (`REVIEWER_INPUT_PLAN_UNCONFIRMED`) until `<review-dir>/plan-confirmation.json` exists. Only the operator writes it: the `review_confirm_plan` card in keryx shell, or the operator's answer to the host question recorded with `keryx review confirm-plan --package <review-dir> --answered-by <source>:<ref> --answer-text "<their words>" --question-text "<the question>"` (`confirmedVia: "host-question"`), or `keryx review confirm-plan` / `keryx review estimate --package <review-dir> --confirm --quote "<their words>"` in their terminal (the terminal forms refuse without a TTY). Unattended, the plan is not dispatched: report the estimate and stop.
|
|
861
861
|
|
|
862
862
|
## Early verdict — the verdict is interim until `complete`
|
|
863
863
|
|
|
864
864
|
As soon as every reviewer result and the verifier's are on disk, save each verifier pass to `research-evidence/verifications.json` first (without it a refuted finding still counts), then run `keryx review verdict <id|path> [--json]`: it reads `<package>/raw`, where every host keeps the round's results (`--raw <dir>` only for results kept elsewhere; the shared `.metaproject/data/review/raw` mixes in other rounds' replies). It prints the interim verdict and writes `verdict-interim.md` into the package. Give the operator that verdict in the same message; do not hold it for the mutation pass, the ledger or `complete`.
|
|
865
865
|
|
|
866
|
-
The tail keeps running after it: the mutation pass (`keryx review mutate`), `ledger build`, `ingest`, `readiness` and `keryx review complete
|
|
866
|
+
The tail keeps running after it: the mutation pass (`keryx review mutate`), `ledger build`, `ingest`, `readiness` and the `review_complete` card (`keryx review complete` for a human at a terminal). Every managed round finishes them. The round is **not done** until `complete`: the verdict you gave is interim, and the report says so and names what the tail may still change (a surviving mutant, a verifier refutation, a late reviewer result). A light round has no mutation pass: its tail is `ledger build`, `ingest`, `readiness`, `complete`.
|
|
867
867
|
|
|
868
868
|
## Early verifier — start on the first blocker or major
|
|
869
869
|
|
|
@@ -24,8 +24,7 @@ license: "MIT"
|
|
|
24
24
|
|
|
25
25
|
## Preflight — before Step 0, in this order
|
|
26
26
|
|
|
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
|
-
`STATUS: BLOCKED nested_dispatch_unavailable — run this skill in the main session`
|
|
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: `STATUS: BLOCKED nested_dispatch_unavailable — run this skill in the main session`
|
|
29
28
|
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
29
|
3. **Round ceiling.** 5 rounds (`REVIEW_ROUND_CAP`) over the same change. Past it: interactive, ask the operator; unattended, `STATUS: BLOCKED round_cap`.
|
|
31
30
|
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").
|
|
@@ -37,14 +36,18 @@ license: "MIT"
|
|
|
37
36
|
| Spawn | `spawn_subagent` | `Agent` (`subagent_type`) | its spawn tool; none → the runner (Preflight 1) | its own reviewers |
|
|
38
37
|
| Plan | `plan_set`, `plan_update` | its todo/task list | its plan tool (`update_plan`), else the checklist in your reply | none |
|
|
39
38
|
| Question | `ask_user` | `AskUserQuestion` | one question in your reply, then wait | none: defaults, unattended |
|
|
40
|
-
| Operator decision | the card tool | the
|
|
39
|
+
| Operator decision | the card tool | the four acts: `AskUserQuestion`, then the verb ("Operator decisions"); the rest in their terminal | the four acts: its question tool, else the question in your reply, then the verb; the rest in their terminal | refused: recorded Not run |
|
|
41
40
|
| Result file | `save_result_to: <package>/raw/<reviewer>.txt` | the reviewer writes `<package>/raw/<reviewer>.txt` itself, before it replies | as Claude Code | its own package |
|
|
42
41
|
| Wave bytes | `--host keryx-shell` (60000: a child dies at 5 min) | `--host claude-code` (150000) | `--host codex` / `--host other` (150000) | its own |
|
|
43
42
|
|
|
44
43
|
- **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
44
|
- **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
45
|
- **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
|
-
- **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`.
|
|
46
|
+
- **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`, `review_complete`. Call the card and never send the operator to a terminal for these acts; they answer it in the shell or in the Telegram topic. A card is a keryx-verified press and the stronger path: first choice wherever one exists.
|
|
47
|
+
- **Host question, four acts:** `confirm-plan`, `waive-mutation`, `override-reviewers`, `decide-premise`. Where no card exists (Claude Code, Codex, other hosts) never send the operator to a terminal or keryx shell for them. Ask with the host's own question tool, as you would on a card, with the verb's options: `yes` or `no`; `waive` or `keep`; `override` or `keep`; `uphold` or `dismiss`. The question names its subject: for `confirm-plan` the plan numbers as the plan card shows them (reviewers, waves, dispatches, minutes, cost); for `waive-mutation` the mutant ids or their count; for `override-reviewers` the reviewer names; for `decide-premise` the finding id.
|
|
48
|
+
- **Record the answer** with `keryx review <verb> --package <package> --answered-by <source>:<ref> --answer-text "<their words, verbatim>" --question-text "<the question as asked>"`, plus `--reason "<why>"` on `waive-mutation`, `override-reviewers` and `decide-premise` (`decide-premise` also `--finding <global_id> --answer uphold|dismiss`). In keryx shell use the card first: `keryx-shell:<ref>` is only for a plain question the shell itself asked. `claude-code:<ref>` and `codex:<ref>` are for the host harness's own question tool, `<ref>` its question id; `helyx-poll:<poll-id>[:<message-id>]` for a Telegram poll; `other:<ref>` for a host that asks in its reply, `<ref>` the message id or a short label.
|
|
49
|
+
- **What the record means.** `confirmedVia: "host-question"` keeps the question and the answer so a reviewer can check them; keryx does not verify them, which is why it is weaker than a card. Record only an answer the operator gave: free text that is not one of the options, a decline, an answer naming the other choice, or words you wrote is not a decision. Unattended runs and nested keryx (`KERYX_TOOL_CALL=1`) refuse the flag, and so does a `confirm-plan` question that does not show the current plan numbers.
|
|
50
|
+
- **Terminal or card only.** The other acts stay with the operator, who runs the exact command in their own terminal outside keryx shell: `keryx review complete <package> --waive <item> "<their words>"` or `--waive-coverage "<their words>"`, `keryx review chunks estimate --package <package> --answer yes --quote "<their words>"`, `keryx review publish <package>`, `keryx review comments reply … --final`. You wait. Never run `--mode automatic` or `--yes`.
|
|
48
51
|
|
|
49
52
|
## Round plan — the order on every host
|
|
50
53
|
|
|
@@ -53,7 +56,7 @@ license: "MIT"
|
|
|
53
56
|
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
57
|
4. `keryx review slice --ref <base> --package <package> --host <host>`: each slice carries `roles`, `depth` (deep or light) and `audit`.
|
|
55
58
|
5. `keryx review required --mode all --scope .metaproject/data/review/scope.json --depth auto --package <package>`.
|
|
56
|
-
6. `keryx review estimate --package <package>`. Over threshold, the operator confirms
|
|
59
|
+
6. `keryx review estimate --package <package>`. Over threshold, the operator confirms: in keryx shell call the card `review_confirm_plan` (never send them to a terminal); outside it, ask them with the host's question tool, the plan's numbers in the question, and record it with `keryx review confirm-plan --package <package> --answered-by … --answer-text … --question-text …` ("Hosts", Operator decisions); `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
60
|
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
61
|
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. After it (no verifier ran: after the last reviewer), Wave D: `review-premise` (Step 10b).
|
|
59
62
|
9. `keryx review verdict <package>` to the operator, then the tail: mutation pass, `ledger build`, `ingest` (with `--premises`), the operator's answer to each held finding (Wave D), `readiness`, `complete`.
|
|
@@ -62,7 +65,7 @@ license: "MIT"
|
|
|
62
65
|
|
|
63
66
|
Context comes only from your own context stages: scope, the Review Context Pack, the memory stage and the layers. Your session memory, the auto-memory index (`MEMORY.md`), remembered feedback and the earlier conversation are not inputs: do not read, quote or apply them.
|
|
64
67
|
- **Accepted exceptions, the only three:** `prior_findings` of an earlier round of the same package; review-notes and learning that reach you through the memory stage; a reviewer's own `memory_search`.
|
|
65
|
-
- **One memory read.** Run the stage query once and record it with `keryx review start … --memory-query "<query>"`. keryx writes `<package>/tool-calls/orchestrator.jsonl` itself from the moment the package starts: never create, edit or append to it. Readiness item 10, `memory-outside-stage`, blocks `review complete` and `review publish` on any other memory call; only the operator's waiver lifts it: in keryx shell call `review_waive_readiness` (a card, never a terminal), outside it `--waive memory-outside-stage "<operator quote>"` in a terminal; unattended, record it as Not run. Detail: `SKILL.detail.md` § "Clean context".
|
|
68
|
+
- **One memory read.** Run the stage query once and record it with `keryx review start … --memory-query "<query>"`. keryx writes `<package>/tool-calls/orchestrator.jsonl` itself from the moment the package starts: never create, edit or append to it. Readiness item 10, `memory-outside-stage`, blocks the `review_complete` card, `keryx review complete` and `review publish` on any other memory call; only the operator's waiver lifts it: in keryx shell call `review_waive_readiness` (a card, never a terminal), outside it `--waive memory-outside-stage "<operator quote>"` in their terminal (a waiver of readiness stays on the card and terminal path); unattended, record it as Not run. Detail: `SKILL.detail.md` § "Clean context".
|
|
66
69
|
- **Open question.** Look in this skill and `SKILL.detail.md` first. If the answer is not there, ask the user: one question with the host's question tool (`AskUserQuestion`, `ask_user`; "Hosts") interactively; unattended, the whole output is `STATUS: BLOCKED question — <the question>`. Never answer from memory or a guess.
|
|
67
70
|
- **Named limit.** keryx cannot stop a runtime that loads the agent's memory before this skill starts, and reads of personal memory outside `keryx memory` are not visible to the log. The guarantee is instruction plus detection, not isolation.
|
|
68
71
|
|
|
@@ -121,7 +124,7 @@ A reviewer starts in the project root and reads project files; a path outside th
|
|
|
121
124
|
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.
|
|
122
125
|
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.
|
|
123
126
|
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.
|
|
124
|
-
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
|
|
127
|
+
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 (the `review_override_reviewers` card, or their answer to the host question recorded with `keryx review override-reviewers`, "Hosts"). 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` card (or `keryx review complete` at a human's terminal) names what is missing: fix that with a command and close again; it is never `blocked`. `--head` wants a SHA (`git rev-parse HEAD`), not `HEAD`.
|
|
125
128
|
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.
|
|
126
129
|
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).
|
|
127
130
|
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`).
|
|
@@ -131,9 +134,9 @@ A reviewer starts in the project root and reads project files; a path outside th
|
|
|
131
134
|
- **Profiles.** The default is read-only: one round, no edits. `--fix` hands the findings to `flow-orchestrator` or `task-implementer`; the orchestrator never edits code. Control returns to this main session after each hand-off, which loads this skill again (never the implementer, which would nest); each re-review counts toward the ceiling (it adds 1 to the count).
|
|
132
135
|
- **Waves.** Split by diff domain (`core` = logic, security, architecture; `domain` = frontend, backend, conventions; `support` = style, tests), in that order; A/B/C/D below keep their dependency order, except Wave C, which starts early and overlaps A and B (see Wave C); Wave D runs after the last verifier pass. A full or `--all` round runs at most 10 reviewers per wave (`MAX_REVIEWERS_PER_WAVE_FULL`).
|
|
133
136
|
- **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.
|
|
134
|
-
- **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, held ones only once Wave D's claims are saved to `research-evidence/premises.json`. A verdict given before Wave D ran is provisional, and you say so: it counts as open the findings the judge may still hold or lower. The mutation pass, ledger and `
|
|
137
|
+
- **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, held ones only once Wave D's claims are saved to `research-evidence/premises.json`. A verdict given before Wave D ran is provisional, and you say so: it counts as open the findings the judge may still hold or lower. The mutation pass, ledger and the `review_complete` card 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").
|
|
135
138
|
- **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").
|
|
136
|
-
- **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
|
|
139
|
+
- **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 the `review_complete` card has been answered (an agent never runs `keryx review complete`; that command is for a human in a terminal). `completed` means `keryx review readiness` passed, or each failing item but `premise-decisions` (answered per finding, never waived) is waived by the operator: in keryx shell call `review_waive_readiness` (a card, never a terminal), then call `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`).
|
|
137
140
|
|
|
138
141
|
---
|
|
139
142
|
|
|
@@ -244,7 +247,7 @@ keryx review ingest --report <path> [--flow <id>] --ref <ref>
|
|
|
244
247
|
# scope-B reviewer (review-regression). See below.
|
|
245
248
|
keryx review status <review-id-or-path>
|
|
246
249
|
keryx review complete <review-id-or-path>
|
|
247
|
-
[--finding <id> --disposition <state> --evidence <ref>]...
|
|
250
|
+
[--finding <id> --disposition <state> --evidence <ref>]... # human terminal; agents pass `dispositions` to review_complete
|
|
248
251
|
```
|
|
249
252
|
|
|
250
253
|
**An unrecognised option is refused, not ignored** — a misspelled flag exits non-zero rather than printing `status: closed` and writing nothing at all.
|
|
@@ -281,9 +284,9 @@ the `dismissed-*` states. Without it a package keeps only the survivors of an
|
|
|
281
284
|
unlogged triage — which is why precision measured over the recorded corpus
|
|
282
285
|
returns 100% whatever the reviewers actually got right.
|
|
283
286
|
|
|
284
|
-
`
|
|
285
|
-
|
|
286
|
-
|
|
287
|
+
The `review_complete` card records what became of a finding when the fix round closes: pass `dispositions`, a list of `{finding, disposition, evidence}`, one per finding; the operator sees each on the card.
|
|
288
|
+
At a human's terminal the same is `review complete --finding <id> --disposition <state> --evidence <ref>`, a triple repeatable per finding.
|
|
289
|
+
States: `unknown`, `acted-on`, `dismissed-incorrect`,
|
|
287
290
|
`dismissed-wont-fix`, `dismissed-out-of-scope`, `dismissed-deprioritised`.
|
|
288
291
|
Everything except `unknown` must cite where the outcome is written down — the
|
|
289
292
|
commit, the test, the decision. A recorded state and its citation cannot be
|
|
@@ -779,7 +782,7 @@ keryx review blast-radius --ref "${BASE_SHA}" --package "${PKG}" --previous blas
|
|
|
779
782
|
```
|
|
780
783
|
|
|
781
784
|
`PKG` is the review package, created by `keryx review start` before dispatch; `--package` makes scope B read the graph built at the PR head. Give each reviewer `keryx review layers block --package "${PKG}" --files <its slice files> --json` as the payload's `layers` field.
|
|
782
|
-
When `dropped` is non-empty, run `keryx review chunks plan|estimate|next|record|merge --package "${PKG}"` until `next` has none left. `review complete` refuses while a file is uncovered; the coverage waiver is the operator's alone, never yours: in keryx shell call `review_waive_coverage` (a card, never a terminal), then
|
|
785
|
+
When `dropped` is non-empty, run `keryx review chunks plan|estimate|next|record|merge --package "${PKG}"` until `next` has none left. `review complete` refuses while a file is uncovered; the coverage waiver is the operator's alone, never yours: in keryx shell call `review_waive_coverage` (a card, never a terminal), then call `review_complete`; elsewhere the operator runs `keryx review complete <package> --waive-coverage "<their words>"`; unattended, record Not run. The chunk-run question (`estimate`) is answered the same way: `review_chunks_answer`, or the operator's `keryx review chunks estimate --package "${PKG}" --answer yes --quote "<their words>"`. Pass `review_wave: {"package": "${PKG}", "pass": "<chunk id>"}` on every `spawn_subagent` of a chunk pass (`"pass": "round"` for the round's own reviewers): each wave counts its own 32 children and one turn opens at most 25 waves; a wave the package does not record is ignored and counts against the turn's 32.
|
|
783
786
|
The measurements, the rule table and the recompute table: `SKILL.detail.md` § "Scope B — bounds, rejections and recompute".
|
|
784
787
|
|
|
785
788
|
### Path Mode
|
|
@@ -1057,7 +1060,7 @@ whoever reads the merge commit a year later reads the body. When `review.jev.con
|
|
|
1057
1060
|
|
|
1058
1061
|
**Dispatch integrity is a completion prerequisite.** Use one distinct execution per selected reviewer role; do not combine multiple roles in one child or count sections of one response as independent runs. When budgets are tight, use sequential waves, not role batching. Resolve every project-rule anchor before dispatch; missing rules require context recovery or a blocked result, never silently skipped checks. A read-only pass cannot discharge executable obligations: dispatch a capable execution pass and retain its evidence. Partial scope and NEEDS_CONTEXT remain incomplete until rerun over the outstanding scope.
|
|
1059
1062
|
|
|
1060
|
-
**The reviewer set is not yours to shrink.** Run `keryx review required --mode all|diff --scope <scope.json>` (`all` for a pull request, for `--all` and for no narrowing flag; `diff` only when the operator named a narrower mode — pass their words as `--operator-quote` and record them as `dispatch.operatorQuote`, without which the dispatch is refused; on a package `keryx review start` created, closure takes that quote only once the operator recorded the decision themselves — card `review_override_reviewers`, or `keryx review override-reviewers --package <package> --reason "<why>"
|
|
1063
|
+
**The reviewer set is not yours to shrink.** Run `keryx review required --mode all|diff --scope <scope.json>` (`all` for a pull request, for `--all` and for no narrowing flag; `diff` only when the operator named a narrower mode — pass their words as `--operator-quote` and record them as `dispatch.operatorQuote`, without which the dispatch is refused; on a package `keryx review start` created, closure takes that quote only once the operator recorded the decision themselves — card `review_override_reviewers`, or their answer to the host question recorded with `keryx review override-reviewers --package <package> --reason "<why>" --answered-by … --answer-text … --question-text …`), dispatch every reviewer it prints (`--depth auto` is the default and the only way to a light round: it needs the file list and a line count, `--scope` gives both; `--all`, gating and `--depth full` force full; see `SKILL.detail.md` § "Depth floor"), and declare `mode` in `dispatch`; pass the same `--scope` to `keryx review start|ingest` so the package keeps the file list. Closure refuses a smaller set unless `dispatch.override = { operatorQuote, reason }` carries the operator's own words and that same operator record exists. New managed packages declare `reviewerDispatch: "research.json"`. Alongside the research ledger, provide `dispatch: { version: 1, mode: "all"|"diff", selected: [...], unresolvedRules: [], runs: [...] }`. Preserve the actual computed reviewer inventory. Each run has `reviewer`, unique `executionId`, `status: "complete"`, `scopeComplete: true`, `rawEvidence`, nonempty `ruleEvidence`, and boolean `executionRequired`. Required execution needs nonempty `executionEvidence` (command/output references); a source-only scope requires `executionReason`. Do not fabricate IDs or mark truncated responses complete. Runtime validates these declarations at closure and the flow gate, not their truth: independently audit evidence and inventory before freezing. Legacy artifact absence is compatibility, not proof of a correct launch. Research closure is mandatory: see [Runtime research-completion ledger](SKILL.detail.md#runtime-research-completion-ledger).
|
|
1061
1064
|
|
|
1062
1065
|
Dispatch selected reviewers in parallel when independent — this engine dispatches them itself (see "How to spawn"); a host without a dispatch tool runs `keryx review run --autonomous`, a nested session at the depth limit returns `STATUS: BLOCKED` (`nested_dispatch_unavailable`), and neither reviews alone — Preflight 1. Waves follow the diff domains in "Control" and keep their dependency order:
|
|
1063
1066
|
|
|
@@ -1074,7 +1077,7 @@ finding class is unreachable for the entire round, and no amount of verification
|
|
|
1074
1077
|
downstream recovers it. When a testing reviewer returns without a mutation table,
|
|
1075
1078
|
treat it the way you would treat a reviewer that returned without findings *and*
|
|
1076
1079
|
without evidence: ask once, then record that the pass did not run. Unattended: record
|
|
1077
|
-
`Not run: mutation pass` without asking (unattended = `review start --unattended`, or the autonomous runner). Attended (every other start): run `keryx review mutate` ([The mutation pass](SKILL.detail.md#the-mutation-pass-keryx-review-mutate)); skipping it is the operator's waiver (`review_waive_mutation`, or `keryx review waive-mutation
|
|
1080
|
+
`Not run: mutation pass` without asking (unattended = `review start --unattended`, or the autonomous runner). Attended (every other start): run `keryx review mutate` ([The mutation pass](SKILL.detail.md#the-mutation-pass-keryx-review-mutate)); skipping it is the operator's waiver (`review_waive_mutation`, or their answer to the host question recorded with `keryx review waive-mutation … --answered-by …`).
|
|
1078
1081
|
|
|
1079
1082
|
### Wave C — verification, and what it replaced
|
|
1080
1083
|
|
|
@@ -1126,7 +1129,7 @@ A finding can reproduce and still not be a defect: one against a timeline pinned
|
|
|
1126
1129
|
- Before dispatch, renumber any display id that another finding, carried or new, already has, in the saved reviewer results, so the judge, its claims and ingest all see the same ids. A new finding has no `global_id` yet (ingest mints `<reviewId>#<id>` after Wave D); ingest refuses two findings under one key, and a claim naming an id two findings share is rejected as `ambiguous-finding`.
|
|
1127
1130
|
- Dispatch it once after the last verifier pass (no verifier ran: after the last reviewer), over the canonical findings of severity ≥ minor, each with its display `id`, the **real** `reviewer` and `source`, and, for a finding carried from an earlier round, its `global_id`. External (PR-comment, `source: external`) findings are included: they can be challenged, never downgraded. In a delta round only findings without a carried premise. On keryx shell the judge has no shell and no git tool: run `git log -L` and `git blame -L` for each cited site first and pass the output in `context_files`.
|
|
1128
1131
|
- The judge's claims name the `id`, or a carried finding's `global_id`. Save its claims object unchanged to `research-evidence/premises.json` (the verdict reads it there) and pass that file with `keryx review ingest --premises <file>`; never apply verdicts by hand. The merge is delete-only: `upheld` changes nothing, `downgraded` lowers the severity, and a claim that adds, raises or rewrites is rejected and listed in `scope.md`.
|
|
1129
|
-
- `challenged` and `owner-decision` **hold** a finding: it is neither fixable nor clean — not an open blocker or major, never in a fix loop's `fix_context.review_feedback`, and it blocks until answered; the verdict lists it as awaiting the operator. **Asking about a held finding** (every skill that meets one points here): at the tail, after `ingest --premises` wrote `findings.json` (never during Wave D, before the ingest: the answer commands read `findings.json`), in keryx shell call the card `review_decide_premise`, which records the answer; elsewhere put its `question` and `options` to the operator with the host's question tool
|
|
1132
|
+
- `challenged` and `owner-decision` **hold** a finding: it is neither fixable nor clean — not an open blocker or major, never in a fix loop's `fix_context.review_feedback`, and it blocks until answered; the verdict lists it as awaiting the operator. **Asking about a held finding** (every skill that meets one points here): at the tail, after `ingest --premises` wrote `findings.json` (never during Wave D, before the ingest: the answer commands read `findings.json`), in keryx shell call the card `review_decide_premise`, which records the answer; elsewhere put its `question` and `options` to the operator with the host's question tool (`uphold` or `dismiss`, the finding id in the question; never send them to a terminal), then record what they chose: `keryx review decide-premise --package <package> --finding <global_id> --answer uphold|dismiss --reason "<why>" --answered-by <source>:<ref> --answer-text "<their words, verbatim>" --question-text "<the question as asked>"` (`confirmedVia: "host-question"`; "Hosts", Operator decisions). Record only an answer they gave and never write their words or their reason yourself. In a flow, journal the recorded answer. Readiness and the flow review gate fail while a held finding has no decision. Unattended: leave it held and report it.
|
|
1130
1133
|
|
|
1131
1134
|
### `verification_mode`
|
|
1132
1135
|
|
|
@@ -1368,15 +1371,11 @@ the query, is.
|
|
|
1368
1371
|
|
|
1369
1372
|
This form covers a class of defect nothing else in this pipeline reaches:
|
|
1370
1373
|
|
|
1371
|
-
- **The inert change.** The diff adds a prop, a guard, a field or a branch that
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
guard against a producer that never emits `undefined`. The diff looks like it
|
|
1375
|
-
does something and does nothing, and the reviewer that checks whether the change
|
|
1376
|
-
is *correct* passes it, because it is.
|
|
1374
|
+
- **The inert change.** The diff adds a prop, a guard, a field or a branch that nothing can reach — a `disabled` prop on a component its parent unmounts instead
|
|
1375
|
+
of disabling; a field added to two type `Pick`s and never read; a `=== undefined` guard against a producer that never emits `undefined`. The diff looks like it
|
|
1376
|
+
does something and does nothing, and the reviewer that checks whether the change is *correct* passes it, because it is.
|
|
1377
1377
|
- **The missing clear.** State is set on one path and no path unsets it.
|
|
1378
|
-
- **The unreachable branch.** A value the code handles that its producer cannot
|
|
1379
|
-
emit.
|
|
1378
|
+
- **The unreachable branch.** A value the code handles that its producer cannot emit.
|
|
1380
1379
|
|
|
1381
1380
|
An inert change is `minor` when it is merely dead, and `major` when its deadness
|
|
1382
1381
|
means the bug it was added to fix is still live — the second is the common case
|
|
@@ -1567,6 +1566,6 @@ When a prompt carries `JOB_NAME` and `CONTEXT_PATH`, read that context document
|
|
|
1567
1566
|
| "The spawn tool's output has the reviewer's answer" | It is cut (16000 characters on keryx shell) or only the last message. Parse `<package>/raw/<reviewer>.txt` |
|
|
1568
1567
|
| "The plan is over threshold, but the reviewers are ready, so I'll dispatch" | `dispatch-check` and `ledger build` refuse an unconfirmed over-threshold plan; the operator confirms it first ("Round plan" 6) |
|
|
1569
1568
|
| "I'll keep the results in the shared `.metaproject/data/review/raw`" | It holds other rounds' replies, which then leak into this verdict and retry plan. Results live in `<package>/raw` on every host |
|
|
1570
|
-
| "The operator is busy, I'll pass `--yes` / `--mode automatic` / write the quote" | An operator decision is their card or their terminal ("Hosts");
|
|
1569
|
+
| "The operator is busy, I'll pass `--yes` / `--mode automatic` / write the quote" | An operator decision is their card, their answer to the host question, or their terminal ("Hosts"); words you typed are not theirs |
|
|
1571
1570
|
| "User named a module so I'll use diff mode" | Named module/component/store → path mode; diff mode is only for branch changes |
|
|
1572
1571
|
| "Path mode should only show lines I'd flag in diff mode" | Path mode reviews the entire file — all findings apply, not just added lines |
|