@mrciphersmith/keryx 0.3.119 → 0.3.120
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +7083 -3896
- package/dist/core.js +212 -25
- package/package.json +1 -1
- package/src/gdskills/bundled/skills/orchestration/feature-dev/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +36 -39
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +6 -6
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/orchestrator-prompt.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.md +12 -18
- package/src/gdskills/bundled/skills/orchestration/task-implementer/input-contract.schema.json +8 -0
- package/src/gdskills/bundled/skills/orchestration/task-implementer/output-contract.schema.json +19 -0
- package/src/gdskills/bundled/skills/review/review-architecture/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-backend/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-clean-code/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-core-boundaries/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-flow-graph/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-frontend/SKILL.md +3 -7
- package/src/gdskills/bundled/skills/review/review-frontend-conventions/SKILL.md +4 -6
- package/src/gdskills/bundled/skills/review/review-highload/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-logic/SKILL.md +2 -2
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +14 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +16 -49
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +13 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-input.schema.json +38 -0
- package/src/gdskills/bundled/skills/review/review-performance/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-pr-feedback/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-regression/SKILL.md +2 -2
- package/src/gdskills/bundled/skills/review/review-security-code/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-style/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-testing-practices/SKILL.md +1 -1
- package/src/gdskills/contracts/review-finding.schema.json +25 -0
package/dist/core.js
CHANGED
|
@@ -9635,11 +9635,11 @@ var init_skill_length_ceilings = __esm(() => {
|
|
|
9635
9635
|
"orchestration/context-collector": 671,
|
|
9636
9636
|
"orchestration/feature-analyzer": 447,
|
|
9637
9637
|
"orchestration/feature-dev": 177,
|
|
9638
|
-
"orchestration/flow-orchestrator":
|
|
9638
|
+
"orchestration/flow-orchestrator": 691,
|
|
9639
9639
|
"orchestration/issue-analyzer": 373,
|
|
9640
9640
|
"orchestration/job-documenter": 414,
|
|
9641
9641
|
"orchestration/job-orchestrator": 2235,
|
|
9642
|
-
"orchestration/task-implementer":
|
|
9642
|
+
"orchestration/task-implementer": 662,
|
|
9643
9643
|
"planning/autodoc-analyst": 180,
|
|
9644
9644
|
"planning/autodoc-architect": 179,
|
|
9645
9645
|
"planning/autodoc-assembler": 145,
|
|
@@ -9689,8 +9689,8 @@ var init_skill_length_ceilings = __esm(() => {
|
|
|
9689
9689
|
"review/review-clean-code": 543,
|
|
9690
9690
|
"review/review-core-boundaries": 163,
|
|
9691
9691
|
"review/review-flow-graph": 188,
|
|
9692
|
-
"review/review-frontend":
|
|
9693
|
-
"review/review-frontend-conventions":
|
|
9692
|
+
"review/review-frontend": 612,
|
|
9693
|
+
"review/review-frontend-conventions": 211,
|
|
9694
9694
|
"review/review-highload": 548,
|
|
9695
9695
|
"review/review-jev-comments": 184,
|
|
9696
9696
|
"review/review-jev-contract": 193,
|
|
@@ -9700,7 +9700,7 @@ var init_skill_length_ceilings = __esm(() => {
|
|
|
9700
9700
|
"review/review-jev-scenarios": 187,
|
|
9701
9701
|
"review/review-layout": 238,
|
|
9702
9702
|
"review/review-logic": 370,
|
|
9703
|
-
"review/review-orchestrator":
|
|
9703
|
+
"review/review-orchestrator": 1582,
|
|
9704
9704
|
"review/review-performance": 359,
|
|
9705
9705
|
"review/review-pr-contract": 191,
|
|
9706
9706
|
"review/review-pr-feedback": 895,
|
|
@@ -27431,6 +27431,12 @@ import { existsSync as existsSync8 } from "fs";
|
|
|
27431
27431
|
import { readFile as readFile23 } from "fs/promises";
|
|
27432
27432
|
import path50 from "path";
|
|
27433
27433
|
|
|
27434
|
+
// src/review/round-record.ts
|
|
27435
|
+
init_fs();
|
|
27436
|
+
var NOT_REVIEW_PACKAGES = [".", ":(exclude).metaproject/reviews", ":(exclude,glob).metaproject/flows/*/reviews/**"];
|
|
27437
|
+
var REVIEWED_STATUSES = new Set(["reviewed", "decided", "learned", "closed"]);
|
|
27438
|
+
var COMPLETED_STATUSES = new Set(["decided", "learned", "closed"]);
|
|
27439
|
+
|
|
27434
27440
|
// src/review/stack.ts
|
|
27435
27441
|
init_skill_frontmatter();
|
|
27436
27442
|
var STACK_TAGS = ["nestjs", "react", "mobx", "prisma", "playwright", "sql", "http-server"];
|
|
@@ -28302,6 +28308,41 @@ async function unresolvedReferences(projectRoot, content) {
|
|
|
28302
28308
|
|
|
28303
28309
|
// src/review/reviewers.ts
|
|
28304
28310
|
init_skill_frontmatter();
|
|
28311
|
+
function overlapFields(content) {
|
|
28312
|
+
const list = (key) => {
|
|
28313
|
+
const nested = metadataList(content, key);
|
|
28314
|
+
return nested.length > 0 ? nested : frontmatterList(content, key);
|
|
28315
|
+
};
|
|
28316
|
+
const scalar = (key) => {
|
|
28317
|
+
const value = (frontmatterScalar(content, key, "metadata") ?? frontmatterScalar(content, key))?.trim();
|
|
28318
|
+
return value === undefined || value === "" ? undefined : value;
|
|
28319
|
+
};
|
|
28320
|
+
const supersedes = [...new Set(list("supersedes").map((name) => name.trim()).filter(Boolean))];
|
|
28321
|
+
const overlapGroup = scalar("overlap_group");
|
|
28322
|
+
const rank = scalar("overlap_rank");
|
|
28323
|
+
return {
|
|
28324
|
+
...supersedes.length > 0 ? { supersedes } : {},
|
|
28325
|
+
...overlapGroup !== undefined ? { overlapGroup } : {},
|
|
28326
|
+
...rank === "generic" || rank === "specific" ? { overlapRank: rank } : {}
|
|
28327
|
+
};
|
|
28328
|
+
}
|
|
28329
|
+
function overlapGroupsOf(inventory) {
|
|
28330
|
+
const tier = (source, rank) => source === "project-skill" ? 0 : rank === "generic" ? 2 : 1;
|
|
28331
|
+
const members = new Map;
|
|
28332
|
+
for (const reviewer of [...inventory.project, ...inventory.bundled]) {
|
|
28333
|
+
if (reviewer.overlapGroup === undefined)
|
|
28334
|
+
continue;
|
|
28335
|
+
const list = members.get(reviewer.overlapGroup) ?? [];
|
|
28336
|
+
if (!list.some((entry) => entry.name === reviewer.name))
|
|
28337
|
+
list.push({ name: reviewer.name, tier: tier(reviewer.source, reviewer.overlapRank) });
|
|
28338
|
+
members.set(reviewer.overlapGroup, list);
|
|
28339
|
+
}
|
|
28340
|
+
const groups = {};
|
|
28341
|
+
for (const [group, list] of [...members].sort(([a], [b]) => a.localeCompare(b))) {
|
|
28342
|
+
groups[group] = list.sort((a, b) => a.tier - b.tier || a.name.localeCompare(b.name)).map((entry) => entry.name);
|
|
28343
|
+
}
|
|
28344
|
+
return groups;
|
|
28345
|
+
}
|
|
28305
28346
|
function metadataLine(content, label) {
|
|
28306
28347
|
const match = content.match(new RegExp(`^${escapeRegexLiteral(label)}:\\s*(.+)$`, "m"));
|
|
28307
28348
|
return match?.[1]?.trim();
|
|
@@ -28339,7 +28380,8 @@ async function bundledReviewerFromFile(name, skillMdPath, projectRelativePath) {
|
|
|
28339
28380
|
source: "bundled",
|
|
28340
28381
|
path: projectRelativePath,
|
|
28341
28382
|
...engine !== undefined ? { engine } : {},
|
|
28342
|
-
...description !== undefined ? { description } : {}
|
|
28383
|
+
...description !== undefined ? { description } : {},
|
|
28384
|
+
...content !== undefined ? overlapFields(content) : {}
|
|
28343
28385
|
};
|
|
28344
28386
|
}
|
|
28345
28387
|
async function projectInstalledReviewers(bundledRoot) {
|
|
@@ -28394,7 +28436,8 @@ async function collectReviewers(projectRoot, deps = {}) {
|
|
|
28394
28436
|
...origin ? { origin } : {},
|
|
28395
28437
|
...originHash ? { originHash } : {},
|
|
28396
28438
|
...importedAt ? { importedAt } : {},
|
|
28397
|
-
drift: await driftFor(projectRoot, origin, originHash)
|
|
28439
|
+
drift: await driftFor(projectRoot, origin, originHash),
|
|
28440
|
+
...overlapFields(content)
|
|
28398
28441
|
});
|
|
28399
28442
|
}
|
|
28400
28443
|
const family = familyFlagsByReviewer(new Map(project.map((reviewer) => [reviewer.name, reviewer.flags])));
|
|
@@ -28404,6 +28447,22 @@ async function collectReviewers(projectRoot, deps = {}) {
|
|
|
28404
28447
|
return { bundled, bundledSource, project };
|
|
28405
28448
|
}
|
|
28406
28449
|
|
|
28450
|
+
// src/review/cost.ts
|
|
28451
|
+
function estimateTokens(text) {
|
|
28452
|
+
return Math.ceil(text.length / 4);
|
|
28453
|
+
}
|
|
28454
|
+
|
|
28455
|
+
// src/review/jev-select.ts
|
|
28456
|
+
var CORE_MANDATORY_REVIEWER_IDS = [
|
|
28457
|
+
"review-logic",
|
|
28458
|
+
"review-architecture",
|
|
28459
|
+
"review-security-code",
|
|
28460
|
+
"review-highload"
|
|
28461
|
+
];
|
|
28462
|
+
|
|
28463
|
+
// src/review/coverage.ts
|
|
28464
|
+
init_contained_path();
|
|
28465
|
+
|
|
28407
28466
|
// src/review/run-profile.ts
|
|
28408
28467
|
init_fs();
|
|
28409
28468
|
import { existsSync as existsSync6, readFileSync as readFileSync10 } from "fs";
|
|
@@ -28988,6 +29047,17 @@ var LIGHT_MAX_FILES = 5;
|
|
|
28988
29047
|
var LIGHT_MAX_CHANGED_LINES = 300;
|
|
28989
29048
|
var LIGHT_FIXED_REVIEWERS = ["review-logic", "review-testing-practices"];
|
|
28990
29049
|
var LIGHT_CONVENTIONS_PREFERENCE = ["review-frontend-conventions", "review-backend", "review-clean-code"];
|
|
29050
|
+
var NEVER_SUPERSEDED = new Set([...CORE_MANDATORY_REVIEWER_IDS, "review-regression"]);
|
|
29051
|
+
function overlayChangedByRound(reviewer, context) {
|
|
29052
|
+
if (reviewer.source !== "project-skill" || reviewer.path === undefined)
|
|
29053
|
+
return;
|
|
29054
|
+
if (context.files === undefined)
|
|
29055
|
+
return `${reviewer.name} is a project overlay and the round's changed files are not recorded, so it cannot be ruled out that this branch wrote it`;
|
|
29056
|
+
const absolute = path50.isAbsolute(reviewer.path) ? reviewer.path : path50.resolve(context.root ?? process.cwd(), reviewer.path);
|
|
29057
|
+
const dir = path50.relative(context.root ?? process.cwd(), path50.dirname(absolute)).split(path50.sep).join("/");
|
|
29058
|
+
const changed = context.files.find((file) => file === dir || file.startsWith(`${dir}/`));
|
|
29059
|
+
return changed === undefined ? undefined : `${reviewer.name} is a project overlay this round changes (${changed})`;
|
|
29060
|
+
}
|
|
28991
29061
|
var NOT_ROSTER = new Set([
|
|
28992
29062
|
"review-orchestrator",
|
|
28993
29063
|
"review-verifier",
|
|
@@ -29129,6 +29199,83 @@ var PR_CONTRACT = "review-pr-contract";
|
|
|
29129
29199
|
function dispatchKnowsRole(dispatch, role) {
|
|
29130
29200
|
return Array.isArray(dispatch.roles) && dispatch.roles.includes(role);
|
|
29131
29201
|
}
|
|
29202
|
+
function applySupersedes(required, inventory, excluded, context = {}, notes = []) {
|
|
29203
|
+
const kept = new Set(required);
|
|
29204
|
+
for (const reviewer of [...inventory.project, ...inventory.bundled]) {
|
|
29205
|
+
if (!kept.has(reviewer.name))
|
|
29206
|
+
continue;
|
|
29207
|
+
const targets = (reviewer.supersedes ?? []).filter((target) => target !== reviewer.name && kept.has(target));
|
|
29208
|
+
if (targets.length === 0)
|
|
29209
|
+
continue;
|
|
29210
|
+
const core = targets.filter((target) => NEVER_SUPERSEDED.has(target));
|
|
29211
|
+
if (core.length > 0)
|
|
29212
|
+
notes.push(`${reviewer.name} supersedes ${core.join(", ")}: ignored, ${core.length === 1 ? "it is a core role" : "they are core roles"} that always run`);
|
|
29213
|
+
const rest = targets.filter((target) => !NEVER_SUPERSEDED.has(target));
|
|
29214
|
+
if (rest.length === 0)
|
|
29215
|
+
continue;
|
|
29216
|
+
const changed = overlayChangedByRound(reviewer, context);
|
|
29217
|
+
if (changed !== undefined) {
|
|
29218
|
+
notes.push(`${reviewer.name} supersedes ${rest.join(", ")}: ignored, ${changed}`);
|
|
29219
|
+
continue;
|
|
29220
|
+
}
|
|
29221
|
+
for (const target of rest) {
|
|
29222
|
+
kept.delete(target);
|
|
29223
|
+
excluded.push({ reviewer: target, reason: `superseded by ${reviewer.name}` });
|
|
29224
|
+
}
|
|
29225
|
+
}
|
|
29226
|
+
return required.filter((name) => kept.has(name));
|
|
29227
|
+
}
|
|
29228
|
+
function applyOverlapGroups(required, inventory, files, excluded, context = {}, notes = []) {
|
|
29229
|
+
const kept = new Set(required);
|
|
29230
|
+
const byName = new Map([...inventory.project, ...inventory.bundled].map((reviewer) => [reviewer.name, reviewer]));
|
|
29231
|
+
const projectGates = new Map(inventory.project.filter((r) => r.pathsSource !== "none" && r.paths.length > 0).map((r) => [r.name, r.paths.map(globToRegExp2)]));
|
|
29232
|
+
const gateOf = (name) => {
|
|
29233
|
+
const bundled = BUNDLED_PATH_GATES[name];
|
|
29234
|
+
if (bundled !== undefined)
|
|
29235
|
+
return bundled;
|
|
29236
|
+
const patterns = projectGates.get(name);
|
|
29237
|
+
return patterns === undefined ? undefined : (file) => patterns.some((p) => p.test(file));
|
|
29238
|
+
};
|
|
29239
|
+
const covers = (winner, loser) => {
|
|
29240
|
+
const wide = gateOf(winner);
|
|
29241
|
+
if (wide === undefined)
|
|
29242
|
+
return true;
|
|
29243
|
+
if (files === undefined)
|
|
29244
|
+
return false;
|
|
29245
|
+
const narrow = gateOf(loser);
|
|
29246
|
+
return files.filter((file) => narrow === undefined || narrow(file)).every((file) => wide(file));
|
|
29247
|
+
};
|
|
29248
|
+
for (const [group, members] of Object.entries(overlapGroupsOf(inventory))) {
|
|
29249
|
+
const present = members.filter((member) => kept.has(member));
|
|
29250
|
+
const winner = present[0];
|
|
29251
|
+
if (winner === undefined)
|
|
29252
|
+
continue;
|
|
29253
|
+
const winnerReviewer = byName.get(winner);
|
|
29254
|
+
const changed = winnerReviewer === undefined ? undefined : overlayChangedByRound(winnerReviewer, { ...context, files });
|
|
29255
|
+
for (const member of present.slice(1)) {
|
|
29256
|
+
if (!covers(winner, member))
|
|
29257
|
+
continue;
|
|
29258
|
+
if (NEVER_SUPERSEDED.has(member)) {
|
|
29259
|
+
notes.push(`overlap group ${group}: ${member} kept beside ${winner}, it is a core role that always runs`);
|
|
29260
|
+
continue;
|
|
29261
|
+
}
|
|
29262
|
+
if (changed !== undefined) {
|
|
29263
|
+
notes.push(`overlap group ${group}: ${member} kept beside ${winner}, ${changed}`);
|
|
29264
|
+
continue;
|
|
29265
|
+
}
|
|
29266
|
+
kept.delete(member);
|
|
29267
|
+
excluded.push({ reviewer: member, reason: `overlap group ${group}: ${winner} reads the same lines` });
|
|
29268
|
+
}
|
|
29269
|
+
}
|
|
29270
|
+
return required.filter((name) => kept.has(name));
|
|
29271
|
+
}
|
|
29272
|
+
function lightConventionsReviewer(roster, inventory) {
|
|
29273
|
+
const pick = LIGHT_CONVENTIONS_PREFERENCE.find((name) => roster.includes(name));
|
|
29274
|
+
if (pick === undefined)
|
|
29275
|
+
return;
|
|
29276
|
+
const group = Object.values(overlapGroupsOf(inventory)).find((members) => members.includes(pick));
|
|
29277
|
+
return group?.find((member) => roster.includes(member)) ?? pick;
|
|
29278
|
+
}
|
|
29132
29279
|
async function requiredReviewers(root, mode, files, options = {}) {
|
|
29133
29280
|
const inventory = await collectReviewers(root);
|
|
29134
29281
|
const stack = await detectProjectStack(root);
|
|
@@ -29153,7 +29300,7 @@ async function requiredReviewers(root, mode, files, options = {}) {
|
|
|
29153
29300
|
}
|
|
29154
29301
|
const filesKnown = files !== undefined;
|
|
29155
29302
|
if (options.depth === "light") {
|
|
29156
|
-
const conventions =
|
|
29303
|
+
const conventions = lightConventionsReviewer(roster, inventory);
|
|
29157
29304
|
const keep = new Set([...LIGHT_FIXED_REVIEWERS, ...conventions === undefined ? [] : [conventions]]);
|
|
29158
29305
|
const light = roster.filter((name) => keep.has(name));
|
|
29159
29306
|
for (const name of roster)
|
|
@@ -29162,7 +29309,9 @@ async function requiredReviewers(root, mode, files, options = {}) {
|
|
|
29162
29309
|
for (const name of [REGRESSION, PR_CONTRACT])
|
|
29163
29310
|
if (inventory.bundled.some((r) => r.name === name))
|
|
29164
29311
|
excluded.push({ reviewer: name, reason: "light round: not part of the light set" });
|
|
29165
|
-
|
|
29312
|
+
const notes = [];
|
|
29313
|
+
const kept = applySupersedes(light, inventory, excluded, { root, files }, notes).sort();
|
|
29314
|
+
return { mode, depth: "light", required: kept, excluded, filesKnown, ...notes.length === 0 ? {} : { notes } };
|
|
29166
29315
|
}
|
|
29167
29316
|
const gated = (name) => {
|
|
29168
29317
|
if (!filesKnown)
|
|
@@ -29202,13 +29351,15 @@ async function requiredReviewers(root, mode, files, options = {}) {
|
|
|
29202
29351
|
else
|
|
29203
29352
|
excluded.push({ reviewer: PR_CONTRACT, reason: "no pr-context.md in the package: there is no PR body checklist to compare with the diff" });
|
|
29204
29353
|
}
|
|
29205
|
-
|
|
29354
|
+
const notes = [];
|
|
29355
|
+
const collapsed = applyOverlapGroups(applySupersedes(required, inventory, excluded, { root, files }, notes), inventory, files, excluded, { root }, notes).sort();
|
|
29356
|
+
return { mode, depth: "full", required: collapsed, excluded, filesKnown, ...notes.length === 0 ? {} : { notes } };
|
|
29206
29357
|
}
|
|
29207
29358
|
async function gitDiffStats(root, target) {
|
|
29208
29359
|
try {
|
|
29209
29360
|
assertGitRevision(target.ref);
|
|
29210
29361
|
assertGitRevision(target.head);
|
|
29211
|
-
const proc = Bun.spawn(["git", "diff", "--no-color", "-U20", "--end-of-options", `${target.ref}...${target.head}
|
|
29362
|
+
const proc = Bun.spawn(["git", "diff", "--no-color", "-U20", "--end-of-options", `${target.ref}...${target.head}`, "--", ...NOT_REVIEW_PACKAGES], { cwd: root, stdout: "pipe", stderr: "pipe", timeout: 15000, killSignal: "SIGKILL", maxBuffer: 8 * 1024 * 1024 });
|
|
29212
29363
|
const [stdout, exitCode] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
29213
29364
|
if (exitCode !== 0 || proc.signalCode !== null)
|
|
29214
29365
|
return;
|
|
@@ -29345,13 +29496,50 @@ async function reviewerSetErrors(dispatch, options, effective) {
|
|
|
29345
29496
|
depth: effective?.depth ?? "full"
|
|
29346
29497
|
});
|
|
29347
29498
|
const selected = new Set(Array.isArray(record.selected) ? record.selected.filter(text) : []);
|
|
29499
|
+
const skips = dispatchSkips(record).filter((skip) => need.required.includes(skip.reviewer) && !selected.has(skip.reviewer));
|
|
29500
|
+
const decided = await skipDecision(options.packageDir, skips.map((skip) => skip.reviewer));
|
|
29501
|
+
if (decided.ok)
|
|
29502
|
+
for (const skip of skips)
|
|
29503
|
+
selected.add(skip.reviewer);
|
|
29348
29504
|
const missing = need.required.filter((name) => !selected.has(name));
|
|
29349
29505
|
if (!missing.length)
|
|
29350
29506
|
return [];
|
|
29507
|
+
if (!decided.ok)
|
|
29508
|
+
return [`dispatch.skipped is not accepted: ${decided.refusal}`];
|
|
29351
29509
|
return [
|
|
29352
29510
|
`dispatch covers ${need.required.length - missing.length} of ${need.required.length} reviewers that mode ${record.mode} requires; missing: ${missing.join(", ")}. ` + "Dispatch them, or record an operator override {operatorQuote, reason}; `keryx review required` prints the set" + (effective?.refused === undefined ? "" : `. The dispatch says depth light, but the full set applies: ${effective.refused}`)
|
|
29353
29511
|
];
|
|
29354
29512
|
}
|
|
29513
|
+
async function skipDecision(packageDir, skipped) {
|
|
29514
|
+
if (skipped.length === 0)
|
|
29515
|
+
return { ok: true };
|
|
29516
|
+
const writers = decisionWriters("reviewer-override", packageDir === undefined ? "<review-dir>" : path50.relative(process.cwd(), packageDir) || ".");
|
|
29517
|
+
if (packageDir === undefined)
|
|
29518
|
+
return { ok: false, refusal: `skipping ${skipped.join(", ")} needs the operator's decision recorded in the package: ${writers}` };
|
|
29519
|
+
const { required } = await reviewersLeftOut(packageDir, undefined);
|
|
29520
|
+
const outside = skipped.filter((name) => !required.includes(name));
|
|
29521
|
+
if (outside.length > 0)
|
|
29522
|
+
return { ok: false, refusal: `${outside.join(", ")} cannot be skipped: no operator decision can name ${outside.length === 1 ? "it" : "them"} (not in the mode-all required set ${required.join(", ") || "none"}); run ${outside.length === 1 ? "it" : "them"}` };
|
|
29523
|
+
const skip = new Set(skipped);
|
|
29524
|
+
const now = await operatorDecisionSubject(packageDir, "reviewer-override", required.filter((name) => !skip.has(name)));
|
|
29525
|
+
if (await confirmedDecision(packageDir, "reviewer-override", now) !== undefined)
|
|
29526
|
+
return { ok: true };
|
|
29527
|
+
return { ok: false, refusal: `skipping ${skipped.join(", ")} narrows the reviewer set and needs the operator's decision for head ${now.head ?? "unknown"} leaving out ${now.items.join(", ") || "nothing"}: ${writers} with --selected naming every other required reviewer` };
|
|
29528
|
+
}
|
|
29529
|
+
function dispatchSkips(dispatch) {
|
|
29530
|
+
const record = dispatch && typeof dispatch === "object" && !Array.isArray(dispatch) ? dispatch : undefined;
|
|
29531
|
+
if (record === undefined || !Array.isArray(record.skipped))
|
|
29532
|
+
return [];
|
|
29533
|
+
const out = [];
|
|
29534
|
+
for (const entry of record.skipped) {
|
|
29535
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
29536
|
+
continue;
|
|
29537
|
+
const { reviewer, reason } = entry;
|
|
29538
|
+
if (typeof reviewer === "string" && reviewer.trim() !== "" && typeof reason === "string" && reason.trim() !== "")
|
|
29539
|
+
out.push({ reviewer: reviewer.trim(), reason: reason.trim() });
|
|
29540
|
+
}
|
|
29541
|
+
return out;
|
|
29542
|
+
}
|
|
29355
29543
|
async function packageScopeFiles(packageDir) {
|
|
29356
29544
|
try {
|
|
29357
29545
|
const parsed = JSON.parse(await readFile23(path50.join(packageDir, "scope-files.json"), "utf8"));
|
|
@@ -29361,22 +29549,15 @@ async function packageScopeFiles(packageDir) {
|
|
|
29361
29549
|
}
|
|
29362
29550
|
}
|
|
29363
29551
|
function projectRootOfPackage(packageDir) {
|
|
29364
|
-
|
|
29365
|
-
|
|
29366
|
-
|
|
29367
|
-
|
|
29368
|
-
|
|
29369
|
-
|
|
29552
|
+
const resolved = path50.resolve(packageDir);
|
|
29553
|
+
const parts = resolved.split(path50.sep);
|
|
29554
|
+
const at = parts.lastIndexOf(".metaproject");
|
|
29555
|
+
if (at > 0)
|
|
29556
|
+
return parts.slice(0, at).join(path50.sep) || path50.sep;
|
|
29557
|
+
const found = resolveProjectRoot(resolved);
|
|
29558
|
+
return found !== resolved ? found : path50.resolve(resolved, "..", "..", "..");
|
|
29370
29559
|
}
|
|
29371
29560
|
|
|
29372
|
-
// src/review/jev-select.ts
|
|
29373
|
-
var CORE_MANDATORY_REVIEWER_IDS = [
|
|
29374
|
-
"review-logic",
|
|
29375
|
-
"review-architecture",
|
|
29376
|
-
"review-security-code",
|
|
29377
|
-
"review-highload"
|
|
29378
|
-
];
|
|
29379
|
-
|
|
29380
29561
|
// src/review/conform-state.ts
|
|
29381
29562
|
init_redact();
|
|
29382
29563
|
|
|
@@ -29662,6 +29843,11 @@ function coveredSliceIds(dispatch, retry) {
|
|
|
29662
29843
|
if (typeof id === "string")
|
|
29663
29844
|
ids.add(id);
|
|
29664
29845
|
}
|
|
29846
|
+
if (Array.isArray(dispatch?.fixSlicesCovered)) {
|
|
29847
|
+
for (const id of dispatch.fixSlicesCovered)
|
|
29848
|
+
if (typeof id === "string")
|
|
29849
|
+
ids.add(id);
|
|
29850
|
+
}
|
|
29665
29851
|
const reviewers = record(record(retry)?.reviewers);
|
|
29666
29852
|
for (const entry of Object.values(reviewers ?? {})) {
|
|
29667
29853
|
const covered = record(entry)?.covered;
|
|
@@ -29980,6 +30166,7 @@ function normalizeText(value) {
|
|
|
29980
30166
|
init_fs();
|
|
29981
30167
|
import { readFile as readFile25 } from "fs/promises";
|
|
29982
30168
|
import path54 from "path";
|
|
30169
|
+
var SEVERITY_SET = new Set(["blocker", "major", "minor", "info"]);
|
|
29983
30170
|
var KNOWN_REVIEW_STATES = new Set(["APPROVED", "CHANGES_REQUESTED", "COMMENTED", "DISMISSED", "PENDING"]);
|
|
29984
30171
|
var PR_COMMENT_STATE_VERSION = 1;
|
|
29985
30172
|
function prCommentsStatePath(cwd, repo, number) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.120",
|
|
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": {
|
|
@@ -113,7 +113,7 @@ Input: codebase_path=<project_root>, scope=changed, base_branch=<base>
|
|
|
113
113
|
- `git diff main...HEAD` — review the full diff
|
|
114
114
|
- Check for: TODOs left behind, console.logs, hardcoded values
|
|
115
115
|
- Verify all acceptance criteria from Phase 1 spec
|
|
116
|
-
3. Fix any findings (max 2 review-fix cycles)
|
|
116
|
+
3. Fix any findings (max 2 review-fix cycles). Each next round is `keryx review start` or `keryx review plan` on the new head: the round comes from `round.json`; no own range, no whole-branch re-review (the `git diff main...HEAD` above is round 1 only).
|
|
117
117
|
4. Re-run `code-verifier` after any fixes
|
|
118
118
|
|
|
119
119
|
### Phase 8: DELIVER + CHANGE REPORT
|
|
@@ -77,11 +77,12 @@ flowchart TD
|
|
|
77
77
|
C --> D["flow-init context enrichment"]
|
|
78
78
|
D --> E["freeze AC + start flow"]
|
|
79
79
|
E --> F["dispatch tests-creator / task-implementer"]
|
|
80
|
-
F -->
|
|
80
|
+
F --> FG{"first green: ask operator - open PR or harden first"}
|
|
81
|
+
FG --> G["code-verifier + review-orchestrator"]
|
|
81
82
|
G --> H{"All tasks, checks, review OK?"}
|
|
82
83
|
H -- "no" --> F
|
|
83
84
|
H -- "yes" --> I{"Ask user how to finish"}
|
|
84
|
-
I -- "create PR" --> J["create PR
|
|
85
|
+
I -- "create PR" --> J["create PR; each round = review start on the new head (delta)"]
|
|
85
86
|
J --> K{"review clean, PR mergeable?"}
|
|
86
87
|
K -- "no, attempts < 5" --> J
|
|
87
88
|
K -- "no, attempts = 5 or repetition detected" --> R["enrich context and change fix strategy"]
|
|
@@ -177,9 +178,7 @@ Record attempts as they happen, not only on resume:
|
|
|
177
178
|
keryx flow task attempt <id> <Tn> --outcome started|failed|blocked [--detail "<what happened>"]
|
|
178
179
|
```
|
|
179
180
|
|
|
180
|
-
`attempts.count` is append-only and lives in `flow.json`. A counter that lives
|
|
181
|
-
only in the orchestrator's context resets to zero exactly when the loop bound
|
|
182
|
-
matters most, which makes it not a counter.
|
|
181
|
+
`attempts.count` is append-only and lives in `flow.json`. A counter that lives only in the orchestrator's context resets to zero exactly when the loop bound matters most, which makes it not a counter.
|
|
183
182
|
|
|
184
183
|
### 0.1 Route
|
|
185
184
|
|
|
@@ -244,27 +243,26 @@ Write or update:
|
|
|
244
243
|
- `tasks.md` - task definitions grouped by context, test, implement, review, docs;
|
|
245
244
|
- `acceptance-criteria.md` - verifiable `ACn` criteria, each ending in one marker: [verify: exec `<command>`] or [verify: invariant `<command>`] (a backticked command), [verify: judged] (a human checks: live run, operator decision, text quality), or [verify: none — <reason>]. If a kind is unclear, ask the operator ONE question listing the criteria with proposed kinds, before `flow freeze`. Behaviour with an external dependency or a failure path gets its own failure-scenario criterion with an observable result (saved, error visible, retry creates no duplicate); a check claimed to catch a regression carries `· sensitive: revert|mutation|failure-scenario` and the confirm note carries `evidence: <ref>` (revert/mutation run or failure-scenario reference).
|
|
246
245
|
|
|
246
|
+
### UI or state scope: a state × event matrix in plan.md
|
|
247
|
+
|
|
248
|
+
When the scope touches UI, a store, or other code that holds state across events, `plan.md` carries a state × event matrix before `flow freeze`. Rows are the states the surface can be in (for example loading, loaded, empty, missing, first-load failed, refresh failed); columns are the events that reach it (for example the user switches selection, unmount, workspace or context change, retry). Each cell is one line: what the user sees and which state survives, citing the project's styling/UX rules where they decide it. Every non-trivial cell gets an `ACn` row, or one AC per group of cells with the same result; a cell that is genuinely "no change" needs none. List only the states and events the change reaches:
|
|
249
|
+
|
|
250
|
+
| state \ event | user switches selection | unmount | retry |
|
|
251
|
+
|---|---|---|---|
|
|
252
|
+
| loading | drop the stale response; loading shows for the new one | no state write after unmount | — |
|
|
253
|
+
| refresh failed | keep loaded data; the error does not follow to the new selection | — | re-fetch; error stays inline until success |
|
|
254
|
+
|
|
255
|
+
It does not apply when the scope holds no UI or state (CLI output, pure functions, docs, build config): write `State × event matrix: not applicable — <why>` in `plan.md`. The matrix is what workers and fix rounds check behavior against; a choice no cell decides is a `behavior_changes` entry for the operator (Phase 2).
|
|
256
|
+
|
|
247
257
|
### A verification step in the plan is a task, not a sentence
|
|
248
258
|
|
|
249
|
-
Every check the plan says must happen before the work is accepted - run the
|
|
250
|
-
thing end to end, confirm two components agree, exercise the path no test
|
|
251
|
-
covers - is added with `keryx flow task add` and closed with
|
|
252
|
-
`keryx flow task done`. Prose in `plan.md` blocks nothing, and an
|
|
253
|
-
orchestrator that wrote the step is the same one deciding whether to run it.
|
|
259
|
+
Every check the plan says must happen before the work is accepted - run the thing end to end, confirm two components agree, exercise the path no test covers - is added with `keryx flow task add` and closed with `keryx flow task done`. Prose in `plan.md` blocks nothing, and an orchestrator that wrote the step is the same one deciding whether to run it.
|
|
254
260
|
|
|
255
|
-
The failure this prevents is specific and has happened: a plan listed
|
|
256
|
-
"confirm both components agree on the same input" as step 5, the
|
|
257
|
-
implementation shipped without it, and review found that the two did not
|
|
258
|
-
agree at all - the change could not work in production. The check had been
|
|
259
|
-
identified correctly and then skipped, because nothing made skipping it
|
|
260
|
-
visible.
|
|
261
|
+
The failure this prevents is specific and has happened: a plan listed "confirm both components agree on the same input" as step 5, the implementation shipped without it, and review found that the two did not agree at all - the change could not work in production. The check had been identified correctly and then skipped, because nothing made skipping it visible.
|
|
261
262
|
|
|
262
|
-
Tasks are the mechanism for this. `keryx flow complete` runs a `tasks` gate
|
|
263
|
-
over them, so an unrun verification step keeps the flow open instead of being
|
|
264
|
-
quietly dropped.
|
|
263
|
+
Tasks are the mechanism for this. `keryx flow complete` runs a `tasks` gate over them, so an unrun verification step keeps the flow open instead of being quietly dropped.
|
|
265
264
|
|
|
266
|
-
Know the gate's exact scope, because for years this file claimed a gate that
|
|
267
|
-
did not exist and 24 completed flows shipped with an open task:
|
|
265
|
+
Know the gate's exact scope, because for years this file claimed a gate that did not exist and 24 completed flows shipped with an open task:
|
|
268
266
|
|
|
269
267
|
- the gate is **opt-in per flow package**, keyed on `gates.tasks` in
|
|
270
268
|
`flow.json`, which `keryx flow init` writes for every flow it creates. A
|
|
@@ -389,11 +387,7 @@ fi
|
|
|
389
387
|
keryx flow task done <id> <Tn>
|
|
390
388
|
```
|
|
391
389
|
|
|
392
|
-
Why each path is checked on its own: `git status --porcelain --` with no path
|
|
393
|
-
lists the whole tree, `git add` fails on a path that is neither on disk nor
|
|
394
|
-
tracked, and `git commit` with nothing staged exits 1. The trailing
|
|
395
|
-
`-- "${PENDING[@]}"` commits only these paths, so a file another lane staged is
|
|
396
|
-
not swept into this commit.
|
|
390
|
+
Why each path is checked on its own: `git status --porcelain --` with no path lists the whole tree, `git add` fails on a path that is neither on disk nor tracked, and `git commit` with nothing staged exits 1. The trailing `-- "${PENDING[@]}"` commits only these paths, so a file another lane staged is not swept into this commit.
|
|
397
391
|
|
|
398
392
|
If new work is discovered:
|
|
399
393
|
|
|
@@ -415,6 +409,8 @@ properly formatted `subagent-result`.
|
|
|
415
409
|
| `BLOCKED` | `keryx flow block <id> --reason "<worker reason>"`; resolve or escalate one concise question, then `flow unblock` and re-dispatch. |
|
|
416
410
|
| `FAILED` | Emitted by harness **child** workers (`src/harness/child/contract.ts`), never by skill workers — `task-implementer` maps its own `failed` onto `BLOCKED`. Retry once with the same dispatch. If it fails again, block the flow and surface the error to the user. |
|
|
417
411
|
|
|
412
|
+
**A `behavior_changes` entry with `needs_operator: true` is a question, not a concern to journal.** task-implementer lists every user-visible tradeoff it made (message wording, error placement, state reset) there. Before accepting that result, ask the operator with the question tool — one question per entry, the implemented choice and each of its `alternatives` as options — and record the answer in `journal.md`. An answer other than the implemented one is a fix task on the same `Tn` before the boundary commit, not a review finding later.
|
|
413
|
+
|
|
418
414
|
Carry `run_id`/`dispatch_id` across retries so the flow journal stays traceable,
|
|
419
415
|
and record every dispatch against the task's persisted counter so a session
|
|
420
416
|
restart does not reset the budget:
|
|
@@ -432,7 +428,8 @@ Before accepting implementation:
|
|
|
432
428
|
1. Run focused tests for touched scope.
|
|
433
429
|
2. Run `code-verifier`.
|
|
434
430
|
3. Run `keryx health run` when Code Health is enabled.
|
|
435
|
-
4.
|
|
431
|
+
4. **First-green gate, once per flow.** The first time every implement/test task is `done` and steps 1–3 pass, ask the operator one question (question tool, options) before any review round: `issue AC met — open the PR with the standard review`, or `harden first` with each extra matrix cell or failure path you would add as its own option. Record the answer in `journal.md`; picked hardening becomes `keryx flow task add` tasks (plus `keryx flow ac update` when it adds criteria) before review. A dispatched run has no operator: it takes the standard review and journals that the dispatch answered the gate.
|
|
432
|
+
5. Check the bounds, then run `review-orchestrator` with relevant domains — in this session,
|
|
436
433
|
never handed whole to one subagent: one without a dispatch tool returns `STATUS: BLOCKED`.
|
|
437
434
|
```bash
|
|
438
435
|
keryx review budget --spent <usd-so-far> --full --outstanding <subagents still running, 0 if none>
|
|
@@ -440,16 +437,15 @@ Before accepting implementation:
|
|
|
440
437
|
A non-zero exit means the spend ceiling (3 USD by default) has been reached:
|
|
441
438
|
**stop and ask the user** rather than dispatching another fan-out — never shrink the fan-out to fit.
|
|
442
439
|
Dispatch prompts point at sources (files, SHAs, the PR) and never paraphrase a contract.
|
|
440
|
+
Every round opens with `keryx review start` on the current head, never a hand-built range (Phase 4 loop, step 1).
|
|
443
441
|
|
|
444
|
-
`--outstanding` is the part that matters here. `review-orchestrator` runs inline
|
|
445
|
-
in this session and dispatches reviewers in parallel; keryx cannot observe other
|
|
446
|
-
subagents. Passing the count still running is the only thing that makes the
|
|
447
|
-
concurrency cap mean anything; omit it and the cap bounds the reviewer fan-out
|
|
448
|
-
alone, which the review record then states plainly. Add `--full` for `--all`.
|
|
442
|
+
`--outstanding` is the part that matters here. `review-orchestrator` runs inline in this session and dispatches reviewers in parallel; keryx cannot observe other subagents. Passing the count still running is the only thing that makes the concurrency cap mean anything; omit it and the cap bounds the reviewer fan-out alone, which the review record then states plainly. Add `--full` for `--all`.
|
|
449
443
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
444
|
+
6. If findings require code changes, run a fix round through `task-implementer`, recorded as a flow fix task:
|
|
445
|
+
- **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.
|
|
446
|
+
- **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.
|
|
447
|
+
- **`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.
|
|
448
|
+
7. Close the skill-learning loop (see `rules/core/skill-lifecycle.mdc`). Collect
|
|
453
449
|
the `skill_drift` fields from task-implementer results and the
|
|
454
450
|
`## Skill Learning` block from review-orchestrator. For each flagged
|
|
455
451
|
project-skill, dispatch a subagent — on a cheaper / non-flagship model if one
|
|
@@ -536,10 +532,8 @@ the bound plus an escalation — never an unbounded loop.
|
|
|
536
532
|
|
|
537
533
|
### PR review/fix loop
|
|
538
534
|
|
|
539
|
-
1.
|
|
540
|
-
|
|
541
|
-
2. If findings or required check failures remain, create or update a flow fix
|
|
542
|
-
task, dispatch `task-implementer`, push the fix, and run review again.
|
|
535
|
+
1. Each round — the first and every one after a fix — is `keryx review start` on the PR's current head, then `review-orchestrator` from that package. `review start` finds the previous round itself (local package, git history, the PR's round block, the flow package) and, when the previous reviewed head is an ancestor, reviews the delta: fix changes with a small roster (logic, regression, the findings' authors, the verifier) and the prior findings attached, new changes as round 1, unchanged code carried with its dispositions. Never pass a whole-branch range or `--all` per round; `<package>/round.json` records the round, its source and its mode.
|
|
536
|
+
2. If findings or required check failures remain, run a fix round under Phase 3 step 6 (flow fix task, `task-implementer`), push the fix, and run the next round as in step 1.
|
|
543
537
|
|
|
544
538
|
**A required check failure is not automatically a fix task.** When `review.jev.ci_triage` is on, run `keryx review ci-triage --run <id> --json` on each failed run first: `top: flaky` (or a `deterministic` override) → rerun once (`gh run rerun --failed <id>`), record it in `journal.md`, and treat a second failure as `real-regression` regardless; `top: real-regression` → investigate and fix as usual; `top: infra` → report it in the completion notes and leave the code alone. The verdict is advisory only — never the sole reason to skip a fix task.
|
|
545
539
|
|
|
@@ -691,4 +685,7 @@ Stop and re-read this skill if you are thinking:
|
|
|
691
685
|
| "The worker returned BLOCKED twice — faster if I implement this task myself." | The implementer never self-accepts and the orchestrator never implements. Block the flow, escalate one concise question, then unblock and re-dispatch. Doing the work here erases the boundary the whole flow model rests on. |
|
|
692
686
|
| "Verification is described in the plan, so it will happen." | A verification step in the plan is a task, not a sentence. If it is not a task with a status, nothing records whether it ran, and the flow reaches `implemented` with an unrun gate. |
|
|
693
687
|
| "The review fan-out is cheap, so the budget check can wait." | `keryx review budget --spent … --outstanding …` gates the fan-out, and `review-orchestrator` runs inline in this same main session, where keryx cannot see the in-flight subagents; declare them with `--outstanding`. Skipping the check means the cap bounds nothing. |
|
|
688
|
+
| "The reviewer wrote the fix; applying it is the fastest close." | A `suggested_fix` is a hypothesis. Most majors in a long fix loop are introduced by fixes: reproduce the finding red, check the suggestion against the state × event matrix, then fix (Phase 3 step 6). |
|
|
689
|
+
| "While I'm in this code, the fix round can also harden the empty state." | Scope freeze: a fix round fixes findings. New hardening is an operator decision at the first-green gate or as an explicit question, never silent growth. |
|
|
690
|
+
| "The implementer journaled its UX choice, so it is decided." | A `behavior_changes` entry with `needs_operator: true` is decided by the operator, asked with options before the result is accepted (Phase 2). A journal line decides nothing. |
|
|
694
691
|
| "I'll set the verification kind later." | An `unclassified` criterion cannot become data afterwards without a recorded rewrite: `keryx flow ac update <id> --criterion ACn --text "…" --reason "<why>"`. Put the `[verify: …]` marker on every criterion before `flow freeze`. |
|
|
@@ -1125,12 +1125,12 @@ runs on the session model. That is a correct answer, not a failure.
|
|
|
1125
1125
|
|
|
1126
1126
|
```
|
|
1127
1127
|
flags: <selected flags, e.g. --backend --security --testing-practices>
|
|
1128
|
-
commit_range: <BASE_SHA>..HEAD
|
|
1128
|
+
commit_range: <BASE_SHA>..HEAD # round 1 only; a later round takes its range from round.json
|
|
1129
1129
|
issue_url: <issue URL, when the job has one — enables the Stage 1 spec gate>
|
|
1130
1130
|
context_doc: <JOBS_ROOT>/<job-name>/ai/context.md
|
|
1131
1131
|
verification_mode: annotate
|
|
1132
1132
|
managed_review: { mode: "review-flow", target: "branch", target_ref: "<feature-branch>" }
|
|
1133
|
-
is_fix_round: <true on any round after the first>
|
|
1133
|
+
is_fix_round: <true on any round after the first; the round, its range and prior_findings come from `review start`/`review plan`, never from your own range>
|
|
1134
1134
|
pr_comments: { enabled: <true when a PR exists> }
|
|
1135
1135
|
```
|
|
1136
1136
|
|
|
@@ -1298,11 +1298,11 @@ FOR iteration in [1, 2, 3]:
|
|
|
1298
1298
|
4. Run the sanity check (step 2.5.2 logic) — verify commits were made
|
|
1299
1299
|
5. Run the next managed round — the FULL 2.6.1 sequence, not a bare re-dispatch:
|
|
1300
1300
|
keryx review budget --spent <usd> --outstanding <n>
|
|
1301
|
-
keryx review
|
|
1301
|
+
keryx review plan --target branch --ref <feature-branch> --head <new-head>
|
|
1302
|
+
# start + scope + blast-radius + slice + required + estimate on the new head;
|
|
1303
|
+
# round.json names the delta, scope.json and blast-radius.json land in the package
|
|
1302
1304
|
keryx review comments collect --repo <r> --pr <n> --sha <new-head> --round <N+1> --out <file>
|
|
1303
|
-
|
|
1304
|
-
keryx review blast-radius --ref "$BASE_SHA" --previous blast-radius.json --json > blast-radius.json
|
|
1305
|
-
<run review-orchestrator inline, in this session, with is_fix_round: true>
|
|
1305
|
+
<run review-orchestrator inline, in this session; it takes the round from round.json>
|
|
1306
1306
|
keryx review ingest --report <new-report> --ref <feature-branch> --head <new-head> \
|
|
1307
1307
|
--scope scope.json --blast-radius blast-radius.json \
|
|
1308
1308
|
--verifications <file> --refuted <file> --outstanding <n>
|
|
@@ -179,7 +179,7 @@ If `blocker` or `major` findings exist:
|
|
|
179
179
|
1. Group by file
|
|
180
180
|
2. Launch task-implementer (`task_type: "fix"`) with the findings
|
|
181
181
|
3. Run the sanity check — verify commits were made
|
|
182
|
-
4. Run the NEXT managed round (the
|
|
182
|
+
4. Run the NEXT managed round: `keryx review plan --target branch --ref <feature-branch> --head <new-head>` (or `review start`) on the new head, then review-orchestrator. The round, range and prior findings come from `round.json`: no own range, no `--all` per round, no whole-branch re-review.
|
|
183
183
|
5. Disposition every finding of the previous round:
|
|
184
184
|
```bash
|
|
185
185
|
keryx review complete <review-id> --finding F-001 --disposition acted-on --evidence "<commit>"
|