@akagilnc/pi-workflow-roles 0.1.1751

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.
Files changed (192) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +104 -0
  3. package/README.zh-CN.md +133 -0
  4. package/THIRD_PARTY_NOTICES.md +60 -0
  5. package/dist/activation-ledger-git.js +68 -0
  6. package/dist/activation-ledger-session.js +120 -0
  7. package/dist/activation-ledger-topology.js +239 -0
  8. package/dist/activation-reconciliation.js +61 -0
  9. package/dist/audit-escalation.js +108 -0
  10. package/dist/auditor-dossier-tool.js +35 -0
  11. package/dist/canonical-json.js +78 -0
  12. package/dist/compliance-transport.js +77 -0
  13. package/dist/doctor-contracts.js +172 -0
  14. package/dist/dossier-resolution.js +103 -0
  15. package/dist/evidence-child-executor.js +661 -0
  16. package/dist/exact-utf8.js +12 -0
  17. package/dist/git-object-id.js +7 -0
  18. package/dist/in-process-session.js +50 -0
  19. package/dist/merger-contracts.js +76 -0
  20. package/dist/navigator-attendance.js +995 -0
  21. package/dist/navigator-invocation-identity.js +220 -0
  22. package/dist/open-tool-schema.js +39 -0
  23. package/dist/package-contracts/collector-output.js +50 -0
  24. package/dist/package-contracts/fixer-output.js +72 -0
  25. package/dist/package-contracts/fixer-packet.js +77 -0
  26. package/dist/package-contracts/judge-output.js +17 -0
  27. package/dist/package-contracts/reviewer-output.js +82 -0
  28. package/dist/package-contracts/terminating-tools.js +173 -0
  29. package/dist/package-contracts/worker-output.js +13 -0
  30. package/dist/package-owned-tool-idle.js +104 -0
  31. package/dist/packaged-role-registry.js +34 -0
  32. package/dist/public-cli/main.js +23867 -0
  33. package/dist/public-command-renderer.js +20 -0
  34. package/dist/reviewer-agent.js +93 -0
  35. package/dist/reviewer-child-executor.js +23 -0
  36. package/dist/reviewer-construction.js +95 -0
  37. package/dist/reviewer-dispatch.js +77 -0
  38. package/dist/reviewer-execution-ledger.js +160 -0
  39. package/dist/reviewer-failure-diagnostic.js +17 -0
  40. package/dist/reviewer-git-snapshot.js +38 -0
  41. package/dist/reviewer-pinned-git.js +146 -0
  42. package/dist/reviewer-preflight-error.js +15 -0
  43. package/dist/reviewer-prompt-identity.js +10 -0
  44. package/dist/reviewer-scope-prompt.js +21 -0
  45. package/dist/reviewer-workspace.js +151 -0
  46. package/dist/sha256.js +5 -0
  47. package/dist/sitian-record-entry.js +33 -0
  48. package/dist/stderr-jsonl.js +26 -0
  49. package/dist/stream-idle-guard.js +75 -0
  50. package/dist/tool-execution-observation.js +141 -0
  51. package/dist/uuidv7.js +21 -0
  52. package/dist/work-subject-identity.js +53 -0
  53. package/extensions/role-runtime.ts +303 -0
  54. package/package.json +69 -0
  55. package/packets/fixer-prerequisites.json +6 -0
  56. package/packets/fixer-repair.md +5 -0
  57. package/packets/judge-apply.md +77 -0
  58. package/packets/judge-authority.md +64 -0
  59. package/packets/judge-plan.md +55 -0
  60. package/packets/judge-review.md +49 -0
  61. package/packets/judge-submission.md +34 -0
  62. package/resources/methods/code-review/SKILL.md +92 -0
  63. package/resources/methods/code-review/agents/openai.yaml +3 -0
  64. package/resources/methods/code-review/provenance.json +26 -0
  65. package/resources/methods/diagnosing-bugs/SKILL.md +134 -0
  66. package/resources/methods/diagnosing-bugs/agents/openai.yaml +3 -0
  67. package/resources/methods/diagnosing-bugs/provenance.json +31 -0
  68. package/resources/methods/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
  69. package/resources/methods/resolving-merge-conflicts/SKILL.md +14 -0
  70. package/resources/methods/resolving-merge-conflicts/agents/openai.yaml +3 -0
  71. package/resources/methods/resolving-merge-conflicts/provenance.json +26 -0
  72. package/resources/methods/tdd/SKILL.md +38 -0
  73. package/resources/methods/tdd/agents/openai.yaml +3 -0
  74. package/resources/methods/tdd/mocking.md +59 -0
  75. package/resources/methods/tdd/provenance.json +36 -0
  76. package/resources/methods/tdd/tests.md +77 -0
  77. package/resources/navigator-route-playbook.md +32 -0
  78. package/schemas/tool-execution-observation.schema.json +107 -0
  79. package/scripts/build-package.mjs +65 -0
  80. package/scripts/generate-tool-execution-observation-schema.ts +7 -0
  81. package/souls/coder.md +10 -0
  82. package/souls/collector.md +11 -0
  83. package/souls/doctor-auditor.md +23 -0
  84. package/souls/doctor.md +8 -0
  85. package/souls/fixer-auditor.md +33 -0
  86. package/souls/fixer.md +13 -0
  87. package/souls/judge-auditor.md +33 -0
  88. package/souls/judge.md +74 -0
  89. package/souls/merger.md +5 -0
  90. package/souls/navigator.md +5 -0
  91. package/souls/reviewer-auditor.md +25 -0
  92. package/souls/reviewer.md +11 -0
  93. package/src/activation-ledger-git.ts +96 -0
  94. package/src/activation-ledger-session.ts +188 -0
  95. package/src/activation-ledger-topology.ts +301 -0
  96. package/src/activation-ledger.ts +240 -0
  97. package/src/activation-reconciliation.ts +163 -0
  98. package/src/activation-trace.ts +38 -0
  99. package/src/audit-escalation.ts +177 -0
  100. package/src/auditor-dossier-tool.ts +48 -0
  101. package/src/auditor-soul.ts +28 -0
  102. package/src/canonical-json.ts +74 -0
  103. package/src/canonical-skill-binding.ts +107 -0
  104. package/src/collector-config.ts +89 -0
  105. package/src/collector-evidence.ts +461 -0
  106. package/src/collector-github.ts +656 -0
  107. package/src/collector-identity.ts +161 -0
  108. package/src/collector-ledger.ts +827 -0
  109. package/src/collector-receipt.ts +87 -0
  110. package/src/collector-role.ts +592 -0
  111. package/src/collector-tool-schemas.ts +19 -0
  112. package/src/compliance-transport.ts +130 -0
  113. package/src/doctor-auditor.ts +53 -0
  114. package/src/doctor-contracts.ts +166 -0
  115. package/src/doctor-evidence.ts +47 -0
  116. package/src/doctor-role.ts +18 -0
  117. package/src/dossier-resolution.ts +137 -0
  118. package/src/evidence-child-executor.ts +775 -0
  119. package/src/exact-utf8.ts +9 -0
  120. package/src/factory-board.ts +1822 -0
  121. package/src/git-object-id.ts +11 -0
  122. package/src/human-format.ts +65 -0
  123. package/src/in-process-session.ts +78 -0
  124. package/src/judge-auditor.ts +55 -0
  125. package/src/judge-recording-anti-forge.ts +53 -0
  126. package/src/judge-role.ts +160 -0
  127. package/src/merger-contracts.ts +71 -0
  128. package/src/merger-git-state.ts +76 -0
  129. package/src/merger-role.ts +60 -0
  130. package/src/navigator-attendance.ts +1254 -0
  131. package/src/navigator-invocation-identity.ts +446 -0
  132. package/src/open-tool-schema.ts +46 -0
  133. package/src/package-contracts/collector-output.ts +109 -0
  134. package/src/package-contracts/fixer-output.ts +81 -0
  135. package/src/package-contracts/fixer-packet.ts +93 -0
  136. package/src/package-contracts/judge-output.ts +39 -0
  137. package/src/package-contracts/reviewer-output.ts +115 -0
  138. package/src/package-contracts/terminating-tools.ts +259 -0
  139. package/src/package-contracts/worker-output.ts +36 -0
  140. package/src/package-owned-tool-idle.ts +134 -0
  141. package/src/package-resources/method-skill-binding.ts +87 -0
  142. package/src/package-resources/method-skill.ts +358 -0
  143. package/src/packaged-role-registry.ts +36 -0
  144. package/src/public-cli/cli-errors.ts +11 -0
  145. package/src/public-cli/cli-io.ts +4 -0
  146. package/src/public-cli/cli.ts +912 -0
  147. package/src/public-cli/coder-run.ts +575 -0
  148. package/src/public-cli/collector-run.ts +375 -0
  149. package/src/public-cli/command-renderer.ts +8 -0
  150. package/src/public-cli/config.ts +346 -0
  151. package/src/public-cli/doctor-run.ts +355 -0
  152. package/src/public-cli/explicit-internal.ts +274 -0
  153. package/src/public-cli/fixer-run.ts +587 -0
  154. package/src/public-cli/host-pi-runtime.ts +112 -0
  155. package/src/public-cli/invocation.ts +1958 -0
  156. package/src/public-cli/judge-run.ts +507 -0
  157. package/src/public-cli/main.ts +15 -0
  158. package/src/public-cli/merger-run.ts +681 -0
  159. package/src/public-cli/public-run-credentials.ts +71 -0
  160. package/src/public-cli/registry.ts +153 -0
  161. package/src/public-cli/reviewer-run.ts +561 -0
  162. package/src/public-cli/run-lifecycle.ts +884 -0
  163. package/src/public-cli/settlement.ts +3765 -0
  164. package/src/public-cli/terminal.ts +325 -0
  165. package/src/public-command-renderer.ts +43 -0
  166. package/src/reviewer-agent.ts +94 -0
  167. package/src/reviewer-auditor.ts +53 -0
  168. package/src/reviewer-child-executor.ts +31 -0
  169. package/src/reviewer-construction.ts +137 -0
  170. package/src/reviewer-dispatch.ts +94 -0
  171. package/src/reviewer-execution-ledger.ts +206 -0
  172. package/src/reviewer-failure-diagnostic.ts +18 -0
  173. package/src/reviewer-git-snapshot.ts +53 -0
  174. package/src/reviewer-pinned-git.ts +144 -0
  175. package/src/reviewer-preflight-error.ts +14 -0
  176. package/src/reviewer-prompt-identity.ts +17 -0
  177. package/src/reviewer-role.ts +193 -0
  178. package/src/reviewer-scope-prompt.ts +24 -0
  179. package/src/reviewer-settlement.ts +63 -0
  180. package/src/reviewer-workspace.ts +111 -0
  181. package/src/role-runtime.ts +884 -0
  182. package/src/sha256.ts +6 -0
  183. package/src/sitian-record-entry.ts +57 -0
  184. package/src/stderr-jsonl.ts +28 -0
  185. package/src/stream-idle-guard.ts +98 -0
  186. package/src/ticket-snapshot.ts +662 -0
  187. package/src/ticket-trajectory.ts +1000 -0
  188. package/src/tool-execution-observation.ts +168 -0
  189. package/src/uuidv7.ts +1 -0
  190. package/src/work-subject-identity.ts +94 -0
  191. package/src/worker-role.ts +434 -0
  192. package/src/worker-submission-gates.ts +225 -0
