ai-foreman 1.7.28 → 2.0.0

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 (144) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/claude.d.ts +23 -12
  3. package/dist/adapters/claude.js +228 -24
  4. package/dist/adapters/codex.d.ts +17 -7
  5. package/dist/adapters/codex.js +195 -17
  6. package/dist/adapters/recovering.d.ts +2 -0
  7. package/dist/adapters/recovering.js +5 -1
  8. package/dist/adapters/structured.d.ts +71 -0
  9. package/dist/adapters/structured.js +213 -0
  10. package/dist/adapters/types.d.ts +86 -9
  11. package/dist/agentRun.d.ts +2 -0
  12. package/dist/agentRun.js +2 -1
  13. package/dist/boundedTurnEvidence.d.ts +18 -0
  14. package/dist/boundedTurnEvidence.js +76 -0
  15. package/dist/branch/currentGuard.d.ts +2 -0
  16. package/dist/branch/currentGuard.js +2 -0
  17. package/dist/branch/planner.js +1 -4
  18. package/dist/branch/runner.d.ts +2 -1
  19. package/dist/branch/runner.js +10 -7
  20. package/dist/buildAdmission.js +4 -0
  21. package/dist/buildApproval.d.ts +30 -0
  22. package/dist/buildApproval.js +68 -0
  23. package/dist/buildAssignment.d.ts +1 -0
  24. package/dist/buildAssignment.js +82 -23
  25. package/dist/buildInterventions.d.ts +2 -2
  26. package/dist/buildInterventions.js +11 -4
  27. package/dist/buildLaunchGate.js +3 -2
  28. package/dist/builderGuidanceFollowup.js +17 -6
  29. package/dist/cli/manager.js +22 -20
  30. package/dist/cli/start.js +32 -24
  31. package/dist/cli/tickets.js +40 -24
  32. package/dist/continuity.d.ts +10 -14
  33. package/dist/continuity.js +198 -179
  34. package/dist/continuityOrigin.d.ts +13 -0
  35. package/dist/continuityOrigin.js +91 -0
  36. package/dist/foreman.d.ts +20 -5
  37. package/dist/foreman.js +57 -187
  38. package/dist/graph/boundary.d.ts +1 -1
  39. package/dist/graph/boundary.js +1 -1
  40. package/dist/graph/host.js +8 -19
  41. package/dist/graph/manager.d.ts +1 -1
  42. package/dist/graph/manager.js +1 -1
  43. package/dist/graph/preparation.d.ts +38 -0
  44. package/dist/graph/preparation.js +7 -6
  45. package/dist/graph/semantic.d.ts +1 -0
  46. package/dist/graph/semantic.js +4 -1
  47. package/dist/graph/session.js +7 -2
  48. package/dist/graph/turn.d.ts +2 -3
  49. package/dist/graph/turn.js +28 -40
  50. package/dist/handoffProtocol.js +2 -2
  51. package/dist/handoffs.d.ts +3 -1
  52. package/dist/handoffs.js +90 -59
  53. package/dist/hostGraphApplicability.d.ts +46 -0
  54. package/dist/hostGraphApplicability.js +97 -0
  55. package/dist/hostQaAugmentation.d.ts +18 -0
  56. package/dist/hostQaAugmentation.js +81 -0
  57. package/dist/hostQaComparison.d.ts +21 -2
  58. package/dist/hostQaComparison.js +43 -28
  59. package/dist/hostSourcePreservation.d.ts +38 -0
  60. package/dist/hostSourcePreservation.js +72 -0
  61. package/dist/interviews.d.ts +7 -0
  62. package/dist/interviews.js +18 -1
  63. package/dist/legacyCommunicationDisplay.d.ts +2 -0
  64. package/dist/legacyCommunicationDisplay.js +2 -0
  65. package/dist/managerPacket.js +3 -3
  66. package/dist/markers.d.ts +4 -3
  67. package/dist/markers.js +4 -20
  68. package/dist/probeIpc.d.ts +14 -0
  69. package/dist/probeIpc.js +29 -0
  70. package/dist/qaBuildGate.d.ts +21 -0
  71. package/dist/qaBuildGate.js +48 -12
  72. package/dist/qaBuilderCoverage.js +22 -13
  73. package/dist/qaBuilderEvidence.d.ts +16 -1
  74. package/dist/qaBuilderEvidence.js +48 -8
  75. package/dist/qaContractCoverage.d.ts +2 -2
  76. package/dist/qaContractCoverage.js +11 -7
  77. package/dist/qaContractDelivery.js +34 -20
  78. package/dist/qaCoverageAssembly.d.ts +22 -0
  79. package/dist/qaCoverageAssembly.js +16 -0
  80. package/dist/qaDeliveryJournal.d.ts +2 -0
  81. package/dist/qaDeliveryJournal.js +19 -0
  82. package/dist/qaEffectiveConfig.d.ts +1 -0
  83. package/dist/qaEffectiveConfig.js +6 -1
  84. package/dist/qaFailureDelivery.js +97 -85
  85. package/dist/qaHostTransformation.d.ts +26 -0
  86. package/dist/qaHostTransformation.js +100 -0
  87. package/dist/qaPreparation.d.ts +8 -20
  88. package/dist/qaPreparation.js +133 -229
  89. package/dist/qaPreparationInputs.js +19 -0
  90. package/dist/qaPreparationStore.js +34 -6
  91. package/dist/qaPrerequisites.d.ts +2 -1
  92. package/dist/qaPrerequisites.js +21 -1
  93. package/dist/qaProtocolV2.d.ts +15 -0
  94. package/dist/qaRecovery.js +21 -4
  95. package/dist/qaReview.js +232 -394
  96. package/dist/qaReviewEvidence.d.ts +7 -3
  97. package/dist/qaReviewEvidence.js +15 -4
  98. package/dist/qaRuntime.js +1 -1
  99. package/dist/qaSnapshot.js +71 -21
  100. package/dist/qaStructuredResponse.d.ts +10 -0
  101. package/dist/qaStructuredResponse.js +57 -0
  102. package/dist/qaTimeline.js +5 -3
  103. package/dist/qaToolRecovery.d.ts +12 -0
  104. package/dist/qaToolRecovery.js +44 -0
  105. package/dist/qaVerificationContract.d.ts +3 -2
  106. package/dist/qaVerificationContract.js +58 -34
  107. package/dist/readinessGate.js +5 -4
  108. package/dist/runtimeReadiness.js +23 -15
  109. package/dist/statusReporter.d.ts +2 -0
  110. package/dist/statusReporter.js +2 -0
  111. package/dist/structuredCutover.d.ts +31 -0
  112. package/dist/structuredCutover.js +510 -0
  113. package/dist/structuredCutoverFiles.d.ts +10 -0
  114. package/dist/structuredCutoverFiles.js +181 -0
  115. package/dist/structuredDispatchAuthority.d.ts +22 -0
  116. package/dist/structuredDispatchAuthority.js +110 -0
  117. package/dist/structuredDispatchRecord.d.ts +29 -0
  118. package/dist/structuredDispatchRecord.js +36 -0
  119. package/dist/structuredLiveBudget.d.ts +2 -0
  120. package/dist/structuredLiveBudget.js +23 -0
  121. package/dist/structuredOperation.d.ts +18 -0
  122. package/dist/structuredOperation.js +257 -0
  123. package/dist/supervisedStart.d.ts +1 -1
  124. package/dist/supervisedStart.js +78 -31
  125. package/dist/supervisor.d.ts +18 -2
  126. package/dist/supervisor.js +74 -17
  127. package/dist/ticketPlanning.d.ts +1 -373
  128. package/dist/ticketPlanning.js +2 -26
  129. package/dist/ticketPopulation.d.ts +0 -3
  130. package/dist/ticketPopulation.js +4 -21
  131. package/dist/ticketPopulationPageAuthority.d.ts +13 -0
  132. package/dist/ticketPopulationPageAuthority.js +30 -0
  133. package/dist/ticketPopulationPaging.d.ts +27 -0
  134. package/dist/ticketPopulationPaging.js +105 -0
  135. package/dist/tickets/renderMarkdown.d.ts +14 -0
  136. package/dist/tickets/renderMarkdown.js +39 -5
  137. package/dist/tickets/ticketLoader.js +10 -0
  138. package/dist/tickets/ticketSchema.d.ts +107 -0
  139. package/dist/tickets/ticketSchema.js +4 -1
  140. package/dist/workflowDb.d.ts +100 -2
  141. package/dist/workflowDb.js +781 -35
  142. package/dist/workflowReader.d.ts +1 -0
  143. package/dist/workflowReader.js +9 -0
  144. package/package.json +15 -7
