@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,93 @@
1
+ import { Type, type Static } from "typebox";
2
+ import { Value } from "typebox/value";
3
+
4
+ export const FIXER_PREREQUISITE_ID_PATTERN = "^[A-Za-z0-9][A-Za-z0-9._-]*$";
5
+
6
+ export const fixerPrerequisiteSchema = Type.Object({
7
+ id: Type.String({ pattern: FIXER_PREREQUISITE_ID_PATTERN }),
8
+ requirement: Type.String({ pattern: "\\S" }),
9
+ }, { additionalProperties: false });
10
+
11
+ export const fixerPrerequisitesSchema = Type.Array(fixerPrerequisiteSchema);
12
+ export type FixerPrerequisite = Readonly<Static<typeof fixerPrerequisiteSchema>>;
13
+ export type FixerInvocationInput = Readonly<{
14
+ instructions: string;
15
+ prerequisites: readonly FixerPrerequisite[];
16
+ }>;
17
+
18
+ function causeMessage(cause: unknown): string {
19
+ if (cause instanceof Error) return cause.message;
20
+ if (typeof cause === "string") return cause;
21
+ try {
22
+ return JSON.stringify(cause) ?? String(cause);
23
+ } catch {
24
+ return String(cause);
25
+ }
26
+ }
27
+
28
+ export class FixerPacketValidationError extends Error {
29
+ readonly code = "AK_INVALID_FIX_PACKET";
30
+
31
+ constructor(cause?: unknown) {
32
+ const prefix = "Fixer prerequisites or instructions violate the invocation contract";
33
+ super(
34
+ cause === undefined ? prefix : `${prefix}: ${causeMessage(cause)}`,
35
+ cause === undefined ? undefined : { cause },
36
+ );
37
+ this.name = "FixerPacketValidationError";
38
+ }
39
+ }
40
+
41
+ function fail(cause: unknown): never {
42
+ throw new FixerPacketValidationError(cause);
43
+ }
44
+
45
+ function parseFailure(value: unknown): never {
46
+ if (!Array.isArray(value)) fail(new Error("Fixer prerequisites must be a JSON array"));
47
+ for (const entry of value) {
48
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
49
+ fail(new Error("Fixer prerequisite entry must be an object with id and requirement fields"));
50
+ }
51
+ const keys = Object.keys(entry);
52
+ if (keys.length !== 2 || !keys.includes("id") || !keys.includes("requirement")) {
53
+ fail(new Error("Fixer prerequisite entry fields must be exactly id and requirement"));
54
+ }
55
+ if (
56
+ typeof (entry as Record<string, unknown>).id !== "string" ||
57
+ !(new RegExp(FIXER_PREREQUISITE_ID_PATTERN)).test((entry as { id: string }).id)
58
+ ) {
59
+ fail(new Error(`Fixer prerequisite id violates pattern ${FIXER_PREREQUISITE_ID_PATTERN}`));
60
+ }
61
+ if (
62
+ typeof (entry as Record<string, unknown>).requirement !== "string" ||
63
+ !/\S/.test((entry as { requirement: string }).requirement)
64
+ ) {
65
+ fail(new Error("Fixer prerequisite requirement must be nonblank"));
66
+ }
67
+ }
68
+ fail(new Error("Fixer prerequisites violate the attachment schema"));
69
+ }
70
+
71
+ export function validateFixerPrerequisites(value: unknown): readonly FixerPrerequisite[] {
72
+ if (!Value.Check(fixerPrerequisitesSchema, value)) parseFailure(value);
73
+ const entries = value as Static<typeof fixerPrerequisitesSchema>;
74
+ const ids = new Set<string>();
75
+ const prerequisites = entries.map((entry) => {
76
+ if (ids.has(entry.id)) {
77
+ fail(new Error(`Fixer prerequisites contain duplicate id: ${entry.id}`));
78
+ }
79
+ ids.add(entry.id);
80
+ return Object.freeze({ id: entry.id, requirement: entry.requirement });
81
+ });
82
+ return Object.freeze(prerequisites);
83
+ }
84
+
85
+ export function parseFixerPrerequisites(source: string): readonly FixerPrerequisite[] {
86
+ let decoded: unknown;
87
+ try {
88
+ decoded = JSON.parse(source);
89
+ } catch (error) {
90
+ fail(error);
91
+ }
92
+ return validateFixerPrerequisites(decoded);
93
+ }
@@ -0,0 +1,39 @@
1
+ /** Package-owned Judge output leaf — no role registration surface. */
2
+
3
+ export const JUDGE_OUTPUT_TOOL_NAME = "ak_judge_output";
4
+ export const JUDGE_ACCEPTED_TEXT = "Judge verdict accepted";
5
+
6
+ export type JudgeClass = {
7
+ name: string;
8
+ owner: string;
9
+ boundary: string;
10
+ disposition: string;
11
+ };
12
+
13
+ export type JudgeVerdict =
14
+ | { judgeStatus: "converged"; note?: string; evidence?: unknown }
15
+ | {
16
+ judgeStatus: "continue";
17
+ fix: { summary: string };
18
+ classes: JudgeClass[];
19
+ note?: string;
20
+ evidence?: unknown;
21
+ }
22
+ | {
23
+ judgeStatus: "escalate";
24
+ decisionGate: { question: string; options: string[] };
25
+ note?: string;
26
+ evidence?: unknown;
27
+ };
28
+
29
+ export function validateAcceptedJudgeDetails(verdict: unknown): JudgeVerdict {
30
+ if (verdict === null || typeof verdict !== "object" || Array.isArray(verdict)) throw new Error("Judge verdict has no execution discriminator");
31
+ let judgeStatus: unknown;
32
+ try {
33
+ judgeStatus = (verdict as Record<string, unknown>).judgeStatus;
34
+ } catch {
35
+ throw new Error("Judge verdict has no execution discriminator");
36
+ }
37
+ if (["converged", "continue", "escalate"].includes(String(judgeStatus))) return verdict as JudgeVerdict;
38
+ throw new Error("Judge verdict has no execution discriminator");
39
+ }
@@ -0,0 +1,115 @@
1
+ /** Package-owned Reviewer intent and runtime-receipt leaves — no role registration surface. */
2
+
3
+ import type { ReviewerAcceptedEvidence, ReviewerFailureClassification, ReviewerWorkspaceDisposition } from "../reviewer-execution-ledger.ts";
4
+
5
+ export const REVIEWER_OUTPUT_TOOL_NAME = "ak_reviewer_output";
6
+ export const REVIEWER_ACCEPTED_TEXT = "Reviewer report accepted";
7
+
8
+ export type ReviewerIntent =
9
+ | Readonly<{ status: "completed" }>
10
+ | Readonly<{ status: "refused"; diagnostic: string }>;
11
+ /** Child report is plain text — no length/digest identity shell (ADR 0031). */
12
+ export type VerbatimChildReport = Readonly<{ text: string }>;
13
+ /** Prompt projection on the receipt face is plain text (ADR 0031). */
14
+ export type ReviewerReceiptPrompt = Readonly<{ text: string }>;
15
+ /** Canonical Skill content on the receipt face is plain text (ADR 0031/0032). */
16
+ export type ReviewerReceiptSkillContent = Readonly<{ text: string }>;
17
+ type RuntimeReviewerOutcomeCommon = Readonly<{
18
+ prompt: ReviewerReceiptPrompt;
19
+ workspaceDisposition: ReviewerWorkspaceDisposition;
20
+ }>;
21
+ export type RuntimeReviewerOutcome = RuntimeReviewerOutcomeCommon & (
22
+ | Readonly<{ status: "successful"; failure?: never }>
23
+ | Readonly<{ status: "failed"; failure: ReviewerFailureClassification; diagnostic: string }>
24
+ );
25
+ export type RuntimeReviewerAcceptedBatch = Readonly<{
26
+ identity: string;
27
+ legs: readonly Readonly<{ axis: "standards" | "spec"; prompt: ReviewerReceiptPrompt }>[];
28
+ }>;
29
+ export type RuntimeReviewerReceiptV2 = Readonly<{
30
+ version: 2;
31
+ status: "completed" | "refused";
32
+ diagnostic?: string;
33
+ acceptedBatch?: RuntimeReviewerAcceptedBatch;
34
+ reports: Readonly<Partial<Record<"standards" | "spec", VerbatimChildReport>>>;
35
+ outcomes: Readonly<Partial<Record<"standards" | "spec", RuntimeReviewerOutcome>>>;
36
+ identities: Readonly<{
37
+ canonicalSkill: ReviewerReceiptSkillContent;
38
+ construction?: Readonly<{ recipe: ReviewerAcceptedEvidence["recipe"] }>;
39
+ target?: ReviewerAcceptedEvidence["target"];
40
+ }>;
41
+ }>;
42
+
43
+ function isRecord(value: unknown): value is Record<string, unknown> {
44
+ return typeof value === "object" && value !== null && !Array.isArray(value);
45
+ }
46
+ function read(value: unknown, key: string): unknown {
47
+ if (!isRecord(value)) return undefined;
48
+ try { return value[key]; } catch { return undefined; }
49
+ }
50
+
51
+ export function validateReviewerIntent(output: unknown): ReviewerIntent {
52
+ const status = read(output, "status");
53
+ if (status === "completed") return { status };
54
+ if (status === "refused") return { status, diagnostic: read(output, "diagnostic") as string };
55
+ throw new Error("Reviewer output has no recognized execution intent");
56
+ }
57
+
58
+ /** Validate runtime-owned facts at their real identity seams (target pins + plain text). */
59
+ export function validateRuntimeReviewerReceipt(output: unknown): RuntimeReviewerReceiptV2 {
60
+ const acceptedBatch = read(output, "acceptedBatch");
61
+ const identities = read(output, "identities");
62
+ const construction = read(identities, "construction");
63
+ const target = read(identities, "target");
64
+ const reports = read(output, "reports");
65
+ const outcomes = read(output, "outcomes");
66
+ const legs = read(acceptedBatch, "legs");
67
+
68
+ // A recognizable accepted batch must remain bound to its exact target pin.
69
+ if (acceptedBatch !== undefined || construction !== undefined || target !== undefined) {
70
+ if (!isRecord(acceptedBatch) || !isRecord(construction) || !isRecord(target) || !Array.isArray(legs))
71
+ throw new Error("Incomplete Reviewer accepted-batch identity");
72
+ const objectFormat = read(target, "objectFormat");
73
+ const objectId = (value: unknown): boolean => typeof value === "string" && new RegExp(objectFormat === "sha1" ? "^[0-9a-f]{40}$" : "^[0-9a-f]{64}$").test(value);
74
+ const refs = read(target, "refs");
75
+ const skillText = read(read(identities, "canonicalSkill"), "text");
76
+ if (typeof skillText !== "string" ||
77
+ read(construction, "recipe") !== "reviewer-common-bundle-v1" ||
78
+ (objectFormat !== "sha1" && objectFormat !== "sha256") || !objectId(read(target, "targetHead")) || !isRecord(refs) ||
79
+ Object.values(refs).some((ref) => !isRecord(ref) || !objectId(read(ref, "objectId")) || (read(ref, "peeledCommitId") !== null && !objectId(read(ref, "peeledCommitId")))))
80
+ throw new Error("Invalid Reviewer construction or target identity");
81
+
82
+ const expectedAxes = legs.map((leg) => read(leg, "axis"));
83
+ if (expectedAxes[0] !== "standards" || (expectedAxes.length === 2 && expectedAxes[1] !== "spec") || expectedAxes.length < 1 || expectedAxes.length > 2)
84
+ throw new Error("Invalid Reviewer accepted-leg projection");
85
+ if (!isRecord(outcomes) || !isRecord(reports)) throw new Error("Accepted Reviewer batch lacks outcomes or reports");
86
+ const outcomeAxes = Object.keys(outcomes).filter(axis => axis === "standards" || axis === "spec");
87
+ if (outcomeAxes.length !== expectedAxes.length || outcomeAxes.some((axis, index) => axis !== expectedAxes[index]))
88
+ throw new Error("Reviewer outcomes must exactly cover accepted legs in canonical order");
89
+
90
+ for (const [index, axisValue] of expectedAxes.entries()) {
91
+ const axis = axisValue as "standards" | "spec";
92
+ const outcome = read(outcomes, axis);
93
+ if (!isRecord(outcome)) throw new Error("Reviewer accepted leg lacks outcome");
94
+ const expectedPrompt = read(read(legs[index], "prompt"), "text");
95
+ const actualPrompt = read(read(outcome, "prompt"), "text");
96
+ if (expectedPrompt !== actualPrompt) throw new Error("Reviewer outcome prompt disagrees with accepted leg");
97
+ const status = read(outcome, "status");
98
+ const report = read(reports, axis);
99
+ if (status === "successful" && report === undefined)
100
+ throw new Error("Successful Reviewer outcome lacks report");
101
+ if (status === "failed" && report !== undefined) throw new Error("Failed Reviewer outcome cannot bind a report");
102
+ }
103
+ }
104
+ return output as RuntimeReviewerReceiptV2;
105
+ }
106
+
107
+ /** Project thin submitted intent onto runtime enrichment without comparing runtime-owned fields. */
108
+ export function projectReviewerIntentToReceipt(intentValue: unknown, receiptValue: unknown): RuntimeReviewerReceiptV2 {
109
+ const intent = validateReviewerIntent(intentValue);
110
+ const receipt = validateRuntimeReviewerReceipt(receiptValue);
111
+ if (receipt.status !== intent.status || (intent.status === "completed" ? receipt.diagnostic !== undefined : receipt.diagnostic !== intent.diagnostic)) {
112
+ throw new Error("Reviewer intent and runtime receipt disagree");
113
+ }
114
+ return receipt;
115
+ }
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Package-owned terminating tool registry.
3
+ * Package roles share these terminating leaves.
4
+ */
5
+
6
+ import {
7
+ COLLECTOR_ACCEPTED_TEXT,
8
+ COLLECTOR_OUTPUT_TOOL,
9
+ validateAcceptedCollectorReceipt,
10
+ type CollectorReceipt,
11
+ } from "./collector-output.ts";
12
+ import {
13
+ JUDGE_ACCEPTED_TEXT,
14
+ JUDGE_OUTPUT_TOOL_NAME,
15
+ validateAcceptedJudgeDetails,
16
+ type JudgeVerdict,
17
+ } from "./judge-output.ts";
18
+ import {
19
+ REVIEWER_ACCEPTED_TEXT,
20
+ REVIEWER_OUTPUT_TOOL_NAME,
21
+ projectReviewerIntentToReceipt,
22
+ validateReviewerIntent,
23
+ validateRuntimeReviewerReceipt,
24
+ type ReviewerIntent,
25
+ type RuntimeReviewerReceiptV2,
26
+ } from "./reviewer-output.ts";
27
+ import { isAuditEscalationResult } from "../audit-escalation.ts";
28
+ import { DOCTOR_OUTPUT_TOOL_NAME, validateDoctorSubmissionShape, validateRecordedDoctorOutput, type DoctorOutput, type DoctorSubmission } from "../doctor-contracts.ts";
29
+ import { MERGER_ACCEPTED_TEXT, MERGER_OUTPUT_TOOL_NAME, validateMergerOutput, type MergerOutput } from "../merger-contracts.ts";
30
+ import {
31
+ CODER_ACCEPTED_TEXT,
32
+ CODER_OUTPUT_TOOL_NAME,
33
+ FIXER_ACCEPTED_TEXT,
34
+ FIXER_OUTPUT_TOOL_NAME,
35
+ validateAcceptedWorkerDetails,
36
+ type WorkerOutput,
37
+ } from "./worker-output.ts";
38
+
39
+ export {
40
+ CODER_ACCEPTED_TEXT,
41
+ CODER_OUTPUT_TOOL_NAME,
42
+ COLLECTOR_ACCEPTED_TEXT,
43
+ COLLECTOR_OUTPUT_TOOL,
44
+ FIXER_ACCEPTED_TEXT,
45
+ FIXER_OUTPUT_TOOL_NAME,
46
+ JUDGE_ACCEPTED_TEXT,
47
+ JUDGE_OUTPUT_TOOL_NAME,
48
+ REVIEWER_ACCEPTED_TEXT,
49
+ REVIEWER_OUTPUT_TOOL_NAME,
50
+ MERGER_ACCEPTED_TEXT,
51
+ MERGER_OUTPUT_TOOL_NAME,
52
+ validateAcceptedCollectorReceipt,
53
+ validateAcceptedJudgeDetails,
54
+ projectReviewerIntentToReceipt,
55
+ validateReviewerIntent,
56
+ validateRuntimeReviewerReceipt,
57
+ validateAcceptedWorkerDetails,
58
+ validateDoctorSubmissionShape,
59
+ validateRecordedDoctorOutput,
60
+ validateMergerOutput,
61
+ };
62
+ export type {
63
+ CollectorReceipt,
64
+ JudgeVerdict,
65
+ ReviewerIntent,
66
+ RuntimeReviewerReceiptV2,
67
+ WorkerOutput,
68
+ DoctorOutput,
69
+ DoctorSubmission,
70
+ MergerOutput,
71
+ };
72
+
73
+ export const TERMINATING_TOOL_NAMES = [
74
+ CODER_OUTPUT_TOOL_NAME,
75
+ FIXER_OUTPUT_TOOL_NAME,
76
+ REVIEWER_OUTPUT_TOOL_NAME,
77
+ JUDGE_OUTPUT_TOOL_NAME,
78
+ COLLECTOR_OUTPUT_TOOL,
79
+ DOCTOR_OUTPUT_TOOL_NAME,
80
+ MERGER_OUTPUT_TOOL_NAME,
81
+ ] as const;
82
+
83
+ export type TerminatingToolName = (typeof TERMINATING_TOOL_NAMES)[number];
84
+
85
+ export type AcceptedDetails =
86
+ | WorkerOutput
87
+ | RuntimeReviewerReceiptV2
88
+ | JudgeVerdict
89
+ | CollectorReceipt
90
+ | DoctorOutput
91
+ | MergerOutput;
92
+
93
+ export function isTerminatingToolName(
94
+ name: string,
95
+ ): name is TerminatingToolName {
96
+ return (TERMINATING_TOOL_NAMES as readonly string[]).includes(name);
97
+ }
98
+
99
+ export function acceptedTextFor(toolName: TerminatingToolName): string {
100
+ switch (toolName) {
101
+ case CODER_OUTPUT_TOOL_NAME:
102
+ return CODER_ACCEPTED_TEXT;
103
+ case FIXER_OUTPUT_TOOL_NAME:
104
+ return FIXER_ACCEPTED_TEXT;
105
+ case REVIEWER_OUTPUT_TOOL_NAME:
106
+ return REVIEWER_ACCEPTED_TEXT;
107
+ case JUDGE_OUTPUT_TOOL_NAME:
108
+ return JUDGE_ACCEPTED_TEXT;
109
+ case COLLECTOR_OUTPUT_TOOL:
110
+ return COLLECTOR_ACCEPTED_TEXT;
111
+ case DOCTOR_OUTPUT_TOOL_NAME:
112
+ return "Doctor output accepted";
113
+ case MERGER_OUTPUT_TOOL_NAME:
114
+ return MERGER_ACCEPTED_TEXT;
115
+ }
116
+ }
117
+
118
+ export class AcceptedDetailsContractError extends Error {
119
+ constructor(message: string, options?: ErrorOptions) {
120
+ super(message, options);
121
+ this.name = "AcceptedDetailsContractError";
122
+ }
123
+ }
124
+
125
+ function safeProperty(candidate: Record<string, unknown> | undefined, property: string): unknown {
126
+ try {
127
+ return candidate?.[property];
128
+ } catch {
129
+ return undefined;
130
+ }
131
+ }
132
+
133
+ export function validateAcceptedDetails(
134
+ toolName: TerminatingToolName,
135
+ details: unknown,
136
+ ): AcceptedDetails {
137
+ const candidate = details !== null && typeof details === "object" && !Array.isArray(details)
138
+ ? details as Record<string, unknown>
139
+ : undefined;
140
+ let auditEscalation = false;
141
+ try {
142
+ auditEscalation = isAuditEscalationResult(details);
143
+ } catch {
144
+ // Hostile getters are not recognizable audit escalation evidence.
145
+ }
146
+ if (auditEscalation || safeProperty(candidate, "kind") === "audit_escalation") {
147
+ throw new AcceptedDetailsContractError(
148
+ "audit escalation is not an accepted role receipt",
149
+ );
150
+ }
151
+ const discriminator = safeProperty(candidate, toolName === JUDGE_OUTPUT_TOOL_NAME ? "judgeStatus" : "status");
152
+ const lawfulStatuses: Readonly<Record<TerminatingToolName, readonly string[]>> = {
153
+ [CODER_OUTPUT_TOOL_NAME]: ["planned", "completed", "refused", "unfinished"],
154
+ [FIXER_OUTPUT_TOOL_NAME]: ["planned", "completed", "refused", "partially_completed", "unfinished"],
155
+ [REVIEWER_OUTPUT_TOOL_NAME]: ["completed", "refused"],
156
+ [JUDGE_OUTPUT_TOOL_NAME]: ["converged", "continue", "escalate"],
157
+ [COLLECTOR_OUTPUT_TOOL]: [],
158
+ [DOCTOR_OUTPUT_TOOL_NAME]: ["completed", "refused"],
159
+ [MERGER_OUTPUT_TOOL_NAME]: ["completed", "escalate"],
160
+ };
161
+ const collectorDiscriminator = toolName === COLLECTOR_OUTPUT_TOOL && Array.isArray(candidate?.groups);
162
+ const runtimeBindingMissing =
163
+ (toolName === DOCTOR_OUTPUT_TOOL_NAME && discriminator === "completed" && !(candidate?.cost !== null && typeof candidate?.cost === "object")) ||
164
+ (toolName === REVIEWER_OUTPUT_TOOL_NAME && candidate?.version !== 2);
165
+ if (runtimeBindingMissing || (!collectorDiscriminator && (typeof discriminator !== "string" || !lawfulStatuses[toolName].includes(discriminator)))) {
166
+ throw new AcceptedDetailsContractError("terminating receipt has no recognized execution discriminator");
167
+ }
168
+ try {
169
+ switch (toolName) {
170
+ case CODER_OUTPUT_TOOL_NAME:
171
+ return validateAcceptedWorkerDetails(details, "Coder");
172
+ case FIXER_OUTPUT_TOOL_NAME:
173
+ return validateAcceptedWorkerDetails(details, "Fixer");
174
+ case REVIEWER_OUTPUT_TOOL_NAME:
175
+ return validateRuntimeReviewerReceipt(details);
176
+ case JUDGE_OUTPUT_TOOL_NAME:
177
+ return validateAcceptedJudgeDetails(details);
178
+ case COLLECTOR_OUTPUT_TOOL:
179
+ return validateAcceptedCollectorReceipt(details);
180
+ case DOCTOR_OUTPUT_TOOL_NAME:
181
+ return validateRecordedDoctorOutput(details);
182
+ case MERGER_OUTPUT_TOOL_NAME:
183
+ return validateMergerOutput(details);
184
+ }
185
+ } catch (error) {
186
+ if (error instanceof Error && error.constructor === Error) throw new AcceptedDetailsContractError(error.message, { cause: error });
187
+ throw error;
188
+ }
189
+ }
190
+
191
+ export function validateAcceptedLifecycle(
192
+ toolName: TerminatingToolName,
193
+ argumentsValue: unknown,
194
+ detailsValue: unknown,
195
+ ): AcceptedDetails {
196
+ const details = validateAcceptedDetails(toolName, detailsValue);
197
+ if (toolName === DOCTOR_OUTPUT_TOOL_NAME) {
198
+ const testimony = validateDoctorSubmissionShape(argumentsValue);
199
+ if (testimony.status === "refused") {
200
+ if (!deepEqual(testimony, details)) throw new Error("accepted tool lifecycle details mismatch");
201
+ return details;
202
+ }
203
+ const receipt = details as DoctorOutput;
204
+ if (receipt.status !== "completed") throw new Error("accepted tool lifecycle details mismatch");
205
+ const { cost: _runtimeCost, ...projected } = receipt;
206
+ if (!deepEqual(testimony, projected)) throw new Error("accepted tool lifecycle details mismatch");
207
+ return details;
208
+ }
209
+ const argumentsDetails = validateAcceptedDetails(toolName, argumentsValue);
210
+ if (!deepEqual(argumentsDetails, details)) throw new Error("accepted tool lifecycle details mismatch");
211
+ return details;
212
+ }
213
+
214
+ /** Machine-facing facts from an accepted terminating receipt. No presentation joins. */
215
+ export type AcceptedFacts = {
216
+ status?: string;
217
+ commit?: string;
218
+ };
219
+
220
+ export function acceptedFacts(toolName: TerminatingToolName, details: AcceptedDetails): AcceptedFacts {
221
+ switch (toolName) {
222
+ case CODER_OUTPUT_TOOL_NAME:
223
+ case FIXER_OUTPUT_TOOL_NAME:
224
+ case REVIEWER_OUTPUT_TOOL_NAME:
225
+ case DOCTOR_OUTPUT_TOOL_NAME: return { status: (details as { status: string }).status };
226
+ case JUDGE_OUTPUT_TOOL_NAME: return { status: (details as { judgeStatus: string }).judgeStatus };
227
+ case MERGER_OUTPUT_TOOL_NAME: {
228
+ const output = details as unknown as Record<string, unknown>;
229
+ return { status: output.status as string, ...(output.status === "completed" && typeof output.mergeCommitId === "string" ? { commit: output.mergeCommitId } : {}) };
230
+ }
231
+ case COLLECTOR_OUTPUT_TOOL:
232
+ return { status: "collected" };
233
+ }
234
+ }
235
+
236
+ /** Deep structural equality for lifecycle agreement checks. */
237
+ export function deepEqual(a: unknown, b: unknown): boolean {
238
+ if (Object.is(a, b)) return true;
239
+ if (typeof a !== typeof b) return false;
240
+ if (a === null || b === null) return a === b;
241
+ if (Array.isArray(a)) {
242
+ if (!Array.isArray(b) || a.length !== b.length) return false;
243
+ return a.every((item, index) => deepEqual(item, b[index]));
244
+ }
245
+ if (typeof a === "object") {
246
+ if (typeof b !== "object" || b === null || Array.isArray(b)) return false;
247
+ const aKeys = Object.keys(a as object).sort();
248
+ const bKeys = Object.keys(b as object).sort();
249
+ if (aKeys.length !== bKeys.length) return false;
250
+ if (!aKeys.every((key, index) => key === bKeys[index])) return false;
251
+ return aKeys.every((key) =>
252
+ deepEqual(
253
+ (a as Record<string, unknown>)[key],
254
+ (b as Record<string, unknown>)[key],
255
+ )
256
+ );
257
+ }
258
+ return false;
259
+ }
@@ -0,0 +1,36 @@
1
+ /** Package-owned independent Coder and Fixer output leaves. */
2
+
3
+ export {
4
+ FIXER_ACCEPTED_TEXT,
5
+ FIXER_OUTPUT_TOOL_NAME,
6
+ fixerOutputSchema,
7
+ validateFixerOutput,
8
+ validateFixerOutputForPacket,
9
+ } from "./fixer-output.ts";
10
+ export type {
11
+ FixerBlocker,
12
+ FixerClassResult,
13
+ FixerOutput,
14
+ FixerPhase,
15
+ } from "./fixer-output.ts";
16
+ export { fixerPrerequisiteSchema, fixerPrerequisitesSchema, parseFixerPrerequisites, validateFixerPrerequisites } from "./fixer-packet.ts";
17
+ export type { FixerInvocationInput, FixerPrerequisite } from "./fixer-packet.ts";
18
+ import { validateFixerOutput, type FixerOutput } from "./fixer-output.ts";
19
+
20
+ export const CODER_OUTPUT_TOOL_NAME = "ak_coder_output";
21
+ export const CODER_ACCEPTED_TEXT = "Coder report accepted";
22
+ export type WorkerRoleLabel = "Coder" | "Fixer";
23
+ export type CoderOutput =
24
+ | { status: "planned"; report: string }
25
+ | { status: "completed" | "refused"; report: string }
26
+ | { status: "unfinished"; report: string; remainingScope: string };
27
+ export type WorkerOutput = CoderOutput | FixerOutput;
28
+
29
+ export function validateAcceptedCoderDetails(output: unknown): CoderOutput {
30
+ return output as CoderOutput;
31
+ }
32
+
33
+ /** Structural production validator for an accepted current leaf. */
34
+ export function validateAcceptedWorkerDetails(output: unknown, roleLabel: WorkerRoleLabel = "Coder"): WorkerOutput {
35
+ return roleLabel === "Fixer" ? validateFixerOutput(output) : validateAcceptedCoderDetails(output);
36
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * #102 package-owned tool idle backstop.
3
+ *
4
+ * Fixed 183000ms silence clock on package-owned tool execute only.
5
+ * Real producing onUpdate resets; final resolve/reject clears; timeout throws so
6
+ * Pi settles the current call as an LLM-visible isError tool result. No retry,
7
+ * role failure, process termination, signal abort, config, or Pi built-in coverage.
8
+ */
9
+ import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
10
+
11
+ import {
12
+ DEFAULT_STREAM_IDLE_TIMEOUT_MS,
13
+ createStreamIdleGuard,
14
+ } from "./stream-idle-guard.ts";
15
+ import { isProducingToolUpdate } from "./tool-execution-observation.ts";
16
+
17
+ export const PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS = DEFAULT_STREAM_IDLE_TIMEOUT_MS;
18
+ export const PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_CODE = "AK_PACKAGE_OWNED_TOOL_IDLE_TIMEOUT" as const;
19
+
20
+ const WRAPPED = Symbol.for("ak.packageOwnedToolIdleWrapped");
21
+
22
+ export class PackageOwnedToolIdleTimeoutError extends Error {
23
+ readonly code = PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_CODE;
24
+ readonly idleTimeoutMs = PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS;
25
+
26
+ constructor() {
27
+ super(`package-owned tool idle timeout after ${PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS}ms`);
28
+ this.name = "PackageOwnedToolIdleTimeoutError";
29
+ }
30
+ }
31
+
32
+ /** Minimal executable tool shape accepted by the shared idle wrapper. */
33
+ export type PackageOwnedToolLike = {
34
+ readonly name: string;
35
+ execute: (...args: never[]) => Promise<unknown>;
36
+ };
37
+
38
+ /**
39
+ * Package-tool activity includes content production and host-only details
40
+ * progress. Keep the observation-plane oracle separate: its stderr heartbeat
41
+ * contract remains content-driven. Pi's known execute-entry placeholder
42
+ * (`content: [], details: undefined`) is not activity.
43
+ */
44
+ function isPackageOwnedToolActivityUpdate(partialResult: unknown): boolean {
45
+ if (isProducingToolUpdate(partialResult)) return true;
46
+ if (typeof partialResult !== "object" || partialResult === null) return false;
47
+ const details = (partialResult as { details?: unknown }).details;
48
+ if (details === undefined || details === null) return false;
49
+ if (typeof details === "string") return details.length > 0;
50
+ if (Array.isArray(details)) return details.length > 0;
51
+ if (typeof details === "object") return Reflect.ownKeys(details).length > 0;
52
+ return true;
53
+ }
54
+
55
+ /**
56
+ * Single shared execute wrapper for package-owned tool definitions.
57
+ * Idempotent: wrapping twice returns the same protected definition.
58
+ */
59
+ export function wrapPackageOwnedToolDefinition<T extends PackageOwnedToolLike>(tool: T): T {
60
+ // Mark the execute function, not the tool object — callers may spread tool fields
61
+ // onto a new definition with a different execute (e.g. auditor customTools).
62
+ if ((tool.execute as { [WRAPPED]?: boolean })[WRAPPED] === true) return tool;
63
+
64
+ const originalExecute = tool.execute.bind(tool) as (
65
+ ...args: unknown[]
66
+ ) => Promise<unknown>;
67
+
68
+ const wrappedExecute = function packageOwnedToolIdleExecute(
69
+ ...args: unknown[]
70
+ ): Promise<unknown> {
71
+ const signal = args[2] as AbortSignal | undefined;
72
+ const onUpdate = args[3] as ((partialResult: unknown) => void) | undefined;
73
+ return new Promise((resolve, reject) => {
74
+ let settled = false;
75
+ const idle = createStreamIdleGuard({
76
+ idleTimeoutMs: PACKAGE_OWNED_TOOL_IDLE_TIMEOUT_MS,
77
+ });
78
+
79
+ const settle = (deliver: () => void): void => {
80
+ if (settled) return;
81
+ settled = true;
82
+ idle.signal.removeEventListener("abort", onIdle);
83
+ idle.dispose();
84
+ deliver();
85
+ };
86
+ const onIdle = (): void => {
87
+ settle(() => reject(new PackageOwnedToolIdleTimeoutError()));
88
+ };
89
+ idle.signal.addEventListener("abort", onIdle, { once: true });
90
+
91
+ const guardedOnUpdate = onUpdate === undefined
92
+ ? undefined
93
+ : (partialResult: unknown) => {
94
+ if (settled) return;
95
+ if (isPackageOwnedToolActivityUpdate(partialResult)) idle.poke();
96
+ onUpdate(partialResult);
97
+ };
98
+
99
+ const callArgs = args.slice();
100
+ // Preserve the original signal at args[2]; timeout must not abort it.
101
+ callArgs[2] = signal;
102
+ callArgs[3] = guardedOnUpdate;
103
+
104
+ void Promise.resolve()
105
+ .then(() => originalExecute(...callArgs))
106
+ .then(
107
+ (result) => settle(() => resolve(result)),
108
+ (error: unknown) => settle(() => reject(error)),
109
+ );
110
+ });
111
+ };
112
+ (wrappedExecute as { [WRAPPED]?: boolean })[WRAPPED] = true;
113
+
114
+ return {
115
+ ...tool,
116
+ execute: wrappedExecute as T["execute"],
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Install the shared registration surface on an ExtensionAPI once.
122
+ * All subsequent pi.registerTool calls for package-owned tools are wrapped.
123
+ */
124
+ export function installPackageOwnedToolRegistration(pi: ExtensionAPI): void {
125
+ const current = pi.registerTool;
126
+ if ((current as { [WRAPPED]?: boolean })[WRAPPED] === true) return;
127
+
128
+ const original = current.bind(pi);
129
+ const installed = ((tool: ToolDefinition<any, any, any>) => {
130
+ original(wrapPackageOwnedToolDefinition(tool) as ToolDefinition<any, any, any>);
131
+ }) as typeof pi.registerTool;
132
+ (installed as { [WRAPPED]?: boolean })[WRAPPED] = true;
133
+ pi.registerTool = installed;
134
+ }