ai-foreman 1.7.29 → 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 (136) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/claude.d.ts +23 -12
  3. package/dist/adapters/claude.js +227 -23
  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 +85 -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 +15 -0
  22. package/dist/buildApproval.js +25 -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 +22 -26
  31. package/dist/cli/tickets.d.ts +0 -7
  32. package/dist/cli/tickets.js +41 -40
  33. package/dist/continuity.d.ts +10 -14
  34. package/dist/continuity.js +198 -179
  35. package/dist/continuityOrigin.d.ts +13 -0
  36. package/dist/continuityOrigin.js +91 -0
  37. package/dist/foreman.d.ts +10 -4
  38. package/dist/foreman.js +51 -193
  39. package/dist/graph/boundary.d.ts +1 -1
  40. package/dist/graph/boundary.js +1 -1
  41. package/dist/graph/host.js +8 -19
  42. package/dist/graph/manager.d.ts +1 -1
  43. package/dist/graph/manager.js +1 -1
  44. package/dist/graph/preparation.d.ts +38 -0
  45. package/dist/graph/preparation.js +7 -6
  46. package/dist/graph/semantic.d.ts +1 -0
  47. package/dist/graph/semantic.js +4 -1
  48. package/dist/graph/session.js +7 -2
  49. package/dist/graph/turn.d.ts +2 -3
  50. package/dist/graph/turn.js +28 -40
  51. package/dist/handoffProtocol.js +2 -2
  52. package/dist/handoffs.d.ts +3 -1
  53. package/dist/handoffs.js +90 -59
  54. package/dist/hostGraphApplicability.d.ts +2 -1
  55. package/dist/hostGraphApplicability.js +17 -19
  56. package/dist/hostQaAugmentation.js +21 -4
  57. package/dist/hostQaComparison.d.ts +21 -2
  58. package/dist/hostQaComparison.js +43 -29
  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 +47 -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 +4 -9
  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/qaFailureDelivery.js +97 -85
  83. package/dist/qaHostTransformation.d.ts +26 -0
  84. package/dist/qaHostTransformation.js +100 -0
  85. package/dist/qaPreparation.d.ts +8 -20
  86. package/dist/qaPreparation.js +133 -248
  87. package/dist/qaPreparationInputs.js +19 -0
  88. package/dist/qaPrerequisites.d.ts +2 -1
  89. package/dist/qaPrerequisites.js +21 -1
  90. package/dist/qaProtocolV2.d.ts +15 -0
  91. package/dist/qaRecovery.js +21 -4
  92. package/dist/qaReview.js +220 -395
  93. package/dist/qaReviewEvidence.d.ts +7 -3
  94. package/dist/qaReviewEvidence.js +15 -4
  95. package/dist/qaRuntime.js +1 -1
  96. package/dist/qaSnapshot.js +71 -21
  97. package/dist/qaStructuredResponse.d.ts +10 -0
  98. package/dist/qaStructuredResponse.js +57 -0
  99. package/dist/qaTimeline.js +5 -3
  100. package/dist/qaVerificationContract.d.ts +1 -2
  101. package/dist/qaVerificationContract.js +24 -35
  102. package/dist/readinessGate.js +5 -4
  103. package/dist/runtimeReadiness.js +23 -15
  104. package/dist/statusReporter.d.ts +2 -0
  105. package/dist/statusReporter.js +2 -0
  106. package/dist/structuredCutover.d.ts +31 -0
  107. package/dist/structuredCutover.js +510 -0
  108. package/dist/structuredCutoverFiles.d.ts +10 -0
  109. package/dist/structuredCutoverFiles.js +181 -0
  110. package/dist/structuredDispatchAuthority.d.ts +22 -0
  111. package/dist/structuredDispatchAuthority.js +110 -0
  112. package/dist/structuredDispatchRecord.d.ts +29 -0
  113. package/dist/structuredDispatchRecord.js +36 -0
  114. package/dist/structuredLiveBudget.d.ts +2 -0
  115. package/dist/structuredLiveBudget.js +23 -0
  116. package/dist/structuredOperation.d.ts +18 -0
  117. package/dist/structuredOperation.js +257 -0
  118. package/dist/supervisedStart.js +35 -21
  119. package/dist/ticketPlanning.d.ts +1 -373
  120. package/dist/ticketPlanning.js +2 -26
  121. package/dist/ticketPopulation.d.ts +0 -3
  122. package/dist/ticketPopulation.js +4 -21
  123. package/dist/ticketPopulationPageAuthority.d.ts +13 -0
  124. package/dist/ticketPopulationPageAuthority.js +30 -0
  125. package/dist/ticketPopulationPaging.d.ts +27 -0
  126. package/dist/ticketPopulationPaging.js +105 -0
  127. package/dist/tickets/renderMarkdown.d.ts +14 -0
  128. package/dist/tickets/renderMarkdown.js +39 -5
  129. package/dist/tickets/ticketLoader.js +10 -0
  130. package/dist/tickets/ticketSchema.d.ts +107 -0
  131. package/dist/tickets/ticketSchema.js +4 -1
  132. package/dist/workflowDb.d.ts +100 -2
  133. package/dist/workflowDb.js +763 -39
  134. package/dist/workflowReader.d.ts +1 -0
  135. package/dist/workflowReader.js +9 -0
  136. package/package.json +15 -7
package/dist/foreman.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { GraphPurpose } from "rafi-spec";
1
+ import type { GraphPurpose, OutputContract } from "rafi-spec";
2
2
  import type { BuilderAdapter, PermissionHandler, PermissionRequest, TurnResult } from "./adapters/types.js";
3
3
  import type { PermissionPolicy } from "./permissions/policy.js";
4
4
  import { type AnsweredProviderQuestion } from "./providerQuestions.js";
@@ -67,7 +67,7 @@ export interface ForemanNotificationOptions {
67
67
  desktop: boolean;
68
68
  terminalBell: boolean;
69
69
  }
70
- /** Drives one builder through a batch of N steps via the STEP_STATUS protocol. */
70
+ /** Drives one builder through contracted native operations. */
71
71
  export declare class BuildInterruptedError extends Error {
72
72
  constructor();
73
73
  }
