ai-foreman 1.7.13 → 1.7.15

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 (48) hide show
  1. package/README.md +33 -3
  2. package/dist/adapters/claude.d.ts +5 -0
  3. package/dist/adapters/claude.js +124 -16
  4. package/dist/adapters/codex.d.ts +2 -0
  5. package/dist/adapters/codex.js +32 -8
  6. package/dist/adapters/recovering.d.ts +2 -0
  7. package/dist/adapters/recovering.js +6 -0
  8. package/dist/adapters/types.d.ts +22 -0
  9. package/dist/agentRun.js +24 -4
  10. package/dist/branch/currentGuard.d.ts +2 -0
  11. package/dist/branch/currentGuard.js +9 -0
  12. package/dist/branch/finalization.d.ts +22 -0
  13. package/dist/branch/finalization.js +120 -0
  14. package/dist/branch/git.js +7 -2
  15. package/dist/branch/runner.d.ts +3 -2
  16. package/dist/branch/runner.js +277 -75
  17. package/dist/buildRuns.d.ts +1 -0
  18. package/dist/buildRuns.js +33 -3
  19. package/dist/cli/start.js +314 -86
  20. package/dist/cli/state.d.ts +4 -0
  21. package/dist/cli/state.js +69 -0
  22. package/dist/continuity.d.ts +4 -0
  23. package/dist/continuity.js +26 -3
  24. package/dist/foreman.d.ts +22 -4
  25. package/dist/foreman.js +105 -19
  26. package/dist/handoffs.d.ts +5 -2
  27. package/dist/handoffs.js +93 -12
  28. package/dist/index.js +2 -0
  29. package/dist/qaFailureDelivery.d.ts +100 -0
  30. package/dist/qaFailureDelivery.js +570 -0
  31. package/dist/qaProtocolV2.d.ts +233 -0
  32. package/dist/qaProtocolV2.js +114 -0
  33. package/dist/qaRecovery.d.ts +15 -3
  34. package/dist/qaRecovery.js +477 -83
  35. package/dist/qaReview.d.ts +42 -17
  36. package/dist/qaReview.js +1216 -457
  37. package/dist/qaRuntime.d.ts +14 -0
  38. package/dist/qaRuntime.js +44 -0
  39. package/dist/qaSnapshot.d.ts +34 -0
  40. package/dist/qaSnapshot.js +302 -34
  41. package/dist/roles.js +1 -0
  42. package/dist/stateTransfer.d.ts +57 -0
  43. package/dist/stateTransfer.js +644 -0
  44. package/dist/statusReporter.d.ts +2 -0
  45. package/dist/statusReporter.js +6 -0
  46. package/dist/workflowDb.d.ts +245 -2
  47. package/dist/workflowDb.js +647 -6
  48. package/package.json +15 -3
