@ferris1225/pi-subagents 4.1.3 → 4.1.4
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 +55 -32
- package/agents/cleaner.md +2 -2
- package/agents/documenter.md +3 -3
- package/agents/reviewer.md +2 -2
- package/agents/worker.md +2 -2
- package/package.json +1 -1
- package/src/completion.ts +160 -160
- package/src/config.ts +5 -5
- package/src/dispatch.ts +63 -50
- package/src/fixloop.ts +75 -95
- package/src/models.ts +189 -189
- package/src/prompt.ts +3 -3
- package/src/recovery.ts +145 -145
- package/src/session-fork.ts +80 -80
- package/src/thread-lifecycle.ts +3 -3
package/src/dispatch.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The `subagent` tool: dispatches explorer/worker/cleaner/documenter/reviewer agents as isolated pi
|
|
3
3
|
* child processes, single or parallel. Owns the public dispatch contract,
|
|
4
|
-
* per-run status tracking, managed writer →
|
|
5
|
-
* reviewer
|
|
6
|
-
* generations, final integration, and completion ownership live in
|
|
4
|
+
* per-run status tracking, managed writer → reviewer → documenter workflows,
|
|
5
|
+
* bounded worker/reviewer fix rounds, and internal step launching. Stable
|
|
6
|
+
* thread generations, final integration, and completion ownership live in
|
|
7
7
|
* thread-lifecycle.ts.
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -17,12 +17,10 @@ import { discoverAgents, resolveAgentTools, type AgentConfig } from "./agents.ts
|
|
|
17
17
|
import { loadConfig } from "./config.ts";
|
|
18
18
|
import { formatUsage, queuedResult } from "./format.ts";
|
|
19
19
|
import {
|
|
20
|
-
|
|
20
|
+
buildFinalDocumenterBrief,
|
|
21
21
|
buildFinalReviewBrief,
|
|
22
22
|
buildFixTaskBrief,
|
|
23
|
-
buildPostWriterDocumenterBrief,
|
|
24
23
|
buildReReviewBrief,
|
|
25
|
-
buildReviewPassDocumenterBrief,
|
|
26
24
|
type ChainStep,
|
|
27
25
|
type ManagedWorkflowOutcome,
|
|
28
26
|
} from "./fixloop.ts";
|
|
@@ -407,8 +405,14 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
407
405
|
return step.result;
|
|
408
406
|
};
|
|
409
407
|
|
|
410
|
-
|
|
408
|
+
/** Bounded worker → reviewer fix rounds. Documentation sync deliberately
|
|
409
|
+
* stays out of the rounds: code fixes would invalidate it, and one final
|
|
410
|
+
* documenter covers the settled diff once. */
|
|
411
|
+
const runFixRounds = async (
|
|
412
|
+
triggeringReviewer: SingleResult,
|
|
413
|
+
): Promise<{ lastReview?: SingleResult; lastWorker?: SingleResult }> => {
|
|
411
414
|
let lastReviewer = triggeringReviewer;
|
|
415
|
+
const outcome: { lastReview?: SingleResult; lastWorker?: SingleResult } = {};
|
|
412
416
|
for (let round = 1; round <= request.config.maxFixRounds; round++) {
|
|
413
417
|
if (!canContinue()) break;
|
|
414
418
|
const workerResult = await launchStep(
|
|
@@ -416,30 +420,46 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
416
420
|
buildFixTaskBrief(lastReviewer, round, request.config.maxFixRounds),
|
|
417
421
|
`fix round ${round}`,
|
|
418
422
|
);
|
|
419
|
-
if (
|
|
420
|
-
|
|
421
|
-
let documenterResult: SingleResult | undefined;
|
|
422
|
-
if (enabled("documenter")) {
|
|
423
|
-
documenterResult = await launchStep(
|
|
424
|
-
"documenter",
|
|
425
|
-
buildDocumenterTaskBrief(workerResult, round, lastReviewer),
|
|
426
|
-
`docs round ${round}`,
|
|
427
|
-
);
|
|
428
|
-
if (!canContinue() || isFailedResult(documenterResult)) break;
|
|
429
|
-
}
|
|
423
|
+
if (isFailedResult(workerResult) || !canContinue()) break;
|
|
424
|
+
outcome.lastWorker = workerResult;
|
|
430
425
|
|
|
431
426
|
const reviewResult = await launchStep(
|
|
432
427
|
"reviewer",
|
|
433
|
-
buildReReviewBrief(lastReviewer, round, workerResult,
|
|
428
|
+
buildReReviewBrief(lastReviewer, round, workerResult, {
|
|
429
|
+
documenterPending: enabled("documenter"),
|
|
430
|
+
}),
|
|
434
431
|
`re-review round ${round}`,
|
|
435
432
|
);
|
|
436
|
-
if (
|
|
433
|
+
if (isFailedResult(reviewResult) || !canContinue()) break;
|
|
434
|
+
outcome.lastReview = reviewResult;
|
|
437
435
|
const verdict = reviewVerdict(getResultOutput(reviewResult));
|
|
438
436
|
// REVIEW_PASS settles. No verdict is advisory/malformed and must never
|
|
439
437
|
// trigger another writer. Only an explicit REVIEW_FAIL consumes a fix.
|
|
440
438
|
if (verdict !== "fail") break;
|
|
441
439
|
lastReviewer = reviewResult;
|
|
442
440
|
}
|
|
441
|
+
return outcome;
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
/** One final documentation sync after the gate settles. It is skipped
|
|
445
|
+
* when documenter is disabled, the last writer already was a
|
|
446
|
+
* documenter with no code fixes after it, or the chain ended on a
|
|
447
|
+
* failing/crashed gate (docs for rejected code would be rework). */
|
|
448
|
+
const runFinalDocumentation = async (
|
|
449
|
+
lastWriterResult: SingleResult | undefined,
|
|
450
|
+
finalReviewResult: SingleResult | undefined,
|
|
451
|
+
): Promise<void> => {
|
|
452
|
+
if (!canContinue() || !enabled("documenter")) return;
|
|
453
|
+
if (lastWriterResult?.agent === "documenter") return;
|
|
454
|
+
if (finalReviewResult) {
|
|
455
|
+
if (isFailedResult(finalReviewResult)) return;
|
|
456
|
+
if (reviewVerdict(getResultOutput(finalReviewResult)) === "fail") return;
|
|
457
|
+
}
|
|
458
|
+
await launchStep(
|
|
459
|
+
"documenter",
|
|
460
|
+
buildFinalDocumenterBrief(lastWriterResult, finalReviewResult),
|
|
461
|
+
"final documentation sync",
|
|
462
|
+
);
|
|
443
463
|
};
|
|
444
464
|
|
|
445
465
|
try {
|
|
@@ -448,43 +468,36 @@ export function registerSubagentTool(pi: ExtensionAPI, runtime: SubagentRuntime)
|
|
|
448
468
|
// never create an already-aborted downstream child.
|
|
449
469
|
if (!canContinue()) return { kind: request.plan.kind, steps };
|
|
450
470
|
if (request.plan.kind === "auto-fix") {
|
|
451
|
-
await runFixRounds(initialStepResult);
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if (
|
|
469
|
-
canContinue() &&
|
|
470
|
-
(!documenterResult || !isFailedResult(documenterResult)) &&
|
|
471
|
-
enabled("reviewer")
|
|
472
|
-
) {
|
|
473
|
-
const reviewResult = await launchStep(
|
|
474
|
-
"reviewer",
|
|
475
|
-
buildFinalReviewBrief(initialStepResult, documenterResult),
|
|
476
|
-
"final review",
|
|
477
|
-
);
|
|
471
|
+
const fixOutcome = await runFixRounds(initialStepResult);
|
|
472
|
+
await runFinalDocumentation(fixOutcome.lastWorker, fixOutcome.lastReview ?? initialStepResult);
|
|
473
|
+
} else if (request.plan.kind === "review-pass-sync") {
|
|
474
|
+
// The direct passing review already gated the pending code; sync
|
|
475
|
+
// docs once and deliver without a second gate.
|
|
476
|
+
await runFinalDocumentation(undefined, initialStepResult);
|
|
477
|
+
} else if (enabled("reviewer")) {
|
|
478
|
+
const gateReview = await launchStep(
|
|
479
|
+
"reviewer",
|
|
480
|
+
buildFinalReviewBrief(initialStepResult, { documenterPending: enabled("documenter") }),
|
|
481
|
+
"final review",
|
|
482
|
+
);
|
|
483
|
+
if (!isFailedResult(gateReview)) {
|
|
484
|
+
let fixOutcome: Awaited<ReturnType<typeof runFixRounds>> = {};
|
|
478
485
|
if (
|
|
479
486
|
canContinue() &&
|
|
480
|
-
|
|
481
|
-
reviewVerdict(getResultOutput(reviewResult)) === "fail" &&
|
|
487
|
+
reviewVerdict(getResultOutput(gateReview)) === "fail" &&
|
|
482
488
|
enabled("worker") &&
|
|
483
489
|
request.config.maxFixRounds > 0
|
|
484
490
|
) {
|
|
485
|
-
await runFixRounds(
|
|
491
|
+
fixOutcome = await runFixRounds(gateReview);
|
|
486
492
|
}
|
|
493
|
+
await runFinalDocumentation(
|
|
494
|
+
fixOutcome.lastWorker ?? initialStepResult,
|
|
495
|
+
fixOutcome.lastReview ?? gateReview,
|
|
496
|
+
);
|
|
487
497
|
}
|
|
498
|
+
} else {
|
|
499
|
+
// No gate configured: the documenter is the only downstream stage.
|
|
500
|
+
await runFinalDocumentation(initialStepResult, undefined);
|
|
488
501
|
}
|
|
489
502
|
return { kind: request.plan.kind, steps };
|
|
490
503
|
} finally {
|
package/src/fixloop.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Managed workflow policy and handoff formatting.
|
|
3
3
|
*
|
|
4
|
-
* Successful top-level writers
|
|
5
|
-
*
|
|
6
|
-
* documentation sync
|
|
7
|
-
*
|
|
8
|
-
* reviewer
|
|
9
|
-
*
|
|
4
|
+
* Successful top-level writers continue through an independent code review
|
|
5
|
+
* gate; bounded worker → reviewer fix rounds close its findings; one final
|
|
6
|
+
* documentation sync runs after the gate settles, so code fixes never
|
|
7
|
+
* invalidate an earlier docs pass and reviewers stay focused on code. A direct
|
|
8
|
+
* passing reviewer gets that same single final documentation sync instead of a
|
|
9
|
+
* second gate; a direct failing reviewer uses the same fix rounds plus the
|
|
10
|
+
* final sync. Internal steps are launched by dispatch directly, so they never
|
|
11
|
+
* re-enter this top-level policy or wake the main agent mid-chain.
|
|
10
12
|
*/
|
|
11
13
|
|
|
12
14
|
import { isWriteCapableAgent, type AgentConfig } from "./agents.ts";
|
|
@@ -107,7 +109,9 @@ export function getManagedWorkflowPlan(
|
|
|
107
109
|
if (result.agent !== "reviewer") return undefined;
|
|
108
110
|
|
|
109
111
|
const verdict = reviewVerdict(getResultOutput(result));
|
|
110
|
-
|
|
112
|
+
// The pass stands as the code gate; documenter then syncs docs once and the
|
|
113
|
+
// workflow delivers without a second gate.
|
|
114
|
+
if (verdict === "pass" && availability.documenter) {
|
|
111
115
|
return { kind: "review-pass-sync", initialRelation: "pre-documentation review" };
|
|
112
116
|
}
|
|
113
117
|
if (verdict === "fail" && availability.worker && shouldTriggerFixLoop(result, config)) {
|
|
@@ -135,77 +139,56 @@ export function buildFixTaskBrief(reviewerResult: SingleResult, round: number, m
|
|
|
135
139
|
`Fix EVERY finding in the reviewer's findings list — there is no severity triage; all of them get fixed.`,
|
|
136
140
|
`If a finding is factually wrong or clearly out of scope, say so explicitly instead of fixing it.`,
|
|
137
141
|
`Do NOT refactor unrelated code beyond what the findings require.`,
|
|
138
|
-
`Do NOT commit, push, publish, tag, or release; do not bump versions. The parent chain still owns
|
|
142
|
+
`Do NOT commit, push, publish, tag, or release; do not bump versions. The parent chain still owns re-review and the final documentation sync.`,
|
|
139
143
|
`After editing, run the project's format/build/tests when they exist and report`,
|
|
140
144
|
`exactly what you changed (paths + short rationale) so a reviewer can verify.`,
|
|
141
145
|
remaining > 0
|
|
142
146
|
? `A reviewer will re-review your changes automatically after you finish.`
|
|
143
|
-
: `This is the last auto-fix round;
|
|
147
|
+
: `This is the last auto-fix round; the workflow runs any enabled final documentation sync and then delivers.`,
|
|
144
148
|
].join("\n");
|
|
145
149
|
}
|
|
146
150
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
151
|
+
/** Build the single documentation handoff that runs after the review gate
|
|
152
|
+
* settles. The last writer's report (top-level writer or final fix-round
|
|
153
|
+
* worker) and the terminal gate review are leads; the pending diff stays
|
|
154
|
+
* authoritative. At most one of the two is undefined in every managed flow. */
|
|
155
|
+
export function buildFinalDocumenterBrief(
|
|
156
|
+
lastWriterResult?: SingleResult,
|
|
157
|
+
finalReviewResult?: SingleResult,
|
|
158
|
+
): string {
|
|
159
|
+
const reportSections = [
|
|
160
|
+
...(lastWriterResult
|
|
161
|
+
? [
|
|
162
|
+
`The last writer (${lastWriterResult.agent}) reported:`,
|
|
163
|
+
`---`,
|
|
164
|
+
getResultOutput(lastWriterResult),
|
|
165
|
+
`---`,
|
|
166
|
+
``,
|
|
167
|
+
]
|
|
168
|
+
: []),
|
|
169
|
+
...(finalReviewResult
|
|
170
|
+
? [
|
|
171
|
+
`The final gate review reported:`,
|
|
172
|
+
`---`,
|
|
173
|
+
getResultOutput(finalReviewResult),
|
|
174
|
+
`---`,
|
|
175
|
+
``,
|
|
176
|
+
]
|
|
177
|
+
: []),
|
|
178
|
+
];
|
|
161
179
|
return [
|
|
162
|
-
|
|
180
|
+
`Final documentation sync: the review gate settled and you are the last managed stage before delivery.`,
|
|
163
181
|
``,
|
|
164
182
|
...reportSections,
|
|
165
|
-
`Inspect the actual git diff (the complete pending diff) and relevant implementation; the
|
|
166
|
-
`
|
|
183
|
+
`Inspect the actual git diff (the complete pending diff) and relevant implementation; the reports are only leads.`,
|
|
184
|
+
`Apply every documentation note the reviews recorded, then synchronize stale README/docs, examples, API comments, docstrings, and explanatory comments with the behavior that will be committed.`,
|
|
167
185
|
`Change documentation surfaces only; never alter runtime behavior or tests to make prose true.`,
|
|
168
186
|
`Make zero edits when the diff creates no documentation drift.`,
|
|
169
|
-
`Do NOT commit, push, publish, tag, or release; do not bump versions.
|
|
187
|
+
`Do NOT commit, push, publish, tag, or release; do not bump versions. The parent workflow delivers directly after you; no fresh reviewer runs.`,
|
|
170
188
|
`Report exact documentation/comment paths changed, or state explicitly that no sync was needed.`,
|
|
171
189
|
].join("\n");
|
|
172
190
|
}
|
|
173
191
|
|
|
174
|
-
/** Build the pre-commit documentation handoff after one auto-fix worker. */
|
|
175
|
-
export function buildDocumenterTaskBrief(
|
|
176
|
-
workerResult: SingleResult,
|
|
177
|
-
round: number,
|
|
178
|
-
reviewerResult?: SingleResult,
|
|
179
|
-
): string {
|
|
180
|
-
return buildDocumentationBrief({
|
|
181
|
-
title: `Documentation sync after auto-fix round ${round}.`,
|
|
182
|
-
reports: [
|
|
183
|
-
...(reviewerResult ? [{ label: "The triggering reviewer reported", result: reviewerResult }] : []),
|
|
184
|
-
{ label: "The worker reported", result: workerResult },
|
|
185
|
-
],
|
|
186
|
-
closing: "a fresh reviewer gate runs after you.",
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/** Build the automatic documentation stage after a successful top-level writer. */
|
|
191
|
-
export function buildPostWriterDocumenterBrief(writerResult: SingleResult): string {
|
|
192
|
-
return buildDocumentationBrief({
|
|
193
|
-
title: `Documentation sync after successful top-level ${writerResult.agent}.`,
|
|
194
|
-
reports: [{ label: `The ${writerResult.agent} reported`, result: writerResult }],
|
|
195
|
-
closing: "the managed workflow owns any final reviewer and delivery.",
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/** A direct passing review cannot be the final gate while documenter is enabled:
|
|
200
|
-
* the preliminary report is context, but the actual pending diff is authoritative. */
|
|
201
|
-
export function buildReviewPassDocumenterBrief(reviewerResult: SingleResult): string {
|
|
202
|
-
return buildDocumentationBrief({
|
|
203
|
-
title: "Documentation sync required before accepting a direct passing review.",
|
|
204
|
-
reports: [{ label: "The preliminary reviewer reported", result: reviewerResult }],
|
|
205
|
-
closing: "the preliminary pass is not final and a fresh reviewer gate runs after you.",
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
192
|
/**
|
|
210
193
|
* One step of an auto-fix chain as delivered: the run id (so the condensed
|
|
211
194
|
* summary can point at per-run detail via subagent_status), the result, and
|
|
@@ -278,33 +261,36 @@ export function formatManagedWorkflowSummary(
|
|
|
278
261
|
);
|
|
279
262
|
}
|
|
280
263
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
264
|
+
export interface GateBriefOptions {
|
|
265
|
+
/** A final documenter runs after the gate settles. Documentation drift is
|
|
266
|
+
* then routed to it as non-gating notes instead of failing the gate. */
|
|
267
|
+
documenterPending: boolean;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Build the code gate that runs directly after a top-level writer, before any
|
|
271
|
+
* documentation. Reports carry intent; the actual pending diff remains
|
|
272
|
+
* authoritative. */
|
|
284
273
|
export function buildFinalReviewBrief(
|
|
285
274
|
initialResult: SingleResult,
|
|
286
|
-
|
|
275
|
+
options: GateBriefOptions,
|
|
287
276
|
): string {
|
|
288
|
-
const documenterSection = documenterResult
|
|
289
|
-
? [
|
|
290
|
-
``,
|
|
291
|
-
`The documenter's full sync report:`,
|
|
292
|
-
`---`,
|
|
293
|
-
getResultOutput(documenterResult),
|
|
294
|
-
`---`,
|
|
295
|
-
]
|
|
296
|
-
: [];
|
|
297
277
|
return [
|
|
298
|
-
`Fresh
|
|
278
|
+
`Fresh code gate for a managed ${initialResult.agent} workflow.`,
|
|
299
279
|
``,
|
|
300
280
|
`The top-level ${initialResult.agent}'s full report:`,
|
|
301
281
|
`---`,
|
|
302
282
|
getResultOutput(initialResult),
|
|
303
283
|
`---`,
|
|
304
|
-
...documenterSection,
|
|
305
284
|
``,
|
|
306
|
-
`Run \`git status\` and \`git diff\` and inspect the actual pending code
|
|
307
|
-
`Remain read-only. Verify correctness, regressions,
|
|
285
|
+
`Run \`git status\` and \`git diff\` and inspect the actual pending code; the report is context, not proof.`,
|
|
286
|
+
`Remain read-only. Verify correctness, regressions, and tests.`,
|
|
287
|
+
...(options.documenterPending
|
|
288
|
+
? [
|
|
289
|
+
`Documentation sync runs AFTER this gate, so documentation drift is not a gate finding: record needed`,
|
|
290
|
+
`documentation updates as a separate short "## Documentation notes" list for the final documenter,`,
|
|
291
|
+
`and fail the gate only for code or test findings.`,
|
|
292
|
+
]
|
|
293
|
+
: []),
|
|
308
294
|
`This is an acceptance gate, not an advisory audit. End with exactly one standalone machine verdict line:`,
|
|
309
295
|
`VERDICT: REVIEW_PASS when no finding remains, otherwise VERDICT: REVIEW_FAIL.`,
|
|
310
296
|
].join("\n");
|
|
@@ -312,29 +298,19 @@ export function buildFinalReviewBrief(
|
|
|
312
298
|
|
|
313
299
|
/**
|
|
314
300
|
* The re-review brief handed to the reviewer after a worker fix round. Includes
|
|
315
|
-
* the prior review
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
* a verified resolution.
|
|
301
|
+
* the prior review and worker report so the reviewer can adjudicate rejections
|
|
302
|
+
* instead of restating findings. The convergence contract keeps rounds from
|
|
303
|
+
* ping-ponging: rule on the open findings once, add only defects this round's
|
|
304
|
+
* edits introduced, never re-open a verified resolution.
|
|
320
305
|
*/
|
|
321
306
|
export function buildReReviewBrief(
|
|
322
307
|
reviewerResult: SingleResult,
|
|
323
308
|
round: number,
|
|
324
309
|
workerResult: SingleResult,
|
|
325
|
-
|
|
310
|
+
options: GateBriefOptions = { documenterPending: false },
|
|
326
311
|
): string {
|
|
327
312
|
const review = getResultOutput(reviewerResult);
|
|
328
313
|
const workerReport = getResultOutput(workerResult);
|
|
329
|
-
const documenterSection = documenterResult
|
|
330
|
-
? [
|
|
331
|
-
``,
|
|
332
|
-
`The documenter's pre-commit sync report:`,
|
|
333
|
-
`---`,
|
|
334
|
-
getResultOutput(documenterResult),
|
|
335
|
-
`---`,
|
|
336
|
-
]
|
|
337
|
-
: [];
|
|
338
314
|
return [
|
|
339
315
|
`Re-review after auto-fix round ${round}.`,
|
|
340
316
|
``,
|
|
@@ -347,7 +323,6 @@ export function buildReReviewBrief(
|
|
|
347
323
|
`---`,
|
|
348
324
|
workerReport,
|
|
349
325
|
`---`,
|
|
350
|
-
...documenterSection,
|
|
351
326
|
``,
|
|
352
327
|
`Rule on EVERY previous finding: resolved, or still open. A finding the worker rejected must be`,
|
|
353
328
|
`adjudicated ONCE — accept the rejection unless you can concretely refute the worker's reasoning;`,
|
|
@@ -355,6 +330,11 @@ export function buildReReviewBrief(
|
|
|
355
330
|
`Run \`git diff\` to see what changed, then add NEW findings only when they are defects this round's`,
|
|
356
331
|
`edits introduced or exposed (or a load-bearing issue the earlier review genuinely missed).`,
|
|
357
332
|
`Do NOT re-open a finding you verified as resolved.`,
|
|
333
|
+
...(options.documenterPending
|
|
334
|
+
? [
|
|
335
|
+
`Carry any unresolved "## Documentation notes" forward verbatim; the final documenter applies them after the chain settles.`,
|
|
336
|
+
]
|
|
337
|
+
: []),
|
|
358
338
|
`REQUEST_CHANGES only while an open finding remains; otherwise APPROVE.`,
|
|
359
339
|
`End with your machine-readable verdict line as usual (VERDICT: REVIEW_PASS / REVIEW_FAIL).`,
|
|
360
340
|
].join("\n");
|