@@ -0,0 +1,912 @@
1
+ /**
2
+ * Public ak-role CLI dispatcher (roles / config / layered help / Judge run).
3
+ */
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
6
+
7
+ import {
8
+ effectiveSeatConfigurations,
9
+ formatModelSpec,
10
+ loadCredentialProviders,
11
+ loadPublicCliConfig,
12
+ parseModelSpec,
13
+ resolveEffectiveSeat,
14
+ savePublicCliConfig,
15
+ setPersistentSeatConfig,
16
+ type CredentialProviders,
17
+ type EffectiveSeat,
18
+ type InvocationModelOverride,
19
+ type PublicCliConfig,
20
+ } from "./config.ts";
21
+ import { CliUsageError } from "./cli-errors.ts";
22
+ import type { CliIo } from "./cli-io.ts";
23
+ import {
24
+ type ExplicitInternalPiRunner,
25
+ } from "./explicit-internal.ts";
26
+ import {
27
+ parseCoderArgv,
28
+ parseCollectorArgv,
29
+ parseDoctorArgv,
30
+ parseFixerArgv,
31
+ parseJudgeArgv,
32
+ parseMergerArgv,
33
+ parseReviewerArgv,
34
+ } from "./invocation.ts";
35
+ import { runPublicCoder, runPublicCoderResume } from "./coder-run.ts";
36
+ import { runPublicCollector } from "./collector-run.ts";
37
+ import { runPublicDoctor } from "./doctor-run.ts";
38
+ import { runPublicFixer, runPublicFixerResume } from "./fixer-run.ts";
39
+ import { runPublicJudge, runPublicResume } from "./judge-run.ts";
40
+ import { runPublicMerger, runPublicMergerResume } from "./merger-run.ts";
41
+ import { runPublicReviewer, runPublicReviewerResume } from "./reviewer-run.ts";
42
+ import { peekRoleRunRole } from "./run-lifecycle.ts";
43
+ import {
44
+ INTERNAL_ROLE_ENTRYPOINT_RELATIVE,
45
+ isPublicCliSupportCommand,
46
+ isPublicConfigurableSeat,
47
+ listHelpCapabilities,
48
+ type PublicThinkingLevel,
49
+ } from "./registry.ts";
50
+ import {
51
+ formatCliDiagnostic,
52
+ presentStructuralRejection,
53
+ } from "./settlement.ts";
54
+ import type { TerminalResult } from "./terminal.ts";
55
+
56
+ export {
57
+ buildExplicitInternalActivationArgs,
58
+ resolveInternalRoleEntrypoint,
59
+ } from "./explicit-internal.ts";
60
+ export { CliUsageError } from "./cli-errors.ts";
61
+ export type { CliIo } from "./cli-io.ts";
62
+
63
+ export type CliEnv = {
64
+ home?: string;
65
+ agentDir?: string;
66
+ /** Process cwd for any Pi subprocess owned by ak-role. */
67
+ cwd?: string;
68
+ packageRoot: string;
69
+ credentials?: CredentialProviders;
70
+ io?: CliIo;
71
+ /** Injectable Pi runner (tests); production resolves `pi` on PATH. */
72
+ piRunner?: ExplicitInternalPiRunner;
73
+ /** Optional caller correlation id (#78 host channel). */
74
+ correlationId?: string;
75
+ /** Extra Pi args for Judge runs (tests: faux provider). */
76
+ judgeExtraPiArgs?: readonly string[];
77
+ /** Override Judge role-run timeout (tests). */
78
+ judgeTimeoutMs?: number;
79
+ /** Extra Pi args for Coder runs (tests: faux provider). */
80
+ coderExtraPiArgs?: readonly string[];
81
+ /** Override Coder role-run timeout (tests). */
82
+ coderTimeoutMs?: number;
83
+ /** Extra Pi args for Collector runs (tests: faux provider). */
84
+ collectorExtraPiArgs?: readonly string[];
85
+ /** Override Collector role-run timeout (tests). */
86
+ collectorTimeoutMs?: number;
87
+ /** Extra Pi args for Doctor runs (tests: faux provider). */
88
+ doctorExtraPiArgs?: readonly string[];
89
+ /** Override Doctor role-run timeout (tests). */
90
+ doctorTimeoutMs?: number;
91
+ /** Extra Pi args for Fixer runs (tests: faux provider). */
92
+ fixerExtraPiArgs?: readonly string[];
93
+ /** Override Fixer role-run timeout (tests). */
94
+ fixerTimeoutMs?: number;
95
+ /** Extra Pi args for Reviewer runs (tests: faux provider). */
96
+ reviewerExtraPiArgs?: readonly string[];
97
+ /** Override Reviewer role-run timeout (tests). */
98
+ reviewerTimeoutMs?: number;
99
+ /** Extra Pi args for Merger runs (tests: faux provider). */
100
+ mergerExtraPiArgs?: readonly string[];
101
+ /** Override Merger role-run timeout (tests). */
102
+ mergerTimeoutMs?: number;
103
+ createRunId?: () => string;
104
+ };
105
+
106
+ export type CliResult = {
107
+ exitCode: number;
108
+ /** Settled Terminal when an admitted Role run produced one (programmatic/tests). */
109
+ terminal?: TerminalResult;
110
+ };
111
+
112
+ const THINKING_LEVELS = new Set([
113
+ "off",
114
+ "minimal",
115
+ "low",
116
+ "medium",
117
+ "high",
118
+ "xhigh",
119
+ "max",
120
+ ]);
121
+
122
+ function defaultIo(): CliIo {
123
+ return {
124
+ stdout: (text) => {
125
+ process.stdout.write(text);
126
+ },
127
+ stderr: (text) => {
128
+ process.stderr.write(text);
129
+ },
130
+ };
131
+ }
132
+
133
+ function resolveHome(env: CliEnv): string {
134
+ return env.home ?? process.env.HOME ?? homedir();
135
+ }
136
+
137
+ function resolveAgentDir(env: CliEnv, home: string): string {
138
+ return (
139
+ env.agentDir ??
140
+ process.env.PI_CODING_AGENT_DIR ??
141
+ join(home, ".pi", "agent")
142
+ );
143
+ }
144
+
145
+ type ParsedGlobal = {
146
+ command?: string;
147
+ args: string[];
148
+ model?: string;
149
+ thinking?: PublicThinkingLevel;
150
+ help: boolean;
151
+ };
152
+
153
+ function parseThinking(value: string): PublicThinkingLevel {
154
+ if (!THINKING_LEVELS.has(value)) {
155
+ throw new CliUsageError(`unknown thinking level: ${value}`);
156
+ }
157
+ return value as PublicThinkingLevel;
158
+ }
159
+
160
+ function parseArgv(argv: readonly string[]): ParsedGlobal {
161
+ const args = [...argv];
162
+ let model: string | undefined;
163
+ let thinking: PublicThinkingLevel | undefined;
164
+ let help = false;
165
+ const positional: string[] = [];
166
+
167
+ // Global flags may appear before or after the subcommand
168
+ // (`ak-role --model x roles` and `ak-role roles --model x`).
169
+ while (args.length > 0) {
170
+ const token = args.shift()!;
171
+ if (token === "--") {
172
+ positional.push(...args);
173
+ break;
174
+ }
175
+ if (token === "--help" || token === "-h") {
176
+ help = true;
177
+ continue;
178
+ }
179
+ if (token === "--model") {
180
+ const value = args.shift();
181
+ if (value === undefined) throw new CliUsageError("--model requires a value");
182
+ model = value;
183
+ continue;
184
+ }
185
+ if (token.startsWith("--model=")) {
186
+ model = token.slice("--model=".length);
187
+ continue;
188
+ }
189
+ if (token === "--thinking") {
190
+ const value = args.shift();
191
+ if (value === undefined) throw new CliUsageError("--thinking requires a value");
192
+ thinking = parseThinking(value);
193
+ continue;
194
+ }
195
+ if (token.startsWith("--thinking=")) {
196
+ thinking = parseThinking(token.slice("--thinking=".length));
197
+ continue;
198
+ }
199
+ if (token.startsWith("-") && token !== "-") {
200
+ // Subcommands may own additional flags later; only reject unknowns at the
201
+ // top level when they appear before any positional command is collected
202
+ // and the command is one that does not accept free flags (enforced below).
203
+ positional.push(token);
204
+ continue;
205
+ }
206
+ positional.push(token);
207
+ }
208
+
209
+ const [command, ...rest] = positional;
210
+ return {
211
+ ...(command === undefined ? {} : { command }),
212
+ args: rest,
213
+ ...(model === undefined ? {} : { model }),
214
+ ...(thinking === undefined ? {} : { thinking }),
215
+ help,
216
+ };
217
+ }
218
+
219
+ function invocationFromParsed(parsed: ParsedGlobal): InvocationModelOverride | undefined {
220
+ if (parsed.model === undefined && parsed.thinking === undefined) return undefined;
221
+ return {
222
+ ...(parsed.model === undefined ? {} : { model: parsed.model }),
223
+ ...(parsed.thinking === undefined ? {} : { thinking: parsed.thinking }),
224
+ };
225
+ }
226
+
227
+ /** Typed facts used by help presentation — not layout. */
228
+ export function helpDocument() {
229
+ return {
230
+ executable: "ak-role",
231
+ capabilities: listHelpCapabilities(),
232
+ internalEntrypoint: INTERNAL_ROLE_ENTRYPOINT_RELATIVE,
233
+ };
234
+ }
235
+
236
+ function renderHelp(): string {
237
+ const doc = helpDocument();
238
+ const lines: string[] = [
239
+ "ak-role — public role CLI",
240
+ "",
241
+ "Support commands:",
242
+ ];
243
+ for (const cap of doc.capabilities) {
244
+ if (cap.kind === "support") {
245
+ lines.push(` ${cap.name}`);
246
+ }
247
+ }
248
+ lines.push("", "Callable roles:");
249
+ for (const cap of doc.capabilities) {
250
+ if (cap.kind === "role") {
251
+ const phaseText =
252
+ cap.phases.length === 1 && cap.phases[0] === null
253
+ ? "no phase"
254
+ : `phases ${cap.phases.filter((p) => p !== null).join("|")}` +
255
+ (cap.defaultPhase ? ` (default ${cap.defaultPhase})` : "");
256
+ lines.push(` ${cap.name} — ${phaseText}`);
257
+ }
258
+ }
259
+ lines.push(
260
+ "",
261
+ "Global options: --model provider/model --thinking level",
262
+ "Persistent config: ak-role config set <seat> <provider/model:thinking>",
263
+ "Effective seats: ak-role roles",
264
+ );
265
+ return `${lines.join("\n")}\n`;
266
+ }
267
+
268
+ function renderRoles(seats: readonly EffectiveSeat[]): string {
269
+ const lines: string[] = ["seat\tkind\tsource\tmodel"];
270
+ for (const seat of seats) {
271
+ const kind = seat.automatic ? "automatic" : "callable";
272
+ const model =
273
+ seat.selection === undefined ? "-" : formatModelSpec(seat.selection);
274
+ lines.push(`${seat.seat}\t${kind}\t${seat.source}\t${model}`);
275
+ }
276
+ return `${lines.join("\n")}\n`;
277
+ }
278
+
279
+ function renderConfig(config: PublicCliConfig): string {
280
+ const lines: string[] = ["seat\tmodel"];
281
+ const keys = Object.keys(config.seats) as (keyof typeof config.seats)[];
282
+ if (keys.length === 0) {
283
+ lines.push("(empty)");
284
+ } else {
285
+ for (const seat of keys.sort()) {
286
+ const selection = config.seats[seat];
287
+ if (selection === undefined) continue;
288
+ lines.push(`${seat}\t${formatModelSpec(selection)}`);
289
+ }
290
+ }
291
+ return `${lines.join("\n")}\n`;
292
+ }
293
+
294
+ async function runConfigCommand(
295
+ args: readonly string[],
296
+ home: string,
297
+ io: CliIo,
298
+ ): Promise<number> {
299
+ if (args.length === 0 || args[0] === "get" || args[0] === "list" || args[0] === "show") {
300
+ const config = await loadPublicCliConfig(home);
301
+ if (args[0] === "get" && args[1] !== undefined) {
302
+ if (!isPublicConfigurableSeat(args[1])) {
303
+ throw new CliUsageError(`unknown configurable seat: ${args[1]}`);
304
+ }
305
+ const selection = config.seats[args[1]];
306
+ if (selection === undefined) {
307
+ io.stdout(`${args[1]}\t(unconfigured)\n`);
308
+ } else {
309
+ io.stdout(`${args[1]}\t${formatModelSpec(selection)}\n`);
310
+ }
311
+ return 0;
312
+ }
313
+ io.stdout(renderConfig(config));
314
+ return 0;
315
+ }
316
+
317
+ if (args[0] === "set") {
318
+ if (args.length < 3) {
319
+ throw new CliUsageError(
320
+ "usage: ak-role config set <seat> <provider/model:thinking>",
321
+ );
322
+ }
323
+ // Bulk: repeated seat spec pairs after `set`
324
+ const pairs = args.slice(1);
325
+ if (pairs.length % 2 !== 0) {
326
+ throw new CliUsageError(
327
+ "config set requires seat/spec pairs: ak-role config set <seat> <spec> [<seat> <spec> ...]",
328
+ );
329
+ }
330
+ let config = await loadPublicCliConfig(home);
331
+ for (let i = 0; i < pairs.length; i += 2) {
332
+ const seat = pairs[i]!;
333
+ const spec = pairs[i + 1]!;
334
+ if (!isPublicConfigurableSeat(seat)) {
335
+ throw new CliUsageError(`unknown configurable seat: ${seat}`);
336
+ }
337
+ config = setPersistentSeatConfig(config, seat, parseModelSpec(spec));
338
+ }
339
+ await savePublicCliConfig(config, home);
340
+ io.stdout(renderConfig(config));
341
+ return 0;
342
+ }
343
+
344
+ throw new CliUsageError(`unknown config subcommand: ${args[0]}`);
345
+ }
346
+
347
+ export async function runAkRole(
348
+ argv: readonly string[],
349
+ env: CliEnv,
350
+ ): Promise<CliResult> {
351
+ const io = env.io ?? defaultIo();
352
+ const home = resolveHome(env);
353
+
354
+ try {
355
+ const parsed = parseArgv(argv);
356
+
357
+ if (
358
+ parsed.help ||
359
+ parsed.command === undefined ||
360
+ parsed.command === "help"
361
+ ) {
362
+ // Layered help: `help <topic>` still derives from the same typed registry.
363
+ if (parsed.command === "help" && parsed.args[0] !== undefined) {
364
+ const topic = parsed.args[0];
365
+ const caps = listHelpCapabilities();
366
+ const match = caps.find((cap) => cap.name === topic);
367
+ if (match === undefined) {
368
+ throw new CliUsageError(`unknown help topic: ${topic}`);
369
+ }
370
+ if (match.kind === "support") {
371
+ io.stdout(`command\t${match.name}\tkind\tsupport\n`);
372
+ } else {
373
+ io.stdout(
374
+ `command\t${match.name}\tkind\trole\tphases\t${match.phases
375
+ .map((p) => (p === null ? "none" : p))
376
+ .join(",")}\tdefault\t${match.defaultPhase ?? "none"}\n`,
377
+ );
378
+ }
379
+ return { exitCode: 0 };
380
+ }
381
+ io.stdout(renderHelp());
382
+ return { exitCode: 0 };
383
+ }
384
+
385
+ if (parsed.command === "roles") {
386
+ if (parsed.args.length > 0) {
387
+ throw new CliUsageError("roles takes no arguments");
388
+ }
389
+ const config = await loadPublicCliConfig(home);
390
+ const credentials =
391
+ env.credentials ??
392
+ (await loadCredentialProviders(resolveAgentDir(env, home)));
393
+ const seats = effectiveSeatConfigurations(
394
+ config,
395
+ credentials,
396
+ invocationFromParsed(parsed),
397
+ );
398
+ io.stdout(renderRoles(seats));
399
+ return { exitCode: 0 };
400
+ }
401
+
402
+ if (parsed.command === "config") {
403
+ return { exitCode: await runConfigCommand(parsed.args, home, io) };
404
+ }
405
+
406
+ // Resume reopens an exact Role run after a typed HTTP 429 (#108/#109).
407
+ // Seat and dispatch follow the durable admitted role (role-correct continuation).
408
+ if (parsed.command === "resume") {
409
+ const agentDir = resolveAgentDir(env, home);
410
+ const cwd = env.cwd ?? process.cwd();
411
+ const config = await loadPublicCliConfig(home);
412
+ const credentials =
413
+ env.credentials ?? (await loadCredentialProviders(agentDir));
414
+ const resumeRunId = parsed.args[0];
415
+ const resumeRole =
416
+ resumeRunId === undefined || resumeRunId.trim() === ""
417
+ ? undefined
418
+ : await peekRoleRunRole(home, resumeRunId);
419
+ if (resumeRole === "collector") {
420
+ throw new CliUsageError(
421
+ "collector role runs are one-shot and cannot be resumed",
422
+ );
423
+ }
424
+ if (resumeRole === "doctor") {
425
+ throw new CliUsageError(
426
+ "doctor role runs are one-shot and cannot be resumed",
427
+ );
428
+ }
429
+ const resumeSeatRole =
430
+ resumeRole === "coder"
431
+ ? "coder"
432
+ : resumeRole === "fixer"
433
+ ? "fixer"
434
+ : resumeRole === "reviewer"
435
+ ? "reviewer"
436
+ : resumeRole === "merger"
437
+ ? "merger"
438
+ : "judge";
439
+ // Temporary model/thinking override for this resume only — never persists.
440
+ const seat = resolveEffectiveSeat(
441
+ config,
442
+ resumeSeatRole,
443
+ credentials,
444
+ invocationFromParsed(parsed),
445
+ );
446
+ if (resumeRole === "coder") {
447
+ const result = await runPublicCoderResume(
448
+ parsed.args,
449
+ {
450
+ home,
451
+ agentDir,
452
+ packageRoot: env.packageRoot,
453
+ cwd,
454
+ credentials,
455
+ ...(env.correlationId === undefined
456
+ ? {}
457
+ : { correlationId: env.correlationId }),
458
+ ...(env.piRunner === undefined ? {} : { piRunner: env.piRunner }),
459
+ ...(seat.selection === undefined ? {} : { model: seat.selection }),
460
+ ...(env.coderExtraPiArgs === undefined
461
+ ? {}
462
+ : { extraPiArgs: env.coderExtraPiArgs }),
463
+ ...(env.coderTimeoutMs === undefined
464
+ ? {}
465
+ : { timeoutMs: env.coderTimeoutMs }),
466
+ },
467
+ io,
468
+ );
469
+ return {
470
+ exitCode: result.exitCode,
471
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
472
+ };
473
+ }
474
+ if (resumeRole === "fixer") {
475
+ const result = await runPublicFixerResume(
476
+ parsed.args,
477
+ {
478
+ home,
479
+ agentDir,
480
+ packageRoot: env.packageRoot,
481
+ cwd,
482
+ credentials,
483
+ ...(env.correlationId === undefined
484
+ ? {}
485
+ : { correlationId: env.correlationId }),
486
+ ...(env.piRunner === undefined ? {} : { piRunner: env.piRunner }),
487
+ ...(seat.selection === undefined ? {} : { model: seat.selection }),
488
+ ...(env.fixerExtraPiArgs === undefined
489
+ ? {}
490
+ : { extraPiArgs: env.fixerExtraPiArgs }),
491
+ ...(env.fixerTimeoutMs === undefined
492
+ ? {}
493
+ : { timeoutMs: env.fixerTimeoutMs }),
494
+ },
495
+ io,
496
+ );
497
+ return {
498
+ exitCode: result.exitCode,
499
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
500
+ };
501
+ }
502
+ if (resumeRole === "reviewer") {
503
+ const result = await runPublicReviewerResume(
504
+ parsed.args,
505
+ {
506
+ home,
507
+ agentDir,
508
+ packageRoot: env.packageRoot,
509
+ cwd,
510
+ credentials,
511
+ ...(env.correlationId === undefined
512
+ ? {}
513
+ : { correlationId: env.correlationId }),
514
+ ...(env.piRunner === undefined ? {} : { piRunner: env.piRunner }),
515
+ ...(seat.selection === undefined ? {} : { model: seat.selection }),
516
+ ...(env.reviewerExtraPiArgs === undefined
517
+ ? {}
518
+ : { extraPiArgs: env.reviewerExtraPiArgs }),
519
+ ...(env.reviewerTimeoutMs === undefined
520
+ ? {}
521
+ : { timeoutMs: env.reviewerTimeoutMs }),
522
+ },
523
+ io,
524
+ );
525
+ return {
526
+ exitCode: result.exitCode,
527
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
528
+ };
529
+ }
530
+ if (resumeRole === "merger") {
531
+ const result = await runPublicMergerResume(
532
+ parsed.args,
533
+ {
534
+ home,
535
+ agentDir,
536
+ packageRoot: env.packageRoot,
537
+ cwd,
538
+ credentials,
539
+ ...(env.correlationId === undefined
540
+ ? {}
541
+ : { correlationId: env.correlationId }),
542
+ ...(env.piRunner === undefined ? {} : { piRunner: env.piRunner }),
543
+ ...(seat.selection === undefined ? {} : { model: seat.selection }),
544
+ ...(env.mergerExtraPiArgs === undefined
545
+ ? {}
546
+ : { extraPiArgs: env.mergerExtraPiArgs }),
547
+ ...(env.mergerTimeoutMs === undefined
548
+ ? {}
549
+ : { timeoutMs: env.mergerTimeoutMs }),
550
+ },
551
+ io,
552
+ );
553
+ return {
554
+ exitCode: result.exitCode,
555
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
556
+ };
557
+ }
558
+ const result = await runPublicResume(
559
+ parsed.args,
560
+ {
561
+ home,
562
+ agentDir,
563
+ packageRoot: env.packageRoot,
564
+ cwd,
565
+ credentials,
566
+ ...(env.correlationId === undefined
567
+ ? {}
568
+ : { correlationId: env.correlationId }),
569
+ ...(env.piRunner === undefined ? {} : { piRunner: env.piRunner }),
570
+ ...(seat.selection === undefined ? {} : { model: seat.selection }),
571
+ ...(env.judgeExtraPiArgs === undefined
572
+ ? {}
573
+ : { extraPiArgs: env.judgeExtraPiArgs }),
574
+ ...(env.judgeTimeoutMs === undefined
575
+ ? {}
576
+ : { timeoutMs: env.judgeTimeoutMs }),
577
+ },
578
+ io,
579
+ );
580
+ return {
581
+ exitCode: result.exitCode,
582
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
583
+ };
584
+ }
585
+
586
+ if (isPublicCliSupportCommand(parsed.command)) {
587
+ throw new CliUsageError(`unhandled support command: ${parsed.command}`);
588
+ }
589
+
590
+ // Judge is the first complete public Role run path (#106).
591
+ if (parsed.command === "judge") {
592
+ const agentDir = resolveAgentDir(env, home);
593
+ const cwd = env.cwd ?? process.cwd();
594
+ const config = await loadPublicCliConfig(home);
595
+ const credentials =
596
+ env.credentials ?? (await loadCredentialProviders(agentDir));
597
+ const seat = resolveEffectiveSeat(
598
+ config,
599
+ "judge",
600
+ credentials,
601
+ invocationFromParsed(parsed),
602
+ );
603
+ const result = await runPublicJudge(
604
+ parsed.args,
605
+ {
606
+ home,
607
+ agentDir,
608
+ packageRoot: env.packageRoot,
609
+ cwd,
610
+ credentials,
611
+ ...(env.correlationId === undefined
612
+ ? {}
613
+ : { correlationId: env.correlationId }),
614
+ ...(env.piRunner === undefined ? {} : { piRunner: env.piRunner }),
615
+ ...(seat.selection === undefined ? {} : { model: seat.selection }),
616
+ ...(env.judgeExtraPiArgs === undefined
617
+ ? {}
618
+ : { extraPiArgs: env.judgeExtraPiArgs }),
619
+ ...(env.judgeTimeoutMs === undefined
620
+ ? {}
621
+ : { timeoutMs: env.judgeTimeoutMs }),
622
+ ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
623
+ },
624
+ io,
625
+ parseJudgeArgv,
626
+ );
627
+ return {
628
+ exitCode: result.exitCode,
629
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
630
+ };
631
+ }
632
+
633
+ // Coder public run path with package-owned TDD method (#109).
634
+ if (parsed.command === "coder") {
635
+ const agentDir = resolveAgentDir(env, home);
636
+ const cwd = env.cwd ?? process.cwd();
637
+ const config = await loadPublicCliConfig(home);
638
+ const credentials =
639
+ env.credentials ?? (await loadCredentialProviders(agentDir));
640
+ const seat = resolveEffectiveSeat(
641
+ config,
642
+ "coder",
643
+ credentials,
644
+ invocationFromParsed(parsed),
645
+ );
646
+ const result = await runPublicCoder(
647
+ parsed.args,
648
+ {
649
+ home,
650
+ agentDir,
651
+ packageRoot: env.packageRoot,
652
+ cwd,
653
+ credentials,
654
+ ...(env.correlationId === undefined
655
+ ? {}
656
+ : { correlationId: env.correlationId }),
657
+ ...(env.piRunner === undefined ? {} : { piRunner: env.piRunner }),
658
+ ...(seat.selection === undefined ? {} : { model: seat.selection }),
659
+ ...(env.coderExtraPiArgs === undefined
660
+ ? {}
661
+ : { extraPiArgs: env.coderExtraPiArgs }),
662
+ ...(env.coderTimeoutMs === undefined
663
+ ? {}
664
+ : { timeoutMs: env.coderTimeoutMs }),
665
+ ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
666
+ },
667
+ io,
668
+ parseCoderArgv,
669
+ );
670
+ return {
671
+ exitCode: result.exitCode,
672
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
673
+ };
674
+ }
675
+
676
+ // Fixer public run path with optional package-owned diagnosing-bugs (#110).
677
+ if (parsed.command === "fixer") {
678
+ const agentDir = resolveAgentDir(env, home);
679
+ const cwd = env.cwd ?? process.cwd();
680
+ const config = await loadPublicCliConfig(home);
681
+ const credentials =
682
+ env.credentials ?? (await loadCredentialProviders(agentDir));
683
+ const seat = resolveEffectiveSeat(
684
+ config,
685
+ "fixer",
686
+ credentials,
687
+ invocationFromParsed(parsed),
688
+ );
689
+ const result = await runPublicFixer(
690
+ parsed.args,
691
+ {
692
+ home,
693
+ agentDir,
694
+ packageRoot: env.packageRoot,
695
+ cwd,
696
+ credentials,
697
+ ...(env.correlationId === undefined
698
+ ? {}
699
+ : { correlationId: env.correlationId }),
700
+ ...(env.piRunner === undefined ? {} : { piRunner: env.piRunner }),
701
+ ...(seat.selection === undefined ? {} : { model: seat.selection }),
702
+ ...(env.fixerExtraPiArgs === undefined
703
+ ? {}
704
+ : { extraPiArgs: env.fixerExtraPiArgs }),
705
+ ...(env.fixerTimeoutMs === undefined
706
+ ? {}
707
+ : { timeoutMs: env.fixerTimeoutMs }),
708
+ ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
709
+ },
710
+ io,
711
+ parseFixerArgv,
712
+ );
713
+ return {
714
+ exitCode: result.exitCode,
715
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
716
+ };
717
+ }
718
+
719
+ // Collector public run path from explicit PR + leg declarations (#112).
720
+ if (parsed.command === "collector") {
721
+ const agentDir = resolveAgentDir(env, home);
722
+ const cwd = env.cwd ?? process.cwd();
723
+ const config = await loadPublicCliConfig(home);
724
+ const credentials =
725
+ env.credentials ?? (await loadCredentialProviders(agentDir));
726
+ const seat = resolveEffectiveSeat(
727
+ config,
728
+ "collector",
729
+ credentials,
730
+ invocationFromParsed(parsed),
731
+ );
732
+ const result = await runPublicCollector(
733
+ parsed.args,
734
+ {
735
+ home,
736
+ agentDir,
737
+ packageRoot: env.packageRoot,
738
+ cwd,
739
+ credentials,
740
+ ...(env.correlationId === undefined
741
+ ? {}
742
+ : { correlationId: env.correlationId }),
743
+ ...(env.piRunner === undefined ? {} : { piRunner: env.piRunner }),
744
+ ...(seat.selection === undefined ? {} : { model: seat.selection }),
745
+ ...(env.collectorExtraPiArgs === undefined
746
+ ? {}
747
+ : { extraPiArgs: env.collectorExtraPiArgs }),
748
+ ...(env.collectorTimeoutMs === undefined
749
+ ? {}
750
+ : { timeoutMs: env.collectorTimeoutMs }),
751
+ ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
752
+ },
753
+ io,
754
+ parseCollectorArgv,
755
+ );
756
+ return {
757
+ exitCode: result.exitCode,
758
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
759
+ };
760
+ }
761
+
762
+ // Reviewer public run path with package-owned adapted code-review (#111).
763
+ if (parsed.command === "reviewer") {
764
+ const agentDir = resolveAgentDir(env, home);
765
+ const cwd = env.cwd ?? process.cwd();
766
+ const config = await loadPublicCliConfig(home);
767
+ const credentials =
768
+ env.credentials ?? (await loadCredentialProviders(agentDir));
769
+ const seat = resolveEffectiveSeat(
770
+ config,
771
+ "reviewer",
772
+ credentials,
773
+ invocationFromParsed(parsed),
774
+ );
775
+ const result = await runPublicReviewer(
776
+ parsed.args,
777
+ {
778
+ home,
779
+ agentDir,
780
+ packageRoot: env.packageRoot,
781
+ cwd,
782
+ credentials,
783
+ ...(env.correlationId === undefined
784
+ ? {}
785
+ : { correlationId: env.correlationId }),
786
+ ...(env.piRunner === undefined ? {} : { piRunner: env.piRunner }),
787
+ ...(seat.selection === undefined ? {} : { model: seat.selection }),
788
+ ...(env.reviewerExtraPiArgs === undefined
789
+ ? {}
790
+ : { extraPiArgs: env.reviewerExtraPiArgs }),
791
+ ...(env.reviewerTimeoutMs === undefined
792
+ ? {}
793
+ : { timeoutMs: env.reviewerTimeoutMs }),
794
+ ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
795
+ },
796
+ io,
797
+ parseReviewerArgv,
798
+ );
799
+ return {
800
+ exitCode: result.exitCode,
801
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
802
+ };
803
+ }
804
+
805
+ // Doctor public run path from Issue identity + optional confined runs root (#113).
806
+ if (parsed.command === "doctor") {
807
+ const agentDir = resolveAgentDir(env, home);
808
+ const cwd = env.cwd ?? process.cwd();
809
+ const config = await loadPublicCliConfig(home);
810
+ const credentials =
811
+ env.credentials ?? (await loadCredentialProviders(agentDir));
812
+ const seat = resolveEffectiveSeat(
813
+ config,
814
+ "doctor",
815
+ credentials,
816
+ invocationFromParsed(parsed),
817
+ );
818
+ const result = await runPublicDoctor(
819
+ parsed.args,
820
+ {
821
+ home,
822
+ agentDir,
823
+ packageRoot: env.packageRoot,
824
+ cwd,
825
+ credentials,
826
+ ...(env.correlationId === undefined
827
+ ? {}
828
+ : { correlationId: env.correlationId }),
829
+ ...(env.piRunner === undefined ? {} : { piRunner: env.piRunner }),
830
+ ...(seat.selection === undefined ? {} : { model: seat.selection }),
831
+ ...(env.doctorExtraPiArgs === undefined
832
+ ? {}
833
+ : { extraPiArgs: env.doctorExtraPiArgs }),
834
+ ...(env.doctorTimeoutMs === undefined
835
+ ? {}
836
+ : { timeoutMs: env.doctorTimeoutMs }),
837
+ ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
838
+ },
839
+ io,
840
+ parseDoctorArgv,
841
+ );
842
+ return {
843
+ exitCode: result.exitCode,
844
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
845
+ };
846
+ }
847
+
848
+ // Merger public run path: derive active-merge envelope + forced merge-only method (#114).
849
+ if (parsed.command === "merger") {
850
+ const agentDir = resolveAgentDir(env, home);
851
+ const cwd = env.cwd ?? process.cwd();
852
+ const config = await loadPublicCliConfig(home);
853
+ const credentials =
854
+ env.credentials ?? (await loadCredentialProviders(agentDir));
855
+ const seat = resolveEffectiveSeat(
856
+ config,
857
+ "merger",
858
+ credentials,
859
+ invocationFromParsed(parsed),
860
+ );
861
+ const result = await runPublicMerger(
862
+ parsed.args,
863
+ {
864
+ home,
865
+ agentDir,
866
+ packageRoot: env.packageRoot,
867
+ cwd,
868
+ credentials,
869
+ ...(env.correlationId === undefined
870
+ ? {}
871
+ : { correlationId: env.correlationId }),
872
+ ...(env.piRunner === undefined ? {} : { piRunner: env.piRunner }),
873
+ ...(seat.selection === undefined ? {} : { model: seat.selection }),
874
+ ...(env.mergerExtraPiArgs === undefined
875
+ ? {}
876
+ : { extraPiArgs: env.mergerExtraPiArgs }),
877
+ ...(env.mergerTimeoutMs === undefined
878
+ ? {}
879
+ : { timeoutMs: env.mergerTimeoutMs }),
880
+ ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
881
+ },
882
+ io,
883
+ parseMergerArgv,
884
+ );
885
+ return {
886
+ exitCode: result.exitCode,
887
+ ...(result.terminal === undefined ? {} : { terminal: result.terminal }),
888
+ };
889
+ }
890
+
891
+ // #115: every PUBLIC_CALLABLE_ROLE has a completed handler above. Unknown
892
+ // tokens (including misspelled role names) are structural rejects.
893
+ throw new CliUsageError(`unknown command: ${parsed.command}`);
894
+ } catch (error) {
895
+ if (error instanceof CliUsageError) {
896
+ // Non-judge structural paths share the same rejection presenter as Judge.
897
+ presentStructuralRejection(error, io);
898
+ return { exitCode: 2 };
899
+ }
900
+ // Unrecognized outer failure: retain actual name/message identity (no wash).
901
+ if (error instanceof Error) {
902
+ const label =
903
+ error.name !== "" && error.name !== "Error"
904
+ ? `${error.name}: ${error.message}`
905
+ : error.message;
906
+ io.stderr(formatCliDiagnostic(label || error.name || "unrecognized exception"));
907
+ return { exitCode: 1 };
908
+ }
909
+ io.stderr(formatCliDiagnostic(String(error)));
910
+ return { exitCode: 1 };
911
+ }
912
+ }