@@ -0,0 +1,233 @@
1
+ import type { QaFindingRefV2 } from "rafi-spec";
2
+ export declare const QA_PROTOCOL_VERSION: 2;
3
+ export type QaProtocolState = "idle" | "source-frozen" | "review-ready" | "turn-intended" | "turn-uncertain" | "review-failed" | "remediation-intended" | "remediation-uncertain" | "recheck-required" | "operator-menu" | "passed" | "waived" | "finalizing" | "completed";
4
+ export interface FrozenQaSourceStateV2 {
5
+ version: 2;
6
+ runId: string;
7
+ ticketId: string;
8
+ originDigest: string;
9
+ contentDigest: string;
10
+ digest: string;
11
+ capturedAt: string;
12
+ paths: Array<{
13
+ path: string;
14
+ staged: string[];
15
+ unstaged: string[];
16
+ headObject?: string;
17
+ indexObject?: string;
18
+ worktreeObject?: string;
19
+ untracked?: {
20
+ kind: "file" | "symlink";
21
+ mode: number;
22
+ digest: string;
23
+ };
24
+ }>;
25
+ }
26
+ export interface QaConfinementV2 {
27
+ version: 2;
28
+ sourceMode: "read-only";
29
+ scratchMode: "isolated";
30
+ networkMode: "disabled" | "provider-required";
31
+ settingsSources: "none";
32
+ environmentDigest: string;
33
+ policyDigest: string;
34
+ }
35
+ export interface QaReviewBasisV2 {
36
+ version: 2;
37
+ ticketDigest: string;
38
+ instructionDigest: string;
39
+ roleInstructionsDigest: string;
40
+ skillsDigest: string;
41
+ runtimeDigest: string;
42
+ validationChecklistDigest: string;
43
+ confinementDigest: string;
44
+ digest: string;
45
+ }
46
+ export interface ProviderSessionRefV2 {
47
+ version: 2;
48
+ provider: "claude" | "codex";
49
+ sessionId: string;
50
+ role: "qa";
51
+ stream: "qa";
52
+ generation: number;
53
+ cwd: string;
54
+ configRoot: string;
55
+ createdAt: string;
56
+ validatedAt: string;
57
+ }
58
+ export interface QaSessionHandleV2 {
59
+ version: 2;
60
+ session: ProviderSessionRefV2;
61
+ sourceStateDigest: string;
62
+ reviewBasisDigest: string;
63
+ confinementDigest: string;
64
+ acceptedHandoffReceiptDigest?: string;
65
+ }
66
+ export interface QaTurnIntentV2 {
67
+ version: 2;
68
+ operationId: string;
69
+ runId: string;
70
+ ticketId: string;
71
+ reviewNumber: number;
72
+ sessionGeneration: number;
73
+ slot: string;
74
+ sourceStateDigest: string;
75
+ reviewBasisDigest: string;
76
+ providerSession: ProviderSessionRefV2;
77
+ instructionDigest: string;
78
+ intendedAt: string;
79
+ }
80
+ export interface QaTurnReceiptV2 {
81
+ version: 2;
82
+ operationId: string;
83
+ dispatch: "not-dispatched" | "completed" | "uncertain";
84
+ providerTurnId?: string;
85
+ providerInstructionDigest?: string;
86
+ rawResponseDigest?: string;
87
+ cleanedResponseDigest?: string;
88
+ eventStreamDigest?: string;
89
+ terminalEventObserved: boolean;
90
+ sourceStateDigest: string;
91
+ reviewBasisDigest: string;
92
+ completedAt: string;
93
+ }
94
+ export interface HandoffAcceptanceReceiptV2 {
95
+ version: 2;
96
+ operationId: string;
97
+ runId: string;
98
+ ticketId: string;
99
+ qaRevision: number;
100
+ sourceStateDigest: string;
101
+ predecessorSourceStateDigest: string;
102
+ reviewBasisDigest: string;
103
+ requiresFullReview: boolean;
104
+ confinementDigest: string;
105
+ predecessor: ProviderSessionRefV2;
106
+ successor: ProviderSessionRefV2;
107
+ manifestDigest: string;
108
+ continuityCheckpointDigest: string;
109
+ acceptanceCheckpointDigest: string;
110
+ packetDigest: string;
111
+ inventoryDigest: string;
112
+ resources: Array<{
113
+ label: string;
114
+ digest: string;
115
+ authoritative: boolean;
116
+ requiredForRecovery: boolean;
117
+ mediaType: string;
118
+ path: string;
119
+ purpose: string;
120
+ bytes: number;
121
+ }>;
122
+ acceptedAt: string;
123
+ }
124
+ export interface BuilderRemediationReceiptV2 {
125
+ version: 2;
126
+ operationId: string;
127
+ runId: string;
128
+ ticketId: string;
129
+ reportDigest: string;
130
+ sourceStateDigest: string;
131
+ requestDigest: string;
132
+ responseDigest: string;
133
+ summaryDigest: string;
134
+ providerTurnId: string;
135
+ completedAt: string;
136
+ }
137
+ export interface QaPassCertificateV2 {
138
+ version: 2;
139
+ certificateId: string;
140
+ runId: string;
141
+ ticketId: string;
142
+ qaRevision: number;
143
+ sourceStateDigest: string;
144
+ reviewBasisDigest: string;
145
+ turnReceiptDigest: string;
146
+ unresolvedReportCount: 0;
147
+ issuedAt: string;
148
+ consumedAt?: string;
149
+ consumedBy?: string;
150
+ }
151
+ export type QaReportDisposition = "open" | "recheck-required" | "superseded" | "verified-fixed" | "waived";
152
+ export interface QaReducerStateV2 {
153
+ version: 2;
154
+ runId: string;
155
+ ticketId: string;
156
+ revision: number;
157
+ state: QaProtocolState;
158
+ reviewNumber: number;
159
+ remediationGeneration: number;
160
+ sourceStateDigest?: string;
161
+ reviewBasisDigest?: string;
162
+ sessionGeneration: number;
163
+ retrySlot?: string;
164
+ openReportDigests: string[];
165
+ passCertificateId?: string;
166
+ }
167
+ export type QaReducerEventV2 = {
168
+ type: "source-frozen";
169
+ sourceStateDigest: string;
170
+ } | {
171
+ type: "review-ready";
172
+ reviewBasisDigest: string;
173
+ sessionGeneration: number;
174
+ } | {
175
+ type: "turn-intended";
176
+ slot: string;
177
+ } | {
178
+ type: "turn-uncertain";
179
+ } | {
180
+ type: "source-drift";
181
+ } | {
182
+ type: "review-failed";
183
+ reportDigest: string;
184
+ } | {
185
+ type: "remediation-intended";
186
+ } | {
187
+ type: "remediation-uncertain";
188
+ } | {
189
+ type: "remediation-failed";
190
+ } | {
191
+ type: "remediation-received";
192
+ } | {
193
+ type: "remediation-source-changed";
194
+ reason: string;
195
+ } | {
196
+ type: "source-drift-before-remediation";
197
+ reason: string;
198
+ } | {
199
+ type: "operator-menu";
200
+ } | {
201
+ type: "review-passed";
202
+ passCertificateId: string;
203
+ } | {
204
+ type: "waived";
205
+ } | {
206
+ type: "pass-invalidated";
207
+ reason: string;
208
+ } | {
209
+ type: "finalization-started";
210
+ } | {
211
+ type: "finalization-invalidated";
212
+ reason: string;
213
+ } | {
214
+ type: "completed";
215
+ };
216
+ export declare function initialQaReducerState(runId: string, ticketId: string): QaReducerStateV2;
217
+ /** Pure, exhaustive transition function. Invalid protocol edges fail closed. */
218
+ export declare function reduceQaState(current: QaReducerStateV2, event: QaReducerEventV2): QaReducerStateV2;
219
+ export declare function canonicalJson(value: unknown): string;
220
+ export declare function qaDigest(domain: string, value: unknown): string;
221
+ export declare function createQaFindingRefs(input: {
222
+ runId: string;
223
+ ticketId: string;
224
+ reviewAttemptId: string;
225
+ reportDigest: string;
226
+ rawFindingIds: string[];
227
+ }): QaFindingRefV2[];
228
+ export declare const QA_REPORT_CORRECTION_POLICY: Readonly<{
229
+ initialSlot: "initial";
230
+ sameSessionCorrectionSlots: readonly string[];
231
+ automaticFreshReconstruction: false;
232
+ operatorFreshReconstruction: false;
233
+ }>;
@@ -0,0 +1,114 @@
1
+ import { createHash } from "node:crypto";
2
+ export const QA_PROTOCOL_VERSION = 2;
3
+ export function initialQaReducerState(runId, ticketId) {
4
+ return { version: 2, runId, ticketId, revision: 0, state: "idle", reviewNumber: 0, remediationGeneration: 0, sessionGeneration: 0, openReportDigests: [] };
5
+ }
6
+ /** Pure, exhaustive transition function. Invalid protocol edges fail closed. */
7
+ export function reduceQaState(current, event) {
8
+ const next = { ...current, revision: current.revision + 1 };
9
+ switch (event.type) {
10
+ case "source-frozen":
11
+ if (!["idle", "recheck-required", "operator-menu", "turn-uncertain", "remediation-uncertain", "passed"].includes(current.state))
12
+ invalid(current, event);
13
+ return { ...next, state: "source-frozen", sourceStateDigest: event.sourceStateDigest, reviewBasisDigest: undefined, retrySlot: undefined, passCertificateId: undefined };
14
+ case "review-ready":
15
+ if (current.state !== "source-frozen")
16
+ invalid(current, event);
17
+ return { ...next, state: "review-ready", reviewBasisDigest: event.reviewBasisDigest, sessionGeneration: event.sessionGeneration, reviewNumber: current.reviewNumber + 1 };
18
+ case "turn-intended":
19
+ if (current.state !== "review-ready" && current.state !== "turn-intended")
20
+ invalid(current, event);
21
+ return { ...next, state: "turn-intended", retrySlot: event.slot };
22
+ case "turn-uncertain":
23
+ if (current.state !== "turn-intended")
24
+ invalid(current, event);
25
+ return { ...next, state: "turn-uncertain" };
26
+ case "source-drift":
27
+ if (current.state !== "turn-intended")
28
+ invalid(current, event);
29
+ return { ...next, state: "recheck-required", retrySlot: undefined };
30
+ case "review-failed":
31
+ if (current.state !== "turn-intended")
32
+ invalid(current, event);
33
+ return { ...next, state: "review-failed", retrySlot: undefined, openReportDigests: unique([...current.openReportDigests, event.reportDigest]) };
34
+ case "remediation-intended":
35
+ if (current.state !== "review-failed")
36
+ invalid(current, event);
37
+ return { ...next, state: "remediation-intended", remediationGeneration: current.remediationGeneration + 1 };
38
+ case "remediation-uncertain":
39
+ if (current.state !== "remediation-intended")
40
+ invalid(current, event);
41
+ return { ...next, state: "remediation-uncertain" };
42
+ case "remediation-failed":
43
+ if (current.state !== "remediation-intended")
44
+ invalid(current, event);
45
+ return { ...next, state: "operator-menu" };
46
+ case "remediation-received":
47
+ if (current.state !== "remediation-intended")
48
+ invalid(current, event);
49
+ return { ...next, state: "recheck-required" };
50
+ case "remediation-source-changed":
51
+ if (!["operator-menu", "remediation-uncertain", "remediation-intended"].includes(current.state) || !event.reason.trim())
52
+ invalid(current, event);
53
+ return { ...next, state: "recheck-required", retrySlot: undefined };
54
+ case "source-drift-before-remediation":
55
+ if (!["review-failed", "remediation-intended"].includes(current.state) || !event.reason.trim())
56
+ invalid(current, event);
57
+ return { ...next, state: "recheck-required", retrySlot: undefined };
58
+ case "operator-menu":
59
+ if (!["idle", "source-frozen", "review-ready", "turn-intended", "turn-uncertain", "review-failed", "remediation-uncertain", "recheck-required"].includes(current.state))
60
+ invalid(current, event);
61
+ return { ...next, state: "operator-menu", retrySlot: undefined };
62
+ case "review-passed":
63
+ if (current.state !== "turn-intended")
64
+ invalid(current, event);
65
+ return { ...next, state: "passed", retrySlot: undefined, openReportDigests: [], passCertificateId: event.passCertificateId };
66
+ case "waived":
67
+ if (current.state !== "operator-menu" && current.state !== "review-failed")
68
+ invalid(current, event);
69
+ return { ...next, state: "waived", openReportDigests: [] };
70
+ case "pass-invalidated":
71
+ if (current.state !== "passed" || !event.reason.trim())
72
+ invalid(current, event);
73
+ return { ...next, state: "operator-menu", retrySlot: undefined, passCertificateId: undefined };
74
+ case "finalization-started":
75
+ if (current.state !== "passed" && current.state !== "waived")
76
+ invalid(current, event);
77
+ return { ...next, state: "finalizing" };
78
+ case "finalization-invalidated":
79
+ if (current.state !== "finalizing" || !event.reason.trim())
80
+ invalid(current, event);
81
+ return { ...next, state: "operator-menu", retrySlot: undefined, passCertificateId: undefined };
82
+ case "completed":
83
+ if (current.state !== "finalizing")
84
+ invalid(current, event);
85
+ return { ...next, state: "completed" };
86
+ }
87
+ }
88
+ export function canonicalJson(value) {
89
+ if (value === null || typeof value !== "object")
90
+ return JSON.stringify(value);
91
+ if (Array.isArray(value))
92
+ return `[${value.map(canonicalJson).join(",")}]`;
93
+ const object = value;
94
+ return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(object[key])}`).join(",")}}`;
95
+ }
96
+ export function qaDigest(domain, value) {
97
+ return createHash("sha256").update(`rafi.qa.v2\0${domain}\0`).update(canonicalJson(value)).digest("hex");
98
+ }
99
+ export function createQaFindingRefs(input) {
100
+ return input.rawFindingIds.map((rawId, ordinal) => {
101
+ const identity = { runId: input.runId, ticketId: input.ticketId, reviewAttemptId: input.reviewAttemptId, reportDigest: input.reportDigest, ordinal, rawId };
102
+ return { version: 2, findingKey: qaDigest("qa-finding-ref", identity), reportDigest: input.reportDigest, reviewAttemptId: input.reviewAttemptId, ordinal, rawId };
103
+ });
104
+ }
105
+ export const QA_REPORT_CORRECTION_POLICY = Object.freeze({
106
+ initialSlot: "initial",
107
+ sameSessionCorrectionSlots: Object.freeze(["correction-1"]),
108
+ automaticFreshReconstruction: false,
109
+ operatorFreshReconstruction: false,
110
+ });
111
+ function invalid(state, event) {
112
+ throw new Error(`invalid QA V2 transition ${state.state} -> ${event.type}`);
113
+ }
114
+ function unique(values) { return [...new Set(values)]; }
@@ -3,6 +3,7 @@ import { type FrozenQaSourceState } from "./qaSnapshot.js";
3
3
  export declare const QA_RECOVERY_ROOT = ".foreman/qa-report-recovery";