@@ -1,9 +1,40 @@
1
1
  import { processStartIdentity } from "./processIdentity.js";
2
+ import { stopCause } from "rafi-spec";
2
3
  import { WorkflowDb } from "./workflowDb.js";
4
+ /**
5
+ * Retry only the short synchronous persistence operation. The caller owns
6
+ * the retry budget and remains responsible for deciding whether ownership is
7
+ * safe after the budget is exhausted.
8
+ */
9
+ export async function persistWithBoundedRetry(persist, sleep, attempts = 3) {
10
+ let error;
11
+ let completedAttempts = 0;
12
+ for (let attempt = 1; attempt <= Math.max(1, attempts); attempt++) {
13
+ completedAttempts = attempt;
14
+ try {
15
+ persist();
16
+ return { ok: true, attempts: attempt };
17
+ }
18
+ catch (failure) {
19
+ error = failure;
20
+ if (attempt < attempts && retryablePersistenceError(failure))
21
+ await sleep(100 * attempt);
22
+ else
23
+ break;
24
+ }
25
+ }
26
+ return { ok: false, attempts: completedAttempts, error };
27
+ }
28
+ function retryablePersistenceError(error) {
29
+ const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
30
+ const message = error instanceof Error ? error.message : String(error);
31
+ return code === "SQLITE_BUSY" || code === "SQLITE_LOCKED" || /database is (?:busy|locked)/i.test(message);
32
+ }
3
33
  /** Durable worker restart loop. The supervisor never owns the project's mutation lease. */
