@mrciphersmith/keryx 0.3.85 → 0.3.86
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 +3 -1
- package/dist/cli.js +144 -27
- package/dist/core.js +5 -5
- package/package.json +1 -1
- package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +18 -18
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +28 -36
- package/src/gdskills/bundled/skills/planning/autodoc-orchestrator/SKILL.md +1 -2
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +155 -1
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +88 -185
package/README.md
CHANGED
|
@@ -600,7 +600,9 @@ Grouped by what you are trying to do, not by internal module layout.
|
|
|
600
600
|
trends.
|
|
601
601
|
- **review** — managed review packages, standalone under `.metaproject/reviews/`
|
|
602
602
|
or inside the flow package when attached to a flow, so review findings become
|
|
603
|
-
durable project artifacts.
|
|
603
|
+
durable project artifacts. `review-orchestrator` is the default way a review is
|
|
604
|
+
done: the main session runs it inline, and a session that cannot spawn the
|
|
605
|
+
reviewers stops with one `STATUS: BLOCKED` line instead of reviewing alone. A finding points at the code it **quotes**: the
|
|
604
606
|
line is derived by locating that quote at the commit the round records, not
|
|
605
607
|
taken on the reviewer's word, and a quote that cannot be found is marked
|
|
606
608
|
unlocatable rather than carrying a number nobody checked. Each round also
|
package/dist/cli.js
CHANGED
|
@@ -8637,6 +8637,9 @@ var init_git_local_ignore = __esm(() => {
|
|
|
8637
8637
|
init_git_worktrees();
|
|
8638
8638
|
});
|
|
8639
8639
|
|
|
8640
|
+
// src/lib/orchestrators-inline.ts
|
|
8641
|
+
var ORCHESTRATORS_INLINE_NOTE = "The orchestrators (`review-orchestrator`, `flow-orchestrator`, `job-orchestrator`) run in the main session: the main agent reads the SKILL.md and runs it inline, and never hands a whole orchestrator to a subagent, because a subagent cannot spawn the reviewers and workers the orchestrator dispatches.";
|
|
8642
|
+
|
|
8640
8643
|
// src/lib/config-dir.ts
|
|
8641
8644
|
import {
|
|
8642
8645
|
appendFileSync,
|
|
@@ -10336,6 +10339,7 @@ function renderProjectMetaprojectReferenceBlock({
|
|
|
10336
10339
|
"For commands, search, diff, test logs, lint/build output, and large file reads that can produce long output, use the Metaproject gdctx skill by default before loading raw command output into context.",
|
|
10337
10340
|
"For a non-trivial navigation, debugging, review, or investigation task, end with a short routing audit: `graph_used`, `wiki_used`, `ctx_used`, and `raw_rg_used: yes/no`. An omitted layer must be justified (`not-relevant`/`unavailable`), not silently skipped.",
|
|
10338
10341
|
"For implementation, review, refactoring, planning, documentation, or quality tasks, use project-local Metaproject skills first: .metaproject/skills/catalog.md, .metaproject/project-skills/, then .metaproject/skills/gdskills/. External/global skills are fallback only when explicitly needed.",
|
|
10342
|
+
ORCHESTRATORS_INLINE_NOTE,
|
|
10339
10343
|
"For creating, changing, debugging, reviewing, or running tests, use the Metaproject testing skill and read .metaproject/data/testing/context.md before broad test search or raw logs.",
|
|
10340
10344
|
"For lessons learned, decisions, constraints, repeated mistakes, and historical project context, use the Metaproject memory skill before broad documentation search.",
|
|
10341
10345
|
...enableTasks ? [flowPolicy] : [],
|
|
@@ -11091,6 +11095,7 @@ function renderIndexMarkdown({
|
|
|
11091
11095
|
"For implementation, review, refactoring, planning, documentation, or quality tasks, check `skills/catalog.md` and project-local gdskills before any external/global skill set.",
|
|
11092
11096
|
"For Metaproject requirements packages under `docs/requirements` (README, PRD, specification, policies, schemas), use `skills/gdskills/planning/docpack-orchestrator/SKILL.md`; for current-codebase reverse-engineering documentation, use `autodoc-orchestrator` from `skills/catalog.md`.",
|
|
11093
11097
|
"For known modules/components/stores/services/domain entities, check generated project skills under `project-skills/<module>/<entity>/` before generic guidance.",
|
|
11098
|
+
ORCHESTRATORS_INLINE_NOTE,
|
|
11094
11099
|
"When orchestrating multi-agent work, dispatch gdskills workers through the schema contracts in `core/gdskills/contracts/` (subagent-dispatch -> subagent-result) and read `rules/core/subagent-status-protocol.md`; validate a concrete message with `keryx skills contracts validate <file> --schema <name>`."
|
|
11095
11100
|
] : [],
|
|
11096
11101
|
...enableHealth ? [
|
|
@@ -16351,6 +16356,7 @@ Resolution order:
|
|
|
16351
16356
|
- User asks which project skill applies to a file/task: run \`keryx skills route <query-or-target>\` before reading broad files.
|
|
16352
16357
|
- User asks whether a project skill is still valid: run \`keryx skills verify <skill-or-target>\`.
|
|
16353
16358
|
- User asks to create/update a Metaproject requirements package, PRD/spec package, or \`docs/requirements/<name>\` documentation: load \`gdskills/planning/docpack-orchestrator/SKILL.md\`; use \`autodoc-orchestrator\` instead only for reverse-engineering documentation from the current codebase.
|
|
16359
|
+
- Orchestrators: ${ORCHESTRATORS_INLINE_NOTE}
|
|
16354
16360
|
|
|
16355
16361
|
| Skill | Category | Purpose | Entry |
|
|
16356
16362
|
|---|---|---|---|
|
|
@@ -17226,12 +17232,12 @@ var init_skill_length_ceilings = __esm(() => {
|
|
|
17226
17232
|
"orchestration/flow-orchestrator": 694,
|
|
17227
17233
|
"orchestration/issue-analyzer": 373,
|
|
17228
17234
|
"orchestration/job-documenter": 414,
|
|
17229
|
-
"orchestration/job-orchestrator":
|
|
17235
|
+
"orchestration/job-orchestrator": 2235,
|
|
17230
17236
|
"orchestration/task-implementer": 668,
|
|
17231
17237
|
"planning/autodoc-analyst": 180,
|
|
17232
17238
|
"planning/autodoc-architect": 179,
|
|
17233
17239
|
"planning/autodoc-assembler": 145,
|
|
17234
|
-
"planning/autodoc-orchestrator":
|
|
17240
|
+
"planning/autodoc-orchestrator": 342,
|
|
17235
17241
|
"planning/autodoc-scanner": 183,
|
|
17236
17242
|
"planning/autodoc-writer": 270,
|
|
17237
17243
|
"planning/brainstorm": 115,
|
|
@@ -17288,7 +17294,7 @@ var init_skill_length_ceilings = __esm(() => {
|
|
|
17288
17294
|
"review/review-jev-scenarios": 187,
|
|
17289
17295
|
"review/review-layout": 238,
|
|
17290
17296
|
"review/review-logic": 377,
|
|
17291
|
-
"review/review-orchestrator":
|
|
17297
|
+
"review/review-orchestrator": 1615,
|
|
17292
17298
|
"review/review-performance": 374,
|
|
17293
17299
|
"review/review-pr-feedback": 895,
|
|
17294
17300
|
"review/review-regression": 216,
|
|
@@ -19281,7 +19287,8 @@ var init_tools = __esm(() => {
|
|
|
19281
19287
|
"apply_patch",
|
|
19282
19288
|
"shell_exec",
|
|
19283
19289
|
"web_fetch",
|
|
19284
|
-
"web_search"
|
|
19290
|
+
"web_search",
|
|
19291
|
+
"spawn_subagent"
|
|
19285
19292
|
];
|
|
19286
19293
|
TOOL_TARGET_MAPS = {
|
|
19287
19294
|
claude: new Map([
|
|
@@ -21098,7 +21105,7 @@ var package_default;
|
|
|
21098
21105
|
var init_package = __esm(() => {
|
|
21099
21106
|
package_default = {
|
|
21100
21107
|
name: "@mrciphersmith/keryx",
|
|
21101
|
-
version: "0.3.
|
|
21108
|
+
version: "0.3.86",
|
|
21102
21109
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
21103
21110
|
private: false,
|
|
21104
21111
|
publishConfig: {
|
|
@@ -87272,22 +87279,35 @@ function spendFromTokens(input) {
|
|
|
87272
87279
|
}
|
|
87273
87280
|
return (inputTokens * inputRatePerMillion + outputTokens * outputRatePerMillion) / 1e6;
|
|
87274
87281
|
}
|
|
87282
|
+
function reviewerDomain(reviewer) {
|
|
87283
|
+
if (CORE_REVIEWERS.has(reviewer)) {
|
|
87284
|
+
return "core";
|
|
87285
|
+
}
|
|
87286
|
+
if (SUPPORT_REVIEWERS.has(reviewer)) {
|
|
87287
|
+
return "support";
|
|
87288
|
+
}
|
|
87289
|
+
return "domain";
|
|
87290
|
+
}
|
|
87275
87291
|
function planReviewerWaves(reviewers, options = {}) {
|
|
87276
|
-
const cap = options.cap ??
|
|
87292
|
+
const cap = options.cap ?? MAX_REVIEWERS_PER_WAVE_FULL;
|
|
87277
87293
|
if (!Number.isInteger(cap) || cap < 1) {
|
|
87278
|
-
throw new Error(`Invalid concurrency cap: ${cap}. Expected a positive integer; the default is ${
|
|
87294
|
+
throw new Error(`Invalid concurrency cap: ${cap}. Expected a positive integer; the default is ${MAX_REVIEWERS_PER_WAVE_FULL}.`);
|
|
87279
87295
|
}
|
|
87280
87296
|
const outstanding = options.outstanding;
|
|
87281
87297
|
if (outstanding !== undefined && (!Number.isInteger(outstanding) || outstanding < 0)) {
|
|
87282
87298
|
throw new Error(`Invalid outstanding subagent count: ${outstanding}. Expected a non-negative integer.`);
|
|
87283
87299
|
}
|
|
87284
|
-
const
|
|
87300
|
+
const ceiling = options.full === true ? Math.min(cap, MAX_REVIEWERS_PER_WAVE_FULL) : cap;
|
|
87301
|
+
const effective = Math.max(1, ceiling - (outstanding ?? 0));
|
|
87285
87302
|
const waves = [];
|
|
87286
|
-
for (
|
|
87287
|
-
|
|
87303
|
+
for (const domain of DOMAIN_ORDER) {
|
|
87304
|
+
const group = reviewers.filter((name) => reviewerDomain(name) === domain);
|
|
87305
|
+
for (let index = 0;index < group.length; index += effective) {
|
|
87306
|
+
waves.push(group.slice(index, index + effective));
|
|
87307
|
+
}
|
|
87288
87308
|
}
|
|
87289
87309
|
return {
|
|
87290
|
-
cap,
|
|
87310
|
+
cap: ceiling,
|
|
87291
87311
|
outstanding,
|
|
87292
87312
|
effective,
|
|
87293
87313
|
waves,
|
|
@@ -87388,7 +87408,7 @@ function renderCapsMarkdown(record) {
|
|
|
87388
87408
|
function escapePipes2(value) {
|
|
87389
87409
|
return value.replace(/\|/g, "\\|");
|
|
87390
87410
|
}
|
|
87391
|
-
var DEFAULT_MAX_FINDINGS_PER_REVIEWER = 10, SEVERITY_RANK3, DEFAULT_SPEND_CEILING_USD = 3,
|
|
87411
|
+
var DEFAULT_MAX_FINDINGS_PER_REVIEWER = 10, SEVERITY_RANK3, DEFAULT_SPEND_CEILING_USD = 3, MAX_REVIEWERS_PER_WAVE_FULL = 10, CORE_REVIEWERS, SUPPORT_REVIEWERS, DOMAIN_ORDER;
|
|
87392
87412
|
var init_caps = __esm(() => {
|
|
87393
87413
|
SEVERITY_RANK3 = {
|
|
87394
87414
|
blocker: 0,
|
|
@@ -87396,6 +87416,26 @@ var init_caps = __esm(() => {
|
|
|
87396
87416
|
minor: 2,
|
|
87397
87417
|
info: 3
|
|
87398
87418
|
};
|
|
87419
|
+
CORE_REVIEWERS = new Set([
|
|
87420
|
+
"review-logic",
|
|
87421
|
+
"review-security-code",
|
|
87422
|
+
"review-architecture",
|
|
87423
|
+
"review-highload",
|
|
87424
|
+
"review-regression",
|
|
87425
|
+
"review-core-boundaries",
|
|
87426
|
+
"code-boss-reviewer",
|
|
87427
|
+
"code-ai-review"
|
|
87428
|
+
]);
|
|
87429
|
+
SUPPORT_REVIEWERS = new Set([
|
|
87430
|
+
"review-style",
|
|
87431
|
+
"review-clean-code",
|
|
87432
|
+
"review-testing-practices",
|
|
87433
|
+
"review-vantage-testing",
|
|
87434
|
+
"review-vantage-styling",
|
|
87435
|
+
"review-layout",
|
|
87436
|
+
"code-style-review"
|
|
87437
|
+
]);
|
|
87438
|
+
DOMAIN_ORDER = ["core", "domain", "support"];
|
|
87399
87439
|
});
|
|
87400
87440
|
|
|
87401
87441
|
// src/lib/git-hooks.ts
|
|
@@ -180594,20 +180634,38 @@ async function importProjectSkills(options) {
|
|
|
180594
180634
|
const plans = await Promise.all(sources.map((source) => planOne(options, source)));
|
|
180595
180635
|
const refusedPackages = new Set(plans.flatMap((plan) => ("row" in plan) && isRefusedStatus(plan.row.status) ? [plan.row.name] : []));
|
|
180596
180636
|
const candidates = sources.filter((source) => !skippedAsDeprecated(source)).filter((source) => options.force === true || !BUNDLED_NAMES.has(source.name));
|
|
180597
|
-
const ruleDecisions = await decideReferencedRules(options, candidates, refusedPackages);
|
|
180598
180637
|
const templatePlans = [];
|
|
180638
|
+
const refusedByTemplates = new Set;
|
|
180599
180639
|
for (const source of candidates) {
|
|
180600
180640
|
if (source.sourcePath === undefined || refusedPackages.has(source.name))
|
|
180601
180641
|
continue;
|
|
180602
|
-
|
|
180642
|
+
const own = await planTemplates(options, { module: source.module, name: source.name, sourcePath: source.sourcePath }, options.force === true);
|
|
180643
|
+
if (packageRefusedByTemplates(own)) {
|
|
180644
|
+
refusedByTemplates.add(source.name);
|
|
180645
|
+
if (plans.some((plan) => ("write" in plan) && plan.write.name === source.name))
|
|
180646
|
+
refusedPackages.add(source.name);
|
|
180647
|
+
templatePlans.push(...withheldTemplates(own));
|
|
180648
|
+
} else {
|
|
180649
|
+
templatePlans.push(...own);
|
|
180650
|
+
}
|
|
180603
180651
|
}
|
|
180652
|
+
const ruleDecisions = await decideReferencedRules(options, candidates, refusedPackages);
|
|
180604
180653
|
await refuseUncontainedWrites(label, options.projectRoot, [
|
|
180605
|
-
...plans.flatMap((plan) => ("write" in plan) ? projectSkillWritePaths(plan.write.module, plan.write.name, "single") : []),
|
|
180654
|
+
...plans.flatMap((plan) => ("write" in plan) && !refusedByTemplates.has(plan.write.name) ? projectSkillWritePaths(plan.write.module, plan.write.name, "single") : []),
|
|
180606
180655
|
...ruleDecisions.flatMap((decision) => ("placement" in decision) ? [decision.placement.target] : []),
|
|
180607
|
-
...templatePlans
|
|
180656
|
+
...templateTargets(templatePlans)
|
|
180608
180657
|
]);
|
|
180609
180658
|
const imported = [];
|
|
180610
180659
|
for (const plan of plans) {
|
|
180660
|
+
if ("write" in plan && refusedByTemplates.has(plan.write.name)) {
|
|
180661
|
+
imported.push(refusedByTemplateRow({
|
|
180662
|
+
name: plan.write.name,
|
|
180663
|
+
module: plan.write.module,
|
|
180664
|
+
path: projectSkillPackagePath(plan.write.module, plan.write.name),
|
|
180665
|
+
origin: plan.write.origin
|
|
180666
|
+
}, options.dryRun === true));
|
|
180667
|
+
continue;
|
|
180668
|
+
}
|
|
180611
180669
|
imported.push("row" in plan ? plan.row : await writeOne(options, plan));
|
|
180612
180670
|
}
|
|
180613
180671
|
addFlagWarnings(imported, sources, existingFlags);
|
|
@@ -180652,18 +180710,29 @@ async function updateProjectSkills(options) {
|
|
|
180652
180710
|
plans.push(await planUpdate(options, entry));
|
|
180653
180711
|
}
|
|
180654
180712
|
const templatePlans = [];
|
|
180713
|
+
const refusedByTemplates = new Set;
|
|
180655
180714
|
for (const plan of plans) {
|
|
180656
180715
|
if (!("content" in plan) || plan.sourcePath === undefined)
|
|
180657
180716
|
continue;
|
|
180658
|
-
|
|
180717
|
+
const own = await planTemplates(options, { module: projectSkillSlug(plan.entry.module), name: projectSkillSlug(plan.entry.name), sourcePath: plan.sourcePath }, true);
|
|
180718
|
+
if (packageRefusedByTemplates(own)) {
|
|
180719
|
+
refusedByTemplates.add(plan);
|
|
180720
|
+
templatePlans.push(...withheldTemplates(own));
|
|
180721
|
+
} else {
|
|
180722
|
+
templatePlans.push(...own);
|
|
180723
|
+
}
|
|
180659
180724
|
}
|
|
180660
180725
|
await refuseUncontainedWrites("keryx skills update", options.projectRoot, [
|
|
180661
|
-
...plans.flatMap((plan) => ("content" in plan) ? projectSkillWritePaths(projectSkillSlug(plan.entry.module), projectSkillSlug(plan.entry.name), "single") : []),
|
|
180662
|
-
...templatePlans
|
|
180726
|
+
...plans.flatMap((plan) => ("content" in plan) && !refusedByTemplates.has(plan) ? projectSkillWritePaths(projectSkillSlug(plan.entry.module), projectSkillSlug(plan.entry.name), "single") : []),
|
|
180727
|
+
...templateTargets(templatePlans)
|
|
180663
180728
|
]);
|
|
180664
180729
|
const existingFlags = await projectReviewerFlags(options.projectRoot);
|
|
180665
180730
|
const imported = [];
|
|
180666
180731
|
for (const plan of plans) {
|
|
180732
|
+
if (refusedByTemplates.has(plan) && "entry" in plan) {
|
|
180733
|
+
imported.push(refusedByTemplateRow({ name: plan.entry.name, module: plan.entry.module, path: plan.entry.path, origin: plan.origin }, options.dryRun === true));
|
|
180734
|
+
continue;
|
|
180735
|
+
}
|
|
180667
180736
|
imported.push("row" in plan ? plan.row : await writeUpdate(options, plan));
|
|
180668
180737
|
}
|
|
180669
180738
|
addFlagWarnings(imported, plans.map((plan) => ("content" in plan) ? plan : undefined), existingFlags);
|
|
@@ -181211,7 +181280,7 @@ async function planOne(options, source) {
|
|
|
181211
181280
|
status: "skipped",
|
|
181212
181281
|
path: dest,
|
|
181213
181282
|
origin: source.origin,
|
|
181214
|
-
reason: "already exists; pass --force to overwrite",
|
|
181283
|
+
reason: "already exists; pass --force to overwrite, or run `keryx skills update` to refresh it from its origin",
|
|
181215
181284
|
wired: wiringNote(source.module)
|
|
181216
181285
|
}
|
|
181217
181286
|
};
|
|
@@ -181565,6 +181634,40 @@ async function listTemplateFiles(sourcePath) {
|
|
|
181565
181634
|
await walk(root, "");
|
|
181566
181635
|
return files;
|
|
181567
181636
|
}
|
|
181637
|
+
async function fileInTheWay(projectRoot, target) {
|
|
181638
|
+
let current = projectRoot;
|
|
181639
|
+
let relative = "";
|
|
181640
|
+
for (const part of target.split("/").slice(0, -1)) {
|
|
181641
|
+
current = path343.join(current, part);
|
|
181642
|
+
relative = relative === "" ? part : `${relative}/${part}`;
|
|
181643
|
+
try {
|
|
181644
|
+
if (!(await stat37(current)).isDirectory())
|
|
181645
|
+
return relative;
|
|
181646
|
+
} catch {
|
|
181647
|
+
return;
|
|
181648
|
+
}
|
|
181649
|
+
}
|
|
181650
|
+
return;
|
|
181651
|
+
}
|
|
181652
|
+
function packageRefusedByTemplates(plans) {
|
|
181653
|
+
return plans.some((plan) => ("row" in plan) && isRefusedStatus(plan.row.status));
|
|
181654
|
+
}
|
|
181655
|
+
function withheldTemplates(plans) {
|
|
181656
|
+
return plans.map((plan) => ("row" in plan) ? plan : {
|
|
181657
|
+
row: {
|
|
181658
|
+
...plan.write.row,
|
|
181659
|
+
status: "skipped",
|
|
181660
|
+
reason: "not written: another template of this package was refused"
|
|
181661
|
+
}
|
|
181662
|
+
});
|
|
181663
|
+
}
|
|
181664
|
+
function refusedByTemplateRow(pkg, dryRun) {
|
|
181665
|
+
return {
|
|
181666
|
+
...pkg,
|
|
181667
|
+
status: dryRun ? "would-refuse" : "refused",
|
|
181668
|
+
reason: "a template of this package was refused by the security gate; nothing of the package was written"
|
|
181669
|
+
};
|
|
181670
|
+
}
|
|
181568
181671
|
async function planTemplates(options, pkg, replace) {
|
|
181569
181672
|
const plans = [];
|
|
181570
181673
|
const packageKey = `${pkg.module}/${pkg.name}`;
|
|
@@ -181579,6 +181682,11 @@ async function planTemplates(options, pkg, replace) {
|
|
|
181579
181682
|
continue;
|
|
181580
181683
|
}
|
|
181581
181684
|
const targetAbs = path343.join(options.projectRoot, target);
|
|
181685
|
+
const blocker = await fileInTheWay(options.projectRoot, target);
|
|
181686
|
+
if (blocker !== undefined) {
|
|
181687
|
+
plans.push({ row: { ...base, status: "skipped", reason: `${blocker} is a file, not a directory; nothing was written there` } });
|
|
181688
|
+
continue;
|
|
181689
|
+
}
|
|
181582
181690
|
const exists = await pathExists(targetAbs);
|
|
181583
181691
|
if (exists && !await isRegularFile(targetAbs)) {
|
|
181584
181692
|
plans.push({ row: { ...base, status: "skipped", reason: `${target} is not a regular file; nothing was written there` } });
|
|
@@ -181594,8 +181702,14 @@ async function planTemplates(options, pkg, replace) {
|
|
|
181594
181702
|
plans.push({ row: { ...base, status: "unchanged" } });
|
|
181595
181703
|
continue;
|
|
181596
181704
|
}
|
|
181597
|
-
if (existing !== undefined && !replace) {
|
|
181598
|
-
plans.push({
|
|
181705
|
+
if (existing !== undefined && !replace && verdict.kind === "write") {
|
|
181706
|
+
plans.push({
|
|
181707
|
+
row: {
|
|
181708
|
+
...base,
|
|
181709
|
+
status: "differs",
|
|
181710
|
+
reason: "the project's copy is another file than the package's; pass --force to replace it, or run `keryx skills update`, which replaces it too"
|
|
181711
|
+
}
|
|
181712
|
+
});
|
|
181599
181713
|
continue;
|
|
181600
181714
|
}
|
|
181601
181715
|
if (verdict.kind !== "write") {
|
|
@@ -181881,7 +181995,7 @@ Examples:
|
|
|
181881
181995
|
var BUNDLED_NAMES, PATHS_NONE_WARNING = 'paths: none \u2014 dispatched on every round. Declare `metadata.paths: "<glob>, <glob>"` in its frontmatter to gate it on the diff.', DEFAULT_FETCHER = async (url) => {
|
|
181882
181996
|
const response = await fetch(url);
|
|
181883
181997
|
return { ok: response.ok, status: response.status, text: await response.text() };
|
|
181884
|
-
}, DEPRECATED_REASON = "deprecated", KNOWN_CATEGORIES, isString4 = (value) => value !== undefined, REFUSED_NOTE = "A refused file was not written.", ALLOW_FLAGGED_HINT = "A prompt-injection refusal can be overridden after you read the file: run the command again with --allow-flagged.", IMPORT_HEADER_LABELS, IMPORT_HEADER_LINE, BYTE_ORDER_MARK = 65279, TEMPLATES_DIR = "templates", ALLOW_FLAGGED_FLAG = "--allow-flagged", SECURITY_GATE_HELP = `Security gate: a SKILL.md or rule is written as the gate returns it, so a secret is
|
|
181998
|
+
}, DEPRECATED_REASON = "deprecated", KNOWN_CATEGORIES, isString4 = (value) => value !== undefined, REFUSED_NOTE = "A refused file was not written.", ALLOW_FLAGGED_HINT = "A prompt-injection refusal can be overridden after you read the file: run the command again with --allow-flagged.", IMPORT_HEADER_LABELS, IMPORT_HEADER_LINE, BYTE_ORDER_MARK = 65279, TEMPLATES_DIR = "templates", templateTargets = (plans) => plans.flatMap((plan) => ("write" in plan) ? [plan.write.target] : plan.row.status === "skipped" ? [] : [plan.row.path]), ALLOW_FLAGGED_FLAG = "--allow-flagged", SECURITY_GATE_HELP = `Security gate: a SKILL.md or rule is written as the gate returns it, so a secret is
|
|
181885
181999
|
masked and the row says it was redacted, and any other finding is reported on the
|
|
181886
182000
|
row. Text with a prompt-injection finding is refused in every security mode: the
|
|
181887
182001
|
row is \`refused\` (\`would-refuse\` on a dry run) with the gate's own summary,
|
|
@@ -188097,9 +188211,12 @@ async function runBudget(args) {
|
|
|
188097
188211
|
ceiling: parseMoney(optionValue(args, "--ceiling"), "--ceiling")
|
|
188098
188212
|
});
|
|
188099
188213
|
const reviewers = optionValue(args, "--reviewers")?.split(",").map((item) => item.trim()).filter(Boolean) ?? [];
|
|
188214
|
+
if (args.some((arg) => arg.startsWith("--full=")))
|
|
188215
|
+
throw new Error("`--full` takes no value.");
|
|
188100
188216
|
const plan = planReviewerWaves(reviewers, {
|
|
188101
188217
|
cap: parseNonNegativeInteger(optionValue(args, "--parallel"), "--parallel"),
|
|
188102
|
-
outstanding: parseNonNegativeInteger(optionValue(args, "--outstanding"), "--outstanding")
|
|
188218
|
+
outstanding: parseNonNegativeInteger(optionValue(args, "--outstanding"), "--outstanding"),
|
|
188219
|
+
full: args.includes("--full")
|
|
188103
188220
|
});
|
|
188104
188221
|
console.log("# review budget");
|
|
188105
188222
|
console.log("");
|
|
@@ -189739,7 +189856,7 @@ Usage:
|
|
|
189739
189856
|
[--no-related-tests] [--final] [--previous <blast-radius.json>]
|
|
189740
189857
|
[--json | --brief] [--out <file>]
|
|
189741
189858
|
keryx review budget [--spent <usd>] [--ceiling <usd>]
|
|
189742
|
-
[--reviewers a,b] [--parallel <n>] [--outstanding <n>]
|
|
189859
|
+
[--reviewers a,b] [--parallel <n>] [--outstanding <n>] [--full]
|
|
189743
189860
|
keryx review tier [--scope <scope>] [--fix-attempt <n>] [--forced-strategy-change]
|
|
189744
189861
|
[--findings <n>] [--diff-lines <n>]
|
|
189745
189862
|
[--verifier ${VERIFICATION_METHODS.join("|")}] [--security]
|
|
@@ -190041,7 +190158,7 @@ budget:
|
|
|
190041
190158
|
The gate to run BEFORE dispatching, where stopping is still possible. Exits
|
|
190042
190159
|
non-zero when spend has reached the ceiling (default ${DEFAULT_SPEND_CEILING_USD} USD) so the
|
|
190043
190160
|
orchestrator asks the operator instead of proceeding. Also prints the reviewer
|
|
190044
|
-
dispatch waves for the concurrency cap (default ${
|
|
190161
|
+
dispatch waves for the concurrency cap (default ${MAX_REVIEWERS_PER_WAVE_FULL} in flight; split by diff domain, never above ${MAX_REVIEWERS_PER_WAVE_FULL} for a full review).
|
|
190045
190162
|
--outstanding <n> is what an enclosing orchestrator already has in flight.
|
|
190046
190163
|
WITHOUT it the cap bounds this plan only: keryx cannot observe subagents in
|
|
190047
190164
|
another process, so it does NOT bind the total across job-orchestrator ->
|
|
@@ -190199,7 +190316,7 @@ var init_review = __esm(() => {
|
|
|
190199
190316
|
"--parallel",
|
|
190200
190317
|
"--outstanding"
|
|
190201
190318
|
];
|
|
190202
|
-
BUDGET_FLAGS = ["--spent", "--ceiling", "--parallel", "--outstanding", "--reviewers"];
|
|
190319
|
+
BUDGET_FLAGS = ["--spent", "--ceiling", "--parallel", "--outstanding", "--reviewers", "--full"];
|
|
190203
190320
|
COMMENTS_COLLECT_FLAGS = [
|
|
190204
190321
|
"--repo",
|
|
190205
190322
|
"--pr",
|
package/dist/core.js
CHANGED
|
@@ -9995,7 +9995,6 @@ var init_route_tokens = __esm(() => {
|
|
|
9995
9995
|
"them"
|
|
9996
9996
|
]);
|
|
9997
9997
|
});
|
|
9998
|
-
|
|
9999
9998
|
// src/gdskills/skill-frontmatter.ts
|
|
10000
9999
|
function frontmatterLines(content) {
|
|
10001
10000
|
const text = content.startsWith("\uFEFF") ? content.slice(1) : content;
|
|
@@ -10791,12 +10790,12 @@ var init_skill_length_ceilings = __esm(() => {
|
|
|
10791
10790
|
"orchestration/flow-orchestrator": 694,
|
|
10792
10791
|
"orchestration/issue-analyzer": 373,
|
|
10793
10792
|
"orchestration/job-documenter": 414,
|
|
10794
|
-
"orchestration/job-orchestrator":
|
|
10793
|
+
"orchestration/job-orchestrator": 2235,
|
|
10795
10794
|
"orchestration/task-implementer": 668,
|
|
10796
10795
|
"planning/autodoc-analyst": 180,
|
|
10797
10796
|
"planning/autodoc-architect": 179,
|
|
10798
10797
|
"planning/autodoc-assembler": 145,
|
|
10799
|
-
"planning/autodoc-orchestrator":
|
|
10798
|
+
"planning/autodoc-orchestrator": 342,
|
|
10800
10799
|
"planning/autodoc-scanner": 183,
|
|
10801
10800
|
"planning/autodoc-writer": 270,
|
|
10802
10801
|
"planning/brainstorm": 115,
|
|
@@ -10853,7 +10852,7 @@ var init_skill_length_ceilings = __esm(() => {
|
|
|
10853
10852
|
"review/review-jev-scenarios": 187,
|
|
10854
10853
|
"review/review-layout": 238,
|
|
10855
10854
|
"review/review-logic": 377,
|
|
10856
|
-
"review/review-orchestrator":
|
|
10855
|
+
"review/review-orchestrator": 1615,
|
|
10857
10856
|
"review/review-performance": 374,
|
|
10858
10857
|
"review/review-pr-feedback": 895,
|
|
10859
10858
|
"review/review-regression": 216,
|
|
@@ -11732,7 +11731,8 @@ var init_tools = __esm(() => {
|
|
|
11732
11731
|
"apply_patch",
|
|
11733
11732
|
"shell_exec",
|
|
11734
11733
|
"web_fetch",
|
|
11735
|
-
"web_search"
|
|
11734
|
+
"web_search",
|
|
11735
|
+
"spawn_subagent"
|
|
11736
11736
|
];
|
|
11737
11737
|
TOOL_TARGET_MAPS = {
|
|
11738
11738
|
claude: new Map([
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.86",
|
|
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": {
|
|
@@ -21,6 +21,10 @@ license: "MIT"
|
|
|
21
21
|
|
|
22
22
|
# Flow Orchestrator
|
|
23
23
|
|
|
24
|
+
## Preflight
|
|
25
|
+
|
|
26
|
+
Run in the main session: this skill dispatches workers. Without the `Agent` tool, or with the depth budget spent (Claude Code nests at most 3 deep), the whole output is this line, with no tool calls and no work of your own: `STATUS: BLOCKED nested_dispatch_unavailable — run this skill in the main session`
|
|
27
|
+
|
|
24
28
|
## Purpose
|
|
25
29
|
|
|
26
30
|
Flow Orchestrator is the Task Manager-aware implementation orchestrator. Plan bridge: publish the tasks with `plan_set` under the ids `T1`…`Tn`, mirror each `keryx flow task done` with `plan_update`, and publish Phase 4's completion choice as `proposed` — see the `session-plan-bridge` rule.
|
|
@@ -28,8 +32,6 @@ It wraps the existing gdskills pipeline with `keryx flow` state.
|
|
|
28
32
|
|
|
29
33
|
Use this skill instead of `job-orchestrator` when the user wants a managed story/issue lifecycle with frozen acceptance criteria, task state, an explicit completion choice, Code Health, and a durable flow package in `.metaproject/flows/`.
|
|
30
34
|
|
|
31
|
-
Do not modify `job-orchestrator` or `task-implementer` behavior. They remain usable without Task Manager. This skill coordinates them through flow state.
|
|
32
|
-
|
|
33
35
|
## Hard Preconditions
|
|
34
36
|
|
|
35
37
|
1. Read `.metaproject/index.md` and `.metaproject/metaproject.json`.
|
|
@@ -122,9 +124,8 @@ it already tried. The flow package does.
|
|
|
122
124
|
keryx flow next <id> --json
|
|
123
125
|
```
|
|
124
126
|
|
|
125
|
-
This is the first task
|
|
126
|
-
`
|
|
127
|
-
instead of re-derived by you from prose.
|
|
127
|
+
This is the first task not `done` whose declared `dependsOn` are all
|
|
128
|
+
`done` — computed from the package, not re-derived from prose.
|
|
128
129
|
|
|
129
130
|
4. **Read the `resume` field before dispatching anything.** It has three
|
|
130
131
|
answers and they are not interchangeable:
|
|
@@ -141,10 +142,9 @@ it already tried. The flow package does.
|
|
|
141
142
|
or close the task (`keryx flow task done <id> <Tn>`). Re-dispatching
|
|
142
143
|
over an unresolved attempt is how the same work gets done twice.
|
|
143
144
|
|
|
144
|
-
`keryx flow next` also lists every OTHER not-done task
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
idle.
|
|
145
|
+
`keryx flow next` also lists every OTHER not-done task with an unresolved
|
|
146
|
+
attempt, under `unresolved`: parallel dispatches that never reported back.
|
|
147
|
+
Resolve them the same way before assuming the flow is idle.
|
|
148
148
|
|
|
149
149
|
5. Before dispatching a worker for that task, record the attempt:
|
|
150
150
|
|
|
@@ -436,19 +436,17 @@ Before accepting implementation:
|
|
|
436
436
|
4. Check the bounds, then run `review-orchestrator` with relevant domains — in this session,
|
|
437
437
|
never handed whole to one subagent: one without a dispatch tool returns `STATUS: BLOCKED`.
|
|
438
438
|
```bash
|
|
439
|
-
keryx review budget --spent <usd-so-far> --outstanding <subagents
|
|
439
|
+
keryx review budget --spent <usd-so-far> --full --outstanding <subagents still running, 0 if none>
|
|
440
440
|
```
|
|
441
441
|
A non-zero exit means the spend ceiling (3 USD by default) has been reached:
|
|
442
442
|
**stop and ask the user** rather than dispatching another fan-out — never shrink the fan-out to fit.
|
|
443
443
|
Dispatch prompts point at sources (files, SHAs, the PR) and never paraphrase a contract.
|
|
444
444
|
|
|
445
|
-
`--outstanding` is the part that matters here. `review-orchestrator`
|
|
446
|
-
dispatches reviewers in parallel
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
alone, which the review record then states plainly rather than implying
|
|
451
|
-
otherwise.
|
|
445
|
+
`--outstanding` is the part that matters here. `review-orchestrator` runs inline
|
|
446
|
+
in this session and dispatches reviewers in parallel; keryx cannot observe other
|
|
447
|
+
subagents. Passing the count still running is the only thing that makes the
|
|
448
|
+
concurrency cap mean anything; omit it and the cap bounds the reviewer fan-out
|
|
449
|
+
alone, which the review record then states plainly. Add `--full` for `--all`.
|
|
452
450
|
|
|
453
451
|
5. If findings require code changes, dispatch fix work through `task-implementer`
|
|
454
452
|
and record the fix task in the flow.
|
|
@@ -586,7 +584,9 @@ the bound plus an escalation — never an unbounded loop.
|
|
|
586
584
|
findings, affected graph, relevant wiki, and health/testing artifacts;
|
|
587
585
|
identify the likely cycle cause; choose a materially different fix strategy
|
|
588
586
|
or split the work into narrower tasks; record the decision in `journal.md`;
|
|
589
|
-
then continue with the enriched context.
|
|
587
|
+
then continue with the enriched context. The review round count is not reset:
|
|
588
|
+
past `REVIEW_ROUND_CAP` rounds on one merge-base..HEAD, report to the operator
|
|
589
|
+
instead of running another round.
|
|
590
590
|
6. Never merge while findings or required checks remain unresolved. If the
|
|
591
591
|
re-planned approach still cannot produce a mergeable PR, leave the flow
|
|
592
592
|
`in-progress` and report the blocker instead of forcing completion.
|
|
@@ -22,14 +22,12 @@ metadata:
|
|
|
22
22
|
license: "MIT"
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
-
<SUBAGENT-STOP>
|
|
26
|
-
If you were dispatched as a subagent to execute a specific task, skip this skill entirely.
|
|
27
|
-
This skill is for orchestrators and interactive session-level routing only.
|
|
28
|
-
Proceed directly with your assigned task.
|
|
29
|
-
</SUBAGENT-STOP>
|
|
30
|
-
|
|
31
25
|
# Job Orchestrator
|
|
32
26
|
|
|
27
|
+
## Preflight
|
|
28
|
+
|
|
29
|
+
Run in the main session: this skill dispatches sub-agents. Without the `Agent` tool, or with the depth budget spent (Claude Code nests at most 3 deep), the whole output is this line, with no tool calls and no work of your own: `STATUS: BLOCKED nested_dispatch_unavailable — run this skill in the main session`
|
|
30
|
+
|
|
33
31
|
## Purpose
|
|
34
32
|
|
|
35
33
|
Dynamic orchestrator that builds execution plans based on user intent. Unlike a fixed pipeline, the orchestrator adapts its workflow to what the user actually needs — from "just analyze this issue" to "implement, review, and create a PR". It dispatches sub-agents (`issue-analyzer`, `context-collector`, `tests-creator`, `task-implementer`, `code-verifier`, `review-orchestrator`) and persists every step, document and retry through `keryx job`, which writes `.metaproject/jobs/<job-name>/`. Plan bridge: publish these steps with `plan_set` under the ids `keryx job status` reports, mirror each `keryx job step` with `plan_update`, and mark them `proposed` while the approval above is open — see the `session-plan-bridge` rule.
|
|
@@ -1059,16 +1057,14 @@ carried in the dispatch prompt and reported in 2.9.
|
|
|
1059
1057
|
**Step 1 — check the budget before dispatching, while stopping is still possible.**
|
|
1060
1058
|
|
|
1061
1059
|
```bash
|
|
1062
|
-
keryx review budget --spent <usd-so-far> --outstanding <subagents
|
|
1060
|
+
keryx review budget --spent <usd-so-far> --full --outstanding <subagents still running, 0 if none>
|
|
1063
1061
|
```
|
|
1064
1062
|
|
|
1065
|
-
`--outstanding` is
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
total only when the parent declares its own in-flight count.** Omit `--outstanding`
|
|
1071
|
-
and the cap bounds the reviewer fan-out alone, which the record then states plainly.
|
|
1063
|
+
`--outstanding` is the number of subagents this session still has running while the
|
|
1064
|
+
round starts (0 when none). All three orchestrators run inline in the main session, so the
|
|
1065
|
+
reviewers are the only fan-out; keryx cannot observe other subagents, so **the wave size
|
|
1066
|
+
(at most 10 for `--all` or a full review; add `--full`) binds the total only when you
|
|
1067
|
+
declare that count.** Omit it and the record states that the cap bounds the reviewers alone.
|
|
1072
1068
|
|
|
1073
1069
|
A non-zero exit means the spend ceiling (3 USD by default) is reached: stop and ask
|
|
1074
1070
|
the user rather than dispatching another fan-out.
|
|
@@ -1122,30 +1118,26 @@ replaced. The command names no model — it ranks what the provider reports at r
|
|
|
1122
1118
|
and, when it cannot rank anything, prints `inherit: true`, which means the dispatch
|
|
1123
1119
|
runs on the session model. That is a correct answer, not a failure.
|
|
1124
1120
|
|
|
1125
|
-
**Step 6 —
|
|
1121
|
+
**Step 6 — run `review-orchestrator` inline, in this main session.** Load
|
|
1122
|
+
`skills/gdskills/review/review-orchestrator/SKILL.md` yourself; do **not** wrap it in a
|
|
1123
|
+
`Task` — a subagent cannot spawn the reviewers and the skill answers
|
|
1124
|
+
`STATUS: BLOCKED nested_dispatch_unavailable`. Give it these inputs:
|
|
1126
1125
|
|
|
1127
1126
|
```
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
issue_url: <issue URL, when the job has one — enables the Stage 1 spec gate>
|
|
1137
|
-
context_doc: <JOBS_ROOT>/<job-name>/ai/context.md
|
|
1138
|
-
verification_mode: annotate
|
|
1139
|
-
managed_review: { mode: "review-flow", target: "branch", target_ref: "<feature-branch>" }
|
|
1140
|
-
is_fix_round: <true on any round after the first>
|
|
1141
|
-
pr_comments: { enabled: <true when a PR exists> }
|
|
1142
|
-
|
|
1143
|
-
Emit the unified report AND the fenced ```json keryx:findings``` block.
|
|
1144
|
-
Dispatch review-verifier (Wave C) over the consolidated findings and return
|
|
1145
|
-
its verification claims as a file path.
|
|
1146
|
-
})
|
|
1127
|
+
flags: <selected flags, e.g. --backend --security --testing-practices>
|
|
1128
|
+
commit_range: <BASE_SHA>..HEAD
|
|
1129
|
+
issue_url: <issue URL, when the job has one — enables the Stage 1 spec gate>
|
|
1130
|
+
context_doc: <JOBS_ROOT>/<job-name>/ai/context.md
|
|
1131
|
+
verification_mode: annotate
|
|
1132
|
+
managed_review: { mode: "review-flow", target: "branch", target_ref: "<feature-branch>" }
|
|
1133
|
+
is_fix_round: <true on any round after the first>
|
|
1134
|
+
pr_comments: { enabled: <true when a PR exists> }
|
|
1147
1135
|
```
|
|
1148
1136
|
|
|
1137
|
+
It emits the unified report AND the fenced ```json keryx:findings``` block, and runs
|
|
1138
|
+
`review-verifier` (Wave C) over the consolidated findings, returning its verification
|
|
1139
|
+
claims as a file path.
|
|
1140
|
+
|
|
1149
1141
|
**Step 7 — verification is part of the round, not an extra.** `review-orchestrator`
|
|
1150
1142
|
dispatches `review-verifier` in Wave C over the consolidated findings. The verifier
|
|
1151
1143
|
**runs something** and can only delete — it never raises a severity, adds a finding,
|
|
@@ -1310,7 +1302,7 @@ FOR iteration in [1, 2, 3]:
|
|
|
1310
1302
|
keryx review comments collect --repo <r> --pr <n> --sha <new-head> --round <N+1> --out <file>
|
|
1311
1303
|
keryx review scope --ref "$BASE_SHA" --json > scope.json
|
|
1312
1304
|
keryx review blast-radius --ref "$BASE_SHA" --previous blast-radius.json --json > blast-radius.json
|
|
1313
|
-
<
|
|
1305
|
+
<run review-orchestrator inline, in this session, with is_fix_round: true>
|
|
1314
1306
|
keryx review ingest --report <new-report> --ref <feature-branch> --head <new-head> \
|
|
1315
1307
|
--scope scope.json --blast-radius blast-radius.json \
|
|
1316
1308
|
--verifications <file> --refuted <file> --outstanding <n>
|
|
@@ -1985,7 +1977,7 @@ What actually bounds this orchestrator:
|
|
|
1985
1977
|
| review → fix rounds | 3 | 2.7, and `max_review_iterations` (`maximum: 3`) in the input contract |
|
|
1986
1978
|
| repetition, whatever the count says | first repeat | the STUCK CHECK in 2.7, and `keryx review loop` against the durable record |
|
|
1987
1979
|
| retries per step | recorded, not guessed | `metrics.steps[].retries`, incremented by `keryx job step --status in-progress` and read back with `keryx job status --json` |
|
|
1988
|
-
| reviewer fan-out |
|
|
1980
|
+
| reviewer fan-out | ≤10 per wave | `keryx review budget --outstanding <n>` before every dispatch (2.6.1) |
|
|
1989
1981
|
| spend | 3 USD by default | `keryx review budget --spent <usd>` — a non-zero exit means stop and ask |
|
|
1990
1982
|
|
|
1991
1983
|
Each of these is a number some command reads or writes. A guard no command can
|
|
@@ -20,8 +20,7 @@ metadata:
|
|
|
20
20
|
version: 1.0.0
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
Return STATUS: BLOCKED — autodoc-orchestrator must run as top-level agent only. -->
|
|
23
|
+
**Preflight:** run in the main session. Without the `Agent` tool or depth budget, output only `STATUS: BLOCKED nested_dispatch_unavailable — run this skill in the main session`, with no tool calls.
|
|
25
24
|
|
|
26
25
|
# autodoc-orchestrator
|
|
27
26
|
|
|
@@ -426,7 +426,7 @@ re-request after fixes. A gating round runs the composition the start questions
|
|
|
426
426
|
selected.
|
|
427
427
|
|
|
428
428
|
- **No dispatch tool, no review.** A host that cannot dispatch subagents — this
|
|
429
|
-
engine running inside a subagent
|
|
429
|
+
engine running inside a subagent, say — returns
|
|
430
430
|
`STATUS: BLOCKED` with reason `nested_dispatch_unavailable` and no verdict.
|
|
431
431
|
It does not review the diff alone and call that a round. Measured (flow 405):
|
|
432
432
|
a single-agent round on a 150-line frontend PR wrote "did not dispatch any
|
|
@@ -503,3 +503,157 @@ New managed packages carry `research.json`, initially pending. Before closing a
|
|
|
503
503
|
Supply the ledger using `keryx review ingest --research <ledger.json>` alongside the report and findings. Format: `version: 1`, `scopeReviewed: true`, `rawReconciled: true`, `obligations: [...]`. Each obligation requires `id`, `source`, `question`, `status`, `evidence`, `reason`. Status `finding` additionally requires `finding` pointing to exactly one canonical finding ID or global ID. Other terminal statuses are `refuted` and `out-of-scope`, both requiring evidence and reason. Preserve distinct scenarios when deduplicating. Assert census/reconciliation flags only after performing those checks; an empty obligations array is valid only if the actual census found no open observations.
|
|
504
504
|
|
|
505
505
|
Open, deferred and unverifiable obligations block completion. If context, runtime or budget prevents resolution, retain the obligation and report incomplete; do not relabel it refuted or out-of-scope. Completion and the flow gate re-read the ledger even when the manifest says closed. Legacy packages without this artifact remain compatible, not proven complete. This gate validates the declared ledger, not the truth or exhaustiveness of its census; independent source/raw audits remain necessary.
|
|
506
|
+
|
|
507
|
+
## Why Wave C verifies instead of re-scoring
|
|
508
|
+
|
|
509
|
+
Wave C used to run `review-strict`: a meta-pass that re-read the consolidated
|
|
510
|
+
findings and **adjusted their severity with no new evidence**, under an elevation
|
|
511
|
+
table biased 3:1 toward escalation. It was **removed, not improved**, and the
|
|
512
|
+
reason is measured rather than stylistic:
|
|
513
|
+
|
|
514
|
+
- **GPT-4 on GSM8K across self-correction rounds: 95.5 → 91.5 → 89.0.**
|
|
515
|
+
**GPT-3.5 on CommonSenseQA: 75.8 → 38.1.** Among the answers that changed,
|
|
516
|
+
correct → incorrect exceeded incorrect → correct (Huang et al., *Large Language
|
|
517
|
+
Models Cannot Self-Correct Reasoning Yet*, ICLR 2024, arXiv:2310.01798).
|
|
518
|
+
- **Self-Refine (arXiv:2303.17651): +49.2 on dialogue response generation, +0.2
|
|
519
|
+
on maths.** Self-refinement gains are on subjective tasks and vanish on
|
|
520
|
+
verifiable reasoning. Judging whether a null-guard is missing is verifiable
|
|
521
|
+
reasoning.
|
|
522
|
+
|
|
523
|
+
Re-scoring a finding by re-reading it is therefore not a rigour pass; it is a
|
|
524
|
+
coin flip weighted toward more findings. **Do not restore it because it looks
|
|
525
|
+
obviously useful — it looked obviously useful the first time.**
|
|
526
|
+
|
|
527
|
+
`review-verifier` occupies the slot and differs in exactly one way that matters:
|
|
528
|
+
**it runs something.** Verification that executes rejects 85–96% of false reports
|
|
529
|
+
against 4–15% unaided while finding 30–44% more true bugs (AnyPoC,
|
|
530
|
+
arXiv:2604.11950); Meta's TestGen-LLM funnel discards 75% of its own output
|
|
531
|
+
(75% build → 57% build and pass → 25% improve coverage) and the surviving quarter
|
|
532
|
+
reaches 73% human acceptance (arXiv:2402.09171).
|
|
533
|
+
|
|
534
|
+
## Scope B — bounds, rejections and recompute
|
|
535
|
+
|
|
536
|
+
It walks `gdgraph affected` outward from every changed file, ranks by edge
|
|
537
|
+
distance, keeps distance ≤ 2, cuts at 40 files closest-first, and adds a changed
|
|
538
|
+
file's naming-related tests when the graph did not already reach them. Requires a
|
|
539
|
+
built graph — run `keryx gdgraph build` if it refuses.
|
|
540
|
+
|
|
541
|
+
**Do not pick the files yourself, and do not widen it.** "Review the
|
|
542
|
+
functionality so nothing breaks" naively means "review the whole repository every
|
|
543
|
+
round", which is unaffordable *and* actively harmful: review quality decays as
|
|
544
|
+
context grows — measured F1 0.65 at round 2 falling to 0.29 at round 10. An
|
|
545
|
+
unbounded scope B makes later rounds worse than earlier ones.
|
|
546
|
+
|
|
547
|
+
The bounds are measured on this repository, not guessed: at depth 2 the set is a
|
|
548
|
+
median of 19 files (p90 65); depth 3 buys eight more in the median and doubles
|
|
549
|
+
the p90. The 40-file cap fires on 25% of commits and removes only hop-2 entries
|
|
550
|
+
on all but 2 of 80, so it almost never costs a direct dependent — and when it
|
|
551
|
+
does, it says so.
|
|
552
|
+
|
|
553
|
+
**Record the whole thing.** `--out "<review-package>/blast-radius.md"` writes the
|
|
554
|
+
set, the depth, and **every file the cap removed**. A truncation nobody can see
|
|
555
|
+
reads afterwards as "we checked everything", which is the claim this pipeline
|
|
556
|
+
exists to stop making. An empty radius is reported as `unresolved`, not as clean:
|
|
557
|
+
the graph indexes code, so a change to a skill, a rule or a schema has no blast
|
|
558
|
+
radius at all and that is a different fact from "nothing depends on it".
|
|
559
|
+
|
|
560
|
+
### The scope-B question, and what is rejected
|
|
561
|
+
|
|
562
|
+
> Does this change break an existing behaviour **at these sites**?
|
|
563
|
+
|
|
564
|
+
Nothing else. The blast-radius set is **under regression check, not under
|
|
565
|
+
review**. A finding about style, naming or architecture in code the change did
|
|
566
|
+
not touch is refused **by the orchestrator in code** — not discouraged here —
|
|
567
|
+
under three rules, every one of them a fact about the claim rather than about who
|
|
568
|
+
made it:
|
|
569
|
+
|
|
570
|
+
| Rule | Refused because |
|
|
571
|
+
|---|---|
|
|
572
|
+
| `outside-set` | the file is neither in the computed set nor in the changed set; the reviewer went browsing |
|
|
573
|
+
| `non-regression-severity` | below `major`. Under the canonical rubric `minor` states the code behaves correctly and `info` names neither trigger nor outcome; neither can be a claim that something broke |
|
|
574
|
+
| `no-link-to-change` | nothing in the finding names a changed file, module or symbol. A regression claim says THE CHANGE broke this site |
|
|
575
|
+
|
|
576
|
+
Rejections are **recorded, not deleted** — raise the observation under scope A or
|
|
577
|
+
as a separate review. Pass `--brief` output verbatim into the scope-B dispatch:
|
|
578
|
+
the code rejection is the enforcement, but a reviewer told afterwards has already
|
|
579
|
+
spent the round producing findings that will all be refused.
|
|
580
|
+
|
|
581
|
+
`class_scope` on a scope-B finding names the **caller that breaks**, not the
|
|
582
|
+
changed line, because that is the site a human has to look at.
|
|
583
|
+
|
|
584
|
+
### When it is recomputed
|
|
585
|
+
|
|
586
|
+
| Round | Scope A | Scope B |
|
|
587
|
+
|---|---|---|
|
|
588
|
+
| 1 (first after the draft PR) | yes | yes |
|
|
589
|
+
| 2..N | yes | recomputed only if the changed-file set moved |
|
|
590
|
+
| final | yes | **yes, always** |
|
|
591
|
+
|
|
592
|
+
Do not decide this by memory:
|
|
593
|
+
|
|
594
|
+
```bash
|
|
595
|
+
keryx review blast-radius --ref "${BASE_SHA}" --previous blast-radius.json [--final]
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
It prints the decision and the reason, and reuses the previous record when
|
|
599
|
+
nothing moved. The final round recomputes whatever the file set did — otherwise a
|
|
600
|
+
fix introduced in round 3 gets no regression check at all, and the round that
|
|
601
|
+
certifies the flow is the one that checked the least.
|
|
602
|
+
|
|
603
|
+
## Fix rounds — dispositions, regressions, withdrawal
|
|
604
|
+
|
|
605
|
+
### Every prior finding leaves the round with a disposition
|
|
606
|
+
|
|
607
|
+
A fix round that reports only new findings is unreadable: the author cannot tell
|
|
608
|
+
which of their fixes landed. Close the loop explicitly — one line per prior
|
|
609
|
+
finding, in the report, before the new findings:
|
|
610
|
+
|
|
611
|
+
| Disposition | Meaning |
|
|
612
|
+
|---|---|
|
|
613
|
+
| `closed` | Checked against the code, not against the commit message, and the defect is gone |
|
|
614
|
+
| `open` | The fix does not reach the defect; say what is still true |
|
|
615
|
+
| `partial` | One site of the class was fixed and the enumeration named others |
|
|
616
|
+
| `regressed` | The fix removed this defect and introduced another — file the new one separately |
|
|
617
|
+
| `withdrawn` | The finding was wrong. See below |
|
|
618
|
+
|
|
619
|
+
**Check the code, not the commit message.** A commit titled *"report an abandoned
|
|
620
|
+
sync as abandoned"* is a claim; the disposition is whether the branch it renamed
|
|
621
|
+
is reachable and pinned. On a recorded round, two such commits asserted behaviour
|
|
622
|
+
on lines no test could reach.
|
|
623
|
+
|
|
624
|
+
### A fix is a change, and changes get reviewed
|
|
625
|
+
|
|
626
|
+
The most expensive class in a multi-round review is **the defect the fix
|
|
627
|
+
introduced**. It is systematically under-found, for a structural reason: the fix
|
|
628
|
+
arrives framed as the answer to a finding, so it is read as an answer rather than
|
|
629
|
+
as new code. It is new code.
|
|
630
|
+
|
|
631
|
+
So scope A of a fix round includes the fix, reviewed on its own merits, and the
|
|
632
|
+
report carries its own section:
|
|
633
|
+
|
|
634
|
+
```markdown
|
|
635
|
+
## Regressions the fixes introduced
|
|
636
|
+
<[F-NNN] — the finding it was answering, and the new defect it created>
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
Recorded shapes, all from fixes that correctly closed the finding they answered:
|
|
640
|
+
an early return added to stop a fall-through, which then skipped the work the
|
|
641
|
+
caller needed; a persistence call added to save expanded state, which then
|
|
642
|
+
persisted the broken state on the error path too. Both were closed correctly and
|
|
643
|
+
both shipped a new bug in the same commit.
|
|
644
|
+
|
|
645
|
+
### Withdrawing your own earlier finding
|
|
646
|
+
|
|
647
|
+
A finding from a previous round that this round disproves is **withdrawn**,
|
|
648
|
+
explicitly, at the top of the report, with the evidence — before any new finding.
|
|
649
|
+
|
|
650
|
+
This is not a courtesy. An uncorrected wrong finding costs the author a fix they
|
|
651
|
+
did not need, and it stays in `prior_findings` steering later rounds. Withdrawal
|
|
652
|
+
is also the one self-correction this pipeline permits, and it is permitted because
|
|
653
|
+
it is asymmetric: it *deletes* a claim, so it cannot inflate the finding count,
|
|
654
|
+
which is the failure mode that removed the re-scoring pass from Wave C.
|
|
655
|
+
|
|
656
|
+
State what made the original claim wrong, in one sentence, and if the same
|
|
657
|
+
reasoning error has now happened twice in one review, say that too. A reviewer
|
|
658
|
+
that names its own recurring error is calibrating; one that quietly drops a
|
|
659
|
+
finding is hiding a result.
|
|
@@ -19,10 +19,16 @@ metadata:
|
|
|
19
19
|
license: "MIT"
|
|
20
20
|
---
|
|
21
21
|
# Review Orchestrator
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
|
|
23
|
+
**You are the orchestrator. Run the round yourself, inline, in the main session that loaded this skill; never hand the whole round to one subagent.** This skill routes: it detects scope, dispatches specialized reviewers, and consolidates their findings into one report sorted by severity. The reviewers do the reviewing.
|
|
24
|
+
|
|
25
|
+
## Preflight — before Step 0, in this order
|
|
26
|
+
|
|
27
|
+
1. **Can it spawn?** The round needs the `Agent` tool (or the host's equivalent) and depth budget left: Claude Code nests subagents at most 3 deep, and at the limit `Agent` is withheld. If it cannot spawn, the 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`
|
|
29
|
+
2. **Is a round needed?** A diff of only docs, markdown, comments, formatting, lockfiles or generated files, with no CI, auth, migration or rule-bearing config, ends here with one line, `round not needed: <reason>`, and no reviewer. `--all`, an explicit reviewer list or a gating round forces a round.
|
|
30
|
+
3. **Round ceiling.** 5 rounds (`REVIEW_ROUND_CAP`) over the same `merge-base..HEAD`. Past it: interactive, ask the operator; unattended, `STATUS: BLOCKED round_cap`.
|
|
31
|
+
|
|
26
32
|
## Workflow
|
|
27
33
|
|
|
28
34
|
```
|
|
@@ -36,16 +42,16 @@ Review Orchestrator Progress:
|
|
|
36
42
|
- [ ] Step 4: Parse flags / auto-detect domain from scope
|
|
37
43
|
- [ ] Step 5: Start questions, once, after Steps 1 and 3 — reviewers (default `--all`), counterpart verification (default on), model plan; unattended takes the defaults (SKILL.detail.md § "Start questions"; legacy/profile reviewers stay flag-only)
|
|
38
44
|
- [ ] Step 5c: When `review.jev.select` is on, run `keryx review jev-select` over the finalized candidate set and drop its `skip` decisions from Wave A/B — see "Reviewer selection with Jev"
|
|
39
|
-
- [ ] Step 6: Plan sub-agent dispatch and token budgets
|
|
45
|
+
- [ ] Step 6: Plan sub-agent dispatch and token budgets; compute each dispatch's model with `keryx review tier` (6a: publish this plan)
|
|
40
46
|
- [ ] Step 7: Stage 1 gate - spec compliance check (if issue/task provided)
|
|
41
|
-
- [ ] Step 8: Dispatch selected reviewers in
|
|
47
|
+
- [ ] Step 8: Dispatch selected reviewers in parallel with the reviewer-input schema — see "How to spawn"
|
|
42
48
|
- [ ] Step 9: Collect reviewer-finding schema results and handle NEEDS_CONTEXT
|
|
43
49
|
- [ ] Step 9b: `jev-triage` — advisory, annotate-only severity/duplicate/verify-order annotations over the consolidated findings (opt-in `review.jev.triage`)
|
|
44
|
-
- [ ] Step 10: Wave C — dispatch `review-verifier` over the consolidated findings
|
|
50
|
+
- [ ] Step 10: Wave C — when blockers/majors exist, `--verify` is set, or the PR is high-risk, dispatch `review-verifier` over the consolidated findings
|
|
45
51
|
- [ ] Step 11: Sort by severity, deduplicate, emit unified report
|
|
46
52
|
- [ ] Step 12: Emit the machine-readable `keryx:findings` block alongside the report
|
|
47
53
|
- [ ] Step 13: Report the stage counts: dropped by pre-filter, refuted by the verifier, retained
|
|
48
|
-
- [ ] Step 14:
|
|
54
|
+
- [ ] Step 14: Managed rounds only (not `lightweight`, which is report-only), after the final round, on an open pull request at the head you reviewed — answer every external comment once with `keryx review comments reply --final` (render with `--dry-run` and get the user's explicit approval first; unattended: do not publish, hand the rendered body back) — never against a pull request the dispatch named as the caller's
|
|
49
55
|
```
|
|
50
56
|
|
|
51
57
|
Step 0 runs on **every** round. Step 14 runs **once**, after the last one. They are
|
|
@@ -55,22 +61,28 @@ one comment.
|
|
|
55
61
|
|
|
56
62
|
### Step 6 — the model is computed, not chosen
|
|
57
63
|
|
|
58
|
-
Before dispatching each reviewer, run `keryx review tier` with the signals you
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
Before dispatching each reviewer, run `keryx review tier` with the signals you hold (`--scope`, `--findings`, `--diff-lines`, `--fix-attempt`, `--verifier`, `--security`, `--forced-strategy-change`) and paste the `model` block it prints into that dispatch. The tier comes from the command, not from reading `rules/core/model-selection.mdc`.
|
|
65
|
+
|
|
66
|
+
It names a model only if you gave it your session (`--session-provider`/`--session-model` or `KERYX_SESSION_*`) and discovery found another. Otherwise it prints `inherit: true` with the tier — **adaptive**: pick your own runtime's model for that tier (`standard` = your session model; `light`/`deep` = your runtime's lighter/most capable class if its dispatch tool offers one, else the session model). A model id never goes into a dispatch by hand.
|
|
67
|
+
|
|
68
|
+
### Step 6a — the plan bridge
|
|
69
|
+
|
|
70
|
+
Publish this checklist with `plan_set` under the ids `step-0`…`step-14` once, here, when scope and dispatch are fixed. Publish each of steps 0–5 that ran as `completed` and each that did not run as `skipped`; publish steps 6–14 as `pending` (`in_progress` for the one starting now). From then on move each item with `plan_update` as its step completes — see the `session-plan-bridge` rule. `completed` means the step's artifact exists: steps 8–10 become `completed` (in the plan and in the report) only after `keryx review ingest` has recorded their results, and a step whose reviewers were `BLOCKED` is `blocked`.
|
|
71
|
+
|
|
72
|
+
## How to spawn (Steps 8 and 10)
|
|
73
|
+
|
|
74
|
+
- One `Agent` call per reviewer, all calls of a wave in a single message, so the wave runs in parallel.
|
|
75
|
+
- Use the reviewer's exact `subagent_type`. If the runtime has no such type, use `general-purpose` and name the reviewer's `SKILL.md` path in the prompt. Neither available: that reviewer is `BLOCKED`.
|
|
76
|
+
- Prompts carry pointers, not pasted files — the diff, the SHAs, the PR, the schema paths — and never paraphrase a contract.
|
|
77
|
+
- A reply is a result only if it starts with a `STATUS:` line and carries a `REVIEW_RESULT` block matching `reviewer-finding.schema.json`; anything else is `BLOCKED`.
|
|
78
|
+
- The model comes from `keryx review tier` (Step 6).
|
|
62
79
|
|
|
63
|
-
|
|
64
|
-
Working it out in your head is exactly the mechanical step that rule moves into
|
|
65
|
-
code — and it is the step that was documented as running for a whole release
|
|
66
|
-
while nothing called it.
|
|
80
|
+
## Control
|
|
67
81
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
`
|
|
71
|
-
|
|
72
|
-
lighter/most capable class if its dispatch tool offers one, else the session
|
|
73
|
-
model). Never "fix" it by writing a model id into a dispatch.
|
|
82
|
+
- **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).
|
|
83
|
+
- **Waves.** Split by diff domain (`core` = logic, security, architecture; `domain` = frontend, backend, conventions; `support` = style, tests), in that order; A/B/C below keep their dependency order. A full or `--all` round runs at most 10 reviewers per wave (`MAX_REVIEWERS_PER_WAVE_FULL`).
|
|
84
|
+
- **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`. After at most two halvings (`MAX_RATE_HALVINGS`) stop with `STATUS: BLOCKED rate_limited`, naming the queue left.
|
|
85
|
+
- **Done when** every dispatched reviewer's result is recorded with `keryx review ingest`, the stage counts and **Not run** are in the report, and `keryx review complete` has run.
|
|
74
86
|
|
|
75
87
|
---
|
|
76
88
|
|
|
@@ -520,7 +532,8 @@ The rule that follows for reviewers, and that belongs in the dispatch prompt:
|
|
|
520
532
|
|
|
521
533
|
If you cannot locate the producer, ask the operator where it is (Start question 2); record `state: unavailable` only when it cannot be read, and leave the dependent findings at `info`.
|
|
522
534
|
An unavailable producer is a result. Assuming one is not. An input rule records the producer's full check set and is verified every round,
|
|
523
|
-
paired PR or not — SKILL.detail.md § "Counterpart verification".
|
|
535
|
+
paired PR or not — SKILL.detail.md § "Counterpart verification". Unattended, nobody is asked: record
|
|
536
|
+
`state: unavailable` with the reason and continue.
|
|
524
537
|
|
|
525
538
|
A finding whose evidence lives in the producer sets `repo` on the finding itself.
|
|
526
539
|
`file` and `line` alone name a path in the repository under review, so without
|
|
@@ -611,6 +624,8 @@ This PR touches high-risk areas. Build full review context before dispatching re
|
|
|
611
624
|
> pick a letter (default: A)
|
|
612
625
|
```
|
|
613
626
|
|
|
627
|
+
Unattended: take A and record it in the review scope.
|
|
628
|
+
|
|
614
629
|
---
|
|
615
630
|
|
|
616
631
|
## Token and Context Budget Management
|
|
@@ -639,31 +654,22 @@ Default budget guidance:
|
|
|
639
654
|
|
|
640
655
|
## Model Strategy
|
|
641
656
|
|
|
642
|
-
The model
|
|
643
|
-
|
|
644
|
-
the `model` block it prints into that dispatch. Do not assign a model class by
|
|
645
|
-
reading a table or by hand; that mechanical step is exactly what the command
|
|
646
|
-
replaced (see Step 6 above and `job-orchestrator` Step 5, which pins the same
|
|
647
|
-
rule: `model_strategy: "current"` is gone).
|
|
648
|
-
|
|
649
|
-
`model_strategy` now governs only how the *computed* model is surfaced, not
|
|
650
|
-
whether it is computed:
|
|
657
|
+
The model per dispatch is computed by `keryx review tier` (Step 6) and never written by hand.
|
|
658
|
+
`model_strategy` governs only how the computed model is surfaced:
|
|
651
659
|
|
|
652
660
|
| `model_strategy` | Behavior |
|
|
653
661
|
|---|---|
|
|
654
662
|
| `ask` (default, interactive) | Present the `keryx review tier` model plan once before dispatch — Start question 3; the operator may accept it, take all `deep`, or set it. |
|
|
655
663
|
| `adaptive` (default, unattended) | Dispatch on the computed model directly; record the chosen model per reviewer in the final report metadata. |
|
|
656
664
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
- `inherit: true` is the adaptive answer: dispatch on your runtime's model for the block's `tier` (Step 6). Record `model_assignment: adaptive` and the model you actually chose — not a failure.
|
|
660
|
-
- With `model_strategy: ask`, present the model plan once before dispatch, then proceed with the computed model.
|
|
665
|
+
`inherit: true` is the adaptive answer: dispatch on your runtime's model for the block's `tier`, and
|
|
666
|
+
record `model_assignment: adaptive` with the model you chose.
|
|
661
667
|
|
|
662
668
|
---
|
|
663
669
|
|
|
664
670
|
## Scope Detection
|
|
665
671
|
|
|
666
|
-
###
|
|
672
|
+
### Fix round check (Step 1a)
|
|
667
673
|
|
|
668
674
|
A **fix round** is any review of work produced to answer earlier findings. Set
|
|
669
675
|
`is_fix_round: true` on every reviewer input, and populate `prior_findings` with
|
|
@@ -702,63 +708,14 @@ Recording the enumeration matters as much as doing it: a round that searched and
|
|
|
702
708
|
found nothing is a different fact from a round that never searched, and only the
|
|
703
709
|
recorded list distinguishes them.
|
|
704
710
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
| Disposition | Meaning |
|
|
712
|
-
|---|---|
|
|
713
|
-
| `closed` | Checked against the code, not against the commit message, and the defect is gone |
|
|
714
|
-
| `open` | The fix does not reach the defect; say what is still true |
|
|
715
|
-
| `partial` | One site of the class was fixed and the enumeration named others |
|
|
716
|
-
| `regressed` | The fix removed this defect and introduced another — file the new one separately |
|
|
717
|
-
| `withdrawn` | The finding was wrong. See below |
|
|
718
|
-
|
|
719
|
-
**Check the code, not the commit message.** A commit titled *"report an abandoned
|
|
720
|
-
sync as abandoned"* is a claim; the disposition is whether the branch it renamed
|
|
721
|
-
is reachable and pinned. On a recorded round, two such commits asserted behaviour
|
|
722
|
-
on lines no test could reach.
|
|
723
|
-
|
|
724
|
-
#### A fix is a change, and changes get reviewed
|
|
725
|
-
|
|
726
|
-
The most expensive class in a multi-round review is **the defect the fix
|
|
727
|
-
introduced**. It is systematically under-found, for a structural reason: the fix
|
|
728
|
-
arrives framed as the answer to a finding, so it is read as an answer rather than
|
|
729
|
-
as new code. It is new code.
|
|
711
|
+
Every prior finding leaves the round with a disposition — `closed`, `open`, `partial`,
|
|
712
|
+
`regressed` or `withdrawn` — one line each in the report before the new findings, checked against
|
|
713
|
+
the code and not the commit message. A fix is new code: scope A includes it, and the report carries
|
|
714
|
+
a `## Regressions the fixes introduced` section. A finding this round disproves is withdrawn at the
|
|
715
|
+
top of the report with its evidence. The table, recorded shapes and rationale:
|
|
716
|
+
`SKILL.detail.md` § "Fix rounds — dispositions, regressions, withdrawal".
|
|
730
717
|
|
|
731
|
-
|
|
732
|
-
report carries its own section:
|
|
733
|
-
|
|
734
|
-
```markdown
|
|
735
|
-
## Regressions the fixes introduced
|
|
736
|
-
<[F-NNN] — the finding it was answering, and the new defect it created>
|
|
737
|
-
```
|
|
738
|
-
|
|
739
|
-
Recorded shapes, all from fixes that correctly closed the finding they answered:
|
|
740
|
-
an early return added to stop a fall-through, which then skipped the work the
|
|
741
|
-
caller needed; a persistence call added to save expanded state, which then
|
|
742
|
-
persisted the broken state on the error path too. Both were closed correctly and
|
|
743
|
-
both shipped a new bug in the same commit.
|
|
744
|
-
|
|
745
|
-
#### Withdrawing your own earlier finding
|
|
746
|
-
|
|
747
|
-
A finding from a previous round that this round disproves is **withdrawn**,
|
|
748
|
-
explicitly, at the top of the report, with the evidence — before any new finding.
|
|
749
|
-
|
|
750
|
-
This is not a courtesy. An uncorrected wrong finding costs the author a fix they
|
|
751
|
-
did not need, and it stays in `prior_findings` steering later rounds. Withdrawal
|
|
752
|
-
is also the one self-correction this pipeline permits, and it is permitted because
|
|
753
|
-
it is asymmetric: it *deletes* a claim, so it cannot inflate the finding count,
|
|
754
|
-
which is the failure mode that removed the re-scoring pass from Wave C.
|
|
755
|
-
|
|
756
|
-
State what made the original claim wrong, in one sentence, and if the same
|
|
757
|
-
reasoning error has now happened twice in one review, say that too. A reviewer
|
|
758
|
-
that names its own recurring error is calibrating; one that quietly drops a
|
|
759
|
-
finding is hiding a result.
|
|
760
|
-
|
|
761
|
-
### Step 1: Determine Review Mode
|
|
718
|
+
### Step 2: Determine Review Mode
|
|
762
719
|
|
|
763
720
|
Before anything else, determine whether the request is **diff mode** or **path mode**:
|
|
764
721
|
|
|
@@ -832,72 +789,23 @@ keryx review blast-radius --ref "${BASE_SHA}" --brief # wha
|
|
|
832
789
|
scope-B finding arrives without it — pass it back as
|
|
833
790
|
`review ingest ... --blast-radius blast-radius.json`.
|
|
834
791
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
file
|
|
838
|
-
built graph — run `keryx gdgraph build` if it refuses.
|
|
839
|
-
|
|
840
|
-
**Do not pick the files yourself, and do not widen it.** "Review the
|
|
841
|
-
functionality so nothing breaks" naively means "review the whole repository every
|
|
842
|
-
round", which is unaffordable *and* actively harmful: review quality decays as
|
|
843
|
-
context grows — measured F1 0.65 at round 2 falling to 0.29 at round 10. An
|
|
844
|
-
unbounded scope B makes later rounds worse than earlier ones.
|
|
845
|
-
|
|
846
|
-
The bounds are measured on this repository, not guessed: at depth 2 the set is a
|
|
847
|
-
median of 19 files (p90 65); depth 3 buys eight more in the median and doubles
|
|
848
|
-
the p90. The 40-file cap fires on 25% of commits and removes only hop-2 entries
|
|
849
|
-
on all but 2 of 80, so it almost never costs a direct dependent — and when it
|
|
850
|
-
does, it says so.
|
|
851
|
-
|
|
852
|
-
**Record the whole thing.** `--out "<review-package>/blast-radius.md"` writes the
|
|
853
|
-
set, the depth, and **every file the cap removed**. A truncation nobody can see
|
|
854
|
-
reads afterwards as "we checked everything", which is the claim this pipeline
|
|
855
|
-
exists to stop making. An empty radius is reported as `unresolved`, not as clean:
|
|
856
|
-
the graph indexes code, so a change to a skill, a rule or a schema has no blast
|
|
857
|
-
radius at all and that is a different fact from "nothing depends on it".
|
|
858
|
-
|
|
859
|
-
#### The scope-B question, and what is rejected
|
|
860
|
-
|
|
861
|
-
> Does this change break an existing behaviour **at these sites**?
|
|
862
|
-
|
|
863
|
-
Nothing else. The blast-radius set is **under regression check, not under
|
|
864
|
-
review**. A finding about style, naming or architecture in code the change did
|
|
865
|
-
not touch is refused **by the orchestrator in code** — not discouraged here —
|
|
866
|
-
under three rules, every one of them a fact about the claim rather than about who
|
|
867
|
-
made it:
|
|
868
|
-
|
|
869
|
-
| Rule | Refused because |
|
|
870
|
-
|---|---|
|
|
871
|
-
| `outside-set` | the file is neither in the computed set nor in the changed set; the reviewer went browsing |
|
|
872
|
-
| `non-regression-severity` | below `major`. Under the canonical rubric `minor` states the code behaves correctly and `info` names neither trigger nor outcome; neither can be a claim that something broke |
|
|
873
|
-
| `no-link-to-change` | nothing in the finding names a changed file, module or symbol. A regression claim says THE CHANGE broke this site |
|
|
792
|
+
Scope B is bounded and computed: distance ≤ 2 from every changed file, at most 40 files, closest
|
|
793
|
+
first. Do not pick the files or widen the set. Record it with `--out "<review-package>/blast-radius.md"`,
|
|
794
|
+
including every file the cap removed; an empty radius is `unresolved`, not clean.
|
|
874
795
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
796
|
+
A scope-B reviewer answers one question: does this change break an existing behaviour **at these
|
|
797
|
+
sites**? The orchestrator refuses, in code, a finding that is `outside-set`, below `major`
|
|
798
|
+
(`non-regression-severity`), or names nothing the change touched (`no-link-to-change`) — pass
|
|
799
|
+
`--brief` verbatim so no reviewer spends the round on findings that will be refused. `class_scope`
|
|
800
|
+
names the caller that breaks.
|
|
879
801
|
|
|
880
|
-
|
|
881
|
-
changed line, because that is the site a human has to look at.
|
|
882
|
-
|
|
883
|
-
#### When it is recomputed
|
|
884
|
-
|
|
885
|
-
| Round | Scope A | Scope B |
|
|
886
|
-
|---|---|---|
|
|
887
|
-
| 1 (first after the draft PR) | yes | yes |
|
|
888
|
-
| 2..N | yes | recomputed only if the changed-file set moved |
|
|
889
|
-
| final | yes | **yes, always** |
|
|
890
|
-
|
|
891
|
-
Do not decide this by memory:
|
|
802
|
+
Recompute scope B on rounds 2..N only when the changed-file set moved, and always on the final round:
|
|
892
803
|
|
|
893
804
|
```bash
|
|
894
805
|
keryx review blast-radius --ref "${BASE_SHA}" --previous blast-radius.json [--final]
|
|
895
806
|
```
|
|
896
807
|
|
|
897
|
-
|
|
898
|
-
nothing moved. The final round recomputes whatever the file set did — otherwise a
|
|
899
|
-
fix introduced in round 3 gets no regression check at all, and the round that
|
|
900
|
-
certifies the flow is the one that checked the least.
|
|
808
|
+
The measurements, the rule table and the recompute table: `SKILL.detail.md` § "Scope B — bounds, rejections and recompute".
|
|
901
809
|
|
|
902
810
|
### Path Mode
|
|
903
811
|
|
|
@@ -905,10 +813,10 @@ When a path or target is named, collect the candidate files:
|
|
|
905
813
|
|
|
906
814
|
```bash
|
|
907
815
|
# If a directory path is given:
|
|
908
|
-
|
|
816
|
+
keryx ctx rg --files -g '*.{ts,tsx,js,jsx}' <path> | sort
|
|
909
817
|
|
|
910
818
|
# If a module name is given (e.g. "UserStore", "pipelines module"):
|
|
911
|
-
|
|
819
|
+
keryx ctx rg --files -g '*<name>*.{ts,tsx}'
|
|
912
820
|
# Also check common locations: src/stores/, src/modules/, src/components/
|
|
913
821
|
```
|
|
914
822
|
|
|
@@ -1069,7 +977,7 @@ Unattended rounds ask nothing: `--all`, counterpart verified, computed models
|
|
|
1069
977
|
and record that choice in the review scope).
|
|
1070
978
|
|
|
1071
979
|
A gating round runs what these answers selected; a host that cannot dispatch subagents
|
|
1072
|
-
returns `STATUS: BLOCKED` (`nested_dispatch_unavailable`) instead of reviewing alone. Every
|
|
980
|
+
returns `STATUS: BLOCKED` (`nested_dispatch_unavailable`) instead of reviewing alone (Preflight 1). Every
|
|
1073
981
|
round also carries the always-on obligations — SKILL.detail.md § "Depth floor",
|
|
1074
982
|
§ "Counterpart verification", § "Round obligations".
|
|
1075
983
|
|
|
@@ -1092,8 +1000,8 @@ Legacy/profile reviewers are specialized review profiles that predate the review
|
|
|
1092
1000
|
| `--mobx-store` | `code-mobx-store-review` |
|
|
1093
1001
|
| `*.store.ts`, `makeObservable`, `observable`, `computed`, `action.bound` | suggest `code-mobx-store-review` as optional profile reviewer |
|
|
1094
1002
|
|
|
1095
|
-
Legacy/profile reviewers are never auto-included and never prompted for — do not
|
|
1096
|
-
about them. They are exempt from the finding contract (for example `code-ai-review` emits
|
|
1003
|
+
Legacy/profile reviewers are never auto-included and never prompted for, interactive or unattended — do not
|
|
1004
|
+
ask about them. They are exempt from the finding contract (for example `code-ai-review` emits
|
|
1097
1005
|
free-prose Russian with no per-finding severity field, so its output cannot be normalised into
|
|
1098
1006
|
the unified report), which is why inclusion must be a deliberate, explicit act rather than a
|
|
1099
1007
|
default the user has to opt out of on every review. Dispatch them ONLY when the user passes one
|
|
@@ -1185,7 +1093,7 @@ whoever reads the merge commit a year later reads the body. When `review.jev.con
|
|
|
1185
1093
|
|
|
1186
1094
|
New managed packages declare `reviewerDispatch: "research.json"`. Alongside the research ledger, provide `dispatch: { version: 1, 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).
|
|
1187
1095
|
|
|
1188
|
-
Dispatch selected reviewers in parallel when independent — this engine dispatches them itself; a host without a dispatch tool returns `STATUS: BLOCKED` (`nested_dispatch_unavailable`) and never reviews alone
|
|
1096
|
+
Dispatch selected reviewers in parallel when independent — this engine dispatches them itself (see "How to spawn"); a host without a dispatch tool returns `STATUS: BLOCKED` (`nested_dispatch_unavailable`) and never reviews alone — Preflight 1. Waves follow the diff domains in "Control" and keep their dependency order:
|
|
1189
1097
|
|
|
1190
1098
|
1. Wave A - core correctness/risk reviewers: logic, architecture, security/highload when selected.
|
|
1191
1099
|
2. Wave B - domain reviewers: frontend/backend/testing/convention reviewers filtered to relevant files. `review-jev-rules` (flow 330), `review-jev-risk`/`review-jev-scenarios` (flow 332), `review-jev-docs`/`review-jev-comments` (flow 333), `review-jev-contract` (flow 335) also run here, CLI-engine not sub-agent, `"engine": "jev"` in `keryx review reviewers --json`, gated on their own opt-in and a resolvable Jev/OpenRouter credential — measured verdicts per reviewer (keep off by default, experimental, etc.) in `SKILL.detail.md` § "CLI-engine reviewers".
|
|
@@ -1204,34 +1112,17 @@ not something Wave C can hand you.** If Wave B skips its mutation pass, that
|
|
|
1204
1112
|
finding class is unreachable for the entire round, and no amount of verification
|
|
1205
1113
|
downstream recovers it. When a testing reviewer returns without a mutation table,
|
|
1206
1114
|
treat it the way you would treat a reviewer that returned without findings *and*
|
|
1207
|
-
without evidence: ask once, then record that the pass did not run.
|
|
1115
|
+
without evidence: ask once, then record that the pass did not run. Unattended: record
|
|
1116
|
+
`Not run: mutation pass` without asking.
|
|
1208
1117
|
|
|
1209
1118
|
### Wave C — verification, and what it replaced
|
|
1210
1119
|
|
|
1211
|
-
Wave C
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
-
|
|
1217
|
-
**GPT-3.5 on CommonSenseQA: 75.8 → 38.1.** Among the answers that changed,
|
|
1218
|
-
correct → incorrect exceeded incorrect → correct (Huang et al., *Large Language
|
|
1219
|
-
Models Cannot Self-Correct Reasoning Yet*, ICLR 2024, arXiv:2310.01798).
|
|
1220
|
-
- **Self-Refine (arXiv:2303.17651): +49.2 on dialogue response generation, +0.2
|
|
1221
|
-
on maths.** Self-refinement gains are on subjective tasks and vanish on
|
|
1222
|
-
verifiable reasoning. Judging whether a null-guard is missing is verifiable
|
|
1223
|
-
reasoning.
|
|
1224
|
-
|
|
1225
|
-
Re-scoring a finding by re-reading it is therefore not a rigour pass; it is a
|
|
1226
|
-
coin flip weighted toward more findings. **Do not restore it because it looks
|
|
1227
|
-
obviously useful — it looked obviously useful the first time.**
|
|
1228
|
-
|
|
1229
|
-
`review-verifier` occupies the slot and differs in exactly one way that matters:
|
|
1230
|
-
**it runs something.** Verification that executes rejects 85–96% of false reports
|
|
1231
|
-
against 4–15% unaided while finding 30–44% more true bugs (AnyPoC,
|
|
1232
|
-
arXiv:2604.11950); Meta's TestGen-LLM funnel discards 75% of its own output
|
|
1233
|
-
(75% build → 57% build and pass → 25% improve coverage) and the surviving quarter
|
|
1234
|
-
reaches 73% human acceptance (arXiv:2402.09171).
|
|
1120
|
+
Wave C replaced `review-strict`, a meta-pass that re-read the findings and adjusted severity with no new evidence. It was **removed, not improved**, on measured evidence:
|
|
1121
|
+
self-correction fell 95.5 → 91.5 → 89.0 (GPT-4, GSM8K) and 75.8 → 38.1 (GPT-3.5, CommonSenseQA), arXiv:2310.01798; Self-Refine's +49.2 on dialogue response generation is +0.2 on maths
|
|
1122
|
+
(arXiv:2303.17651), so re-reading helps subjective tasks and not verifiable ones. A verifier that **runs something** rejects most false reports.
|
|
1123
|
+
Do not restore the re-score because it looks useful. Full figures: `SKILL.detail.md` § "Why Wave C verifies instead of re-scoring".
|
|
1124
|
+
|
|
1125
|
+
`review-verifier` occupies the slot and differs in one way that matters: it runs something.
|
|
1235
1126
|
|
|
1236
1127
|
It also **never votes.** 80+ agents unanimously endorsed a padding-oracle
|
|
1237
1128
|
vulnerability that did not exist, and a single empirical test killed it: consensus
|
|
@@ -1346,12 +1237,21 @@ Each reviewer must return a `REVIEW_RESULT` object matching `.metaproject/skills
|
|
|
1346
1237
|
## Sub-Agent Report Quality Gate
|
|
1347
1238
|
|
|
1348
1239
|
Before consolidation, validate every reviewer result:
|
|
1349
|
-
-
|
|
1240
|
+
- Every reply starts with a `STATUS:` line, and each status has one action:
|
|
1241
|
+
|
|
1242
|
+
| Status | Action |
|
|
1243
|
+
|---|---|
|
|
1244
|
+
| `DONE` | Consolidate. |
|
|
1245
|
+
| `DONE_WITH_CONCERNS` | Consolidate and carry the concerns into the report. |
|
|
1246
|
+
| `NEEDS_CONTEXT` | One targeted context refill; if still unresolved, keep it as an explicit open question, not a blocker. |
|
|
1247
|
+
| `BLOCKED` | Re-dispatch once; if it fails again, list the pass under **Not run**. |
|
|
1248
|
+
| `RATE_LIMITED` | Halve the wave and requeue (Control). |
|
|
1249
|
+
| anything else, or no result | Treat as `BLOCKED`. |
|
|
1250
|
+
|
|
1350
1251
|
- Required finding fields: id, severity, file, `quote`, problem, impact, suggested_fix, evidence, confidence, reviewer. **`quote`, not `line`:** copy the code the finding is about verbatim out of `file` and do not count lines — `keryx review ingest` locates the quote at the round's head and derives `line` from where it matched, so a number reported here is overwritten. A model is poor at counting lines and good at repeating text it has just read; this asks only for the second. Quote enough to be unambiguous: a snippet appearing twice in the file is recorded `unlocatable` rather than anchored to the first hit. A finding about the round rather than a site carries neither.
|
|
1351
1252
|
- Every blocker must include evidence and a concrete suggested fix.
|
|
1352
1253
|
- Findings without evidence are downgraded to `info` or returned to the reviewer for clarification.
|
|
1353
1254
|
- Duplicate findings are merged by `dedupe_key` or by `(file, quote, problem)`.
|
|
1354
|
-
- `NEEDS_CONTEXT` triggers one targeted context refill. If still unresolved, keep it as an explicit open question, not as a blocker.
|
|
1355
1255
|
- **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.
|
|
1356
1256
|
- **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.
|
|
1357
1257
|
- If a reviewer exceeds `max_findings`, keep blockers/majors first and summarize lower severity findings.
|
|
@@ -1652,6 +1552,8 @@ Automation values, names unchanged:
|
|
|
1652
1552
|
- `comment-and-ai-artifact` -> the PR review plus the follow-up file.
|
|
1653
1553
|
- `none` or legacy `false` -> do not publish.
|
|
1654
1554
|
|
|
1555
|
+
Unattended: do not publish; hand the rendered body back to the caller.
|
|
1556
|
+
|
|
1655
1557
|
Default is do not publish. No resolvable PR number means skip and say so.
|
|
1656
1558
|
|
|
1657
1559
|
The review body is the report rendered from `templates/review-report.md`, English, with the domain file chosen above (`templates/pr-comment-frontend.md` / `templates/pr-comment-backend.md`) — never a summary written freehand. It does not use a tool heading, a finding table, or a meta table. It does not carry a co-author line, a `Generated with` trailer, or any sentence that names a vendor or a product as the author. Name the workflow and reviewers that actually ran, with models, tools, skills and subagents. Never identify the operator from `gh auth status`, git config or a credential; do not sign the review as a person, reviewer or model. A project overlay adds content only through the template's overlay slots.
|
|
@@ -1707,6 +1609,7 @@ If absent, proceed normally — context is optional and non-blocking.
|
|
|
1707
1609
|
| "The spec-gate finding is mine, so `reviewer: review-orchestrator`" | That name makes the finding unroutable in Wave C. File it under the reviewer that ran the comparison |
|
|
1708
1610
|
| "Steps 8–10 are done, I'll tick them now and ingest later" | `completed` means the ingest record exists. A plan that runs ahead of the artifacts is how an unrun review looked finished |
|
|
1709
1611
|
| "The user said publish, so I can post the comment" | That chose whether, not what. Show the rendered body and get approval of it before any GitHub write |
|
|
1612
|
+
| "I cannot spawn reviewers, so I will review it myself" | A review nobody dispatched is not the review the report names. Output the Preflight 1 `BLOCKED` line and stop |
|
|
1710
1613
|
| "No flags means no reviewers" | No flags → run auto-detection; never produce an empty review |
|
|
1711
1614
|
| "User named a module so I'll use diff mode" | Named module/component/store → path mode; diff mode is only for branch changes |
|
|
1712
1615
|
| "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 |
|