4
4
  export declare const QA_CONTEXT_ROOT = ".foreman/qa-recovery-context";
5
5
  export type QaRecoveryIntegrityPolicy = "sealed" | "operator_editable";
6
+ export type QaRecoveryPendingAction = "automatic-recovery" | "successor-acknowledgement" | "qa-correction" | "qa-full-review" | "operator-menu" | "validated-report" | "builder-remediation-pending" | "builder-remediation-in-flight" | "resolved";
6
7
  export interface QaRecoveryResourceV2 {
7
8
  path: string;
8
9
  purpose: string;
@@ -28,7 +29,7 @@ export interface QaRecoveryManifestV2 {
28
29
  attempt: number;
29
30
  recoveryStage: string;
30
31
  correctionTurns: number;
31
- pendingAction: string;
32
+ pendingAction: QaRecoveryPendingAction;
32
33
  originalReviewedStateDigest: string;
33
34
  currentReviewedStateDigest: string;
34
35
  /** Current digest compatibility alias used by handoff APIs. */
@@ -75,7 +76,7 @@ export interface QaRecoveryPacketInput {
75
76
  attempt?: number;
76
77
  recoveryStage: string;
77
78
  correctionTurns?: number;
78
- pendingAction?: string;
79
+ pendingAction?: QaRecoveryPendingAction;
79
80
  resources: Record<string, {
80
81
  value: unknown;
81
82
  purpose: string;
@@ -110,6 +111,14 @@ export declare function compareQaRecoveryReviewedState(packet: QaRecoveryPacket,
110
111
  };
111
112
  /** Create a unique owner-only V2 packet from the immutable state QA reviewed. */
112
113
  export declare function createQaRecoveryPacket(input: QaRecoveryPacketInput): QaRecoveryPacket;
114
+ /**
115
+ * Finish a packet publication whose immutable files reached disk before the
116
+ * authoritative SQLite head was advanced. This is deliberately explicit:
117
+ * ordinary packet loading remains a read-only integrity check.
118
+ */
119
+ export declare function reconcileQaRecoveryPacketPublication(packet: QaRecoveryPacket): QaRecoveryPacket;
120
+ /** Recover only packet files named by an existing SQLite publication intent. */
121
+ export declare function recoverPendingQaRecoveryPublications(projectDir: string, runId: string): QaRecoveryPacket[];
113
122
  export declare function appendQaRecoveryResource(packet: QaRecoveryPacket, relativePath: string, value: string | Buffer | unknown, options: {
114
123
  purpose: string;
115
124
  mediaType?: string;
@@ -117,7 +126,10 @@ export declare function appendQaRecoveryResource(packet: QaRecoveryPacket, relat
117
126
  exact?: boolean;
118
127
  }): QaRecoveryPacket;
119
128
  export declare function appendQaRecoveryReviewedState(packet: QaRecoveryPacket, source: string | FrozenQaSourceState, prefix?: string): QaRecoveryPacket;
120
- export declare function updateQaRecoveryPosition(packet: QaRecoveryPacket, stage: string, correctionTurns: number, pendingAction?: string): QaRecoveryPacket;
129
+ export declare function updateQaRecoveryPosition(packet: QaRecoveryPacket, stage: string, correctionTurns: number, pendingAction?: QaRecoveryPendingAction): QaRecoveryPacket;
130
+ export declare function updateQaRecoveryReviewIdentity(packet: QaRecoveryPacket, reviewAttempt: number, reviewAttemptId: string, cycle?: number): QaRecoveryPacket;
131
+ /** Keep one unresolved packet and append the next malformed review's exact evidence. */
132
+ export declare function appendQaRecoveryAttempt(packet: QaRecoveryPacket, input: QaRecoveryPacketInput): QaRecoveryPacket;
121
133
  export declare function readManualQaReport(packetDirectory: string): unknown;
122
134
  export declare function validateManualQaReport(packet: QaRecoveryPacket): {
123
135
  packet: QaRecoveryPacket;