@@ -133,14 +133,20 @@ export declare class Foreman {
133
133
  /** Send user feedback on the plan; builder responds with a revised list. Does not count toward steps. */
134
134
  sendPreflightFeedback(feedback: string): Promise<void>;
135
135
  /** Send one custom instruction through the same needs_input loop as a batch turn. */
136
- runInstruction(instruction: string, options?: {
136
+ runInstruction(instruction: string, options: {
137
137
  completion?: InstructionCompletion;
138
+ output: OutputContract;
138
139
  }): Promise<{
139
140
  result: TurnResult;
140
141
  status: StepStatus;
141
142
  }>;
143
+ /** The sole convenience path that selects the Builder's work-report contract. */
144
+ runBuilderWork(instruction: string): Promise<{
145
+ result: TurnResult;
146
+ status: StepStatus;
147
+ }>;
142
148
  /** Ask an already-active role session to propose choices for its reported blocker. */
143
- resolveBlocker(adapter: BuilderAdapter, reason: string, mode?: "builder" | "qa"): Promise<{
149
+ resolveBlocker(adapter: BuilderAdapter, reason: string, mode: "builder" | "qa", output: OutputContract): Promise<{
144
150
  result: TurnResult;
145
151
  status: StepStatus;
146
152
  }>;
package/dist/foreman.js CHANGED
@@ -7,12 +7,13 @@ import { renderBuildWorkContext } from "./buildWorkContext.js";
7
7
  import { loadTicketSetupConfigWithDefaults } from "./tickets/setupConfig.js";
8
8
  import { deliverBuilderGuidanceFollowup } from "./builderGuidanceFollowup.js";
9
9
  import { graphContext, sendGraphTurn } from "./graph/turn.js";
10
+ import { workReportOutput, preflightOutput, qaVerdictOutput } from "rafi-spec";
11
+ import { operationInstruction, operationStatus, operationPayload, nativePolicy, structuredTurnErrorMessage } from "./structuredOperation.js";
10
12
  import { qaDigest } from "./qaProtocolV2.js";
11
13
  import { BuildControlBoundary } from "./buildInterventions.js";
12
14
  import { buildScopeRevision } from "./buildApproval.js";
13
15
  import { durableHumanDecision, HumanDecisionRequired, HumanDecisionCancelled, servicePendingHumanDecisions, decisionResponse } from "./humanDecision.js";
14
16
  import { select, text, isCancel } from "@clack/prompts";
15
- import { MARKER_SPEC, QA_MARKER_SPEC } from "./markers.js";
16
17
  import { countProviderQuestions, handleProviderQuestionTool } from "./providerQuestions.js";
17
18
  import { bestEffortDiagnostic } from "./questionTrace.js";
18
19
  import { signalAttention } from "./notify.js";
@@ -30,22 +31,6 @@ import { join, relative } from "node:path";
30
31
  import { SessionUnavailableError, recoverableBuilderTurnError, sessionUnavailableErrorFromFailure } from "./adapters/sessionFailure.js";
31
32
  import { WorkflowDb } from "./workflowDb.js";
32
33
  import { assertBuildAssignmentReconciled, beginBuildAssignment, BuildAssignmentRejected, captureBuildSource, finishBuildAssignment, validateBuildAssignment } from "./buildAssignment.js";
33
- /** Compare Builder product state without Rafi's own tracker bookkeeping. */
34
- function builderProductDigest(source, hostOwnedPaths) {
35
- const ignored = new Set(hostOwnedPaths.map(path => path.replace(/\\/g, "/").replace(/^\.\//, "")));
36
- const hostOwned = (path) => {
37
- const normalized = path.replace(/\\/g, "/").replace(/^\.\//, "");
38
- return ignored.has(normalized)
39
- || [".foreman/", ".rafi/", ".tickets/", ".codex/", ".agents/", "docs-rafi/"]
40
- .some(prefix => normalized.startsWith(prefix))
41
- || [".gitignore", "AGENTS.md", "rafi-config.yaml", "foreman.yaml"].includes(normalized);
42
- };
43
- const inventory = source.pathInventory
44
- .filter(item => !hostOwned(item.path))
45
- .map(item => ({ ...item, path: item.path.replace(/\\/g, "/") }))
46
- .sort((a, b) => a.path.localeCompare(b.path));
47
- return createHash("sha256").update(JSON.stringify(inventory)).digest("hex");
48
- }
49
34
  /** Phrases that suggest the builder ended its turn by asking the human. */
50
35
  const QUESTION_HINTS = [
51
36
  "should i",
@@ -155,7 +140,7 @@ export function buildPrimer(n, trackerPath, ticketsEnabled = false, preferredTic
155
140
  `\n- Foreman records completion and regenerates the tracker after independent QA passes. Do not run ticket-complete commands or mark tracker records done yourself.` +
156
141
  `\n- When available and permitted, \`foreman tickets discover --summary "..." --rationale "..."\` can log newly discovered work and \`foreman tickets update <id> --next-action "..."\` can record optional mid-turn notes.` +
157
142
  `\n- If a ticket CLI is unavailable, include those notes in your response; it is not an implementation blocker.` +
158
- `\n- Always include ticket="<id>" in your STEP_STATUS marker so foreman can update the tracker.`;
143
+ `\n- The host binds your result to the assigned ticket; report completion only for that exact work.`;
159
144
  }
160
145
  else if (trackerPath) {
161
146
  trackerRule = `\n- After completing each ticket or step, update its status in the ticket progress tracker at \`${trackerPath}\`, following the Standard Update Workflow documented in that file.`;
@@ -170,14 +155,14 @@ export function buildPrimer(n, trackerPath, ticketsEnabled = false, preferredTic
170
155
 
171
156
  Rules:
172
157
  - Do exactly ONE ticket or step this turn, then stop.
173
- - ${MARKER_SPEC}
174
- - If a tool action is denied by foreman policy, do not retry the denied form; report it via the blocked marker. The one exception is a required test that you changed by adding a pipe, redirection, substitution, or wrapper: retry the exact required-test command without that extra shell syntax before reporting it blocked.${trackerRule}${preferredTicketRule}
158
+ - Return the result using the selected native JSON output contract.
159
+ - If a tool action is denied by foreman policy, do not retry the denied form; report it using the blocked result. The one exception is a required test that you changed by adding a pipe, redirection, substitution, or wrapper: retry the exact required-test command without that extra shell syntax before reporting it blocked.${trackerRule}${preferredTicketRule}
175
160
 
176
161
  This is step 1 of ${n}. Implement the next ticket or step now.`;
177
162
  }
178
163
  /** Instruction sent on turns 2–N. */
179
164
  export function buildNextStepInstruction(i, n) {
180
- return `Implement the next ticket or step now (exactly one) — this is step ${i} of ${n}. Then end with the STEP_STATUS marker line.`;
165
+ return `Implement the next ticket or step now (exactly one) — this is step ${i} of ${n}. Return the completed work-report through the selected native JSON contract.`;
181
166
  }
182
167
  /** QA review turn: ask the builder to triple-check the ticket or step it just completed. */
183
168
  export function buildQaInstruction() {
@@ -194,10 +179,7 @@ The independent QA workspace is a disposable snapshot and may have a detached HE
194
179
 
195
180
  If an otherwise required test is denied only because you added a pipe, redirection, substitution, or wrapper, retry the exact required-test command without that extra shell syntax. Do not retry other denied actions.
196
181
 
197
- If everything is solid, end with STEP_STATUS: qa_pass.
198
- If anything is off, return the required RAFI_QA_FAILURE_REPORT_START/RAFI_QA_FAILURE_REPORT_END JSON envelope immediately before STEP_STATUS: qa_fail. The report must contain every check, every blocking finding, and all nonblocking observations. Do NOT fix issues on this turn — just report them. Foreman will instruct the Builder to fix them next.
199
-
200
- ${QA_MARKER_SPEC}`;
182
+ Return a native qa-verdict result. Use a passed payload only when verification succeeds. A failed payload must contain the full report: every check, blocking finding, and nonblocking observation. Do not fix issues during this review; the Builder receives failures separately.`;
201
183
  }
202
184
  /** Follow-up turn after qa_fail: have the builder implement the listed fixes. */
203
185
  export function buildQaFixInstruction(issues) {
@@ -205,7 +187,7 @@ export function buildQaFixInstruction(issues) {
205
187
 
206
188
  ${issues}
207
189
 
208
- Fix every one of them now. Then end with STEP_STATUS: done so foreman can re-run QA on the fixes. Triple-check that your fixes actually resolve the issues before emitting done.`;
190
+ Fix every one of them now. Submit the selected native remediation result so Foreman can rerun independent QA. Verify that your fixes resolve the reported issues.`;
209
191
  }
210
192
  /** Pre-flight planning turn: ask the builder to list its next N steps without implementing anything. */
211
193
  export function buildPlanningTurn(n, ticketsContent, preferredTicketId) {
@@ -215,7 +197,7 @@ export function buildPlanningTurn(n, ticketsContent, preferredTicketId) {
215
197
  const preferred = preferredTicketId
216
198
  ? ` The first item must be ticket ${preferredTicketId}; this is an interrupted ticket being resumed.`
217
199
  : "";
218
- return `${header}Before we begin, list the next ${n} ticket(s) or step(s) you plan to implement, in order.${preferred} Be specific — reference ticket IDs or titles where applicable. Do not implement anything yet; output the numbered list only. Do not emit a STEP_STATUS marker on this turn.`;
200
+ return `${header}Before we begin, list the next ${n} ticket(s) or step(s) you plan to implement, in order.${preferred} Be specific — reference ticket IDs or titles where applicable. Do not implement anything yet; return the ordered steps through the build-preflight response.`;
219
201
  }
220
202
  /** Builds the permission callback: classify, log, allow or escalate. */
221
203
  export function createPermissionHandler(policy, log, opts = {}) {
@@ -276,11 +258,11 @@ export function createPermissionHandler(policy, log, opts = {}) {
276
258
  behavior: "deny",
277
259
  message: `Foreman policy: this action needs human approval (${verdict.reason}) ` +
278
260
  `and was NOT performed. Do not retry this exact action. An allow-listed alternative is permitted; for project tests use a literal in-worktree cd, project-local PYTHONPATH, existing virtualenv Python, or uv run --offline --no-sync with an allowed test command. If no permitted alternative can complete the step, ` +
279
- `end your turn with: STEP_STATUS: blocked | reason="needs human approval: ${verdict.reason}".`,
261
+ `return a native blocked result explaining the required approval: ${verdict.reason}.`,
280
262
  };
281
263
  };
282
264
  }
283
- /** Drives one builder through a batch of N steps via the STEP_STATUS protocol. */
265
+ /** Drives one builder through contracted native operations. */
284
266
  export class BuildInterruptedError extends Error {
285
267
  constructor() { super("Build interrupted; current work is preserved for recovery"); this.name = "BuildInterruptedError"; }
286
268
  }
@@ -392,7 +374,7 @@ export class Foreman {
392
374
  * The returned result and status always reflect the final (non-needs_input) turn.
393
375
  */
394
376
  async doTurn(instruction) {
395
- return this.doTurnWith(this.builder, instruction);
377
+ return this.runBuilderWork(instruction);
396
378
  }
397
379
  async prepareBuilderBoundary(frozenAction) {
398
380
  if (!this.builderFactory || !this.projectDir)
@@ -440,19 +422,24 @@ export class Foreman {
440
422
  this.builder = await this.builderFactory(this.projectDir);
441
423
  this.log.write("branch-session", { role: "builder", transition: "missing-session-fresh", continuityLost: true });
442
424
  }
443
- async doTurnWith(adapter, instruction, mode = "builder", completion) {
425
+ async doTurnWith(adapter, instruction, mode = "builder", output, completion) {
444
426
  return this.projectDir
445
- ? withGraphSessionAccess(this.projectDir, adapter, () => this.doTurnWithOwned(adapter, instruction, mode, completion))
446
- : this.doTurnWithOwned(adapter, instruction, mode, completion);
427
+ ? withGraphSessionAccess(this.projectDir, adapter, () => this.doTurnWithOwned(adapter, instruction, mode, output, completion))
428
+ : this.doTurnWithOwned(adapter, instruction, mode, output, completion);
447
429
  }
448
- async doTurnWithOwned(adapter, instruction, mode = "builder", completion) {
430
+ async doTurnWithOwned(adapter, instruction, mode = "builder", output, completion) {
431
+ if (completion)
432
+ throw new Error("Text completion predicates cannot authorize native operation results");
449
433
  const ticket = mode === "builder" ? this.currentTicketId : undefined;
434
+ const statusOf = (turn) => operationStatus(turn, output, ticket);
450
435
  const hostToolObservations = [];
451
436
  const scopedInstruction = (text) => !ticket || text.includes(`Ticket scope: ${ticket}.`) ? text
452
- : `${text}\nTicket scope: ${ticket}. Do not substitute another ticket. Include ticket="${ticket}" in any STEP_STATUS marker.`;
437
+ : `${text}\nTicket scope: ${ticket}. Do not substitute another ticket.`;
453
438
  instruction = scopedInstruction(instruction);
439
+ instruction += `\n\n${operationInstruction(output)}`;
454
440
  const send = async (text, policy) => {
455
441
  this.assertNotInterrupted();
442
+ policy = nativePolicy(output, { ...policy, dispatchId: undefined, journalRunId: this.qaRunId });
456
443
  const context = ticket && this.projectDir ? renderBuildWorkContext(this.ticketForQa(0, ticket), loadTicketSetupConfigWithDefaults(this.projectDir).build.validation_checklist) : "";
457
444
  const contractContext = ticket && this.projectDir ? await ensureBuilderContract(this.projectDir, this.qaRunId, ticket, this.builderWorktree ?? this.projectDir, adapter, this.qaFactory ? (...args) => this.createOwnedQa(...args) : undefined) : "";
458
445
  this.assertNotInterrupted();
@@ -472,8 +459,8 @@ export class Foreman {
472
459
  finally {
473
460
  stopHostObservation();
474
461
  }
475
- const parsed = parseStepStatus(response.text);
476
- const applicable = parsed.kind === "unknown" && looksLikeQuestion(response.text) ? { kind: "needs_input", question: lastLine(response.text) } : parsed;
462
+ const parsed = statusOf(response);
463
+ const applicable = parsed;
477
464
  if (!this.interruptionRequested && assignment && !response.isError && !response.failure && ["done", "plan_complete"].includes(applicable.kind))
478
465
  await collectBuilderContractCoverage(this.projectDir, assignment.runId, assignment.ticketId, assignment.worktree, adapter, assignment.operationId, submitted, response, hostToolObservations, initialSourceDigest);
479
466
  const rejection = assignment ? finishBuildAssignment(this.projectDir, assignment, response, applicable) : undefined;
@@ -496,60 +483,16 @@ export class Foreman {
496
483
  this.builder = await this.beforeBuilderTurn(this.builder, instruction);
497
484
  adapter = this.builder;
498
485
  }
499
- const turnPolicy = { logicalActionId: this.currentTicketId ?? createHash("sha256").update(instruction).digest("hex") };
486
+ const turnPolicy = { logicalActionId: randomUUID() };
500
487
  let result = await send(instruction, turnPolicy);
501
488
  await this.observeBuilderNative(adapter);
502
- let status = parseStepStatus(result.text);
503
- if (!result.isError && !result.failure) {
504
- const completed = completion?.(result.text);
505
- if (completed)
506
- return { result, status: completed };
507
- }
489
+ let status = statusOf(result);
508
490
  if (ticket && validateBuildAssignment(ticket, status))
509
491
  return { result, status };
510
492
  if (result.isError || result.failure)
511
493
  return { result: { ...result, isError: true }, status };
512
- if (status.kind === "unknown") {
513
- if (!status.error && looksLikeQuestion(result.text)) {
514
- status = { kind: "needs_input", question: lastLine(result.text), choices: ["Continue", "Cancel"] };
515
- }
516
- else {
517
- if (adapter === this.builder && this.beforeBuilderTurn) {
518
- this.builder = await this.beforeBuilderTurn(this.builder, instruction);
519
- adapter = this.builder;
520
- }
521
- if (this.projectDir) {
522
- const budget = new WorkflowDb(this.projectDir);
523
- try {
524
- if (!budget.reserveRecoveryAllowance(this.qaRunId, turnPolicy.logicalActionId, "protocol-correction", budget.autonomyPolicy(this.qaRunId)?.limits.protocolCorrections ?? 1))
525
- return { result, status: { kind: "blocked", reason: "shared protocol correction budget exhausted" } };
526
- }
527
- finally {
528
- budget.close();
529
- }
530
- }
531
- const correction = mode === "qa"
532
- ? "Protocol correction only: based on the review already completed, return exactly one final STEP_STATUS: qa_pass or STEP_STATUS: qa_fail marker. Do not repeat QA, tests, tools, or compaction."
533
- : "Protocol correction only: based on the work already completed, return exactly one final STEP_STATUS: done, plan_complete, blocked, or needs_input marker. Do not repeat implementation, tools, or compaction.";
534
- result = await send([
535
- correction,
536
- `The previous marker was rejected: ${JSON.stringify(status.error ?? "missing final marker")}.`,
537
- "STEP_STATUS is a separate plain-text line, not JSON. Do not append braces, commas, a code fence, or commentary after it. Keep the continuity JSON on its own preceding line.",
538
- ...(mode === "builder" ? [
539
- `Only if the assigned work is complete, the exact marker form is: STEP_STATUS: done${ticket ? ` | ticket="${ticket}"` : ""}`,
540
- `If blocked, use: STEP_STATUS: blocked | ${ticket ? `ticket="${ticket}" ` : ""}reason="the actual blocker"`,
541
- "Choose the status supported by the work already performed; do not invent completion.",
542
- ] : ["If successful, use exactly: STEP_STATUS: qa_pass", "If failed, retain the required failure report and use exactly: STEP_STATUS: qa_fail"]),
543
- ].join("\n"), { ...turnPolicy, responseOnly: true });
544
- await this.observeBuilderNative(adapter);
545
- if (result.isError || result.failure)
546
- return { result: { ...result, isError: true }, status: { kind: "blocked", reason: result.text } };
547
- status = parseStepStatus(result.text);
548
- if (status.kind === "unknown")
549
- status = { ...status, error: `protocol correction exhausted: ${status.error ?? "missing final marker"}` };
550
- }
551
- }
552
- let blockerExplanations = 0;
494
+ if (status.kind === "unknown")
495
+ return { result: { ...result, isError: true }, status };
553
496
  while (true) {
554
497
  if (ticket && validateBuildAssignment(ticket, status))
555
498
  return { result, status };
@@ -632,58 +575,11 @@ export class Foreman {
632
575
  await this.observeBuilderNative(adapter);
633
576
  if (result.isError || result.failure)
634
577
  return { result: { ...result, isError: true }, status: { kind: "blocked", reason: result.text } };
635
- status = parseStepStatus(result.text);
636
- if (completion) {
637
- const completed = completion(result.text);
638
- if (completed)
639
- return { result, status: completed };
640
- }
578
+ status = statusOf(result);
641
579
  if (ticket && validateBuildAssignment(ticket, status))
642
580
  return { result, status };
643
581
  }
644
- if (status.kind !== "blocked" || (status.reason?.startsWith("user chose safe pause") || status.reason?.includes("Rafi is waiting for input")) || !process.stdin.isTTY || !process.stdout.isTTY)
645
- break;
646
- if (blockerExplanations++ >= 1)
647
- break;
648
- if (this.projectDir) {
649
- const budget = new WorkflowDb(this.projectDir);
650
- try {
651
- if (!budget.reserveRecoveryAllowance(this.qaRunId, this.currentTicketId ?? "instruction", "blocker-explanation", 1))
652
- break;
653
- }
654
- finally {
655
- budget.close();
656
- }
657
- }
658
- const reason = status.reason ?? "the agent reported an unspecified blocker";
659
- this.log.write("blocked-recovery", { reason, role: adapter === this.builder ? "builder" : "qa" });
660
- const recoveryInstruction = buildBlockerRecoveryInstruction(reason);
661
- if (adapter === this.builder && this.beforeBuilderTurn) {
662
- this.builder = await this.beforeBuilderTurn(this.builder, scopedInstruction(recoveryInstruction));
663
- adapter = this.builder;
664
- }
665
- result = await send(recoveryInstruction, turnPolicy);
666
- await this.observeBuilderNative(adapter);
667
- if (result.isError || result.failure)
668
- return { result: { ...result, isError: true }, status: { kind: "blocked", reason: result.text } };
669
- status = parseStepStatus(result.text);
670
- if (status.kind === "unknown") {
671
- if (this.projectDir && this.qaRunId) {
672
- const budget = new WorkflowDb(this.projectDir);
673
- try {
674
- if (!budget.reserveRecoveryAllowance(this.qaRunId, turnPolicy.logicalActionId, "protocol-correction", budget.autonomyPolicy(this.qaRunId)?.limits.protocolCorrections ?? 1))
675
- break;
676
- }
677
- finally {
678
- budget.close();
679
- }
680
- }
681
- result = await send('Protocol correction only: return the blocker approaches now using exactly one final STEP_STATUS: needs_input marker with question="..." and choices="recommended (Recommended)|alternative|alternative". Do not repeat tools or implementation.', { ...turnPolicy, responseOnly: true });
682
- await this.observeBuilderNative(adapter);
683
- if (result.isError || result.failure)
684
- return { result: { ...result, isError: true }, status: { kind: "blocked", reason: result.text } };
685
- status = parseStepStatus(result.text);
686
- }
582
+ break;
687
583
  }
688
584
  return { result, status };
689
585
  }
@@ -694,7 +590,8 @@ export class Foreman {
694
590
  if (this.beforeBuilderTurn)
695
591
  this.builder = await this.beforeBuilderTurn(this.builder, instruction);
696
592
  this.assertNotInterrupted();
697
- const result = this.projectDir ? await sendGraphTurn(this.builder, instruction, graphContext(this.projectDir, this.builderWorktree ?? this.projectDir, "build-preflight", instruction), { purpose: "planning" }) : await this.builder.sendTurn(instruction, { purpose: "planning" });
593
+ const policy = nativePolicy(preflightOutput, { purpose: "planning", journalRunId: this.qaRunId });
594
+ const result = this.projectDir ? await sendGraphTurn(this.builder, `${instruction}\n\n${operationInstruction(preflightOutput)}`, graphContext(this.projectDir, this.builderWorktree ?? this.projectDir, "build-preflight", instruction), policy) : await this.builder.sendTurn(instruction, policy);
698
595
  this.assertNotInterrupted();
699
596
  await this.observeBuilderNative(this.builder);
700
597
  this.log.write("preflight", {
@@ -703,9 +600,9 @@ export class Foreman {
703
600
  isError: result.isError,
704
601
  });
705
602
  if (result.isError)
706
- throw new Error(result.text);
707
- this.preflightPlan = result.text;
708
- return result.text;
603
+ throw new Error(structuredTurnErrorMessage(result, "Build preflight"));
604
+ this.preflightPlan = operationPayload(result, preflightOutput).steps.map((step, index) => `${index + 1}. ${step.ticket ? step.ticket + ": " : ""}${step.work}`).join("\n");
605
+ return this.preflightPlan;
709
606
  }
710
607
  /** Send user feedback on the plan; builder responds with a revised list. Does not count toward steps. */
711
608
  async sendPreflightFeedback(feedback) {
@@ -713,21 +610,26 @@ export class Foreman {
713
610
  if (this.beforeBuilderTurn)
714
611
  this.builder = await this.beforeBuilderTurn(this.builder, feedback);
715
612
  this.assertNotInterrupted();
716
- const result = this.projectDir ? await sendGraphTurn(this.builder, feedback, graphContext(this.projectDir, this.builderWorktree ?? this.projectDir, "build-preflight", feedback), { purpose: "planning" }) : await this.builder.sendTurn(feedback, { purpose: "planning" });
613
+ const policy = nativePolicy(preflightOutput, { purpose: "planning", journalRunId: this.qaRunId });
614
+ const result = this.projectDir ? await sendGraphTurn(this.builder, feedback, graphContext(this.projectDir, this.builderWorktree ?? this.projectDir, "build-preflight", feedback), policy) : await this.builder.sendTurn(feedback, policy);
717
615
  this.assertNotInterrupted();
718
616
  await this.observeBuilderNative(this.builder);
719
617
  this.log.write("preflight", { feedback: true, costUsd: result.costUsd, isError: result.isError });
720
618
  if (result.isError)
721
- throw new Error(result.text);
722
- this.preflightPlan = result.text;
619
+ throw new Error(structuredTurnErrorMessage(result, "Build preflight"));
620
+ this.preflightPlan = operationPayload(result, preflightOutput).steps.map((step, index) => `${index + 1}. ${step.ticket ? step.ticket + ": " : ""}${step.work}`).join("\n");
723
621
  }
724
622
  /** Send one custom instruction through the same needs_input loop as a batch turn. */
725
- async runInstruction(instruction, options = {}) {
726
- return this.doTurnWith(this.builder, instruction, "builder", options.completion);
623
+ async runInstruction(instruction, options) {
624
+ return this.doTurnWith(this.builder, instruction, "builder", options.output, options.completion);
625
+ }
626
+ /** The sole convenience path that selects the Builder's work-report contract. */
627
+ async runBuilderWork(instruction) {
628
+ return this.doTurnWith(this.builder, instruction, "builder", workReportOutput);
727
629
  }
728
630
  /** Ask an already-active role session to propose choices for its reported blocker. */
729
- async resolveBlocker(adapter, reason, mode = "builder") {
730
- return this.doTurnWith(adapter, buildBlockerRecoveryInstruction(reason), mode);
631
+ async resolveBlocker(adapter, reason, mode, output) {
632
+ return this.doTurnWith(adapter, buildBlockerRecoveryInstruction(reason), mode, output);
731
633
  }
732
634
  async observeBuilderNative(adapter) {
733
635
  if (adapter === this.builder)
@@ -807,7 +709,7 @@ export class Foreman {
807
709
  },
808
710
  evidence: ({ cycle, outcome, detail, qaDiff }) => this.log.write("qa", { stepIndex, cycle, outcome, detail, qaDiff, disposable: true }),
809
711
  onNonconvergence: this.qaNonconvergence,
810
- resolveBlocked: (adapter, reason) => this.resolveBlocker(adapter, reason, "qa"),
712
+ resolveBlocked: (adapter, reason) => this.resolveBlocker(adapter, reason, "qa", qaVerdictOutput),
811
713
  });
812
714
  if (resumedRecovery) {
813
715
  const recoveryDb = new WorkflowDb(this.projectDir);
@@ -1118,7 +1020,7 @@ export class Foreman {
1118
1020
  ? buildPrimer(n, trackerPath, this.ticketsEnabled, pendingTicketId)
1119
1021
  : buildNextStepInstruction(i, n);
1120
1022
  if (pendingTicketId)
1121
- instruction += `\n\nAssigned ticket: ${pendingTicketId}. Implement exactly this ticket. Do not substitute another ticket. End with ticket="${pendingTicketId}" in the STEP_STATUS marker.`;
1023
+ instruction += `\n\nAssigned ticket: ${pendingTicketId}. Implement exactly this ticket. Do not substitute another ticket. Return the native work report for this assigned ticket.`;
1122
1024
  const continuations = answered.filter(decision => decision.interruptionId === `ticket:${pendingTicketId}`);
1123
1025
  const continuationKeys = [];
1124
1026
  if (continuations.length) {
@@ -1140,14 +1042,6 @@ export class Foreman {
1140
1042
  }
1141
1043
  if (i > 1)
1142
1044
  await this.prepareBuilderBoundary(instruction);
1143
- // Current-branch builds do not always populate builderWorktree. The
1144
- // project directory is still the authoritative source boundary, so a
1145
- // plan-only resumed turn must be compared there before QA can run.
1146
- const builderSourceRoot = this.builderWorktree ?? this.projectDir;
1147
- const hostOwnedSourcePaths = this.projectDir && this.ticketsEnabled
1148
- ? generatedTrackerDirtyPaths(loadTicketsConfig(this.projectDir).paths)
1149
- : [];
1150
- const sourceBeforeTurn = builderSourceRoot ? captureBuildSource(builderSourceRoot) : undefined;
1151
1045
  this.builderWorkSessions += 1;
1152
1046
  const turn = () => this.doTurn(instruction);
1153
1047
  let response;
@@ -1190,41 +1084,6 @@ export class Foreman {
1190
1084
  detail = `builder turn errored: ${result.text.slice(0, 200)}`;
1191
1085
  break;
1192
1086
  }
1193
- // A resumed provider can answer the mandatory primer with a planning
1194
- // response while still emitting a done marker. Do not send unchanged
1195
- // source to QA or mark the ticket complete. Retry the bounded
1196
- // implementation once; if it still produces no work, stop as a real
1197
- // Builder blocker.
1198
- if (["done", "plan_complete"].includes(status.kind) && pendingTicketId && sourceBeforeTurn && builderSourceRoot) {
1199
- const sourceAfterTurn = captureBuildSource(builderSourceRoot);
1200
- const beforeProductDigest = builderProductDigest(sourceBeforeTurn, hostOwnedSourcePaths);
1201
- const afterProductDigest = builderProductDigest(sourceAfterTurn, hostOwnedSourcePaths);
1202
- // plan_complete is explicitly a planning response. Even if the
1203
- // provider touched bookkeeping while planning, never send that state
1204
- // to QA as if implementation were complete.
1205
- const responseIsPlanningOnly = /\bplan(?:ning)?\s+only\b|\bdo not implement(?: yet)?\b|\bawait(?:ing)? (?:approval|go-ahead)\b/i.test(result.text)
1206
- && !/\bimplemented\b|\badded\b|\bchanged\b/i.test(result.text);
1207
- if (status.kind === "plan_complete" || responseIsPlanningOnly || afterProductDigest === beforeProductDigest) {
1208
- this.log.write("step", { index: i, statusKind: "plan_only_without_source_change", ticket: pendingTicketId });
1209
- const retry = await this.doTurn(`The previous turn did not complete implementation. Implement the assigned ticket now: ${pendingTicketId}. Inspect the current files, make the required changes and tests, then report truthful evidence with STEP_STATUS: done | ticket="${pendingTicketId}" summary="...". Do not return another plan-only response.`);
1210
- result = retry.result;
1211
- status = retry.status;
1212
- if (result.isError) {
1213
- const recoverable = recoverableBuilderTurnError(result);
1214
- if (recoverable)
1215
- throw recoverable;
1216
- outcome = "blocked";
1217
- detail = `builder implementation retry errored: ${result.text.slice(0, 200)}`;
1218
- break;
1219
- }
1220
- const sourceAfterRetry = captureBuildSource(builderSourceRoot);
1221
- if (status.kind === "plan_complete" || (status.kind === "done" && builderProductDigest(sourceAfterRetry, hostOwnedSourcePaths) === beforeProductDigest)) {
1222
- outcome = "needs-human";
1223
- detail = `Builder returned planning-only responses without changing product source for ${pendingTicketId}`;
1224
- break;
1225
- }
1226
- }
1227
- }
1228
1087
  if (status.kind === "done" || status.kind === "plan_complete") {
1229
1088
  let qaSummary;
1230
1089
  let qaWaived = false;
@@ -1368,7 +1227,7 @@ export class Foreman {
1368
1227
  ? status.error
1369
1228
  : looksLikeQuestion(result.text)
1370
1229
  ? `builder ended with a question: ${lastLine(result.text)}`
1371
- : "builder did not emit a STEP_STATUS marker";
1230
+ : "builder did not submit a valid native operation result";
1372
1231
  break;
1373
1232
  }
1374
1233
  if (completed >= n && this.projectDir) {
@@ -1402,8 +1261,7 @@ function buildBlockerRecoveryInstruction(reason) {
1402
1261
  `You reported this blocker: ${reason}`,
1403
1262
  "Do not end the run. Analyze the durable state and propose two or three safe, materially different approaches the user can choose from.",
1404
1263
  "Put your recommended approach first and end its label with (Recommended). Include the important consequence or tradeoff in every choice label.",
1405
- "Do not perform more implementation or QA work until the user chooses. End exactly with:",
1406
- 'STEP_STATUS: needs_input | question="How should Rafi unblock this work?" choices="recommended approach and consequence (Recommended)|alternative and consequence|another alternative and consequence"',
1264
+ "Do not perform more implementation or QA work until the user chooses. Return a native needs-input result with the question and concrete choices.",
1407
1265
  "After the user answers, apply that guidance in this same session. If still blocked, report blocked again so Rafi can offer a new set of approaches.",
1408
1266
  ].join("\n\n");
1409
1267
  }
@@ -11,5 +11,5 @@ export interface GraphPendingTask {
11
11
  }
12
12
  /** A completed editing assignment plus host-observed source change is required.
13
13
  * Changed bytes alone are deliberately insufficient to establish this trigger. */
14
- export declare function queueGraphMaintenance(root: string, workspace: string, logicalTaskId: string, response: string, sourceChanged: boolean): void;
14
+ export declare function queueGraphMaintenance(root: string, workspace: string, logicalTaskId: string, response: string | boolean, sourceChanged: boolean): void;
15
15
  export declare function maintainGraphTask(root: string, workspace: string, logicalTaskId: string, host: BuilderAdapter): Promise<unknown>;
@@ -9,7 +9,7 @@ export const GRAPH_CHANGE_SIGNAL = "RAFI_GRAPH_MAINTENANCE: meaningful-indexed-c
9
9
  /** A completed editing assignment plus host-observed source change is required.
10
10
  * Changed bytes alone are deliberately insufficient to establish this trigger. */
11
11
  export function queueGraphMaintenance(root, workspace, logicalTaskId, response, sourceChanged) {
12
- if (!sourceChanged || !response.split(/\r?\n/).some(line => line.trim() === GRAPH_CHANGE_SIGNAL))
12
+ if (!sourceChanged || !(typeof response === "boolean" ? response : response.split(/\r?\n/).some(line => line.trim() === GRAPH_CHANGE_SIGNAL)))
13
13
  return;
14
14
  const effective = loadGraphConfig(root);
15
15
  if (!effective.enabled || effective.config?.maintenance !== "selective")
@@ -1,9 +1,11 @@
1
+ import { semanticExtractionOutput } from "rafi-spec";
2
+ import { nativePolicy, operationInstruction, operationPayload, journalStructuredDispatch } from "../structuredOperation.js";
1
3
  import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
2
4
  import { join } from "node:path";
3
5
  import { ClaudeAdapter } from "../adapters/claude.js";
4
6
  import { CodexAdapter } from "../adapters/codex.js";
5
7
  import { WorkflowDb } from "../workflowDb.js";
6
- import { parseSemanticResult, semanticExchange, SEMANTIC_INSTRUCTIONS } from "./semantic.js";
8
+ import { validateSemanticResult, semanticExchange, SEMANTIC_INSTRUCTIONS } from "./semantic.js";
7
9
  import { canonical, confined, digest, bytesDigest } from "./util.js";
8
10
  /** Fresh packet-only worker, using the selected host's exact runtime settings. */
9
11
  export function graphSemanticHost(root, host, createWorker = async (options) => host.agent === "codex" ? new CodexAdapter(options) : ClaudeAdapter.create(options)) {
@@ -101,29 +103,16 @@ export function graphSemanticHost(root, host, createWorker = async (options) =>
101
103
  store.put("job", id, { version: 1, state: "returned-unvalidated", operationId, requestId: request.operationId,
102
104
  inputDigest: request.inputDigest, sessionId: worker?.sessionId(),
103
105
  observations: observations.map(t => ({ turnId: t.turnId, usage: t.usage, costUsd: t.costAuthoritative ? t.costUsd : undefined,
104
- responseDigest: bytesDigest(t.rawResponse ?? t.text), failed: Boolean(t.isError || t.failure) })) });
106
+ responseDigest: bytesDigest(JSON.stringify({ structured: t.structured, rawResponse: t.rawResponse ?? t.text })), failed: Boolean(t.isError || t.failure) })) });
105
107
  };
106
- const turn = await Promise.race([worker.sendTurn(canonical(request), { responseOnly: true, handback: true, logicalActionId: request.operationId }), stopped]);
108
+ const turn = await Promise.race([journalStructuredDispatch(root, worker, `${canonical(request)}\n${operationInstruction(semanticExtractionOutput)}`, nativePolicy(semanticExtractionOutput, { responseOnly: true, handback: true, logicalActionId: request.operationId }), (prompt, policy) => worker.sendTurn(prompt, policy)), stopped]);
107
109
  recordObservation(turn);
108
110
  check();
109
- accountOutput(turn.cleanedResponse ?? turn.text);
110
111
  if (turn.isError || turn.failure)
111
112
  throw new Error(`Semantic provider failed: ${turn.text.slice(0, 500)}`);
112
- let parsed;
113
- try {
114
- parsed = parseSemanticResult(turn.cleanedResponse ?? turn.text, request);
115
- }
116
- catch (error) {
117
- check();
118
- store.put("job", id, { version: 1, state: "repair-intended", operationId, requestId: request.operationId, inputDigest: request.inputDigest, initialResponseDigest: bytesDigest(turn.rawResponse ?? turn.text), observations: observations.map(t => ({ turnId: t.turnId, usage: t.usage, costUsd: t.costAuthoritative ? t.costUsd : undefined })) });
119
- const repaired = await Promise.race([worker.sendTurn(`Format correction only. Preserve your source observations; no tools or new extraction. Return strict JSON matching ${canonical({ operationId: request.operationId, inputDigest: request.inputDigest })}. Validation: ${String(error)}`, { responseOnly: true, handback: true, logicalActionId: request.operationId }), stopped]);
120
- recordObservation(repaired);
121
- check();
122
- accountOutput(repaired.cleanedResponse ?? repaired.text);
123
- if (repaired.isError || repaired.failure)
124
- throw new Error("Semantic format correction failed; reconciliation required");
125
- parsed = parseSemanticResult(repaired.cleanedResponse ?? repaired.text, request);
126
- }
113
+ const payload = operationPayload(turn, semanticExtractionOutput);
114
+ accountOutput(JSON.stringify(payload));
115
+ const parsed = validateSemanticResult(payload, request);
127
116
  const known = observations.every(t => t.usage?.scope === "turn-delta" && t.usage.inputTokens !== undefined && t.usage.outputTokens !== undefined);
128
117
  const authoritative = observations.every(t => t.costAuthoritative === true);
129
118
  store.put("job", id, { version: 1, state: "completed", operationId, sessionId: worker.sessionId(),
@@ -1,6 +1,6 @@
1
1
  import { type ManagerGraphRequestV3 } from "rafi-spec";
2
2
  import type { GraphDerivedAccess } from "./derived.js";
3
- export declare const MANAGER_GRAPH_GUIDANCE = "For substantial architecture/dependency questions, request graph evidence through this additional host envelope: {\"kind\":\"manager_graph_evidence_request\",\"version\":3,\"requestId\":\"unique-id\",\"operations\":[{\"operation\":\"query\",\"query\":\"specific source concepts\"}]}. Do not mix envelopes. This shares your existing evidence lookup budget. Only the host chooses project/source scope. An optional receiptId can select previously delivered evidence belonging to the host-selected run; unavailable historical caches stay unavailable. Current-source graph evidence cannot establish a historical run's implementation or cause. Graph output never authorizes controls.";
3
+ export declare const MANAGER_GRAPH_GUIDANCE = "For substantial architecture/dependency questions, request graph evidence through a native evidence-request result with evidenceType graph whose request is: {\"kind\":\"manager_graph_evidence_request\",\"version\":3,\"requestId\":\"unique-id\",\"operations\":[{\"operation\":\"query\",\"query\":\"specific source concepts\"}]}. Submit one result per turn. This shares your existing evidence lookup budget. Only the host chooses project/source scope. An optional receiptId can select previously delivered evidence belonging to the host-selected run; unavailable historical caches stay unavailable. Current-source graph evidence cannot establish a historical run's implementation or cause. Graph output never authorizes controls.";
4
4
  export declare function parseManagerGraphRequest(text: string): ManagerGraphRequestV3 | undefined;
5
5
  export declare function createManagerGraphEvidence(configRoot: string, runId?: string): {
6
6
  guidance: string;
@@ -4,7 +4,7 @@ import { loadGraphConfig } from "./config.js";
4
4
  import { captureGraphCorpus } from "./corpus.js";
5
5
  import { acquireGraphView, graphUnavailable, readGraph } from "./read.js";
6
6
  import { canonical } from "./util.js";
7
- export const MANAGER_GRAPH_GUIDANCE = `For substantial architecture/dependency questions, request graph evidence through this additional host envelope: {"kind":"manager_graph_evidence_request","version":3,"requestId":"unique-id","operations":[{"operation":"query","query":"specific source concepts"}]}. Do not mix envelopes. This shares your existing evidence lookup budget. Only the host chooses project/source scope. An optional receiptId can select previously delivered evidence belonging to the host-selected run; unavailable historical caches stay unavailable. Current-source graph evidence cannot establish a historical run's implementation or cause. Graph output never authorizes controls.`;
7
+ export const MANAGER_GRAPH_GUIDANCE = `For substantial architecture/dependency questions, request graph evidence through a native evidence-request result with evidenceType graph whose request is: {"kind":"manager_graph_evidence_request","version":3,"requestId":"unique-id","operations":[{"operation":"query","query":"specific source concepts"}]}. Submit one result per turn. This shares your existing evidence lookup budget. Only the host chooses project/source scope. An optional receiptId can select previously delivered evidence belonging to the host-selected run; unavailable historical caches stay unavailable. Current-source graph evidence cannot establish a historical run's implementation or cause. Graph output never authorizes controls.`;
8
8
  export function parseManagerGraphRequest(text) {
9
9
  if (Buffer.byteLength(text) > 8192 || !text.trim().startsWith("{"))
10
10
  return undefined;
@@ -1,4 +1,5 @@
1
1
  import type { BuilderAdapter } from "../adapters/types.js";
2
+ import type { OutputContract } from "rafi-spec";
2
3
  /** One reserved preparation operation owns all optional evidence subturns. */
3
4
  export declare function sendPreparationGraphTurn(adapter: BuilderAdapter, instruction: string, input: {
4
5
  phase: "planner" | "prepare" | "assess" | "challenge" | "repair";
@@ -10,8 +11,10 @@ export declare function sendPreparationGraphTurn(adapter: BuilderAdapter, instru
10
11
  workId: string;
11
12
  operationId: string;
12
13
  remainingMs: number;
14
+ output?: OutputContract;
13
15
  }): Promise<{
14
16
  graphReceiptRefs: string[];
17
+ transportEvidence?: import("../adapters/types.js").RetainedTransportEvidence;
15
18
  text: string;
16
19
  isError: boolean;
17
20
  numTurns: number;
@@ -29,6 +32,7 @@ export declare function sendPreparationGraphTurn(adapter: BuilderAdapter, instru
29
32
  failure?: import("../adapters/types.js").RuntimeFailure;
30
33
  turnId?: string;
31
34
  continuityErrors?: string[];
35
+ continuity?: import("rafi-spec").ContinuityDelta;
32
36
  hostInstruction?: string;
33
37
  providerInstruction?: string;
34
38
  rawResponse?: string;
@@ -38,4 +42,38 @@ export declare function sendPreparationGraphTurn(adapter: BuilderAdapter, instru
38
42
  sessionId?: string;
39
43
  sessionRef?: import("rafi-spec").ProviderSessionRefV1;
40
44
  };
45
+ structured: import("rafi-spec").StructuredResponse;
46
+ selectedDispatch: import("rafi-spec").SelectedDispatchTupleV2;
47
+ } | {
48
+ graphReceiptRefs: string[];
49
+ transportEvidence?: import("../adapters/types.js").RetainedTransportEvidence;
50
+ text: string;
51
+ isError: boolean;
52
+ numTurns: number;
53
+ costUsd: number;
54
+ costAuthoritative?: boolean;
55
+ inputTokens?: number;
56
+ outputTokens?: number;
57
+ usage?: {
58
+ scope: "turn-delta" | "session-cumulative";
59
+ inputTokens?: number;
60
+ outputTokens?: number;
61
+ totalTokens?: number;
62
+ costUsd?: number;
63
+ };
64
+ failure?: import("../adapters/types.js").RuntimeFailure;
65
+ turnId?: string;
66
+ continuityErrors?: string[];
67
+ continuity?: import("rafi-spec").ContinuityDelta;
68
+ hostInstruction?: string;
69
+ providerInstruction?: string;
70
+ rawResponse?: string;
71
+ cleanedResponse?: string;
72
+ providerMetadata?: {
73
+ provider: "claude" | "codex";
74
+ sessionId?: string;
75
+ sessionRef?: import("rafi-spec").ProviderSessionRefV1;
76
+ };
77
+ structured?: undefined;
78
+ selectedDispatch?: import("rafi-spec").SelectedDispatchTupleV2;
41
79
  }>;