@ferris1225/pi-subagents 4.1.9 → 4.1.12

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.
@@ -1,378 +1,248 @@
1
- /**
2
- * Managed workflow policy and handoff formatting.
3
- *
4
- * Successful top-level worker/cleaner runs continue through an independent
5
- * code review gate; bounded worker reviewer fix rounds close its findings.
6
- * Gate findings carry concrete fix instructions that the worker implements
7
- * unless it can justify a sounder fix and push back; re-review adjudicates on
8
- * the resulting code (open findings plus fix-introduced defects only) so the
9
- * rounds converge instead of re-auditing from scratch.
10
- * Reviewers classify documentation drift explicitly, so the low-cost final
11
- * documenter runs only when needed (or conservatively when an older/custom
12
- * reviewer omits the marker). Direct passing/failing gates use the same policy.
13
- * Top-level documenters are explicit standalone writing tasks. Internal steps
14
- * are launched by dispatch directly, so they never re-enter this policy or wake
15
- * the main agent mid-chain.
16
- */
17
-
18
- import { isWriteCapableAgent, type AgentConfig } from "./agents.ts";
19
- import { getResultOutput, isFailedResult, reviewVerdict, type SingleResult } from "./spawn.ts";
20
- import { formatUsageCompact, sumUsage } from "./monitor.ts";
21
-
22
- /**
23
- * Worker fixes allowed after REVIEW_FAIL: one fix, one re-review. Anything
24
- * still unresolved is handed back to the main window instead of burning more
25
- * rounds, keeping chain latency and cost bounded.
26
- */
27
- export const MAX_FIX_ROUNDS = 1;
28
-
29
- /**
30
- * Whether a completed result should trigger the auto-fix loop instead of being
31
- * delivered to the main agent. Only a REVIEW_FAIL verdict from a healthy
32
- * reviewer run counts; failed processes and passing reviews are delivered
33
- * normally. Loop-internal re-review results never reach this path (they are
34
- * awaited inside the loop, not delivered through the completion flow).
35
- */
36
- export function shouldTriggerFixLoop(result: SingleResult): boolean {
37
- if (result.agent !== "reviewer") return false;
38
- if (isFailedResult(result)) return false;
39
- // A dispatch crash (spawn infra, delivery API, ...) is never a real review
40
- // verdict: its output is an error message plus whatever partial text the
41
- // child happened to emit, which could end in a stray `VERDICT: REVIEW_FAIL`.
42
- // Guard explicitly in addition to isFailedResult so the intent is clear and
43
- // a future change to isFailedResult can never let a crashed reviewer start a
44
- // phantom auto-fix chain.
45
- if (result.dispatchFailed) return false;
46
- return reviewVerdict(getResultOutput(result)) === "fail";
47
- }
48
-
49
- export interface WorkflowAgentAvailability {
50
- worker: boolean;
51
- cleaner: boolean;
52
- documenter: boolean;
53
- reviewer: boolean;
54
- writer: boolean;
55
- }
56
-
57
- export function workflowAgentAvailability(
58
- agents: readonly Pick<AgentConfig, "name" | "tools">[],
59
- ): WorkflowAgentAvailability {
60
- const names = new Set(agents.map((agent) => agent.name));
61
- return {
62
- worker: names.has("worker"),
63
- cleaner: names.has("cleaner"),
64
- documenter: names.has("documenter"),
65
- reviewer: names.has("reviewer"),
66
- writer: agents.some(isWriteCapableAgent),
67
- };
68
- }
69
-
70
- export type DocumentationDisposition = "clean" | "needed";
71
-
72
- /** Only the last standalone documentation disposition line counts. Inline
73
- * examples and prose are ignored so a prompt echo cannot suppress a needed
74
- * conservative sync. */
75
- export function documentationDisposition(output: string): DocumentationDisposition | undefined {
76
- const lines = output.split("\n");
77
- for (let index = lines.length - 1; index >= 0; index--) {
78
- const match = /^\s*DOCUMENTATION:\s*(CLEAN|NEEDED)\s*$/i.exec(lines[index]);
79
- if (match) return match[1].toUpperCase() === "CLEAN" ? "clean" : "needed";
80
- }
81
- return undefined;
82
- }
83
-
84
- export type ManagedWorkflowKind = "auto-fix" | "post-writer" | "review-pass-sync";
85
-
86
- export interface ManagedWorkflowPlan {
87
- kind: ManagedWorkflowKind;
88
- initialRelation: string;
89
- }
90
-
91
- /** Conservative pre-run check used to reserve one shared-repository lane
92
- * around a complete writer workflow or a reviewer that needs a stable diff.
93
- * The actual result is classified again by getManagedWorkflowPlan before a
94
- * downstream child starts. */
95
- export function canStartManagedWorkflow(
96
- agent: Pick<AgentConfig, "name" | "tools">,
97
- availability: WorkflowAgentAvailability,
98
- ): boolean {
99
- // Every shared write-capable role—including custom agents—owns the repository
100
- // lane even when no downstream role is enabled. Otherwise its edits can race
101
- // a managed writer's documentation snapshot.
102
- if (isWriteCapableAgent(agent)) return true;
103
- if (agent.name === "reviewer") {
104
- // Hold a stable diff snapshot against every discoverable writer even when
105
- // this review is advisory. Classification happens only after the read-only
106
- // child returns, too late to acquire the lane safely.
107
- return availability.writer;
108
- }
109
- return false;
110
- }
111
-
112
- /** Classify only healthy top-level results. In particular, a reviewer without a
113
- * machine verdict is advisory and cannot start any write-capable child. */
114
- export function getManagedWorkflowPlan(
115
- result: SingleResult,
116
- availability: WorkflowAgentAvailability,
117
- advisoryReview = false,
118
- ): ManagedWorkflowPlan | undefined {
119
- if (result.dispatchFailed || isFailedResult(result)) return undefined;
120
- if (result.agent === "worker" || result.agent === "cleaner") {
121
- if (!availability.documenter && !availability.reviewer) return undefined;
122
- return {
123
- kind: "post-writer",
124
- initialRelation: result.agent === "cleaner" ? "initial cleanup" : "initial implementation",
125
- };
126
- }
127
- // A top-level documenter is already an explicit docs/comments write task. It
128
- // owns the writer lane but delivers directly without an automatic code gate.
129
- if (result.agent === "documenter") return undefined;
130
- if (result.agent !== "reviewer") return undefined;
131
- // An advisory dispatch never chains: the caller asked for a report, so even
132
- // a stray gate verdict must be delivered rather than acted on.
133
- if (advisoryReview) return undefined;
134
-
135
- const output = getResultOutput(result);
136
- const verdict = reviewVerdict(output);
137
- // The pass stands as the code gate. Run the conditional documenter only for
138
- // explicit drift or when an older/custom reviewer omitted the marker.
139
- if (
140
- verdict === "pass" &&
141
- availability.documenter &&
142
- documentationDisposition(output) !== "clean"
143
- ) {
144
- return { kind: "review-pass-sync", initialRelation: "pre-documentation review" };
145
- }
146
- if (verdict === "fail" && availability.worker && shouldTriggerFixLoop(result)) {
147
- return { kind: "auto-fix", initialRelation: "initial review" };
148
- }
149
- return undefined;
150
- }
151
-
152
- /**
153
- * Build the worker task brief for one fix round from a reviewer's findings.
154
- * The worker gets the full review text findings plus their fix instructions
155
- * and closes every finding either by implementing the instruction or by
156
- * shipping a sounder fix with an explicit per-finding pushback. The standing
157
- * pushback and release-boundary contract lives in the worker system prompt;
158
- * the brief carries only what is specific to this round.
159
- */
160
- export function buildFixTaskBrief(reviewerResult: SingleResult, round: number, maxRounds: number): string {
161
- const review = getResultOutput(reviewerResult);
162
- return [
163
- `Auto-fix round ${round} of ${maxRounds} (triggered by a failed review).`,
164
- ``,
165
- `A reviewer ran in an isolated context and returned REQUEST_CHANGES. Its full report:`,
166
- `---`,
167
- review,
168
- `---`,
169
- ``,
170
- `Close EVERY finding — there is no severity triage; all of them get fixed. Each finding carries a fix instruction:`,
171
- `follow it, or ship a sounder fix and push back per finding with your reasoning.`,
172
- `Do NOT refactor unrelated code. Synchronize any existing README/docs/examples/comments directly affected by your fixes.`,
173
- `Run the project's format/build/tests when they exist, then report exactly what you changed (paths + short rationale)`,
174
- `plus any pushback, so a reviewer can verify.`,
175
- `A reviewer re-reviews your changes automatically after you finish; anything still unresolved after that re-review goes back to the main window.`,
176
- ].join("\n");
177
- }
178
-
179
- /** Build the single documentation handoff selected after the review gate
180
- * settles or as the reviewer-disabled fallback. The last writer's report
181
- * (top-level writer or final fix-round worker) and the terminal gate review are
182
- * leads; the pending diff stays authoritative. At most one of the two is
183
- * undefined in every managed flow. */
184
- export function buildFinalDocumenterBrief(
185
- lastWriterResult?: SingleResult,
186
- finalReviewResult?: SingleResult,
187
- ): string {
188
- const reportSections = [
189
- ...(lastWriterResult
190
- ? [
191
- `The last writer (${lastWriterResult.agent}) reported:`,
192
- `---`,
193
- getResultOutput(lastWriterResult),
194
- `---`,
195
- ``,
196
- ]
197
- : []),
198
- ...(finalReviewResult
199
- ? [
200
- `The final gate review reported:`,
201
- `---`,
202
- getResultOutput(finalReviewResult),
203
- `---`,
204
- ``,
205
- ]
206
- : []),
207
- ];
208
- return [
209
- `Final documentation sync: the review gate settled and you are the last managed stage before delivery.`,
210
- ``,
211
- ...reportSections,
212
- `Inspect the actual git diff and relevant implementation; the reports are only leads.`,
213
- `Apply every documentation note the reviews recorded, then synchronize stale README/docs, examples, API comments, docstrings,`,
214
- `and explanatory comments with the behavior that will be committed.`,
215
- `Change documentation surfaces only; make zero edits when the diff creates no documentation drift.`,
216
- `The workflow delivers directly after you; no fresh reviewer runs.`,
217
- `Report exact documentation/comment paths changed, or state explicitly that no sync was needed.`,
218
- ].join("\n");
219
- }
220
-
221
- /**
222
- * One step of an auto-fix chain as delivered: the run id (so the condensed
223
- * summary can point at per-run detail via subagent_status), the result, and
224
- * the human-readable role within the chain ("initial review", "fix round 1",
225
- * "re-review round 2"). runId is optional only for synthetic steps that never
226
- * spawned a child.
227
- */
228
- export interface ChainStep {
229
- runId?: number;
230
- result: SingleResult;
231
- relation: string;
232
- }
233
-
234
- export interface ManagedWorkflowOutcome {
235
- kind: ManagedWorkflowKind;
236
- steps: ChainStep[];
237
- }
238
-
239
- function workflowResultStatus(result: SingleResult): string {
240
- if (isFailedResult(result)) return "failed";
241
- if (result.agent === "reviewer") {
242
- const verdict = reviewVerdict(getResultOutput(result));
243
- return verdict ? verdict.toUpperCase() : "NO_VERDICT";
244
- }
245
- return "completed";
246
- }
247
-
248
- function workflowStepLine(step: ChainStep): string {
249
- const id = step.runId !== undefined ? `#${step.runId} ` : "";
250
- return `- ${id}${step.result.agent} · ${step.relation} · ${workflowResultStatus(step.result)}`;
251
- }
252
-
253
- function appendWorkflowFooter(lines: string[], steps: readonly ChainStep[]): void {
254
- const total = sumUsage(steps.map((step) => step.result.usage));
255
- const usage = formatUsageCompact(total);
256
- lines.push("", `Totals: ${steps.length} run${steps.length === 1 ? "" : "s"}${usage ? ` · ${usage}` : ""}`);
257
- const ids = steps.filter((step) => step.runId !== undefined).map((step) => `#${step.runId}`);
258
- lines.push(`Per-run details: subagent_status ${ids.join(" ")}`);
259
- }
260
-
261
- function formatWorkflowSummary(title: string, steps: readonly ChainStep[]): string {
262
- const lines = [title, "", ...steps.map(workflowStepLine)];
263
- appendWorkflowFooter(lines, steps);
264
- return lines.join("\n");
265
- }
266
-
267
- /** Condensed compatibility summary for a direct REVIEW_FAIL auto-fix chain. */
268
- export function formatChainSummary(
269
- steps: readonly ChainStep[],
270
- terminalResult: SingleResult = steps[steps.length - 1]!.result,
271
- ): string {
272
- const rounds = steps.filter((step) => step.relation.startsWith("fix round")).length;
273
- return formatWorkflowSummary(
274
- `## Auto-fix chain: ${Math.max(1, rounds)} round${rounds === 1 ? "" : "s"} — final ${workflowResultStatus(terminalResult)}`,
275
- steps,
276
- );
277
- }
278
-
279
- /** One clear final delivery for post-writer and direct reviewer → documenter workflows. */
280
- export function formatManagedWorkflowSummary(
281
- steps: readonly ChainStep[],
282
- terminalResult: SingleResult = steps[steps.length - 1]!.result,
283
- ): string {
284
- const route = steps.map((step) => step.result.agent).join(" → ");
285
- const fixRounds = steps.filter((step) => step.relation.startsWith("fix round")).length;
286
- const roundNote = fixRounds > 0 ? ` · ${fixRounds} fix round${fixRounds === 1 ? "" : "s"}` : "";
287
- return formatWorkflowSummary(
288
- `## Managed workflow: ${route}${roundNote} — final ${workflowResultStatus(terminalResult)}`,
289
- steps,
290
- );
291
- }
292
-
293
- export interface GateBriefOptions {
294
- /** A conditional final documenter is available after the gate settles.
295
- * Documentation drift is then routed to it as non-gating notes instead of
296
- * failing the code gate. */
297
- documenterPending: boolean;
298
- }
299
-
300
- /** Build the code gate that runs directly after a top-level writer, before any
301
- * documentation. Reports carry intent; the actual pending diff remains
302
- * authoritative. */
303
- export function buildFinalReviewBrief(
304
- initialResult: SingleResult,
305
- options: GateBriefOptions,
306
- ): string {
307
- return [
308
- `Fresh code gate for a managed ${initialResult.agent} workflow.`,
309
- ``,
310
- `The top-level ${initialResult.agent}'s full report:`,
311
- `---`,
312
- getResultOutput(initialResult),
313
- `---`,
314
- ``,
315
- `Run \`git status\` and \`git diff\` and inspect the actual pending code; the report is context, not proof.`,
316
- `Remain read-only. Attach a concrete fix instruction to EVERY gate finding: what to change, where, and how to verify the fix`,
317
- `— a worker will implement your instructions unless it can justify a sounder fix and push back.`,
318
- ...(options.documenterPending
319
- ? [
320
- `A conditional documentation sync is available AFTER this gate, so documentation drift is not a code-gate finding:`,
321
- `record it under "## Documentation notes" and emit the standalone line DOCUMENTATION: NEEDED,`,
322
- `or DOCUMENTATION: CLEAN when no documentation update is needed.`,
323
- ]
324
- : [
325
- `No documenter is pending, so documentation drift is an ordinary gate finding.`,
326
- ]),
327
- `This is an acceptance gate, not an advisory audit. End with exactly one standalone machine verdict line:`,
328
- `VERDICT: REVIEW_PASS when no finding remains, otherwise VERDICT: REVIEW_FAIL.`,
329
- ].join("\n");
330
- }
331
-
332
- /**
333
- * The re-review brief handed to the reviewer after a worker fix round. Includes
334
- * the prior review and worker report so the reviewer can adjudicate pushback
335
- * instead of restating findings. The convergence contract keeps rounds from
336
- * ping-ponging: judge the resulting code (not instruction obedience), rule on
337
- * the open findings once, add only defects this round's edits introduced,
338
- * never re-open a verified resolution.
339
- */
340
- export function buildReReviewBrief(
341
- reviewerResult: SingleResult,
342
- round: number,
343
- workerResult: SingleResult,
344
- options: GateBriefOptions = { documenterPending: false },
345
- ): string {
346
- const review = getResultOutput(reviewerResult);
347
- const workerReport = getResultOutput(workerResult);
348
- return [
349
- `Re-review after auto-fix round ${round}.`,
350
- ``,
351
- `The previous review (REQUEST_CHANGES) found these issues:`,
352
- `---`,
353
- review,
354
- `---`,
355
- ``,
356
- `The worker's report (what it changed, plus any pushback where it replaced your fix instruction):`,
357
- `---`,
358
- workerReport,
359
- `---`,
360
- ``,
361
- `Rule on EVERY previous finding: resolved, or still open. Judge the code as it now stands — a finding is`,
362
- `resolved when the pending diff fixes it soundly, whether or not the worker followed your fix instruction.`,
363
- `Adjudicate each pushback once: accept the worker's fix unless you can concretely refute its reasoning.`,
364
- `Run \`git diff\` to see what changed, then add NEW findings only for defects this round's edits introduced or exposed.`,
365
- `Re-review never opens findings unrelated to this round's edits; issues the earlier review missed belong to a fresh gate.`,
366
- `Do NOT re-open a finding you verified as resolved.`,
367
- ...(options.documenterPending
368
- ? [
369
- `Carry unresolved "## Documentation notes" forward and add any newly exposed drift there; documentation drift is not a code-gate finding.`,
370
- `Emit exactly one standalone documentation disposition line: DOCUMENTATION: NEEDED when that notes section is required, otherwise DOCUMENTATION: CLEAN.`,
371
- ]
372
- : [
373
- `No documenter is pending, so unresolved documentation drift remains an ordinary gate finding.`,
374
- ]),
375
- `REQUEST_CHANGES only while an open finding remains; otherwise APPROVE.`,
376
- `End with your machine-readable verdict line as usual (VERDICT: REVIEW_PASS / REVIEW_FAIL).`,
377
- ].join("\n");
378
- }
1
+ /**
2
+ * Managed workflow policy and handoff formatting.
3
+ *
4
+ * Successful top-level worker/cleaner runs continue through an independent
5
+ * code review gate. A passing gate may run one conditional documentation
6
+ * sync; a failing gate is delivered to the main agent, which owns the fix
7
+ * decision the runtime never edits code on a reviewer's behalf. Reviewers
8
+ * classify documentation drift explicitly, so the low-cost final documenter
9
+ * runs only when needed (or conservatively when an older/custom reviewer
10
+ * omits the marker). Direct passing gates use the same policy. Top-level
11
+ * documenters are explicit standalone writing tasks. Internal steps are
12
+ * launched by dispatch directly, so they never re-enter this policy or wake
13
+ * the main agent mid-chain.
14
+ */
15
+
16
+ import { isWriteCapableAgent, type AgentConfig } from "./agents.ts";
17
+ import { getResultOutput, isFailedResult, reviewVerdict, type SingleResult } from "./spawn.ts";
18
+ import { formatUsageCompact, sumUsage } from "./monitor.ts";
19
+
20
+ export interface WorkflowAgentAvailability {
21
+ cleaner: boolean;
22
+ documenter: boolean;
23
+ reviewer: boolean;
24
+ writer: boolean;
25
+ }
26
+
27
+ export function workflowAgentAvailability(
28
+ agents: readonly Pick<AgentConfig, "name" | "tools">[],
29
+ ): WorkflowAgentAvailability {
30
+ const names = new Set(agents.map((agent) => agent.name));
31
+ return {
32
+ cleaner: names.has("cleaner"),
33
+ documenter: names.has("documenter"),
34
+ reviewer: names.has("reviewer"),
35
+ writer: agents.some(isWriteCapableAgent),
36
+ };
37
+ }
38
+
39
+ export type DocumentationDisposition = "clean" | "needed";
40
+
41
+ /** Only the last standalone documentation disposition line counts. Inline
42
+ * examples and prose are ignored so a prompt echo cannot suppress a needed
43
+ * conservative sync. */
44
+ export function documentationDisposition(output: string): DocumentationDisposition | undefined {
45
+ const lines = output.split("\n");
46
+ for (let index = lines.length - 1; index >= 0; index--) {
47
+ const match = /^\s*DOCUMENTATION:\s*(CLEAN|NEEDED)\s*$/i.exec(lines[index]);
48
+ if (match) return match[1].toUpperCase() === "CLEAN" ? "clean" : "needed";
49
+ }
50
+ return undefined;
51
+ }
52
+
53
+ export type ManagedWorkflowKind = "post-writer" | "review-pass-sync";
54
+
55
+ export interface ManagedWorkflowPlan {
56
+ kind: ManagedWorkflowKind;
57
+ initialRelation: string;
58
+ }
59
+
60
+ /** Conservative pre-run check used to reserve one shared-repository lane
61
+ * around a complete writer workflow or a reviewer that needs a stable diff.
62
+ * The actual result is classified again by getManagedWorkflowPlan before a
63
+ * downstream child starts. */
64
+ export function canStartManagedWorkflow(
65
+ agent: Pick<AgentConfig, "name" | "tools">,
66
+ availability: WorkflowAgentAvailability,
67
+ ): boolean {
68
+ // Every shared write-capable role—including custom agents—owns the repository
69
+ // lane even when no downstream role is enabled. Otherwise its edits can race
70
+ // a managed writer's documentation snapshot.
71
+ if (isWriteCapableAgent(agent)) return true;
72
+ if (agent.name === "reviewer") {
73
+ // Hold a stable diff snapshot against every discoverable writer even when
74
+ // this review is advisory. Classification happens only after the read-only
75
+ // child returns, too late to acquire the lane safely.
76
+ return availability.writer;
77
+ }
78
+ return false;
79
+ }
80
+
81
+ /** Classify only healthy top-level results. In particular, a reviewer without a
82
+ * machine verdict is advisory and cannot start any write-capable child; a
83
+ * failing gate is not a workflow at all — its findings are delivered to the
84
+ * main agent, which decides the fixes. */
85
+ export function getManagedWorkflowPlan(
86
+ result: SingleResult,
87
+ availability: WorkflowAgentAvailability,
88
+ advisoryReview = false,
89
+ ): ManagedWorkflowPlan | undefined {
90
+ if (result.dispatchFailed || isFailedResult(result)) return undefined;
91
+ if (result.agent === "worker" || result.agent === "cleaner") {
92
+ if (!availability.documenter && !availability.reviewer) return undefined;
93
+ return {
94
+ kind: "post-writer",
95
+ initialRelation: result.agent === "cleaner" ? "initial cleanup" : "initial implementation",
96
+ };
97
+ }
98
+ // A top-level documenter is already an explicit docs/comments write task. It
99
+ // owns the writer lane but delivers directly without an automatic code gate.
100
+ if (result.agent === "documenter") return undefined;
101
+ if (result.agent !== "reviewer") return undefined;
102
+ // An advisory dispatch never chains: the caller asked for a report, so even
103
+ // a stray gate verdict must be delivered rather than acted on.
104
+ if (advisoryReview) return undefined;
105
+
106
+ const output = getResultOutput(result);
107
+ // The pass stands as the code gate. Run the conditional documenter only for
108
+ // explicit drift or when an older/custom reviewer omitted the marker.
109
+ if (
110
+ reviewVerdict(output) === "pass" &&
111
+ availability.documenter &&
112
+ documentationDisposition(output) !== "clean"
113
+ ) {
114
+ return { kind: "review-pass-sync", initialRelation: "pre-documentation review" };
115
+ }
116
+ return undefined;
117
+ }
118
+
119
+ /** Build the single documentation handoff selected after the review gate
120
+ * settles or as the reviewer-disabled fallback. The top-level writer's report
121
+ * and the terminal gate review are leads; the pending diff stays authoritative.
122
+ * At most one of the two is undefined in every managed flow. */
123
+ export function buildFinalDocumenterBrief(
124
+ lastWriterResult?: SingleResult,
125
+ finalReviewResult?: SingleResult,
126
+ ): string {
127
+ const reportSections = [
128
+ ...(lastWriterResult
129
+ ? [
130
+ `The last writer (${lastWriterResult.agent}) reported:`,
131
+ `---`,
132
+ getResultOutput(lastWriterResult),
133
+ `---`,
134
+ ``,
135
+ ]
136
+ : []),
137
+ ...(finalReviewResult
138
+ ? [
139
+ `The final gate review reported:`,
140
+ `---`,
141
+ getResultOutput(finalReviewResult),
142
+ `---`,
143
+ ``,
144
+ ]
145
+ : []),
146
+ ];
147
+ return [
148
+ `Final documentation sync: the review gate settled and you are the last managed stage before delivery.`,
149
+ ``,
150
+ ...reportSections,
151
+ `Inspect the actual git diff and relevant implementation; the reports are only leads.`,
152
+ `Apply every documentation note the reviews recorded, then synchronize stale README/docs, examples, API comments, docstrings,`,
153
+ `and explanatory comments with the behavior that will be committed.`,
154
+ `Change documentation surfaces only; make zero edits when the diff creates no documentation drift.`,
155
+ `The workflow delivers directly after you; no fresh reviewer runs.`,
156
+ `Report exact documentation/comment paths changed, or state explicitly that no sync was needed.`,
157
+ ].join("\n");
158
+ }
159
+
160
+ /**
161
+ * One step of a managed workflow as delivered: the run id (so the condensed
162
+ * summary can point at per-run detail via subagent_status), the result, and
163
+ * the human-readable role within the workflow ("initial implementation",
164
+ * "final review", "final documentation sync"). runId is optional only for
165
+ * synthetic steps that never spawned a child.
166
+ */
167
+ export interface ChainStep {
168
+ runId?: number;
169
+ result: SingleResult;
170
+ relation: string;
171
+ }
172
+
173
+ export interface ManagedWorkflowOutcome {
174
+ kind: ManagedWorkflowKind;
175
+ steps: ChainStep[];
176
+ }
177
+
178
+ function workflowResultStatus(result: SingleResult): string {
179
+ if (isFailedResult(result)) return "failed";
180
+ if (result.agent === "reviewer") {
181
+ const verdict = reviewVerdict(getResultOutput(result));
182
+ return verdict ? verdict.toUpperCase() : "NO_VERDICT";
183
+ }
184
+ return "completed";
185
+ }
186
+
187
+ function workflowStepLine(step: ChainStep): string {
188
+ const id = step.runId !== undefined ? `#${step.runId} ` : "";
189
+ return `- ${id}${step.result.agent} · ${step.relation} · ${workflowResultStatus(step.result)}`;
190
+ }
191
+
192
+ function appendWorkflowFooter(lines: string[], steps: readonly ChainStep[]): void {
193
+ const total = sumUsage(steps.map((step) => step.result.usage));
194
+ const usage = formatUsageCompact(total);
195
+ lines.push("", `Totals: ${steps.length} run${steps.length === 1 ? "" : "s"}${usage ? ` · ${usage}` : ""}`);
196
+ const ids = steps.filter((step) => step.runId !== undefined).map((step) => `#${step.runId}`);
197
+ lines.push(`Per-run details: subagent_status ${ids.join(" ")}`);
198
+ }
199
+
200
+ /** One clear final delivery for post-writer and direct reviewer → documenter workflows. */
201
+ export function formatManagedWorkflowSummary(
202
+ steps: readonly ChainStep[],
203
+ terminalResult: SingleResult = steps[steps.length - 1]!.result,
204
+ ): string {
205
+ const route = steps.map((step) => step.result.agent).join(" → ");
206
+ const lines = [`## Managed workflow: ${route} — final ${workflowResultStatus(terminalResult)}`, "", ...steps.map(workflowStepLine)];
207
+ appendWorkflowFooter(lines, steps);
208
+ return lines.join("\n");
209
+ }
210
+
211
+ export interface GateBriefOptions {
212
+ /** A conditional final documenter is available after the gate settles.
213
+ * Documentation drift is then routed to it as non-gating notes instead of
214
+ * failing the code gate. */
215
+ documenterPending: boolean;
216
+ }
217
+
218
+ /** Build the code gate that runs directly after a top-level writer, before any
219
+ * documentation. Reports carry intent; the actual pending diff remains
220
+ * authoritative. */
221
+ export function buildFinalReviewBrief(
222
+ initialResult: SingleResult,
223
+ options: GateBriefOptions,
224
+ ): string {
225
+ return [
226
+ `Fresh code gate for a managed ${initialResult.agent} workflow.`,
227
+ ``,
228
+ `The top-level ${initialResult.agent}'s full report:`,
229
+ `---`,
230
+ getResultOutput(initialResult),
231
+ `---`,
232
+ ``,
233
+ `Run \`git status\` and \`git diff\` and inspect the actual pending code; the report is context, not proof.`,
234
+ `Remain read-only. Attach a concrete fix instruction to EVERY gate finding: what to change, where, and how to verify the fix`,
235
+ `— the report returns to the main agent, which uses your instructions to drive the fix.`,
236
+ ...(options.documenterPending
237
+ ? [
238
+ `A conditional documentation sync is available AFTER this gate, so documentation drift is not a code-gate finding:`,
239
+ `record it under "## Documentation notes" and emit the standalone line DOCUMENTATION: NEEDED,`,
240
+ `or DOCUMENTATION: CLEAN when no documentation update is needed.`,
241
+ ]
242
+ : [
243
+ `No documenter is pending, so documentation drift is an ordinary gate finding.`,
244
+ ]),
245
+ `This is an acceptance gate, not an advisory audit. End with exactly one standalone machine verdict line:`,
246
+ `VERDICT: REVIEW_PASS when no finding remains, otherwise VERDICT: REVIEW_FAIL.`,
247
+ ].join("\n");
248
+ }