@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,137 @@
1
+ import { sha256Hex } from "./sha256.ts";
2
+ import { reviewerScopePrompt } from "./reviewer-scope-prompt.ts";
3
+ import type { ReviewerPinnedTarget, ReviewerRange } from "./reviewer-pinned-git.ts";
4
+ import type { ReviewerPromptText } from "./reviewer-prompt-identity.ts";
5
+
6
+ export const REVIEWER_CONSTRUCTION_RECIPE = Object.freeze({
7
+ recipeId: "reviewer-common-bundle",
8
+ version: 1,
9
+ runtimeVersion: "1",
10
+ implementationSha256: sha256Hex("reviewer-common-bundle:v1:direct-text-prompts"),
11
+ });
12
+ export type ReviewerConstructionIdentity = typeof REVIEWER_CONSTRUCTION_RECIPE;
13
+ export type ReviewerAxis = "standards" | "spec";
14
+
15
+ export const REVIEWER_AXIS_OUTPUT_ADAPTER = Object.freeze({
16
+ adapterId: "reviewer-axis-output",
17
+ version: 1,
18
+ implementationSha256: sha256Hex("reviewer-axis-output:v1:single-axis-verbatim-report+standards-three-priorities"),
19
+ });
20
+
21
+ /** Typed Standards conclusion keys owned by reviewer construction (presentation labels are not the contract). */
22
+ export const REVIEWER_STANDARDS_CONCLUSION_KEYS = Object.freeze([
23
+ "constitutionality",
24
+ "minimum-necessary-test-cost",
25
+ "complexity",
26
+ ] as const);
27
+ export type ReviewerStandardsConclusionKey = (typeof REVIEWER_STANDARDS_CONCLUSION_KEYS)[number];
28
+
29
+ const REVIEWER_STANDARDS_CONCLUSION_LABELS = Object.freeze({
30
+ constitutionality: "constitutionality",
31
+ "minimum-necessary-test-cost": "minimum-necessary test cost",
32
+ complexity: "complexity",
33
+ } as const satisfies Record<ReviewerStandardsConclusionKey, string>);
34
+
35
+ /** Axis-owned required/excluded Standards conclusions — machine contract for prompt rendering and tests. */
36
+ export type ReviewerAxisOutputContract = Readonly<{
37
+ axis: ReviewerAxis;
38
+ requiredConclusions: readonly ReviewerStandardsConclusionKey[];
39
+ excludedConclusions: readonly ReviewerStandardsConclusionKey[];
40
+ }>;
41
+
42
+ export function reviewerAxisOutputContract(axis: ReviewerAxis): ReviewerAxisOutputContract {
43
+ const three = REVIEWER_STANDARDS_CONCLUSION_KEYS;
44
+ return axis === "standards"
45
+ ? Object.freeze({ axis, requiredConclusions: three, excludedConclusions: Object.freeze([]) })
46
+ : Object.freeze({ axis, requiredConclusions: Object.freeze([]), excludedConclusions: three });
47
+ }
48
+
49
+ function renderConclusionList(
50
+ keys: readonly ReviewerStandardsConclusionKey[],
51
+ finalJoiner: "and" | "or",
52
+ ): string {
53
+ const labels = keys.map((key) => REVIEWER_STANDARDS_CONCLUSION_LABELS[key]);
54
+ if (labels.length === 0) return "";
55
+ if (labels.length === 1) return labels[0]!;
56
+ if (labels.length === 2) return `${labels[0]} ${finalJoiner} ${labels[1]}`;
57
+ return `${labels.slice(0, -1).join(", ")}, ${finalJoiner} ${labels[labels.length - 1]}`;
58
+ }
59
+
60
+ function renderAxisPriorityClause(contract: ReviewerAxisOutputContract): string {
61
+ if (contract.requiredConclusions.length > 0) {
62
+ return `Inside that single Standards report, explicitly conclude on ${renderConclusionList(contract.requiredConclusions, "and")}; a finding or clear no-finding on each counts as a conclusion.`;
63
+ }
64
+ return `Do not discuss ${renderConclusionList(contract.excludedConclusions, "or")}; those are Standards judgements.`;
65
+ }
66
+
67
+ /** Package-owned mechanics layered over the unchanged canonical Skill semantics. */
68
+ export function reviewerAxisMethodAdapter(axis: ReviewerAxis): string {
69
+ const contract = reviewerAxisOutputContract(axis);
70
+ const question = axis === "standards"
71
+ ? "Answer only the canonical Standards question, including its complete smell baseline and burden."
72
+ : "Answer only the canonical Spec question.";
73
+ return [
74
+ `Axis-Output-Adapter: ${REVIEWER_AXIS_OUTPUT_ADAPTER.adapterId}@${REVIEWER_AXIS_OUTPUT_ADAPTER.version}:${axis}`,
75
+ "The complete canonical Skill text below remains authoritative semantic input.",
76
+ "For this already-isolated leg, this package adapter supersedes that Skill's dual-agent orchestration, dual-axis aggregation, and dual-section presentation mechanics.",
77
+ question,
78
+ `Emit one substantive ${axis === "standards" ? "Standards" : "Spec"} report. Incidental cross-axis content, headings, and finding-count annotations are presentation matters, not defects.`,
79
+ renderAxisPriorityClause(contract),
80
+ "Before making any substantive claim, actually use the canonical Skill and fixed range facts; a citation without reading those facts is not evidence.",
81
+ "Independently acquire issue, authority, and context; do not treat caller prose as controlling authority or as the Spec source.",
82
+ "You may use any supplied common fact, including facts relevant to the other axis; access and citation do not change the assigned question.",
83
+ "The returned report is the complete output envelope and its UTF-8 bytes are preserved verbatim; no heading parser, sanitizer, section splitter, rewrite, aggregation, or replacement leg follows.",
84
+ ].join("\n");
85
+ }
86
+
87
+ export type ConstructedReviewerLeg = Readonly<{ axis: "standards" | "spec"; prompt: ReviewerPromptText }>;
88
+ export type ConstructedReviewerDispatch = Readonly<{
89
+ identity: string;
90
+ recipe: "reviewer-common-bundle-v1";
91
+ input: Readonly<{
92
+ canonicalSkill: ReviewerPromptText;
93
+ construction: ReviewerConstructionIdentity;
94
+ }>;
95
+ targetSnapshot: ReviewerPinnedTarget;
96
+ range: ReviewerRange;
97
+ legs: readonly ConstructedReviewerLeg[];
98
+ }>;
99
+
100
+ /** Deterministic compiler: fixed target/range plus packaged Skill in, dispatch text out. */
101
+ export function constructReviewerDispatch(input: {
102
+ identity: string;
103
+ canonicalSkill: string;
104
+ target: ReviewerPinnedTarget;
105
+ range: ReviewerRange;
106
+ reviewScopeKeys?: readonly string[];
107
+ }): ConstructedReviewerDispatch {
108
+ const common = [
109
+ `Target: ${input.range.target}`,
110
+ `Base: ${input.range.base}`,
111
+ `Diff: ${input.range.diffCommand}`,
112
+ reviewerScopePrompt(input.reviewScopeKeys),
113
+ `Recipe: ${REVIEWER_CONSTRUCTION_RECIPE.recipeId}@${REVIEWER_CONSTRUCTION_RECIPE.version}`,
114
+ "Canonical-Skill:",
115
+ input.canonicalSkill,
116
+ "Fixed-Range:",
117
+ JSON.stringify(input.range, null, 2),
118
+ ].join("\n");
119
+ const axes = [{ axis: "standards" as const }, { axis: "spec" as const }];
120
+ const legs = axes.map((x) =>
121
+ Object.freeze({
122
+ axis: x.axis,
123
+ prompt: `${common}\n${reviewerAxisMethodAdapter(x.axis)}\n`,
124
+ }),
125
+ );
126
+ return Object.freeze({
127
+ identity: input.identity,
128
+ recipe: "reviewer-common-bundle-v1",
129
+ input: Object.freeze({
130
+ canonicalSkill: input.canonicalSkill,
131
+ construction: REVIEWER_CONSTRUCTION_RECIPE,
132
+ }),
133
+ targetSnapshot: input.target,
134
+ range: input.range,
135
+ legs: Object.freeze(legs),
136
+ });
137
+ }
@@ -0,0 +1,94 @@
1
+ import { sameReviewerPinnedTarget } from "./reviewer-git-snapshot.ts";
2
+ import { immutableReviewerPin, type ReviewerPinnedGitReader, type ReviewerPinnedTarget } from "./reviewer-pinned-git.ts";
3
+ export { createReviewerPinnedGitReader, immutableReviewerPin, type ReviewerPinnedGitReader, type ReviewerPinnedTarget, type ReviewerRange } from "./reviewer-pinned-git.ts";
4
+ import { isReviewerPromptText, sameReviewerPromptText, type ReviewerPromptText } from "./reviewer-prompt-identity.ts";
5
+ import { sha256Hex } from "./sha256.ts";
6
+ import { constructReviewerDispatch, type ConstructedReviewerDispatch } from "./reviewer-construction.ts";
7
+ import { ReviewerCorrectablePreflightError } from "./reviewer-preflight-error.ts";
8
+ export { sha256Hex } from "./sha256.ts";
9
+ export { isReviewerPromptText as isReviewerPromptIdentity, sameReviewerPromptText as sameReviewerPromptIdentity, type ReviewerPromptText as ReviewerPromptIdentity } from "./reviewer-prompt-identity.ts";
10
+
11
+ export type AcceptedReviewerLeg = ConstructedReviewerDispatch["legs"][number];
12
+ export type AcceptedReviewerDispatch = ConstructedReviewerDispatch;
13
+ export type AcceptedReviewerExecution = Readonly<{
14
+ identity: string;
15
+ recipe: "reviewer-common-bundle-v1";
16
+ targetSnapshot: ReviewerPinnedTarget;
17
+ legs: readonly AcceptedReviewerLeg[];
18
+ }>;
19
+ export const REVIEWER_PREFLIGHT_VIOLATIONS = ["base-invalid", "range-invalid", "prompt-identity-invalid", "target-drift"] as const;
20
+ export type ReviewerPreflightViolation = (typeof REVIEWER_PREFLIGHT_VIOLATIONS)[number];
21
+ export type ReviewerDispatchResult =
22
+ | Readonly<{ status: "rejected"; identity: string; violations: readonly ReviewerPreflightViolation[]; diagnostic: string }>
23
+ | Readonly<{ status: "accepted"; dispatch: AcceptedReviewerDispatch; results: unknown }>;
24
+ export type ReviewerDecisionEvidence =
25
+ | Readonly<{ disposition: "rejected"; identity: string; violations: readonly ReviewerPreflightViolation[]; started: false }>
26
+ | Readonly<{ disposition: "accepted"; identity: string; dispatch: AcceptedReviewerDispatch }>;
27
+ type DispatcherDependencies = Readonly<{
28
+ canonicalSkill: string;
29
+ reader: ReviewerPinnedGitReader;
30
+ reviewScopeKeys?: readonly string[];
31
+ run(execution: AcceptedReviewerExecution, invocation: unknown): Promise<unknown>;
32
+ decisionEvidence?(decision: ReviewerDecisionEvidence): void;
33
+ }>;
34
+ export class ReviewerPreflightError extends Error {
35
+ constructor(readonly code: ReviewerPreflightViolation, readonly diagnostic = `${code} constraint failed`) {
36
+ super(`${code}: ${diagnostic}`);
37
+ }
38
+ }
39
+ export function toReviewerExecution(dispatch: AcceptedReviewerDispatch): AcceptedReviewerExecution {
40
+ return Object.freeze({
41
+ identity: dispatch.identity,
42
+ recipe: dispatch.recipe,
43
+ targetSnapshot: immutableReviewerPin(dispatch.targetSnapshot),
44
+ legs: Object.freeze(dispatch.legs.map((l) => Object.freeze({ axis: l.axis, prompt: l.prompt }))),
45
+ });
46
+ }
47
+ const preflight = (error: unknown): ReviewerPreflightError | undefined => {
48
+ if (error instanceof ReviewerPreflightError) return error;
49
+ if (error instanceof ReviewerCorrectablePreflightError) {
50
+ return new ReviewerPreflightError(error.code as ReviewerPreflightViolation, error.diagnostic);
51
+ }
52
+ return undefined;
53
+ };
54
+ export function createReviewerDispatcher(d: DispatcherDependencies) {
55
+ const target = immutableReviewerPin(d.reader.pin);
56
+ let started = false;
57
+ return Object.freeze({
58
+ async dispatch(baseRevision: string, invocation?: unknown): Promise<ReviewerDispatchResult> {
59
+ if (started) throw new Error("Reviewer fixed dispatch can start exactly once");
60
+ const identity = sha256Hex(JSON.stringify({
61
+ baseRevision,
62
+ target: target.targetHead,
63
+ canonicalSkill: sha256Hex(d.canonicalSkill),
64
+ }));
65
+ let dispatch: AcceptedReviewerDispatch;
66
+ try {
67
+ const base = await d.reader.resolve(baseRevision);
68
+ const range = await d.reader.range(base);
69
+ dispatch = constructReviewerDispatch({
70
+ identity,
71
+ canonicalSkill: d.canonicalSkill,
72
+ target,
73
+ range,
74
+ ...(d.reviewScopeKeys === undefined ? {} : { reviewScopeKeys: d.reviewScopeKeys }),
75
+ });
76
+ if (!sameReviewerPinnedTarget(await d.reader.snapshot(), target)) {
77
+ throw new ReviewerPreflightError("target-drift", "pinned target snapshot changed before child execution");
78
+ }
79
+ } catch (error) {
80
+ const p = preflight(error);
81
+ if (!p) throw error;
82
+ d.decisionEvidence?.(Object.freeze({ disposition: "rejected", identity, violations: Object.freeze([p.code]), started: false }));
83
+ return Object.freeze({ status: "rejected", identity, violations: Object.freeze([p.code]), diagnostic: p.diagnostic });
84
+ }
85
+ started = true;
86
+ d.decisionEvidence?.(Object.freeze({ disposition: "accepted", identity, dispatch }));
87
+ const results = await d.run(toReviewerExecution(dispatch), invocation);
88
+ return Object.freeze({ status: "accepted", dispatch, results });
89
+ },
90
+ });
91
+ }
92
+
93
+ export type { ReviewerPromptText };
94
+ export { isReviewerPromptText, sameReviewerPromptText };
@@ -0,0 +1,206 @@
1
+ import { sameReviewerPinnedTarget } from "./reviewer-git-snapshot.ts";
2
+ import { isReviewerPromptText, sameReviewerPromptText, type ReviewerPromptText } from "./reviewer-prompt-identity.ts";
3
+ import type { ReviewerDispatchRunResult } from "./reviewer-agent.ts";
4
+ import {
5
+ REVIEWER_PREFLIGHT_VIOLATIONS,
6
+ type ReviewerPreflightViolation,
7
+ type ReviewerPinnedTarget,
8
+ type AcceptedReviewerDispatch,
9
+ } from "./reviewer-dispatch.ts";
10
+
11
+ export type ReviewerUsage = Readonly<{
12
+ input: number; output: number; cacheRead: number; cacheWrite: number;
13
+ cacheWrite1h?: number; reasoning?: number; totalTokens: number;
14
+ cost: Readonly<{ input: number; output: number; cacheRead: number; cacheWrite: number; total: number }>;
15
+ }>;
16
+ export type ReviewerTargetSnapshot = ReviewerPinnedTarget;
17
+ export type ReviewerWorkspaceDisposition = "deleted" | "not-created" | Readonly<{ retained: string }>;
18
+ export type ReviewerFailureClassification = "cancelled" | "provider" | "snapshot" | "workspace" | "child" | "unknown";
19
+
20
+ export type ReviewerCompiledLegEvidence = AcceptedReviewerDispatch["legs"][number];
21
+ export type ReviewerAcceptedEvidence = Readonly<
22
+ Omit<AcceptedReviewerDispatch, "targetSnapshot"> & {
23
+ target: AcceptedReviewerDispatch["targetSnapshot"];
24
+ }
25
+ >;
26
+ export function projectAcceptedDispatch(dispatch: AcceptedReviewerDispatch): ReviewerEvidenceEvent {
27
+ return {
28
+ source: "reviewer-dispatch", type: "accepted", identity: dispatch.identity,
29
+ recipe: dispatch.recipe, input: dispatch.input, target: dispatch.targetSnapshot,
30
+ range: dispatch.range, legs: dispatch.legs,
31
+ };
32
+ }
33
+
34
+ type ReviewerLegResultEvidenceCommon = Readonly<{
35
+ dispatchIdentity: string;
36
+ axis: "standards" | "spec";
37
+ prompt: ReviewerPromptText;
38
+ target: ReviewerPinnedTarget;
39
+ workspaceDisposition: ReviewerWorkspaceDisposition;
40
+ }>;
41
+ export type ReviewerLegResultEvidence = ReviewerLegResultEvidenceCommon & (
42
+ | Readonly<{ status: "successful"; report: string; usage: ReviewerUsage; failure?: never }>
43
+ | Readonly<{ status: "failed"; failure: ReviewerFailureClassification; diagnostic: string; report?: never; usage?: never }>
44
+ );
45
+ export type ReviewerEvidenceEvent =
46
+ | (Readonly<{ source: "reviewer-dispatch"; type: "rejected"; identity: string; violations: readonly ReviewerPreflightViolation[]; started: false }>)
47
+ | Readonly<{ source: "reviewer-dispatch"; type: "closed-attempt"; identity: string; reason: "acceptance-closed"; started: false }>
48
+ | (Readonly<{ source: "reviewer-dispatch"; type: "accepted" }> & ReviewerAcceptedEvidence)
49
+ | Readonly<{ source: "reviewer-agent"; type: "dispatch-started"; dispatchIdentity: string; cardinality: 1 | 2 }>
50
+ | (Readonly<{ source: "reviewer-agent"; type: "leg-settled" }> & ReviewerLegResultEvidence)
51
+ | Readonly<{ source: "reviewer-runtime"; type: "fatal"; diagnostics: string; cause: unknown; targetSnapshot?: ReviewerTargetSnapshot; workspaceDisposition?: ReviewerWorkspaceDisposition }>;
52
+
53
+ export type ReviewerExecutionRecord = Readonly<{
54
+ rejections: readonly Readonly<{ identity: string; violations: readonly ReviewerPreflightViolation[]; started: false }>[];
55
+ closedAttempts?: readonly Readonly<{ identity: string; reason: "acceptance-closed"; started: false }>[];
56
+ accepted?: ReviewerAcceptedEvidence;
57
+ started?: Readonly<{ dispatchIdentity: string; cardinality: 1 | 2 }>;
58
+ results: Readonly<Partial<Record<"standards" | "spec", ReviewerLegResultEvidence>>>;
59
+ }>;
60
+ export type ReviewerExecutionLedger = Readonly<{
61
+ append(event: ReviewerEvidenceEvent): void;
62
+ recordInfrastructureFailure<T>(error: T): T;
63
+ recordForAudit(status: "completed" | "refused"): ReviewerExecutionRecord;
64
+ }>;
65
+
66
+ export function projectReviewerDispatchOutcome(
67
+ ledger: ReviewerExecutionLedger,
68
+ dispatch: AcceptedReviewerDispatch,
69
+ result: ReviewerDispatchRunResult,
70
+ ): void {
71
+ if (result.identity !== dispatch.identity) throw new Error("Reviewer runner identity does not match accepted dispatch");
72
+ if (!sameReviewerPinnedTarget(result.target, dispatch.targetSnapshot)) throw new Error("Reviewer runner target does not match accepted pinned target");
73
+ const expectedAxes = dispatch.legs.map(({ axis }) => axis).sort();
74
+ const actualAxes = Object.keys(result.legs).sort();
75
+ if (actualAxes.length !== expectedAxes.length || actualAxes.some((axis, index) => axis !== expectedAxes[index])) {
76
+ throw new Error(`Reviewer runner result axes do not match accepted dispatch: expected ${expectedAxes.join(",")}; received ${actualAxes.join(",")}`);
77
+ }
78
+ for (const leg of dispatch.legs) {
79
+ const actual = result.legs[leg.axis];
80
+ if (actual === undefined) throw new Error(`Reviewer runner omitted ${leg.axis} result`);
81
+ ledger.append(actual.status === "failed"
82
+ ? { source: "reviewer-agent", type: "leg-settled", dispatchIdentity: dispatch.identity, axis: leg.axis, status: "failed", prompt: actual.prompt, target: actual.target, failure: actual.failure, diagnostic: actual.diagnostic, workspaceDisposition: actual.workspaceDisposition }
83
+ : { source: "reviewer-agent", type: "leg-settled", dispatchIdentity: dispatch.identity, axis: leg.axis, status: "successful", prompt: actual.prompt, target: actual.target, report: actual.report, usage: actual.usage, workspaceDisposition: actual.workspaceDisposition });
84
+ }
85
+ }
86
+
87
+ type FatalEvidence = { diagnostics: string; cause: unknown; targetSnapshot?: ReviewerTargetSnapshot; workspaceDisposition?: ReviewerWorkspaceDisposition };
88
+
89
+ function cloneFreeze<T>(value: T): T {
90
+ if (Array.isArray(value)) return Object.freeze(value.map(cloneFreeze)) as T;
91
+ if (typeof value === "object" && value !== null) {
92
+ const copy: Record<string, unknown> = {};
93
+ for (const [key, item] of Object.entries(value)) copy[key] = cloneFreeze(item);
94
+ return Object.freeze(copy) as T;
95
+ }
96
+ return value;
97
+ }
98
+ function hasExactEventShape(event: object, keys: readonly string[]): boolean {
99
+ const actual = Object.keys(event);
100
+ return actual.length === keys.length && actual.every((key) => keys.includes(key));
101
+ }
102
+
103
+ function fatal(error: unknown): FatalEvidence {
104
+ const record = typeof error === "object" && error !== null ? error as Record<string, unknown> : undefined;
105
+ return cloneFreeze({
106
+ diagnostics: "infrastructure-failure",
107
+ cause: error,
108
+ ...(record?.targetSnapshot === undefined ? {} : { targetSnapshot: record.targetSnapshot as ReviewerTargetSnapshot }),
109
+ ...(record?.workspaceDisposition === undefined ? {} : { workspaceDisposition: record.workspaceDisposition as ReviewerWorkspaceDisposition }),
110
+ });
111
+ }
112
+
113
+ export function createReviewerExecutionLedger(): ReviewerExecutionLedger {
114
+ const rejections: Array<{ identity: string; violations: readonly ReviewerPreflightViolation[]; started: false }> = [];
115
+ const closedAttempts: Array<{ identity: string; reason: "acceptance-closed"; started: false }> = [];
116
+ let accepted: ReviewerAcceptedEvidence | undefined;
117
+ let started: { dispatchIdentity: string; cardinality: 1 | 2 } | undefined;
118
+ const results: Partial<Record<"standards" | "spec", ReviewerLegResultEvidence>> = {};
119
+ let infrastructureFailure: FatalEvidence | undefined;
120
+
121
+ function append(raw: ReviewerEvidenceEvent): void {
122
+ const event = cloneFreeze(raw);
123
+ if (event.source === "reviewer-dispatch" && event.type === "rejected") {
124
+ if (!hasExactEventShape(event, ["source", "type", "identity", "violations", "started"]) || event.started !== false ||
125
+ event.violations.length === 0 || event.violations.some((code) => !REVIEWER_PREFLIGHT_VIOLATIONS.includes(code)))
126
+ throw new Error("Rejected dispatch must contain only closed bounded non-start evidence");
127
+ if (accepted !== undefined || started !== undefined) throw new Error("Rejection cannot follow an accepted dispatch");
128
+ rejections.push(cloneFreeze({ identity: event.identity, violations: event.violations, started: false }));
129
+ return;
130
+ }
131
+ if (event.source === "reviewer-dispatch" && event.type === "closed-attempt") {
132
+ if (!hasExactEventShape(event, ["source", "type", "identity", "reason", "started"]) || event.reason !== "acceptance-closed" || event.started !== false)
133
+ throw new Error("Closed attempt must contain only immutable non-start outcome evidence");
134
+ if (accepted === undefined) throw new Error("Closed attempt requires a closed acceptance lifecycle");
135
+ closedAttempts.push(cloneFreeze({ identity: event.identity, reason: event.reason, started: false }));
136
+ return;
137
+ }
138
+ if (event.source === "reviewer-dispatch" && event.type === "accepted") {
139
+ if (accepted !== undefined) throw new Error("Projection permits exactly one accepted dispatch");
140
+ const axes = event.legs.map((leg) => leg.axis);
141
+ if (axes[0] !== "standards" || (axes.length !== 1 && (axes.length !== 2 || axes[1] !== "spec")))
142
+ throw new Error("Accepted dispatch sibling axes disagree");
143
+ if (!isReviewerPromptText(event.input.canonicalSkill))
144
+ throw new Error("Accepted canonical Skill must be plain text");
145
+ for (const leg of event.legs) {
146
+ if (!isReviewerPromptText(leg.prompt))
147
+ throw new Error("Accepted compiled prompt must be plain text");
148
+ }
149
+ accepted = event;
150
+ return;
151
+ }
152
+ if (event.source === "reviewer-runtime" && event.type === "fatal") {
153
+ if (infrastructureFailure === undefined) infrastructureFailure = cloneFreeze({
154
+ diagnostics: event.diagnostics,
155
+ cause: event.cause,
156
+ ...(event.targetSnapshot === undefined ? {} : { targetSnapshot: event.targetSnapshot }),
157
+ ...(event.workspaceDisposition === undefined ? {} : { workspaceDisposition: event.workspaceDisposition }),
158
+ });
159
+ return;
160
+ }
161
+ if (event.type === "dispatch-started") {
162
+ if (accepted === undefined || event.dispatchIdentity !== accepted.identity) throw new Error("Start requires its accepted dispatch");
163
+ if (started !== undefined) throw new Error("Accepted dispatch can start exactly once");
164
+ if (event.cardinality !== accepted.legs.length) throw new Error("Dispatch start cardinality disagrees with acceptance");
165
+ started = cloneFreeze({ dispatchIdentity: event.dispatchIdentity, cardinality: event.cardinality });
166
+ return;
167
+ }
168
+ if (accepted === undefined || started === undefined || event.dispatchIdentity !== accepted.identity)
169
+ throw new Error("Runner result requires its irreversible accepted dispatch start");
170
+ if (results[event.axis] !== undefined) throw new Error(`Reviewer ${event.axis} result can settle exactly once`);
171
+ const compiled = accepted.legs.find((leg) => leg.axis === event.axis);
172
+ if (compiled === undefined) throw new Error(`Reviewer ${event.axis} was not an accepted leg`);
173
+ if (!sameReviewerPromptText(event.prompt, compiled.prompt) || !isReviewerPromptText(event.prompt))
174
+ throw new Error("Actual runner prompt does not exactly match compiled prompt text");
175
+ if (!sameReviewerPinnedTarget(event.target, accepted.target)) throw new Error("Runner target does not match shared pinned target");
176
+ if (event.status === "successful") {
177
+ if (typeof event.report !== "string" || event.report.length === 0 || event.failure !== undefined) throw new Error("Successful settlement requires a report");
178
+ } else if (event.failure === undefined || event.report !== undefined) {
179
+ throw new Error("Failed settlement requires a bounded failure classification and no report");
180
+ }
181
+ results[event.axis] = event;
182
+ }
183
+
184
+ function recordInfrastructureFailure<T>(error: T): T {
185
+ if (infrastructureFailure === undefined) {
186
+ const evidence = fatal(error);
187
+ append({ source: "reviewer-runtime", type: "fatal", ...evidence });
188
+ }
189
+ return error;
190
+ }
191
+ function recordForAudit(status: "completed" | "refused"): ReviewerExecutionRecord {
192
+ if (infrastructureFailure !== undefined) throw Object.assign(new Error(`Reviewer infrastructure previously failed: ${infrastructureFailure.diagnostics}`), { fatalReviewerInfrastructure: true as const });
193
+ if (status === "completed") {
194
+ if (accepted === undefined || started === undefined) throw new Error("Reviewer completed requires exactly one accepted and started dispatch");
195
+ const expected = accepted.legs.map((leg) => leg.axis);
196
+ if (expected.some((axis) => results[axis]?.status !== "successful") || Object.keys(results).length !== expected.length)
197
+ throw new Error(expected.length === 2 ? "Reviewer completed requires both axes settled successfully" : "Reviewer completed requires Standards settled successfully and no Spec evidence");
198
+ } else if (accepted !== undefined) {
199
+ const expected = accepted.legs.map((leg) => leg.axis);
200
+ if (started === undefined || expected.some((axis) => results[axis] === undefined) || Object.keys(results).length !== expected.length)
201
+ throw new Error("Reviewer refused after acceptance requires every expected leg terminal outcome");
202
+ }
203
+ return cloneFreeze({ rejections, closedAttempts, ...(accepted === undefined ? {} : { accepted }), ...(started === undefined ? {} : { started }), results });
204
+ }
205
+ return Object.freeze({ append, recordInfrastructureFailure, recordForAudit });
206
+ }
@@ -0,0 +1,18 @@
1
+ import type { ReviewerFailureClassification } from "./reviewer-execution-ledger.ts";
2
+
3
+ export function normalizeReviewerFailureDiagnostic(error: unknown, failure: ReviewerFailureClassification): string {
4
+ const original = error instanceof Error && Object.hasOwn(error, "reviewerOriginal")
5
+ ? (error as Error & { reviewerOriginal: unknown }).reviewerOriginal
6
+ : error;
7
+ const message = original instanceof Error
8
+ ? original.message.trim()
9
+ : typeof original === "string"
10
+ ? original.trim()
11
+ : typeof original === "object" && original !== null && typeof (original as { errorMessage?: unknown }).errorMessage === "string"
12
+ ? (original as { errorMessage: string }).errorMessage.trim()
13
+ : "";
14
+ if (message !== "") return message;
15
+ return failure === "provider"
16
+ ? "Reviewer Agent provider supplied no diagnostic details"
17
+ : "Reviewer Agent failed without diagnostic details";
18
+ }
@@ -0,0 +1,53 @@
1
+ export const REVIEW_REF_PREFIXES = ["refs/heads", "refs/tags", "refs/remotes"] as const;
2
+
3
+ /** Closed identity for a ref: annotated tags retain both tag object and peeled commit. */
4
+ export type ReviewerRefEntry = Readonly<{
5
+ objectId: string;
6
+ peeledCommitId: string | null;
7
+ }>;
8
+ export type ReviewerRefMap = Readonly<Record<string, ReviewerRefEntry>>;
9
+
10
+ export function parseReviewerRefSnapshot(raw: string): Record<string, ReviewerRefEntry> {
11
+ const refs: Record<string, ReviewerRefEntry> = {};
12
+ for (const line of raw.split("\n")) {
13
+ if (!line) continue;
14
+ const fields = line.split("\0");
15
+ if (fields.length !== 5 || !fields[0] || !fields[1] || !fields[3]) {
16
+ throw new Error(`Malformed Git ref snapshot line: ${line}`);
17
+ }
18
+ const peeledCommitId = fields[3] === "commit"
19
+ ? fields[1]
20
+ : fields[4] === "commit" && fields[2] ? fields[2] : null;
21
+ refs[fields[0]] = Object.freeze({
22
+ objectId: fields[1],
23
+ peeledCommitId,
24
+ });
25
+ }
26
+ return refs;
27
+ }
28
+
29
+ export function reviewerRefSnapshotArgs(): string[] {
30
+ return ["for-each-ref", "--format=%(refname)%00%(objectname)%00%(*objectname)%00%(objecttype)%00%(*objecttype)", ...REVIEW_REF_PREFIXES];
31
+ }
32
+
33
+ export function immutableReviewerRefs(refs: ReviewerRefMap): ReviewerRefMap {
34
+ return Object.freeze(Object.fromEntries(Object.entries(refs)
35
+ .sort(([a], [b]) => a.localeCompare(b))
36
+ .map(([name, entry]) => [name, Object.freeze({ objectId: entry.objectId, peeledCommitId: entry.peeledCommitId })])));
37
+ }
38
+
39
+ export function sameReviewerPinnedTarget(
40
+ actual: Readonly<{ repositoryRoot: string; objectFormat: "sha1" | "sha256"; targetHead: string; refs: ReviewerRefMap }>,
41
+ expected: Readonly<{ repositoryRoot: string; objectFormat: "sha1" | "sha256"; targetHead: string; refs: ReviewerRefMap }>,
42
+ ): boolean {
43
+ return actual.repositoryRoot === expected.repositoryRoot && actual.objectFormat === expected.objectFormat &&
44
+ actual.targetHead === expected.targetHead && sameReviewerRefs(actual.refs, expected.refs);
45
+ }
46
+
47
+ export function sameReviewerRefs(actual: ReviewerRefMap, expected: ReviewerRefMap): boolean {
48
+ const actualEntries = Object.entries(actual).sort(([left], [right]) => left.localeCompare(right));
49
+ const expectedEntries = Object.entries(expected).sort(([left], [right]) => left.localeCompare(right));
50
+ return actualEntries.length === expectedEntries.length && actualEntries.every(([name, value], index) =>
51
+ name === expectedEntries[index]?.[0] && value.objectId === expectedEntries[index]?.[1].objectId &&
52
+ value.peeledCommitId === expectedEntries[index]?.[1].peeledCommitId);
53
+ }
@@ -0,0 +1,144 @@
1
+ import { execFile } from "node:child_process";
2
+ import { access, realpath } from "node:fs/promises";
3
+ import { promisify } from "node:util";
4
+
5
+ import { immutableReviewerRefs, parseReviewerRefSnapshot, reviewerRefSnapshotArgs, type ReviewerRefMap } from "./reviewer-git-snapshot.ts";
6
+ import { sha256Hex } from "./sha256.ts";
7
+ import { ReviewerCorrectablePreflightError } from "./reviewer-preflight-error.ts";
8
+
9
+ export type ReviewerObjectFormat = "sha1" | "sha256";
10
+ export type ReviewerPinnedTarget = Readonly<{
11
+ repositoryRoot: string;
12
+ objectFormat: ReviewerObjectFormat;
13
+ targetHead: string;
14
+ refs: ReviewerRefMap;
15
+ }>;
16
+ export type ReviewerRange = Readonly<{
17
+ base: string;
18
+ target: string;
19
+ diffCommand: string;
20
+ diffSha256: string;
21
+ commits: readonly string[];
22
+ }>;
23
+ export type ReviewerPinnedGitReader = {
24
+ pin: ReviewerPinnedTarget;
25
+ snapshot(): Promise<ReviewerPinnedTarget>;
26
+ resolve(base: string): Promise<string>;
27
+ range(base: string): Promise<ReviewerRange>;
28
+ };
29
+
30
+ const execFileAsync = promisify(execFile);
31
+ type GitProcessError = Error & Readonly<{ code: number | string | null; signal: NodeJS.Signals | null; timedOut: boolean; aborted: boolean; stderr: string; stdout: string }>;
32
+ async function execGit<T extends "utf8" | "buffer">(args: readonly string[], options: { encoding: T; maxBuffer?: number }): Promise<{ stdout: T extends "buffer" ? Buffer : string; stderr: string }> {
33
+ try { return await execFileAsync("git", args, options) as unknown as { stdout: T extends "buffer" ? Buffer : string; stderr: string }; }
34
+ catch (error) {
35
+ const source = error as Partial<GitProcessError>;
36
+ const wrapped = new Error("git process failed", { cause: error }) as GitProcessError;
37
+ Object.assign(wrapped, { code: source.code ?? null, signal: source.signal ?? null, timedOut: (source as { killed?: unknown }).killed === true && source.signal === "SIGTERM", aborted: source.name === "AbortError", stderr: String(source.stderr ?? ""), stdout: String(source.stdout ?? "") });
38
+ throw wrapped;
39
+ }
40
+ }
41
+ function exitCode(error: unknown): number | undefined { const code = typeof error === "object" && error !== null ? (error as { code?: unknown }).code : undefined; return typeof code === "number" ? code : undefined; }
42
+ async function repositoryIsAvailable(root: string): Promise<{ available: boolean; cause?: unknown }> { try { await access(`${root}/.git`); return { available: true }; } catch (cause) { return { available: false, cause }; } }
43
+ export const immutableReviewerPin = (pin: ReviewerPinnedTarget): ReviewerPinnedTarget => Object.freeze({
44
+ repositoryRoot: pin.repositoryRoot, objectFormat: pin.objectFormat, targetHead: pin.targetHead, refs: immutableReviewerRefs(pin.refs),
45
+ });
46
+ async function gitText(root: string, args: readonly string[]): Promise<string> {
47
+ const { stdout } = await execGit(["-C", root, ...args], { encoding: "utf8" });
48
+ return stdout.trim();
49
+ }
50
+
51
+ /** Concrete fixed-point Git I/O; dispatch owns all policy applied to these reads. */
52
+ export async function createReviewerPinnedGitReader(root = process.cwd()): Promise<ReviewerPinnedGitReader> {
53
+ const discoveredRoot = await gitText(root, ["rev-parse", "--show-toplevel"]);
54
+ const repositoryRoot = await realpath(discoveredRoot);
55
+ const objectFormat = await gitText(repositoryRoot, ["rev-parse", "--show-object-format"]);
56
+ if (objectFormat !== "sha1" && objectFormat !== "sha256") throw new Error("Unsupported Git object format");
57
+ const oidWidth = objectFormat === "sha1" ? 40 : 64;
58
+ const targetHead = await gitText(repositoryRoot, ["rev-parse", "HEAD^{commit}"]);
59
+ const reachableCommitIds = Object.freeze((await gitText(repositoryRoot, ["rev-list", targetHead])).split("\n").filter(Boolean));
60
+ const refs = parseReviewerRefSnapshot(await gitText(repositoryRoot, reviewerRefSnapshotArgs()));
61
+ const pin = immutableReviewerPin({ repositoryRoot, objectFormat, targetHead, refs });
62
+ const invalid = (
63
+ code: "base-invalid" | "range-invalid",
64
+ diagnostic: string,
65
+ cause?: unknown,
66
+ ): never => {
67
+ throw new ReviewerCorrectablePreflightError(code, diagnostic, cause === undefined ? undefined : { cause });
68
+ };
69
+ const symbolic = (base: string): string | undefined => {
70
+ const selected = Object.hasOwn(refs, base) ? base : (() => {
71
+ const candidates = [`refs/heads/${base}`, `refs/tags/${base}`, `refs/remotes/${base}`].filter((name) => Object.hasOwn(refs, name));
72
+ if (candidates.length > 1) invalid("base-invalid", "base revision is ambiguous across pinned refs");
73
+ return candidates[0];
74
+ })();
75
+ if (selected === undefined) return undefined;
76
+ const commit = refs[selected]?.peeledCommitId;
77
+ if (commit === null) invalid("base-invalid", "base revision ref must resolve to a commit");
78
+ return commit ?? undefined;
79
+ };
80
+ return Object.freeze({
81
+ pin,
82
+ async snapshot() {
83
+ const liveObjectFormat = await gitText(repositoryRoot, ["rev-parse", "--show-object-format"]);
84
+ if (liveObjectFormat !== "sha1" && liveObjectFormat !== "sha256") throw new Error("Unsupported Git object format");
85
+ return immutableReviewerPin({ repositoryRoot, objectFormat: liveObjectFormat, targetHead: await gitText(repositoryRoot, ["rev-parse", "HEAD^{commit}"]), refs: parseReviewerRefSnapshot(await gitText(repositoryRoot, reviewerRefSnapshotArgs())) });
86
+ },
87
+ async resolve(base: string) {
88
+ if (!/^[A-Za-z0-9._/~^+-]+$/.test(base) || base.startsWith("-") || base.includes("..") || base.includes("@{")) {
89
+ invalid("base-invalid", "base revision syntax is invalid or uses a forbidden revision form");
90
+ }
91
+ let commit: string | undefined;
92
+ const headExpression = /^HEAD((?:~[0-9]+|\^[0-9]+)*)$/.exec(base);
93
+ if (headExpression) {
94
+ try {
95
+ commit = await gitText(repositoryRoot, ["rev-parse", "--verify", `${targetHead}${headExpression[1]}^{commit}`]);
96
+ } catch (error) {
97
+ if (exitCode(error) === 128) {
98
+ const repository = await repositoryIsAvailable(repositoryRoot);
99
+ if (repository.available) invalid("base-invalid", "base revision HEAD ancestry expression must resolve to a reachable commit", error);
100
+ }
101
+ throw error;
102
+ }
103
+ } else if (new RegExp(`^[0-9a-f]{${oidWidth}}$`).test(base)) commit = base;
104
+ else if (new RegExp(`^[0-9a-f]{4,${oidWidth - 1}}$`).test(base) && !(objectFormat === "sha256" && base.length === 40)) {
105
+ const matches = reachableCommitIds.filter((candidate) => candidate.startsWith(base));
106
+ if (matches.length !== 1) invalid("base-invalid", "base revision abbreviation must identify exactly one reachable commit");
107
+ commit = matches[0];
108
+ } else commit = symbolic(base);
109
+ if (commit === undefined) invalid("base-invalid", "base revision must name an existing pinned ref or reachable commit");
110
+ try { commit = await gitText(repositoryRoot, ["rev-parse", "--verify", `${commit}^{commit}`]); } catch (error) {
111
+ if (exitCode(error) === 128) {
112
+ const repository = await repositoryIsAvailable(repositoryRoot);
113
+ if (repository.available) invalid("base-invalid", "base revision must resolve to an existing commit", error);
114
+ }
115
+ throw error;
116
+ }
117
+ try { await gitText(repositoryRoot, ["merge-base", "--is-ancestor", commit, targetHead]); } catch (error) {
118
+ if (exitCode(error) === 1) invalid("base-invalid", "base revision must be an ancestor of the pinned target", error);
119
+ throw error;
120
+ }
121
+ return commit;
122
+ },
123
+ async range(base: string) {
124
+ let mergeBase: string;
125
+ try {
126
+ mergeBase = await gitText(repositoryRoot, ["merge-base", base, targetHead]);
127
+ } catch (error) {
128
+ if (exitCode(error) === 1) {
129
+ invalid("range-invalid", "review range requires a common ancestor for base and pinned target", error);
130
+ }
131
+ throw error;
132
+ }
133
+ if (!mergeBase) invalid("range-invalid", "review range requires a common ancestor for base and pinned target");
134
+ const diffCommand = `git diff ${mergeBase}...${targetHead}`;
135
+ const [{ stdout: diff }, commitsText] = await Promise.all([
136
+ execGit(["-C", repositoryRoot, "diff", `${mergeBase}...${targetHead}`], { encoding: "buffer", maxBuffer: 64 * 1024 * 1024 }),
137
+ gitText(repositoryRoot, ["rev-list", "--reverse", `${mergeBase}..${targetHead}`]),
138
+ ]);
139
+ if (diff.length === 0) invalid("range-invalid", "review range must contain a non-empty diff between base and pinned target");
140
+ return Object.freeze({ base: mergeBase, target: targetHead, diffCommand, diffSha256: sha256Hex(Uint8Array.from(diff)), commits: Object.freeze(commitsText ? commitsText.split("\n") : []) });
141
+ },
142
+
143
+ });
144
+ }