@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,587 @@
1
+ /**
2
+ * Public Fixer Role run: admit → explicit Internal activate → settle Terminal result.
3
+ * #110/#177: package-owned diagnosing-bugs and tdd methods (available, not forced),
4
+ * common Invocation + structural prerequisites, default apply / explicit plan,
5
+ * shared #106 success interface. Controlled-failure settlement reuses #107.
6
+ */
7
+ import { writeFile } from "node:fs/promises";
8
+ import { join } from "node:path";
9
+
10
+ import {
11
+ loadPackagedMethodSkillMaterial,
12
+ resolvePackagedMethodSkillPath,
13
+ type PackagedMethodSkillMaterial,
14
+ type PackagedMethodSkillProvenance,
15
+ } from "../package-resources/method-skill.ts";
16
+ import {
17
+ runExplicitInternalActivation,
18
+ type ExplicitInternalKnownFailure,
19
+ type ExplicitInternalPiRunner,
20
+ type ExplicitInternalPiResult,
21
+ } from "./explicit-internal.ts";
22
+ import { CliUsageError } from "./cli-errors.ts";
23
+ import {
24
+ admitFixerInvocation,
25
+ buildFixerTransportPrompt,
26
+ type AdmittedFixerInvocation,
27
+ } from "./invocation.ts";
28
+ import {
29
+ type CredentialProviders,
30
+ type SeatModelConfig,
31
+ } from "./config.ts";
32
+ import {
33
+ missingCredentialPreDispatchFailure,
34
+ postRunMissingCredentialFailure,
35
+ } from "./public-run-credentials.ts";
36
+ import {
37
+ acquireRunWriterLease,
38
+ clearTypedProviderHttpObservation,
39
+ isSessionPrincipalAvailable,
40
+ isV1ResumableFailure,
41
+ loadResumableFixerRun,
42
+ markRunAdmitted,
43
+ markRunResumable,
44
+ markRunRunning,
45
+ markRunTerminal,
46
+ renderResumeCommand,
47
+ RESUME_TRANSPORT_ENVELOPE,
48
+ RunWriterLeaseHeldError,
49
+ type RunWriterLease,
50
+ readTypedHttp429Observation,
51
+ } from "./run-lifecycle.ts";
52
+ import {
53
+ classifyPostAdmissionFailure,
54
+ exitCodeForTerminalOutcome,
55
+ formatCliDiagnostic,
56
+ formatTerminalResult,
57
+ hasLawfulFixerTerminalResult,
58
+ inspectJudgeSession,
59
+ isLawfulTypedTerminalOutcome,
60
+ presentFailureTerminal,
61
+ presentStructuralRejection,
62
+ resolveAuditedRunnerKnownFailure,
63
+ explicitInternalKnownFailureClassificationInput,
64
+ settleFailureTerminalResult,
65
+ trySettleFixerTerminalResult,
66
+ trySettleComplianceAuditIncompleteTerminalResult,
67
+ } from "./settlement.ts";
68
+ import type { CliIo } from "./cli-io.ts";
69
+ import type {
70
+ ControlledFailureCause,
71
+ TerminalResult,
72
+ } from "./terminal.ts";
73
+
74
+ export type FixerRunEnv = {
75
+ home: string;
76
+ agentDir: string;
77
+ packageRoot: string;
78
+ cwd: string;
79
+ correlationId?: string;
80
+ piRunner?: ExplicitInternalPiRunner;
81
+ model?: SeatModelConfig;
82
+ credentials?: CredentialProviders;
83
+ createRunId?: () => string;
84
+ extraPiArgs?: readonly string[];
85
+ timeoutMs?: number;
86
+ };
87
+
88
+ function buildModelArgs(model: SeatModelConfig | undefined): string[] {
89
+ if (model === undefined) return [];
90
+ return [
91
+ "--provider",
92
+ model.provider,
93
+ "--model",
94
+ model.model,
95
+ "--thinking",
96
+ model.thinking,
97
+ ];
98
+ }
99
+
100
+ /**
101
+ * Build Internal activation extra-args for an admitted Fixer run.
102
+ * Package diagnosing-bugs and tdd Skills are available via --skill on every phase
103
+ * (not forced into the first prompt). Ambient home skills stay disabled.
104
+ */
105
+ export function buildFixerActivationExtraArgs(
106
+ admitted: AdmittedFixerInvocation,
107
+ options: {
108
+ packageRoot: string;
109
+ model?: SeatModelConfig;
110
+ extraPiArgs?: readonly string[];
111
+ },
112
+ ): string[] {
113
+ const prompt = buildFixerTransportPrompt(admitted);
114
+ const diagnosisSkillPath = resolvePackagedMethodSkillPath(
115
+ options.packageRoot,
116
+ "diagnosing-bugs",
117
+ );
118
+ const tddSkillPath = resolvePackagedMethodSkillPath(options.packageRoot, "tdd");
119
+ const prerequisiteArgs =
120
+ admitted.prerequisitesPath === undefined
121
+ ? []
122
+ : ["--ak-fixer-prerequisites", admitted.prerequisitesPath];
123
+ return [
124
+ "--no-skills",
125
+ "--skill",
126
+ diagnosisSkillPath,
127
+ "--skill",
128
+ tddSkillPath,
129
+ "--no-prompt-templates",
130
+ "--no-themes",
131
+ "--no-context-files",
132
+ "--session",
133
+ admitted.sessionFile,
134
+ "--session-dir",
135
+ admitted.sessionDirectory,
136
+ ...(options.extraPiArgs ?? []),
137
+ "--ak-role",
138
+ "fixer",
139
+ "--ak-fixer-phase",
140
+ admitted.phase,
141
+ "--ak-fix-packet",
142
+ admitted.packetPath,
143
+ ...prerequisiteArgs,
144
+ "--mode",
145
+ "json",
146
+ ...buildModelArgs(options.model),
147
+ prompt,
148
+ ];
149
+ }
150
+
151
+ /**
152
+ * Reopen the exact Fixer Pi session for resume. Preserves admitted phase,
153
+ * prerequisites, and package diagnosis/tdd availability; does not resubmit instruction.
154
+ */
155
+ export function buildFixerResumeActivationExtraArgs(
156
+ admitted: AdmittedFixerInvocation,
157
+ options: {
158
+ packageRoot: string;
159
+ model?: SeatModelConfig;
160
+ extraPiArgs?: readonly string[];
161
+ },
162
+ ): string[] {
163
+ const diagnosisSkillPath = resolvePackagedMethodSkillPath(
164
+ options.packageRoot,
165
+ "diagnosing-bugs",
166
+ );
167
+ const tddSkillPath = resolvePackagedMethodSkillPath(options.packageRoot, "tdd");
168
+ const prerequisiteArgs =
169
+ admitted.prerequisitesPath === undefined
170
+ ? []
171
+ : ["--ak-fixer-prerequisites", admitted.prerequisitesPath];
172
+ return [
173
+ "--no-skills",
174
+ "--skill",
175
+ diagnosisSkillPath,
176
+ "--skill",
177
+ tddSkillPath,
178
+ "--no-prompt-templates",
179
+ "--no-themes",
180
+ "--no-context-files",
181
+ "--session",
182
+ admitted.sessionFile,
183
+ "--session-dir",
184
+ admitted.sessionDirectory,
185
+ ...(options.extraPiArgs ?? []),
186
+ "--ak-role",
187
+ "fixer",
188
+ "--ak-fixer-phase",
189
+ admitted.phase,
190
+ "--ak-fix-packet",
191
+ admitted.packetPath,
192
+ ...prerequisiteArgs,
193
+ "--mode",
194
+ "json",
195
+ ...buildModelArgs(options.model),
196
+ RESUME_TRANSPORT_ENVELOPE,
197
+ ];
198
+ }
199
+
200
+ async function presentControlledFailure(
201
+ admitted: AdmittedFixerInvocation,
202
+ failureInput: {
203
+ timedOut: boolean;
204
+ code: number | null;
205
+ stderr: string;
206
+ thrown?: unknown;
207
+ knownFailure?: ExplicitInternalKnownFailure;
208
+ },
209
+ io: CliIo,
210
+ ): Promise<{
211
+ exitCode: number;
212
+ admitted: AdmittedFixerInvocation;
213
+ terminal: TerminalResult;
214
+ }> {
215
+ const hasThrown = Object.hasOwn(failureInput, "thrown");
216
+ const session =
217
+ !hasThrown &&
218
+ !failureInput.timedOut &&
219
+ failureInput.knownFailure === undefined
220
+ ? await inspectJudgeSession(admitted.sessionFile)
221
+ : undefined;
222
+ const failure = classifyPostAdmissionFailure({
223
+ timedOut: failureInput.timedOut,
224
+ code: failureInput.code,
225
+ stderr: failureInput.stderr,
226
+ ...(hasThrown ? { thrown: failureInput.thrown } : {}),
227
+ ...explicitInternalKnownFailureClassificationInput(failureInput.knownFailure),
228
+ ...(session === undefined ? {} : { session }),
229
+ });
230
+
231
+ const hasLawfulTerminalResult = await hasLawfulFixerTerminalResult(admitted);
232
+ const typedHttp429 = await readTypedHttp429Observation(admitted.runDirectory);
233
+ const sessionPrincipalAvailable = await isSessionPrincipalAvailable(
234
+ admitted.sessionFile,
235
+ );
236
+ const resumable =
237
+ sessionPrincipalAvailable &&
238
+ isV1ResumableFailure({
239
+ hasLawfulTerminalResult,
240
+ ...(typedHttp429 === undefined ? {} : { typedHttp429 }),
241
+ });
242
+ if (resumable && typedHttp429 !== undefined) {
243
+ await markRunResumable(admitted.runDirectory, typedHttp429);
244
+ } else {
245
+ await markRunTerminal(admitted.runDirectory).catch(() => undefined);
246
+ }
247
+
248
+ // #107 owns generic controlled-failure settlement — consume, do not re-own.
249
+ const terminal = await settleFailureTerminalResult(
250
+ admitted,
251
+ failure,
252
+ resumable
253
+ ? { resume: { command: renderResumeCommand(admitted.runId) } }
254
+ : {},
255
+ );
256
+ presentFailureTerminal(terminal, io);
257
+ return {
258
+ exitCode: exitCodeForTerminalOutcome(terminal.roleOutcome),
259
+ admitted,
260
+ terminal,
261
+ };
262
+ }
263
+
264
+ async function dispatchAdmittedFixer(input: {
265
+ admitted: AdmittedFixerInvocation;
266
+ env: FixerRunEnv;
267
+ io: CliIo;
268
+ extraArgs: string[];
269
+ lease: RunWriterLease;
270
+ methodMaterial: PackagedMethodSkillMaterial;
271
+ }): Promise<{
272
+ exitCode: number;
273
+ admitted: AdmittedFixerInvocation;
274
+ terminal?: TerminalResult;
275
+ }> {
276
+ const { admitted, env, io, extraArgs, lease, methodMaterial } = input;
277
+ try {
278
+ const missingCredential = missingCredentialPreDispatchFailure(
279
+ env.model,
280
+ env.credentials,
281
+ );
282
+ if (missingCredential !== undefined) {
283
+ return await presentControlledFailure(
284
+ admitted,
285
+ missingCredential,
286
+ io,
287
+ );
288
+ }
289
+ await markRunRunning(admitted.runDirectory);
290
+ await clearTypedProviderHttpObservation(admitted.runDirectory);
291
+
292
+ const childEnv: NodeJS.ProcessEnv = {
293
+ ...process.env,
294
+ HOME: env.home,
295
+ PI_CODING_AGENT_DIR: env.agentDir,
296
+ AK_ROLE_RUN_DIR: admitted.runDirectory,
297
+ };
298
+ if (env.correlationId !== undefined && env.correlationId.trim() !== "") {
299
+ childEnv.AK_CORRELATION_ID = env.correlationId;
300
+ }
301
+
302
+ let result: ExplicitInternalPiResult;
303
+ try {
304
+ result = await runExplicitInternalActivation({
305
+ packageRoot: env.packageRoot,
306
+ extraArgs,
307
+ cwd: admitted.projectRoot,
308
+ home: env.home,
309
+ agentDir: env.agentDir,
310
+ env: childEnv,
311
+ timeoutMs: env.timeoutMs,
312
+ ...(env.piRunner === undefined ? {} : { runner: env.piRunner }),
313
+ });
314
+ } catch (error) {
315
+ return await presentControlledFailure(
316
+ admitted,
317
+ {
318
+ timedOut: false,
319
+ code: null,
320
+ stderr: "",
321
+ thrown: error,
322
+ },
323
+ io,
324
+ );
325
+ }
326
+
327
+ try {
328
+ await writeFile(
329
+ join(admitted.runDirectory, "stderr.log"),
330
+ result.stderr,
331
+ "utf8",
332
+ );
333
+ } catch {
334
+ // continue to lawful / controlled-failure settlement
335
+ }
336
+
337
+ let lawful: TerminalResult | undefined;
338
+ try {
339
+ lawful = await trySettleFixerTerminalResult(admitted, {
340
+ methodProvenance: methodMaterial.provenance,
341
+ methodSkillPath: methodMaterial.skillPath,
342
+ methodSkillConfiguredPath: resolvePackagedMethodSkillPath(
343
+ env.packageRoot,
344
+ "diagnosing-bugs",
345
+ ),
346
+ });
347
+ } catch (error) {
348
+ return await presentControlledFailure(
349
+ admitted,
350
+ {
351
+ timedOut: false,
352
+ code: result.code,
353
+ stderr: result.stderr,
354
+ thrown: error,
355
+ },
356
+ io,
357
+ );
358
+ }
359
+ if (lawful !== undefined && isLawfulTypedTerminalOutcome(lawful.roleOutcome)) {
360
+ await markRunTerminal(admitted.runDirectory).catch(() => undefined);
361
+ io.stdout(formatTerminalResult(lawful));
362
+ return {
363
+ exitCode: exitCodeForTerminalOutcome(lawful.roleOutcome),
364
+ admitted,
365
+ terminal: lawful,
366
+ };
367
+ }
368
+
369
+ const auditIncomplete = await trySettleComplianceAuditIncompleteTerminalResult(admitted);
370
+ if (auditIncomplete !== undefined) {
371
+ await markRunTerminal(admitted.runDirectory).catch(() => undefined);
372
+ if (auditIncomplete.roleOutcome.kind === "failure") {
373
+ presentFailureTerminal(auditIncomplete, io);
374
+ } else {
375
+ io.stdout(formatTerminalResult(auditIncomplete));
376
+ }
377
+ return {
378
+ exitCode: exitCodeForTerminalOutcome(auditIncomplete.roleOutcome),
379
+ admitted,
380
+ terminal: auditIncomplete,
381
+ };
382
+ }
383
+
384
+ const credentialFailure = postRunMissingCredentialFailure(
385
+ result,
386
+ env.model,
387
+ env.credentials,
388
+ );
389
+ const knownFailure = await resolveAuditedRunnerKnownFailure({
390
+ runner: result.knownFailure,
391
+ sessionFile: admitted.sessionFile,
392
+ credential: credentialFailure,
393
+ });
394
+ return await presentControlledFailure(
395
+ admitted,
396
+ {
397
+ timedOut: result.timedOut,
398
+ code: result.code,
399
+ stderr: result.stderr,
400
+ ...(knownFailure === undefined ? {} : { knownFailure }),
401
+ },
402
+ io,
403
+ );
404
+ } finally {
405
+ await lease.release();
406
+ }
407
+ }
408
+
409
+ async function loadFixerMethodMaterial(
410
+ packageRoot: string,
411
+ ): Promise<PackagedMethodSkillMaterial> {
412
+ return await loadPackagedMethodSkillMaterial(packageRoot, "diagnosing-bugs");
413
+ }
414
+
415
+ export async function runPublicFixer(
416
+ argv: readonly string[],
417
+ env: FixerRunEnv,
418
+ io: CliIo,
419
+ parseFixerArgv: (args: readonly string[]) => {
420
+ phase: "plan" | "apply";
421
+ instruction: string;
422
+ attachmentPaths: string[];
423
+ prerequisitesPath?: string;
424
+ project?: string;
425
+ },
426
+ ): Promise<{
427
+ exitCode: number;
428
+ admitted?: AdmittedFixerInvocation;
429
+ terminal?: TerminalResult;
430
+ }> {
431
+ let admitted: AdmittedFixerInvocation;
432
+ try {
433
+ const parsed = parseFixerArgv(argv);
434
+ admitted = await admitFixerInvocation({
435
+ home: env.home,
436
+ cwd: env.cwd,
437
+ phase: parsed.phase,
438
+ instruction: parsed.instruction,
439
+ attachmentPaths: parsed.attachmentPaths,
440
+ ...(parsed.prerequisitesPath === undefined
441
+ ? {}
442
+ : { prerequisitesPath: parsed.prerequisitesPath }),
443
+ ...(parsed.project === undefined ? {} : { project: parsed.project }),
444
+ ...(env.createRunId === undefined ? {} : { createRunId: env.createRunId }),
445
+ });
446
+ } catch (error) {
447
+ if (error instanceof CliUsageError) {
448
+ presentStructuralRejection(error, io);
449
+ return { exitCode: 2 };
450
+ }
451
+ throw error;
452
+ }
453
+
454
+ await markRunAdmitted(admitted);
455
+
456
+ let lease: RunWriterLease;
457
+ try {
458
+ lease = await acquireRunWriterLease(admitted.runDirectory);
459
+ } catch (error) {
460
+ if (error instanceof RunWriterLeaseHeldError) {
461
+ presentStructuralRejection(error, io);
462
+ return { exitCode: 2 };
463
+ }
464
+ throw error;
465
+ }
466
+
467
+ let methodMaterial: PackagedMethodSkillMaterial;
468
+ try {
469
+ methodMaterial = await loadFixerMethodMaterial(env.packageRoot);
470
+ } catch (error) {
471
+ await lease.release();
472
+ return await presentControlledFailure(
473
+ admitted,
474
+ {
475
+ timedOut: false,
476
+ code: null,
477
+ stderr: "",
478
+ thrown: error,
479
+ },
480
+ io,
481
+ );
482
+ }
483
+
484
+ const extraArgs = buildFixerActivationExtraArgs(admitted, {
485
+ packageRoot: env.packageRoot,
486
+ ...(env.model === undefined ? {} : { model: env.model }),
487
+ ...(env.extraPiArgs === undefined ? {} : { extraPiArgs: env.extraPiArgs }),
488
+ });
489
+
490
+ return await dispatchAdmittedFixer({
491
+ admitted,
492
+ env,
493
+ io,
494
+ extraArgs,
495
+ lease,
496
+ methodMaterial,
497
+ });
498
+ }
499
+
500
+ /**
501
+ * Resume a previously admitted Fixer Role run after a typed HTTP 429.
502
+ * Restores role/phase/packet/prerequisites/session identity; model override is temporary.
503
+ */
504
+ export async function runPublicFixerResume(
505
+ argv: readonly string[],
506
+ env: FixerRunEnv,
507
+ io: CliIo,
508
+ ): Promise<{
509
+ exitCode: number;
510
+ admitted?: AdmittedFixerInvocation;
511
+ terminal?: TerminalResult;
512
+ }> {
513
+ const runId = argv[0];
514
+ if (runId === undefined || runId.trim() === "" || runId.startsWith("-")) {
515
+ presentStructuralRejection(
516
+ new CliUsageError("usage: ak-role resume <runId>"),
517
+ io,
518
+ );
519
+ return { exitCode: 2 };
520
+ }
521
+ if (argv.length > 1) {
522
+ presentStructuralRejection(
523
+ new CliUsageError("resume takes exactly one run id"),
524
+ io,
525
+ );
526
+ return { exitCode: 2 };
527
+ }
528
+
529
+ let loaded;
530
+ try {
531
+ loaded = await loadResumableFixerRun(env.home, runId);
532
+ } catch (error) {
533
+ if (error instanceof CliUsageError) {
534
+ presentStructuralRejection(error, io);
535
+ return { exitCode: 2 };
536
+ }
537
+ throw error;
538
+ }
539
+
540
+ const { admitted } = loaded;
541
+
542
+ let lease: RunWriterLease;
543
+ try {
544
+ lease = await acquireRunWriterLease(admitted.runDirectory);
545
+ } catch (error) {
546
+ if (error instanceof RunWriterLeaseHeldError) {
547
+ io.stderr(formatCliDiagnostic(error.message));
548
+ return { exitCode: 1 };
549
+ }
550
+ throw error;
551
+ }
552
+
553
+ let methodMaterial: PackagedMethodSkillMaterial;
554
+ try {
555
+ methodMaterial = await loadFixerMethodMaterial(env.packageRoot);
556
+ } catch (error) {
557
+ await lease.release();
558
+ return await presentControlledFailure(
559
+ admitted,
560
+ {
561
+ timedOut: false,
562
+ code: null,
563
+ stderr: "",
564
+ thrown: error,
565
+ },
566
+ io,
567
+ );
568
+ }
569
+
570
+ const extraArgs = buildFixerResumeActivationExtraArgs(admitted, {
571
+ packageRoot: env.packageRoot,
572
+ ...(env.model === undefined ? {} : { model: env.model }),
573
+ ...(env.extraPiArgs === undefined ? {} : { extraPiArgs: env.extraPiArgs }),
574
+ });
575
+
576
+ return await dispatchAdmittedFixer({
577
+ admitted,
578
+ env,
579
+ io,
580
+ extraArgs,
581
+ lease,
582
+ methodMaterial,
583
+ });
584
+ }
585
+
586
+ // Re-export for tests that assert typed credential failure channel shape.
587
+ export type { ExplicitInternalKnownFailure, PackagedMethodSkillProvenance };
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Standalone-invocation runtime resolution. The public CLI runs outside Pi, while
3
+ * the @earendil-works peers are optional and host-provided (ADR: the machine's Pi
4
+ * installation is the single runtime authority). When the local install cannot
5
+ * resolve them, link the host Pi's own modules into this package's node_modules
6
+ * instead of materializing a second runtime from the registry.
7
+ *
8
+ * Package presence is probed by directory (ancestor node_modules walk, the same
9
+ * package lookup ESM uses) — entry-point resolution is unusable here because the
10
+ * peers export "." with an `import` condition only, which CJS require cannot see.
11
+ */
12
+ import { existsSync, lstatSync, mkdirSync, realpathSync, rmSync, symlinkSync } from "node:fs";
13
+ import { delimiter, dirname, join } from "node:path";
14
+
15
+ export const HOST_PROVIDED_PACKAGES = [
16
+ "@earendil-works/pi-ai",
17
+ "@earendil-works/pi-coding-agent",
18
+ "typebox",
19
+ ] as const;
20
+
21
+ type HostProvidedPackage = (typeof HOST_PROVIDED_PACKAGES)[number];
22
+
23
+ /**
24
+ * Make the host-provided runtime packages resolvable from `packageRoot`.
25
+ * Local resolution wins untouched; otherwise the host `pi` executable on PATH
26
+ * anchors the lookup and the packages are symlinked into the package-own
27
+ * node_modules (highest resolution precedence for the package and its child
28
+ * processes). Throws when neither source can provide a package.
29
+ */
30
+ export function ensureHostPiRuntimeResolvable(
31
+ packageRoot: string,
32
+ env: NodeJS.ProcessEnv = process.env,
33
+ ): void {
34
+ const missing = missingPackages(packageRoot);
35
+ if (missing.length === 0) {
36
+ return;
37
+ }
38
+ const hostCli = findHostPiCli(env);
39
+ if (hostCli === undefined) {
40
+ throw new Error(
41
+ `ak-role cannot resolve ${missing.join(", ")} next to the installed package and found no host \`pi\` executable on PATH. ` +
42
+ "Install Pi (@earendil-works/pi-coding-agent) on this machine or provide the peer packages locally.",
43
+ );
44
+ }
45
+ for (const name of missing) {
46
+ const hostDir = findPackageDirFrom(dirname(hostCli), name);
47
+ if (hostDir === undefined) {
48
+ throw new Error(`ak-role found the host pi at ${hostCli} but could not locate ${name} in its tree.`);
49
+ }
50
+ linkPackage(packageRoot, name, hostDir);
51
+ }
52
+ const unresolved = missingPackages(packageRoot);
53
+ if (unresolved.length > 0) {
54
+ throw new Error(
55
+ `ak-role linked the host Pi runtime from ${hostCli} but ${unresolved.join(", ")} remained unresolvable.`,
56
+ );
57
+ }
58
+ }
59
+
60
+ function missingPackages(packageRoot: string): HostProvidedPackage[] {
61
+ return HOST_PROVIDED_PACKAGES.filter((name) => findPackageDirFrom(packageRoot, name) === undefined);
62
+ }
63
+
64
+ /** Ancestor node_modules walk — the package-directory half of bare-specifier lookup. */
65
+ function findPackageDirFrom(startDir: string, name: string): string | undefined {
66
+ let dir = startDir;
67
+ while (true) {
68
+ const candidate = join(dir, "node_modules", ...name.split("/"));
69
+ if (existsSync(join(candidate, "package.json"))) {
70
+ return realpathSync(candidate);
71
+ }
72
+ const parent = dirname(dir);
73
+ if (parent === dir) {
74
+ return undefined;
75
+ }
76
+ dir = parent;
77
+ }
78
+ }
79
+
80
+ function findHostPiCli(env: NodeJS.ProcessEnv): string | undefined {
81
+ for (const dir of (env.PATH ?? "").split(delimiter)) {
82
+ if (dir === "") {
83
+ continue;
84
+ }
85
+ const candidate = join(dir, "pi");
86
+ if (existsSync(candidate)) {
87
+ return realpathSync(candidate);
88
+ }
89
+ }
90
+ return undefined;
91
+ }
92
+
93
+ function linkPackage(packageRoot: string, name: HostProvidedPackage, targetDir: string): void {
94
+ const linkPath = join(packageRoot, "node_modules", ...name.split("/"));
95
+ mkdirSync(dirname(linkPath), { recursive: true });
96
+ const existing = lstatSync(linkPath, { throwIfNoEntry: false });
97
+ if (existing !== undefined) {
98
+ if (!existing.isSymbolicLink()) {
99
+ throw new Error(
100
+ `ak-role found ${linkPath} present but unresolvable; refusing to replace a non-symlink install.`,
101
+ );
102
+ }
103
+ rmSync(linkPath);
104
+ }
105
+ try {
106
+ symlinkSync(targetDir, linkPath, "dir");
107
+ } catch (error) {
108
+ if ((error as NodeJS.ErrnoException).code !== "EEXIST") {
109
+ throw error;
110
+ }
111
+ }
112
+ }