4
34
  export class DurableSupervisor {
5
35
  options;
6
36
  stopRequested = false;
37
+ requestedStopCause;
7
38
  active;
8
39
  sleep;
9
40
  now;
@@ -12,8 +43,12 @@ export class DurableSupervisor {
12
43
  this.sleep = options.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
13
44
  this.now = options.now ?? (() => new Date());
14
45
  }
15
- requestStop() {
46
+ requestStop(cause = "unknown") {
16
47
  this.stopRequested = true;
48
+ const source = cause === "user-sigint" || cause === "parent-sigterm" ? "signal" : cause === "heartbeat-timeout" ? "heartbeat" : cause === "heartbeat-persistence-failure" ? "persistence" : cause === "human-decision" ? "human-decision" : "host";
49
+ const actor = cause === "user-sigint" ? "user" : cause === "parent-sigterm" ? "parent" : cause === "heartbeat-persistence-failure" ? "database" : cause === "unknown" ? "unknown" : "supervisor";
50
+ this.requestedStopCause = stopCause({ cause, source, actor, runId: this.options.runId, checkpoint: this.options.checkpoint(), at: this.now().toISOString(), requested: true });
51
+ this.options.onStopRequested?.(this.requestedStopCause);
17
52
  void this.active?.stop();
18
53
  }
19
54
  async run() {
@@ -64,24 +99,35 @@ export class DurableSupervisor {
64
99
  db.putSupervisorState(this.options.runId, state);
65
100
  });
66
101
  let lastCheckpoint = this.options.checkpoint();
67
- const heartbeat = setInterval(() => {
68
- state = { ...state, heartbeatAt: this.now().toISOString() };
102
+ let heartbeatInFlight = false;
103
+ const persistHeartbeat = async () => {
104
+ if (heartbeatInFlight || this.stopRequested)
105
+ return;
106
+ heartbeatInFlight = true;
69
107
  try {
70
- saveState();
108
+ state = { ...state, heartbeatAt: this.now().toISOString() };
109
+ const persisted = await persistWithBoundedRetry(saveState, this.sleep);
110
+ if (persisted.ok)
111
+ return;
112
+ this.requestStop("heartbeat-persistence-failure");
113
+ // Ownership failure remains fail-closed; the explicit cause is
114
+ // delivered to the worker-exit evidence by supervisedStart.
71
115
  }
72
- catch {
73
- this.requestStop();
116
+ finally {
117
+ heartbeatInFlight = false;
74
118
  }
75
- }, 10_000);
119
+ };
120
+ let heartbeatTask;
121
+ const heartbeat = setInterval(() => { heartbeatTask = persistHeartbeat(); }, this.options.heartbeatIntervalMs ?? 10_000);
76
122
  heartbeat.unref();
77
123
  try {
78
124
  while (!this.stopRequested) {
79
125
  const pending = db.pendingHumanDecisions(this.options.runId);
80
126
  if (pending.some(decision => !this.options.policy.continueIndependentTickets || !decision.interruptionId.startsWith("ticket:"))) {
81
- state = { ...state, status: "waiting_for_human", workerPid: undefined, heartbeatAt: this.now().toISOString() };
127
+ state = { ...state, status: "waiting_for_human", workerPid: undefined, heartbeatAt: this.now().toISOString(), stopCause: stopCause({ cause: "human-decision", source: "human-decision", actor: "supervisor", runId: this.options.runId, supervisorGeneration: state.generation, workerGeneration: state.workerGeneration, checkpoint: "waiting-for-human", at: this.now().toISOString(), requested: true }) };
82
128
  saveState();
83
129
  if (db.getRun(this.options.runId)?.status === "running")
84
- db.transitionSupervisor(this.options.runId, state.workerGeneration, { status: "paused", checkpoint: "waiting-for-human", event: "worker_waiting_for_human" });
130
+ db.transitionSupervisor(this.options.runId, state.workerGeneration, { status: "paused", checkpoint: "waiting-for-human", event: "worker_waiting_for_human", payload: { cause: state.stopCause } });
85
131
  return { kind: "waiting_for_human" };
86
132
  }
87
133
  const checkpoint = this.options.checkpoint();
@@ -92,22 +138,27 @@ export class DurableSupervisor {
92
138
  this.active = await this.options.spawnWorker(state.workerGeneration);
93
139
  state = { ...state, workerPid: this.active.pid };
94
140
  saveState();
141
+ const workerPid = state.workerPid;
95
142
  const outcome = await this.active.result;
96
143
  this.active = undefined;
97
144
  state = { ...state, workerPid: undefined, heartbeatAt: this.now().toISOString() };
145
+ const cause = this.requestedStopCause
146
+ ? stopCause({ ...this.requestedStopCause, supervisorGeneration: state.generation, workerGeneration: state.workerGeneration, pid: workerPid, processStart: workerPid ? processStartIdentity(workerPid) : undefined, checkpoint: this.options.checkpoint(), at: this.now().toISOString() })
147
+ : stopCause({ cause: "worker-exit", source: "worker", actor: "worker", runId: this.options.runId, supervisorGeneration: state.generation, workerGeneration: state.workerGeneration, pid: workerPid, checkpoint: this.options.checkpoint(), at: this.now().toISOString(), requested: false, detail: outcome.kind === "failed" ? outcome.detail : `worker outcome: ${outcome.kind}` });
148
+ state = { ...state, stopCause: cause };
98
149
  if (outcome.kind !== "crashed") {
99
150
  if (outcome.kind === "failed" && db.getRun(this.options.runId)?.status === "running") {
100
151
  const eligibility = db.preparationEligibility(this.options.runId);
101
152
  db.transitionSupervisor(this.options.runId, state.workerGeneration, {
102
153
  status: eligibility.eligible ? "failed" : "paused",
103
154
  checkpoint: eligibility.eligible ? "worker-failed" : "worker-reconciliation-required",
104
- event: "worker_exit_recorded", payload: { detail: outcome.detail, reasons: eligibility.reasons },
155
+ event: "worker_exit_recorded", payload: { detail: outcome.detail, reasons: eligibility.reasons, cause },
105
156
  });
106
157
  }
107
158
  else if (db.getRun(this.options.runId)?.status === "running")
108
159
  db.transitionSupervisor(this.options.runId, state.workerGeneration, {
109
160
  status: "paused", checkpoint: outcome.kind === "waiting_for_human" && db.pendingHumanDecisions(this.options.runId).length ? "waiting-for-human" : "worker-exited-before-checkpoint",
110
- event: "worker_exit_recorded", payload: { outcome: outcome.kind },
161
+ event: "worker_exit_recorded", payload: { outcome: outcome.kind, cause },
111
162
  });
112
163
  const status = outcome.kind === "completed" ? "stopped" : outcome.kind === "waiting_for_human" ? "waiting_for_human" : outcome.kind === "failed" ? "failed" : "stopped";
113
164
  state = { ...state, status };
@@ -118,7 +169,10 @@ export class DurableSupervisor {
118
169
  if (!eligibility.eligible) {
119
170
  state = { ...state, status: "waiting_for_human" };
120
171
  saveState();
121
- db.transitionSupervisor(this.options.runId, state.workerGeneration, { status: "paused", checkpoint: "worker-reconciliation-required", event: "worker_restart_withheld", payload: { reasons: eligibility.reasons } });
172
+ const cause = stopCause({ ...state.stopCause, cause: "worker-reconciliation-required", source: "reconciliation", actor: "supervisor", checkpoint: "worker-reconciliation-required", at: this.now().toISOString(), requested: false, detail: eligibility.reasons.join("; ") });
173
+ state = { ...state, stopCause: cause };
174
+ saveState();
175
+ db.transitionSupervisor(this.options.runId, state.workerGeneration, { status: "paused", checkpoint: "worker-reconciliation-required", event: "worker_restart_withheld", payload: { reasons: eligibility.reasons, cause } });
122
176
  return { kind: "waiting_for_human" };
123
177
  }
124
178
  const checkpointLimit = this.options.policy.limits.workerRestartsPerCheckpoint;
@@ -128,7 +182,7 @@ export class DurableSupervisor {
128
182
  saveState();
129
183
  if (db.getRun(this.options.runId)?.status === "running")
130
184
  db.transitionSupervisor(this.options.runId, state.workerGeneration, {
131
- status: "failed", checkpoint: "worker-restart-budget-exhausted", event: "worker_restart_budget_exhausted", payload: { checkpoint, detail: outcome.detail },
185
+ status: "failed", checkpoint: "worker-restart-budget-exhausted", event: "worker_restart_budget_exhausted", payload: { checkpoint, detail: outcome.detail, cause: state.stopCause },
132
186
  });
133
187
  return { kind: "failed", detail: `worker restart budget exhausted at ${checkpoint}: ${outcome.detail}` };
134
188
  }
@@ -136,26 +190,29 @@ export class DurableSupervisor {
136
190
  saveState();
137
191
  await this.sleep(Math.min(10_000, 250 * (2 ** Math.min(5, state.checkpointRestarts - 1))));
138
192
  }
139
- state = { ...state, status: "stopped", stopRequestedAt: this.now().toISOString(), workerPid: undefined };
193
+ state = { ...state, status: "stopped", stopRequestedAt: this.now().toISOString(), workerPid: undefined, stopCause: state.stopCause ?? this.requestedStopCause ?? stopCause({ cause: "unknown", source: "host", actor: "unknown", runId: this.options.runId, supervisorGeneration: state.generation, workerGeneration: state.workerGeneration, checkpoint: "worker-stopped", at: this.now().toISOString(), requested: true, detail: "Supervisor stopped without a more specific durable cause" }) };
140
194
  if (db.getRun(this.options.runId)?.status === "running")
141
- db.transitionSupervisor(this.options.runId, state.workerGeneration, { status: "paused", checkpoint: "worker-stopped", event: "supervisor_stop_recorded" });
195
+ db.transitionSupervisor(this.options.runId, state.workerGeneration, { status: "paused", checkpoint: "worker-stopped", event: "supervisor_stop_recorded", payload: { cause: state.stopCause } });
142
196
  saveState();
143
197
  return { kind: "stopped" };
144
198
  }
145
199
  catch (error) {
146
- state = { ...state, status: "failed", workerPid: undefined, heartbeatAt: this.now().toISOString() };
200
+ state = { ...state, status: "failed", workerPid: undefined, heartbeatAt: this.now().toISOString(), stopCause: stopCause({ cause: "launch-failure", source: "launch", actor: "supervisor", runId: this.options.runId, supervisorGeneration: state.generation, workerGeneration: state.workerGeneration, checkpoint: "worker-launch-failed", at: this.now().toISOString(), requested: false, detail: String(error) }) };
147
201
  saveState();
148
202
  if (db.getRun(this.options.runId)?.status === "running") {
149
203
  const eligibility = db.preparationEligibility(this.options.runId);
204
+ const cause = stopCause({ cause: "launch-failure", source: "launch", actor: "supervisor", runId: this.options.runId, supervisorGeneration: state.generation, workerGeneration: state.workerGeneration, checkpoint: eligibility.eligible ? "worker-launch-failed" : "worker-reconciliation-required", at: new Date().toISOString(), requested: false, detail: String(error) });
150
205
  db.transitionSupervisor(this.options.runId, state.workerGeneration, { status: eligibility.eligible ? "failed" : "paused",
151
206
  checkpoint: eligibility.eligible ? "worker-launch-failed" : "worker-reconciliation-required", event: "worker_launch_failed",
152
- payload: { detail: String(error), reasons: eligibility.reasons },
207
+ payload: { detail: String(error), reasons: eligibility.reasons, cause },
153
208
  });
154
209
  }
155
210
  throw error;
156
211
  }
157
212
  finally {
158
213
  clearInterval(heartbeat);
214
+ if (heartbeatTask)
215
+ await heartbeatTask;
159
216
  try {
160
217
  if (ownedAdmission && db.buildAdmission()?.token === ownedAdmission.token)
161
218
  db.releaseBuildAdmission(ownedAdmission);
@@ -2,7 +2,6 @@ import type { TicketDef } from "./tickets/ticketSchema.js";
2
2
  import { type TicketStatus } from "./tickets/stateDb.js";
3
3
  import { type DeliveryConfig } from "./tickets/delivery.js";
4
4
  import { type TicketBuildSetupConfig } from "./tickets/setupConfig.js";
5
- import { z } from "zod";
6
5
  export declare const PROPOSAL_START = "RAFI_PROPOSAL_START";
7
6
  export declare const PROPOSAL_END = "RAFI_PROPOSAL_END";
8
7
  export interface TicketEdit {
@@ -69,378 +68,7 @@ export interface TicketPlanningContext {
69
68
  export interface TicketPlanValidationContext {
70
69
  futureWorkIds?: Iterable<number>;
71
70
  }
72
- export declare const ticketPlanProposalV1Schema: z.ZodObject<{
73
- version: z.ZodLiteral<1>;
74
- title: z.ZodString;
75
- markdown: z.ZodString;
76
- additions: z.ZodArray<z.ZodObject<{
77
- qa_preparation: z.ZodOptional<z.ZodObject<{
78
- version: z.ZodLiteral<1>;
79
- decisionId: z.ZodString;
80
- level: z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>, z.ZodLiteral<4>, z.ZodLiteral<5>]>;
81
- rationale: z.ZodString;
82
- riskFactors: z.ZodArray<z.ZodObject<{
83
- category: z.ZodEnum<{
84
- interaction: "interaction";
85
- compatibility: "compatibility";
86
- "state-transition": "state-transition";
87
- "external-dependency": "external-dependency";
88
- authorization: "authorization";
89
- security: "security";
90
- "data-integrity": "data-integrity";
91
- migration: "migration";
92
- concurrency: "concurrency";
93
- recovery: "recovery";
94
- deployment: "deployment";
95
- "architecture-uncertainty": "architecture-uncertainty";
96
- }>;
97
- rationale: z.ZodString;
98
- references: z.ZodArray<z.ZodString>;
99
- }, z.core.$strict>>;
100
- lowRisk: z.ZodOptional<z.ZodObject<{
101
- narrowBehavior: z.ZodString;
102
- understoodDependencies: z.ZodString;
103
- adequateVerification: z.ZodString;
104
- }, z.core.$strict>>;
105
- policyVersion: z.ZodLiteral<"qa-preparation-v1">;
106
- plannerOperationId: z.ZodString;
107
- plannerIdentity: z.ZodString;
108
- timestamp: z.ZodString;
109
- minimumLevel: z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>, z.ZodLiteral<4>, z.ZodLiteral<5>]>;
110
- minimumReasons: z.ZodArray<z.ZodString>;
111
- predecessorDecisionId: z.ZodOptional<z.ZodString>;
112
- }, z.core.$strict>>;
113
- id: z.ZodString;
114
- order: z.ZodNumber;
115
- documentation: z.ZodOptional<z.ZodObject<{
116
- required: z.ZodBoolean;
117
- targets: z.ZodArray<z.ZodString>;
118
- criteria: z.ZodArray<z.ZodString>;
119
- }, z.core.$strict>>;
120
- title: z.ZodString;
121
- area: z.ZodString;
122
- priority: z.ZodEnum<{
123
- P0: "P0";
124
- P1: "P1";
125
- P2: "P2";
126
- P3: "P3";
127
- }>;
128
- size: z.ZodEnum<{
129
- XS: "XS";
130
- S: "S";
131
- M: "M";
132
- L: "L";
133
- XL: "XL";
134
- }>;
135
- risk: z.ZodEnum<{
136
- Low: "Low";
137
- Medium: "Medium";
138
- High: "High";
139
- }>;
140
- depends_on: z.ZodArray<z.ZodString>;
141
- summary: z.ZodString;
142
- acceptance: z.ZodArray<z.ZodString>;
143
- required_tests: z.ZodArray<z.ZodString>;
144
- likely_files: z.ZodArray<z.ZodString>;
145
- rollback: z.ZodOptional<z.ZodNullable<z.ZodString>>;
146
- notes: z.ZodOptional<z.ZodNullable<z.ZodString>>;
147
- external_refs: z.ZodOptional<z.ZodArray<z.ZodObject<{
148
- provider: z.ZodString;
149
- id: z.ZodString;
150
- key: z.ZodOptional<z.ZodNullable<z.ZodString>>;
151
- url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
152
- }, z.core.$strict>>>;
153
- source_refs: z.ZodOptional<z.ZodArray<z.ZodObject<{
154
- source: z.ZodString;
155
- item: z.ZodString;
156
- source_id: z.ZodOptional<z.ZodString>;
157
- url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
158
- fingerprint: z.ZodOptional<z.ZodNullable<z.ZodString>>;
159
- note: z.ZodOptional<z.ZodNullable<z.ZodString>>;
160
- }, z.core.$strict>>>;
161
- superseded_by: z.ZodOptional<z.ZodArray<z.ZodString>>;
162
- supersedes: z.ZodOptional<z.ZodArray<z.ZodString>>;
163
- plan_ref: z.ZodOptional<z.ZodObject<{
164
- plan_id: z.ZodString;
165
- revision: z.ZodNumber;
166
- slice_ref: z.ZodString;
167
- }, z.core.$strict>>;
168
- }, z.core.$strict>>;
169
- edits: z.ZodArray<z.ZodObject<{
170
- id: z.ZodString;
171
- patch: z.ZodObject<{
172
- qa_preparation: z.ZodOptional<z.ZodOptional<z.ZodObject<{
173
- version: z.ZodLiteral<1>;
174
- decisionId: z.ZodString;
175
- level: z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>, z.ZodLiteral<4>, z.ZodLiteral<5>]>;
176
- rationale: z.ZodString;
177
- riskFactors: z.ZodArray<z.ZodObject<{
178
- category: z.ZodEnum<{
179
- interaction: "interaction";
180
- compatibility: "compatibility";
181
- "state-transition": "state-transition";
182
- "external-dependency": "external-dependency";
183
- authorization: "authorization";
184
- security: "security";
185
- "data-integrity": "data-integrity";
186
- migration: "migration";
187
- concurrency: "concurrency";
188
- recovery: "recovery";
189
- deployment: "deployment";
190
- "architecture-uncertainty": "architecture-uncertainty";
191
- }>;
192
- rationale: z.ZodString;
193
- references: z.ZodArray<z.ZodString>;
194
- }, z.core.$strict>>;
195
- lowRisk: z.ZodOptional<z.ZodObject<{
196
- narrowBehavior: z.ZodString;
197
- understoodDependencies: z.ZodString;
198
- adequateVerification: z.ZodString;
199
- }, z.core.$strict>>;
200
- policyVersion: z.ZodLiteral<"qa-preparation-v1">;
201
- plannerOperationId: z.ZodString;
202
- plannerIdentity: z.ZodString;
203
- timestamp: z.ZodString;
204
- minimumLevel: z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>, z.ZodLiteral<4>, z.ZodLiteral<5>]>;
205
- minimumReasons: z.ZodArray<z.ZodString>;
206
- predecessorDecisionId: z.ZodOptional<z.ZodString>;
207
- }, z.core.$strict>>>;
208
- id: z.ZodOptional<z.ZodString>;
209
- order: z.ZodOptional<z.ZodNumber>;
210
- documentation: z.ZodOptional<z.ZodOptional<z.ZodObject<{
211
- required: z.ZodBoolean;
212
- targets: z.ZodArray<z.ZodString>;
213
- criteria: z.ZodArray<z.ZodString>;
214
- }, z.core.$strict>>>;
215
- title: z.ZodOptional<z.ZodString>;
216
- area: z.ZodOptional<z.ZodString>;
217
- priority: z.ZodOptional<z.ZodEnum<{
218
- P0: "P0";
219
- P1: "P1";
220
- P2: "P2";
221
- P3: "P3";
222
- }>>;
223
- size: z.ZodOptional<z.ZodEnum<{
224
- XS: "XS";
225
- S: "S";
226
- M: "M";
227
- L: "L";
228
- XL: "XL";
229
- }>>;
230
- risk: z.ZodOptional<z.ZodEnum<{
231
- Low: "Low";
232
- Medium: "Medium";
233
- High: "High";
234
- }>>;
235
- depends_on: z.ZodOptional<z.ZodArray<z.ZodString>>;
236
- summary: z.ZodOptional<z.ZodString>;
237
- acceptance: z.ZodOptional<z.ZodArray<z.ZodString>>;
238
- required_tests: z.ZodOptional<z.ZodArray<z.ZodString>>;
239
- likely_files: z.ZodOptional<z.ZodArray<z.ZodString>>;
240
- rollback: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
241
- notes: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodString>>>;
242
- external_refs: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
243
- provider: z.ZodString;
244
- id: z.ZodString;
245
- key: z.ZodOptional<z.ZodNullable<z.ZodString>>;
246
- url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
247
- }, z.core.$strict>>>>;
248
- source_refs: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodObject<{
249
- source: z.ZodString;
250
- item: z.ZodString;
251
- source_id: z.ZodOptional<z.ZodString>;
252
- url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
253
- fingerprint: z.ZodOptional<z.ZodNullable<z.ZodString>>;
254
- note: z.ZodOptional<z.ZodNullable<z.ZodString>>;
255
- }, z.core.$strict>>>>;
256
- superseded_by: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
257
- supersedes: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
258
- plan_ref: z.ZodOptional<z.ZodOptional<z.ZodObject<{
259
- plan_id: z.ZodString;
260
- revision: z.ZodNumber;
261
- slice_ref: z.ZodString;
262
- }, z.core.$strict>>>;
263
- }, z.core.$strict>;
264
- }, z.core.$strict>>;
265
- supersessions: z.ZodArray<z.ZodObject<{
266
- replaced: z.ZodArray<z.ZodString>;
267
- replacements: z.ZodArray<z.ZodString>;
268
- reason: z.ZodString;
269
- }, z.core.$strict>>;
270
- state_changes: z.ZodArray<z.ZodObject<{
271
- id: z.ZodString;
272
- status: z.ZodEnum<{
273
- blocked: "blocked";
274
- in_progress: "in_progress";
275
- done: "done";
276
- planned: "planned";
277
- next: "next";
278
- canceled: "canceled";
279
- obsolete: "obsolete";
280
- }>;
281
- reason: z.ZodString;
282
- }, z.core.$strict>>;
283
- source_reconciliation: z.ZodArray<z.ZodObject<{
284
- source: z.ZodString;
285
- item: z.ZodString;
286
- disposition: z.ZodEnum<{
287
- split: "split";
288
- deferred: "deferred";
289
- combined: "combined";
290
- mapped: "mapped";
291
- excluded: "excluded";
292
- }>;
293
- ticket_ids: z.ZodArray<z.ZodString>;
294
- reason: z.ZodOptional<z.ZodString>;
295
- }, z.core.$strict>>;
296
- delivery: z.ZodOptional<z.ZodNullable<z.ZodObject<{
297
- version: z.ZodLiteral<1>;
298
- plan: z.ZodOptional<z.ZodObject<{
299
- plan_id: z.ZodString;
300
- revision: z.ZodNumber;
301
- }, z.core.$strict>>;
302
- units: z.ZodArray<z.ZodObject<{
303
- id: z.ZodString;
304
- tickets: z.ZodArray<z.ZodString>;
305
- branch_mode: z.ZodEnum<{
306
- current: "current";
307
- "per-ticket": "per-ticket";
308
- shared: "shared";
309
- }>;
310
- completion: z.ZodOptional<z.ZodEnum<{
311
- none: "none";
312
- pr: "pr";
313
- "auto-merge": "auto-merge";
314
- "direct-merge": "direct-merge";
315
- }>>;
316
- provider: z.ZodOptional<z.ZodEnum<{
317
- auto: "auto";
318
- local: "local";
319
- github: "github";
320
- gitlab: "gitlab";
321
- }>>;
322
- pr_ready: z.ZodOptional<z.ZodBoolean>;
323
- merge_method: z.ZodOptional<z.ZodEnum<{
324
- squash: "squash";
325
- merge: "merge";
326
- rebase: "rebase";
327
- }>>;
328
- cleanup: z.ZodOptional<z.ZodBoolean>;
329
- depends_on: z.ZodOptional<z.ZodArray<z.ZodString>>;
330
- dependency_mode: z.ZodOptional<z.ZodEnum<{
331
- combine: "combine";
332
- wait: "wait";
333
- stack: "stack";
334
- }>>;
335
- }, z.core.$strict>>;
336
- stacks: z.ZodOptional<z.ZodArray<z.ZodObject<{
337
- id: z.ZodString;
338
- name: z.ZodString;
339
- units: z.ZodArray<z.ZodString>;
340
- status: z.ZodOptional<z.ZodEnum<{
341
- partial: "partial";
342
- blocked: "blocked";
343
- in_progress: "in_progress";
344
- planned: "planned";
345
- merged: "merged";
346
- awaiting_review: "awaiting_review";
347
- }>>;
348
- completed_prefix: z.ZodOptional<z.ZodArray<z.ZodString>>;
349
- next_ticket: z.ZodOptional<z.ZodString>;
350
- review_links: z.ZodOptional<z.ZodArray<z.ZodString>>;
351
- remote_checked_at: z.ZodOptional<z.ZodString>;
352
- remote_stale: z.ZodOptional<z.ZodBoolean>;
353
- published_branches: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
354
- }, z.core.$strict>>>;
355
- }, z.core.$strict>>>;
356
- build_defaults: z.ZodOptional<z.ZodNullable<z.ZodObject<{
357
- branch_strategy: z.ZodOptional<z.ZodOptional<z.ZodEnum<{
358
- current: "current";
359
- batch: "batch";
360
- "branch-per-ticket": "branch-per-ticket";
361
- }>>>;
362
- completion: z.ZodOptional<z.ZodOptional<z.ZodEnum<{
363
- none: "none";
364
- pr: "pr";
365
- "auto-merge": "auto-merge";
366
- "direct-merge": "direct-merge";
367
- }>>>;
368
- provider: z.ZodOptional<z.ZodOptional<z.ZodEnum<{
369
- auto: "auto";
370
- local: "local";
371
- github: "github";
372
- gitlab: "gitlab";
373
- }>>>;
374
- pr_ready: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
375
- merge_method: z.ZodOptional<z.ZodOptional<z.ZodEnum<{
376
- squash: "squash";
377
- merge: "merge";
378
- rebase: "rebase";
379
- }>>>;
380
- cleanup: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
381
- auto_merge_wait: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
382
- auto_merge_timeout_minutes: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodNumber>>>;
383
- base_branch: z.ZodOptional<z.ZodOptional<z.ZodString>>;
384
- branch_prefix: z.ZodOptional<z.ZodOptional<z.ZodString>>;
385
- branch_policy: z.ZodOptional<z.ZodOptional<z.ZodObject<{
386
- mode: z.ZodEnum<{
387
- size: "size";
388
- global: "global";
389
- }>;
390
- global_strategy: z.ZodEnum<{
391
- current: "current";
392
- batch: "batch";
393
- "branch-per-ticket": "branch-per-ticket";
394
- }>;
395
- by_size: z.ZodObject<{
396
- XS: z.ZodEnum<{
397
- "per-ticket": "per-ticket";
398
- shared: "shared";
399
- }>;
400
- S: z.ZodEnum<{
401
- "per-ticket": "per-ticket";
402
- shared: "shared";
403
- }>;
404
- M: z.ZodEnum<{
405
- "per-ticket": "per-ticket";
406
- shared: "shared";
407
- }>;
408
- L: z.ZodEnum<{
409
- "per-ticket": "per-ticket";
410
- shared: "shared";
411
- }>;
412
- XL: z.ZodEnum<{
413
- "per-ticket": "per-ticket";
414
- shared: "shared";
415
- }>;
416
- }, z.core.$strict>;
417
- }, z.core.$strict>>>;
418
- review: z.ZodOptional<z.ZodOptional<z.ZodObject<{
419
- title_style: z.ZodEnum<{
420
- none: "none";
421
- custom: "custom";
422
- "ticket-title": "ticket-title";
423
- conventional: "conventional";
424
- }>;
425
- title_template: z.ZodNullable<z.ZodString>;
426
- description_sections: z.ZodArray<z.ZodString>;
427
- }, z.core.$strict>>>;
428
- validation_checklist: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
429
- }, z.core.$strict>>>;
430
- future_work: z.ZodArray<z.ZodObject<{
431
- id: z.ZodNumber;
432
- disposition: z.ZodEnum<{
433
- rejected: "rejected";
434
- accepted: "accepted";
435
- merged: "merged";
436
- queued: "queued";
437
- }>;
438
- }, z.core.$strict>>;
439
- next: z.ZodObject<{
440
- ticket_ids: z.ZodArray<z.ZodString>;
441
- replace_existing: z.ZodBoolean;
442
- }, z.core.$strict>;
443
- }, z.core.$strict>;
71
+ export { ticketPlanProposalV1Schema } from "rafi-spec";
444
72
  export declare function readTicketPlanningContext(projectDir: string): TicketPlanningContext;
445
73
  export declare function ticketPlanningFingerprint(projectDir: string): PlanningFingerprint[];
446
74
  export declare function planningFingerprintChanges(projectDir: string, expected: PlanningFingerprint[]): string[];
@@ -10,34 +10,10 @@ import { renderAndWrite } from "./tickets/renderMarkdown.js";
10
10
  import { runAllValidation } from "./tickets/validate.js";
11
11
  import { normalizeDeliveryConfig, validateDeliveryConfig } from "./tickets/delivery.js";
12
12
  import { loadTicketSetupConfig, mergeTicketSetup, saveTicketSetupConfig } from "./tickets/setupConfig.js";
13
- import { z } from "zod";
14
- import { documentationPolicyV1ZodSchema as documentationPolicyZodSchema, qaPreparationDepthDecisionZodSchema, zodIssues } from "rafi-spec";
13
+ import { ticketPlanProposalV1Schema, zodIssues } from "rafi-spec";
15
14
  export const PROPOSAL_START = "RAFI_PROPOSAL_START";
16
15
  export const PROPOSAL_END = "RAFI_PROPOSAL_END";
17
- const providerText = (max = 20_000) => z.string().trim().min(1).max(max);
18
- const identifier = z.string().regex(/^[A-Za-z][A-Za-z0-9-_]{0,127}$/);
19
- const ticketSourceRef = z.strictObject({ source: providerText(500), item: providerText(2_000), source_id: identifier.optional(), url: z.string().url().nullable().optional(), fingerprint: z.string().regex(/^[a-f0-9]{64}$/).nullable().optional(), note: providerText(2_000).nullable().optional() });
20
- const ticketDefinitionProposal = z.strictObject({
21
- qa_preparation: qaPreparationDepthDecisionZodSchema.optional(), id: identifier, order: z.number().int().min(0),
22
- documentation: documentationPolicyZodSchema.optional(),
23
- title: providerText(500), area: providerText(500), priority: z.enum(["P0", "P1", "P2", "P3"]), size: z.enum(["XS", "S", "M", "L", "XL"]), risk: z.enum(["Low", "Medium", "High"]),
24
- depends_on: z.array(identifier).max(200), summary: providerText(), acceptance: z.array(providerText()).min(1).max(200), required_tests: z.array(providerText()).min(1).max(200), likely_files: z.array(providerText(1_000)).max(200),
25
- rollback: z.string().max(20_000).nullable().optional(), notes: z.string().max(20_000).nullable().optional(),
26
- external_refs: z.array(z.strictObject({ provider: providerText(100), id: providerText(500), key: providerText(500).nullable().optional(), url: z.string().url().nullable().optional() })).max(100).optional(),
27
- source_refs: z.array(ticketSourceRef).max(100).optional(), superseded_by: z.array(identifier).max(200).optional(), supersedes: z.array(identifier).max(200).optional(),
28
- plan_ref: z.strictObject({ plan_id: identifier, revision: z.number().int().min(1), slice_ref: identifier }).optional(),
29
- });
30
- const deliveryConfigProposal = z.strictObject({
31
- version: z.literal(1), plan: z.strictObject({ plan_id: identifier, revision: z.number().int().min(1) }).optional(),
32
- units: z.array(z.strictObject({ id: identifier, tickets: z.array(identifier).min(1).max(200), branch_mode: z.enum(["current", "per-ticket", "shared"]), completion: z.enum(["pr", "auto-merge", "direct-merge", "none"]).optional(), provider: z.enum(["auto", "github", "gitlab", "local"]).optional(), pr_ready: z.boolean().optional(), merge_method: z.enum(["squash", "merge", "rebase"]).optional(), cleanup: z.boolean().optional(), depends_on: z.array(identifier).max(200).optional(), dependency_mode: z.enum(["combine", "wait", "stack"]).optional() })).max(200),
33
- stacks: z.array(z.strictObject({ id: identifier, name: providerText(500), units: z.array(identifier).min(1).max(5), status: z.enum(["planned", "in_progress", "partial", "blocked", "awaiting_review", "merged"]).optional(), completed_prefix: z.array(identifier).max(5).optional(), next_ticket: identifier.optional(), review_links: z.array(z.string().url()).max(20).optional(), remote_checked_at: z.string().datetime().optional(), remote_stale: z.boolean().optional(), published_branches: z.record(identifier, providerText(500)).optional() })).max(200).optional(),
34
- });
35
- const buildDefaultsProposal = z.strictObject({
36
- branch_strategy: z.enum(["current", "batch", "branch-per-ticket"]).optional(), completion: z.enum(["pr", "auto-merge", "direct-merge", "none"]).optional(), provider: z.enum(["auto", "github", "gitlab", "local"]).optional(), pr_ready: z.boolean().optional(), merge_method: z.enum(["squash", "merge", "rebase"]).optional(), cleanup: z.boolean().optional(), auto_merge_wait: z.boolean().optional(), auto_merge_timeout_minutes: z.number().int().min(0).nullable().optional(), base_branch: providerText(500).optional(), branch_prefix: providerText(500).optional(), branch_policy: z.strictObject({ mode: z.enum(["global", "size"]), global_strategy: z.enum(["current", "batch", "branch-per-ticket"]), by_size: z.strictObject({ XS: z.enum(["shared", "per-ticket"]), S: z.enum(["shared", "per-ticket"]), M: z.enum(["shared", "per-ticket"]), L: z.enum(["shared", "per-ticket"]), XL: z.enum(["shared", "per-ticket"]) }) }).optional(), review: z.strictObject({ title_style: z.enum(["ticket-title", "conventional", "none", "custom"]), title_template: z.string().max(500).nullable(), description_sections: z.array(providerText(500)).max(50) }).optional(), validation_checklist: z.array(providerText(500)).max(100).optional(),
37
- });
38
- export const ticketPlanProposalV1Schema = z.strictObject({
39
- version: z.literal(1), title: providerText(500), markdown: providerText(100_000), additions: z.array(ticketDefinitionProposal).max(200), edits: z.array(z.strictObject({ id: identifier, patch: ticketDefinitionProposal.partial() })).max(200), supersessions: z.array(z.strictObject({ replaced: z.array(identifier).min(1).max(200), replacements: z.array(identifier).min(1).max(200), reason: providerText() })).max(200), state_changes: z.array(z.strictObject({ id: identifier, status: z.enum(["planned", "next", "in_progress", "blocked", "done", "canceled", "obsolete"]), reason: providerText() })).max(200), source_reconciliation: z.array(z.strictObject({ source: providerText(500), item: providerText(2_000), disposition: z.enum(["mapped", "split", "combined", "deferred", "excluded"]), ticket_ids: z.array(identifier).max(200), reason: providerText().optional() })).max(500), delivery: deliveryConfigProposal.nullable().optional(), build_defaults: buildDefaultsProposal.partial().nullable().optional(), future_work: z.array(z.strictObject({ id: z.number().int().min(0), disposition: z.enum(["accepted", "rejected", "merged", "queued"]) })).max(500), next: z.strictObject({ ticket_ids: z.array(identifier).max(200), replace_existing: z.boolean() }),
40
- });
16
+ export { ticketPlanProposalV1Schema } from "rafi-spec";
41
17
  const TICKET_STATUSES = new Set([
42
18
  "planned", "next", "in_progress", "blocked", "done", "canceled", "obsolete",
43
19
  ]);
@@ -32,8 +32,6 @@ export interface TicketPopulationProposalV1 {
32
32
  tickets: TicketSliceProposal[];
33
33
  retirements: string[];
34
34
  }
35
- export declare const TICKET_POPULATION_PROPOSAL_START = "RAFI_TICKET_POPULATION_PROPOSAL_START";
36
- export declare const TICKET_POPULATION_PROPOSAL_END = "RAFI_TICKET_POPULATION_PROPOSAL_END";
37
35
  export interface PopulationRiskWaiver {
38
36
  ticketId: string;
39
37
  reason: string;
@@ -57,7 +55,6 @@ export declare class TicketPopulationValidationError extends Error {
57
55
  }
58
56
  /** Reject a correction that attempts to turn a risk-note repair into a new plan. */
59
57
  export declare function assertRiskPlanOnlyCorrection(previous: TicketPopulationProposalV1, corrected: TicketPopulationProposalV1, permittedSliceRefs: string[]): void;
60
- export declare function extractTicketPopulationProposal(output: string): TicketPopulationProposalV1;
61
58
  export declare function validateTicketPopulationProposal(proposal: TicketPopulationProposalV1, plan: StructuredPlanV1, existing: TicketDef[], options?: {
62
59
  branchStrategy?: PopulationBranchStrategy;
63
60
  }): string[];