@loucompanion/forge-bridge 0.1.3-dev.fbe15023cdfe → 0.1.4-dev.bfa11ebe98c6

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 (160) hide show
  1. package/README.md +88 -2
  2. package/dist/bridge/bridge-chat.types.d.ts +111 -0
  3. package/dist/bridge/bridge-chat.types.js +401 -0
  4. package/dist/bridge/bridge.base.d.ts +4 -1
  5. package/dist/bridge/bridge.service.d.ts +72 -3
  6. package/dist/bridge/bridge.service.js +1562 -152
  7. package/dist/bridge/bridge.types.d.ts +46 -1
  8. package/dist/bridge/dispatch-result-outbox.d.ts +25 -0
  9. package/dist/bridge/dispatch-result-outbox.js +62 -0
  10. package/dist/bridge/dispatch-result-worker.d.ts +2 -0
  11. package/dist/bridge/dispatch-result-worker.js +122 -0
  12. package/dist/bridge/internals.d.ts +2 -0
  13. package/dist/bridge/internals.js +1 -0
  14. package/dist/bridge/readiness-result-outbox.d.ts +21 -0
  15. package/dist/bridge/readiness-result-outbox.js +54 -0
  16. package/dist/bridge/readiness-result-worker.d.ts +2 -0
  17. package/dist/bridge/readiness-result-worker.js +121 -0
  18. package/dist/bridge/runner-host-capabilities.d.ts +15 -0
  19. package/dist/bridge/runner-host-capabilities.js +93 -0
  20. package/dist/cleanup/cleanup.service.d.ts +1 -0
  21. package/dist/cleanup/cleanup.service.js +70 -9
  22. package/dist/cli/cli.help.js +2 -2
  23. package/dist/cli/cli.service.d.ts +14 -6
  24. package/dist/cli/cli.service.js +75 -16
  25. package/dist/cli/local-config.d.ts +1 -0
  26. package/dist/cli/local-config.js +4 -0
  27. package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
  28. package/dist/runtime/aspnet-runtime-prebuild.js +341 -0
  29. package/dist/runtime/attempt-node-runtime-launcher.d.ts +2 -1
  30. package/dist/runtime/attempt-node-runtime-launcher.js +57 -18
  31. package/dist/runtime/attempt-path-operation.d.ts +21 -0
  32. package/dist/runtime/attempt-path-operation.js +94 -0
  33. package/dist/runtime/attempt-private-directory.d.ts +6 -0
  34. package/dist/runtime/attempt-private-directory.js +54 -0
  35. package/dist/runtime/attempt-process-supervisor.d.ts +11 -0
  36. package/dist/runtime/attempt-process-supervisor.js +67 -20
  37. package/dist/runtime/canonical-source-file.d.ts +3 -0
  38. package/dist/runtime/canonical-source-file.js +54 -0
  39. package/dist/runtime/docker-engine-probe.d.ts +90 -0
  40. package/dist/runtime/docker-engine-probe.js +333 -0
  41. package/dist/runtime/generated-output-reset.d.ts +10 -0
  42. package/dist/runtime/generated-output-reset.js +62 -8
  43. package/dist/runtime/host-database-capability-boundary.d.ts +16 -0
  44. package/dist/runtime/host-database-capability-boundary.js +62 -0
  45. package/dist/runtime/host-diagnostics.d.ts +11 -0
  46. package/dist/runtime/host-diagnostics.js +191 -0
  47. package/dist/runtime/host-execution-root.d.ts +4 -0
  48. package/dist/runtime/host-execution-root.js +39 -0
  49. package/dist/runtime/host-listener-inspector.d.ts +8 -1
  50. package/dist/runtime/host-listener-inspector.js +121 -60
  51. package/dist/runtime/host-runtime-contract.d.ts +12 -1
  52. package/dist/runtime/host-runtime-contract.js +107 -53
  53. package/dist/runtime/host-runtime-lease.d.ts +90 -8
  54. package/dist/runtime/host-runtime-lease.js +1097 -108
  55. package/dist/runtime/host-runtime-promotion.d.ts +12 -3
  56. package/dist/runtime/host-runtime-promotion.js +59 -12
  57. package/dist/runtime/memory-capacity.d.ts +70 -0
  58. package/dist/runtime/memory-capacity.js +123 -0
  59. package/dist/runtime/native-test-postgres-fallback.d.ts +25 -0
  60. package/dist/runtime/native-test-postgres-fallback.js +91 -0
  61. package/dist/runtime/node-host-runtime-adapter.d.ts +3 -0
  62. package/dist/runtime/node-host-runtime-adapter.js +28 -11
  63. package/dist/runtime/pinned-directory-process.d.ts +9 -0
  64. package/dist/runtime/pinned-directory-process.js +61 -0
  65. package/dist/runtime/pinned-source-directory.d.ts +17 -0
  66. package/dist/runtime/pinned-source-directory.js +94 -0
  67. package/dist/runtime/playwright-host-runtime-adapter.d.ts +1 -0
  68. package/dist/runtime/playwright-host-runtime-adapter.js +53 -5
  69. package/dist/runtime/prepared-runtime-identity.d.ts +6 -0
  70. package/dist/runtime/prepared-runtime-identity.js +218 -0
  71. package/dist/runtime/prepared-runtime-readiness.d.ts +30 -0
  72. package/dist/runtime/prepared-runtime-readiness.js +285 -0
  73. package/dist/runtime/runtime-isolation-lease.js +4 -1
  74. package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
  75. package/dist/runtime/workspace-resource-estimator.js +174 -0
  76. package/dist/sandbox/preflight.d.ts +31 -1
  77. package/dist/sandbox/preflight.js +237 -51
  78. package/dist/sandbox/repository-lock.d.ts +2 -1
  79. package/dist/sandbox/repository-lock.js +17 -8
  80. package/dist/sandbox/runtime.d.ts +6 -1
  81. package/dist/sandbox/runtime.js +132 -103
  82. package/dist/sandbox/sandbox.types.d.ts +7 -0
  83. package/dist/sandbox/workspace.service.js +2 -0
  84. package/dist/session/managed-attempt-tools.d.ts +17 -1
  85. package/dist/session/managed-attempt-tools.js +481 -31
  86. package/dist/session/managed-build-coordinator.d.ts +5 -0
  87. package/dist/session/managed-build-coordinator.js +105 -0
  88. package/dist/session/managed-build-receipt.d.ts +21 -0
  89. package/dist/session/managed-build-receipt.js +337 -0
  90. package/dist/session/managed-verification-timeouts.d.ts +8 -0
  91. package/dist/session/managed-verification-timeouts.js +17 -0
  92. package/dist/session/phase-completion.artifacts.d.ts +17 -0
  93. package/dist/session/phase-completion.artifacts.js +224 -22
  94. package/dist/session/phase-completion.d.ts +11 -1
  95. package/dist/session/phase-completion.helpers.d.ts +5 -0
  96. package/dist/session/phase-completion.helpers.js +30 -7
  97. package/dist/session/phase-completion.js +14 -3
  98. package/dist/session/phase-completion.prompts.js +17 -18
  99. package/dist/session/phase-completion.runtime.d.ts +3 -1
  100. package/dist/session/phase-completion.runtime.js +118 -16
  101. package/dist/session/phase-completion.schemas.js +49 -2
  102. package/dist/session/phase-completion.validation.d.ts +1 -0
  103. package/dist/session/phase-completion.validation.js +111 -21
  104. package/dist/session/pre-publication-validation.d.ts +75 -0
  105. package/dist/session/pre-publication-validation.js +657 -0
  106. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.d.ts +72 -0
  107. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.js +462 -0
  108. package/dist/session/provider-cli/ci-repair-readiness.d.ts +8 -0
  109. package/dist/session/provider-cli/ci-repair-readiness.js +23 -3
  110. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +4 -2
  111. package/dist/session/provider-cli/provider-cli.artifacts.js +47 -13
  112. package/dist/session/provider-cli/provider-cli.deterministic.js +34 -2
  113. package/dist/session/provider-cli/provider-cli.process.d.ts +51 -0
  114. package/dist/session/provider-cli/provider-cli.process.js +422 -22
  115. package/dist/session/provider-cli/provider-cli.prompts.d.ts +5 -0
  116. package/dist/session/provider-cli/provider-cli.prompts.js +136 -16
  117. package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
  118. package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
  119. package/dist/session/provider-cli/provider-cli.runtime.d.ts +8 -1
  120. package/dist/session/provider-cli/provider-cli.runtime.js +40 -3
  121. package/dist/session/provider-cli/provider-cli.service.d.ts +30 -5
  122. package/dist/session/provider-cli/provider-cli.service.js +771 -68
  123. package/dist/session/provider-cli/provider-cli.types.d.ts +2 -0
  124. package/dist/session/provider-cli/provider-output-state.d.ts +10 -0
  125. package/dist/session/provider-cli/provider-output-state.js +361 -17
  126. package/dist/session/provider-cli/provider-usage.d.ts +7 -0
  127. package/dist/session/provider-cli/provider-usage.js +28 -5
  128. package/dist/session/session-redaction.js +434 -9
  129. package/dist/session/session.service.js +145 -23
  130. package/dist/session/session.types.d.ts +147 -2
  131. package/dist/session/session.types.js +587 -7
  132. package/dist/session/unreferenced-exports.d.ts +12 -0
  133. package/dist/session/unreferenced-exports.js +59 -0
  134. package/dist/shared/format/format-bytes.d.ts +1 -0
  135. package/dist/shared/format/format-bytes.js +9 -0
  136. package/dist/shared/git/git.js +2 -1
  137. package/dist/shared/path/path.d.ts +1 -0
  138. package/dist/shared/path/path.js +18 -0
  139. package/dist/shared/process/dotnet-toolchain.d.ts +17 -0
  140. package/dist/shared/process/dotnet-toolchain.js +307 -0
  141. package/dist/shared/process/host-process-supervisor.d.ts +2 -0
  142. package/dist/shared/process/host-process-supervisor.js +57 -9
  143. package/dist/shared/process/process.d.ts +2 -0
  144. package/dist/shared/process/process.js +12 -7
  145. package/dist/shared/provider-capabilities.d.ts +3 -0
  146. package/dist/shared/provider-capabilities.js +85 -0
  147. package/dist/shared/redaction/redaction.d.ts +1 -0
  148. package/dist/shared/redaction/redaction.js +39 -3
  149. package/dist/version.d.ts +1 -1
  150. package/dist/version.js +1 -1
  151. package/dist/worktree/dependency-warmer.d.ts +41 -9
  152. package/dist/worktree/dependency-warmer.js +497 -22
  153. package/dist/worktree/index.d.ts +1 -1
  154. package/dist/worktree/story-qa-reference-hydrator.d.ts +4 -0
  155. package/dist/worktree/story-qa-reference-hydrator.js +253 -0
  156. package/dist/worktree/worktree.service.d.ts +7 -0
  157. package/dist/worktree/worktree.service.js +253 -21
  158. package/dist/worktree/worktree.types.d.ts +7 -1
  159. package/package.json +7 -5
  160. package/runtime/windows-job-supervisor.ps1 +380 -14
@@ -1,8 +1,10 @@
1
1
  import fs from "node:fs/promises";
2
+ import os from "node:os";
2
3
  import path from "node:path";
3
4
  import { completionPhaseFromDispatch, completionPhaseOrder, completionRedactionSecretsEnv, completionToolNamesForDebugGate, completionToolNamesForDecomposeGate, completionToolNamesForDraftGate, completionToolNamesForPhase, completionToolNamesForQaGate, debugCompletionPhaseOrder, decomposeCompletionGateOrder, draftCompletionGateOrder, qaCompletionGateOrder, toolServerName, usesArchitectCycleCompletion, usesDebugCycleCompletion, usesDevelopCycleCompletion, usesQaCycleCompletion } from "./phase-completion.js";
4
- import { asDebugCompletionPhase, asJsonRpcMessage, asMutableRecord, asRecord, artifactOwnerRelativeRoot, decomposeGateFromToolName, draftGateFromToolName, errorMessage, forgeBridgeCliBinPath, isDebugCompletionPhase, isDecomposeDispatch, isDevelopCompletionPhase, normalizedOwnerType, parseCompletionRedactionSecrets, parseOptionalJsonObject, parseStrictCompletionPhase, phaseFromToolName, qaGateFromToolName, requiredString, rpcError, tomlString, tomlStringArray, writeFileAtomic, writeJsonRpc } from "./phase-completion.validation.js";
5
+ import { CompletionPayloadValidationError, asDebugCompletionPhase, asJsonRpcMessage, asMutableRecord, asRecord, artifactOwnerRelativeRoot, decomposeGateFromToolName, draftGateFromToolName, errorMessage, forgeBridgeCliBinPath, isDebugCompletionPhase, isDecomposeDispatch, isDevelopCompletionPhase, normalizedOwnerType, parseCompletionRedactionSecrets, parseOptionalJsonObject, parseStrictCompletionPhase, phaseFromToolName, qaGateFromToolName, requiredString, rpcError, tomlString, tomlStringArray, writeFileAtomic, writeJsonRpc } from "./phase-completion.validation.js";
5
6
  import { completionPromptForDebugCycle, completionPromptForDecomposeCycle, completionPromptForDevelopCycle, completionPromptForDraftCycle, completionPromptForPhase, completionPromptForQaCycle, completionToolNextInstruction, completionToolNextInstructionForDebugGate, completionToolNextInstructionForDecomposeGate, completionToolNextInstructionForDraftGate, completionToolNextInstructionForQaGate, nextDebugPhaseAfter, nextPhaseAfter } from "./phase-completion.prompts.js";
7
+ import { configuredNativeTestPostgresFallbackPath, nativeTestPostgresFallbackConfigEnvironment, } from "../runtime/native-test-postgres-fallback.js";
6
8
  import { toolDescription, toolInputSchema } from "./phase-completion.schemas.js";
7
9
  import { writeCompletionArtifact, writeDecomposeGateCompletionArtifact, writeDraftGateCompletionArtifact } from "./phase-completion.artifacts.js";
8
10
  import { callManagedAttemptTool, isManagedAttemptTool, managedAttemptToolDefinition, managedAttemptToolNames, } from "./managed-attempt-tools.js";
@@ -33,13 +35,17 @@ export async function prepareCompletionToolRuntime(options) {
33
35
  serverName: toolServerName,
34
36
  command: process.execPath,
35
37
  args: [forgeBridgeCliBinPath(), ...mcpArgs],
36
- env: completionToolRuntimeEnvironment(options.runtimeEnvironment),
38
+ env: completionToolRuntimeEnvironment(options.runtimeEnvironment, configuredNativeTestPostgresFallbackPath()),
37
39
  cleanup: async () => undefined
38
40
  };
39
- if (options.dispatch.execution.provider.trim().toLowerCase() !== "cursor") {
40
- return runtime;
41
+ const provider = options.dispatch.execution.provider.trim().toLowerCase();
42
+ if (provider === "cursor") {
43
+ return await prepareCursorCompletionToolRuntime(worktreePath, runtime);
41
44
  }
42
- return await prepareCursorCompletionToolRuntime(worktreePath, runtime);
45
+ if (provider === "claude") {
46
+ return await prepareClaudeCompletionToolRuntime(runtime);
47
+ }
48
+ return runtime;
43
49
  }
44
50
  export function buildCompletionToolServerArgs(config) {
45
51
  const args = [
@@ -101,6 +107,14 @@ export function cursorMcpServerConfig(runtime) {
101
107
  env: runtime.env
102
108
  };
103
109
  }
110
+ export function claudeMcpServerConfig(runtime) {
111
+ return {
112
+ type: "stdio",
113
+ command: runtime.command,
114
+ args: [...runtime.args],
115
+ env: runtime.env
116
+ };
117
+ }
104
118
  export function completionToolNamesForServer(config) {
105
119
  return config.decomposeCycle
106
120
  ? decomposeCompletionGateOrder.flatMap((gate) => completionToolNamesForDecomposeGate(gate))
@@ -185,9 +199,11 @@ export class CompletionToolServerSession {
185
199
  ? this.advanceAfterPass(completedPhase)
186
200
  : completedPhase;
187
201
  const nextInstruction = completionToolNextInstruction(completedPhase, status, nextPhase);
188
- const reason = pass
189
- ? `${completedPhase} completion artifact accepted.`
190
- : `${completedPhase} completion artifact recorded with status ${status}; rework is required before phase progression.`;
202
+ const reason = completedPhase === "qa"
203
+ ? `QA artifact recorded locally with status ${status}; API ingestion and acceptance are pending.`
204
+ : pass
205
+ ? `${completedPhase} completion artifact accepted.`
206
+ : `${completedPhase} completion artifact recorded with status ${status}; rework is required before phase progression.`;
191
207
  return {
192
208
  content: [
193
209
  {
@@ -197,6 +213,7 @@ export class CompletionToolServerSession {
197
213
  ],
198
214
  structuredContent: {
199
215
  ...reference,
216
+ ...acceptedArtifactReceipt(reference),
200
217
  gate: {
201
218
  phase: completedPhase,
202
219
  status,
@@ -234,6 +251,7 @@ export class CompletionToolServerSession {
234
251
  content: [{ type: "text", text: `Forge recorded ${completedGate} artifact ${path.relative(reference.repoPath, reference.path)} (${reference.status}, ${reference.contentHash}). ${nextInstruction}` }],
235
252
  structuredContent: {
236
253
  ...reference,
254
+ ...acceptedArtifactReceipt(reference),
237
255
  gate: { phase: "decompose", decomposeGate: completedGate, status, allowed: pass, reason },
238
256
  nextPhase,
239
257
  nextInstruction
@@ -258,6 +276,7 @@ export class CompletionToolServerSession {
258
276
  content: [{ type: "text", text: `Forge recorded ${completedGate} artifact ${path.relative(reference.repoPath, reference.path)} (${reference.status}, ${reference.contentHash}). ${nextInstruction}` }],
259
277
  structuredContent: {
260
278
  ...reference,
279
+ ...acceptedArtifactReceipt(reference),
261
280
  gate: { phase: "draft", draftGate: completedGate, status, allowed: status !== "blocked", reason: nextGate ? `${completedGate} artifact accepted; continue to ${nextGate}.` : `Draft proposal artifact accepted with status ${status}.` },
262
281
  nextPhase,
263
282
  nextInstruction
@@ -291,6 +310,7 @@ export class CompletionToolServerSession {
291
310
  ],
292
311
  structuredContent: {
293
312
  ...reference,
313
+ ...acceptedArtifactReceipt(reference),
294
314
  gate: {
295
315
  phase: completedGate,
296
316
  status,
@@ -321,8 +341,8 @@ export class CompletionToolServerSession {
321
341
  const nextInstruction = completionToolNextInstructionForQaGate(completedGate, status, nextGate);
322
342
  const allowed = status !== "blocked";
323
343
  const reason = nextGate
324
- ? `${completedGate} artifact accepted; continue to ${nextGate}.`
325
- : `QA verdict artifact accepted with status ${status}.`;
344
+ ? `${completedGate} artifact accepted locally; continue to ${nextGate}.`
345
+ : `QA verdict artifact recorded locally with status ${status}; API ingestion and acceptance are pending.`;
326
346
  return {
327
347
  content: [
328
348
  {
@@ -332,6 +352,7 @@ export class CompletionToolServerSession {
332
352
  ],
333
353
  structuredContent: {
334
354
  ...reference,
355
+ ...acceptedArtifactReceipt(reference),
335
356
  gate: {
336
357
  phase: "qa",
337
358
  qaGate: completedGate,
@@ -412,14 +433,28 @@ const completionToolRuntimeEnvironmentNames = [
412
433
  "FORGE_RUNTIME_MODE",
413
434
  "FORGE_RESOURCE_LEASE_ID",
414
435
  "FORGE_DISPATCH_ATTEMPT_ID",
436
+ "FORGE_E2E_OWNERSHIP_MODE",
437
+ "FORGE_E2E_OWNERSHIP_TOKEN",
438
+ "FORGE_WORKTREE_PATH",
415
439
  "FORGE_LOG_DIR",
440
+ "FORGE_CACHE_DIR",
416
441
  "FORGE_TEST_OUTPUT_DIR",
442
+ "FORGE_ATTEMPT_DATABASE_SCHEMA",
443
+ "FORGE_ATTEMPT_DATABASE_READINESS_PATH",
444
+ "FORGE_CI_REPAIR_READINESS_PATH",
445
+ "FORGE_CI_REPAIR_HANDOFF_PATH",
446
+ "FORGE_CI_REPAIR_PROVIDER_ONLY_SPECS",
447
+ "FORGE_PLAYWRIGHT_CONFIG",
448
+ "FORGE_E2E_API_URL",
449
+ "FORGE_E2E_WEB_URL",
450
+ "PLAYWRIGHT_BROWSERS_PATH",
417
451
  "FORGE_TMPDIR",
452
+ "NPM_CONFIG_CACHE",
418
453
  "TMPDIR",
419
454
  "TMP",
420
455
  "TEMP",
421
456
  ];
422
- export function completionToolRuntimeEnvironment(runtimeEnvironment) {
457
+ export function completionToolRuntimeEnvironment(runtimeEnvironment, nativeTestPostgresFallbackPath) {
423
458
  if (!runtimeEnvironment)
424
459
  return {};
425
460
  const env = {};
@@ -428,6 +463,11 @@ export function completionToolRuntimeEnvironment(runtimeEnvironment) {
428
463
  if (value)
429
464
  env[name] = value;
430
465
  }
466
+ // Managed verification reads the operator's native test cluster only through
467
+ // this explicit path, and only while Docker is unavailable.
468
+ if (nativeTestPostgresFallbackPath) {
469
+ env[nativeTestPostgresFallbackConfigEnvironment] = nativeTestPostgresFallbackPath;
470
+ }
431
471
  return env;
432
472
  }
433
473
  const minimumManagedSolutionTestTimeoutMs = 45 * 60 * 1000;
@@ -453,7 +493,8 @@ export function assertManagedQaRegressionEvidence(input, receipts) {
453
493
  const managedCommandId = entry.managed_command_id;
454
494
  if (typeof managedCommandId !== "string" || !managedCommandId.trim()) {
455
495
  throw new Error(`qa_regression_managed_command_id_required: commands[${index}] runs the solution-wide Forge test. `
456
- + "Run it with forge.start_managed_command, continue independent work, call forge.wait_managed_command once for the terminal receipt, and include the returned managed_command_id in this command evidence.");
496
+ + "Run it with forge.start_managed_command, continue independent work, then call forge.wait_managed_command for a transport-safe receipt. "
497
+ + "If command_continues=true, continue useful work or wait again after retry_after_ms. Include the terminal managed_command_id in this command evidence.");
457
498
  }
458
499
  const matchingReceipts = receipts.filter((receipt) => {
459
500
  const tokens = receipt.command;
@@ -464,7 +505,8 @@ export function assertManagedQaRegressionEvidence(input, receipts) {
464
505
  });
465
506
  if (matchingReceipts.length === 0) {
466
507
  throw new Error(`qa_regression_managed_command_required: commands[${index}] runs the solution-wide Forge test. `
467
- + "Run it with forge.start_managed_command, continue independent work, call forge.wait_managed_command once for the terminal receipt, and include its matching managed_command_id; generic-shell timeout evidence is not accepted on a managed host.");
508
+ + "Run it with forge.start_managed_command, continue independent work, then call forge.wait_managed_command for a transport-safe receipt. "
509
+ + "If command_continues=true, continue useful work or wait again after retry_after_ms. Include its matching terminal managed_command_id; generic-shell timeout evidence is not accepted on a managed host.");
468
510
  }
469
511
  const terminalReceipts = matchingReceipts.filter((receipt) => typeof receipt.status === "string" && [
470
512
  "pass",
@@ -475,7 +517,7 @@ export function assertManagedQaRegressionEvidence(input, receipts) {
475
517
  ].includes(receipt.status));
476
518
  if (terminalReceipts.length === 0) {
477
519
  throw new Error(`qa_regression_managed_command_in_progress: commands[${index}] has not produced a terminal managed receipt. `
478
- + "Call forge.wait_managed_command once for its terminal receipt, then resubmit this gate.");
520
+ + "Call forge.wait_managed_command. If command_continues=true, continue useful work or wait again after retry_after_ms; resubmit this gate only after the receipt is terminal.");
479
521
  }
480
522
  const adequateReceipt = terminalReceipts.some((receipt) => {
481
523
  if (receipt.timed_out !== true)
@@ -559,7 +601,7 @@ export function parseCompletionMcpArgs(argv) {
559
601
  const rawPhase = argv[++index];
560
602
  const parsedPhase = parseStrictCompletionPhase(rawPhase);
561
603
  if (!parsedPhase) {
562
- return { ok: false, error: "mcp --phase must be one of: implementation, self-review, validation, qa, architect, draft, decompose, ci-repair, debug-repro, debug-root-cause, debug-fix, debug-regression." };
604
+ return { ok: false, error: "mcp --phase must be one of: implementation, self-review, validation, qa, architect, draft, decompose, ci-repair, comment-repair, debug-repro, debug-root-cause, debug-fix, debug-regression." };
563
605
  }
564
606
  phase = parsedPhase;
565
607
  }
@@ -664,6 +706,28 @@ async function prepareCursorCompletionToolRuntime(worktreePath, runtime) {
664
706
  }
665
707
  };
666
708
  }
709
+ /**
710
+ * Claude Code takes MCP servers from `--mcp-config <file>`. The file lives in an attempt-owned
711
+ * temp directory (never the worktree) because its env block can carry redaction secrets, and
712
+ * passing the JSON inline would not survive Windows shim quoting.
713
+ */
714
+ async function prepareClaudeCompletionToolRuntime(runtime) {
715
+ const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "forge-claude-mcp-"));
716
+ const configPath = path.join(configDir, "mcp.json");
717
+ const config = {
718
+ mcpServers: {
719
+ [runtime.serverName]: claudeMcpServerConfig(runtime)
720
+ }
721
+ };
722
+ await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
723
+ return {
724
+ ...runtime,
725
+ claudeMcpConfigPath: configPath,
726
+ cleanup: async () => {
727
+ await fs.rm(configDir, { recursive: true, force: true });
728
+ }
729
+ };
730
+ }
667
731
  async function handleJsonRpcLine(session, rawLine) {
668
732
  let message;
669
733
  try {
@@ -718,10 +782,48 @@ async function handleJsonRpcRequest(session, method, params) {
718
782
  const call = asRecord(params, "tools/call params");
719
783
  const name = requiredString(call, "name");
720
784
  const args = call.arguments ?? {};
721
- return await session.callTool(name, args);
785
+ try {
786
+ return await session.callTool(name, args);
787
+ }
788
+ catch (error) {
789
+ if (isManagedAttemptTool(name))
790
+ throw error;
791
+ return completionToolRejectedResult(name, error);
792
+ }
722
793
  }
723
794
  throw new Error(`Unsupported MCP method: ${method}`);
724
795
  }
796
+ export function completionToolRejectedResult(toolName, error) {
797
+ const issues = error instanceof CompletionPayloadValidationError
798
+ ? [...error.issues]
799
+ : [errorMessage(error)];
800
+ const repairInstruction = "Repair every listed issue and resubmit this same gate once. Do not inspect the artifact path: rejected calls never write or replace completion artifacts.";
801
+ return {
802
+ content: [{
803
+ type: "text",
804
+ text: `Forge rejected ${toolName}. No artifact was written.\n${issues.map(issue => `- ${issue}`).join("\n")}\n${repairInstruction}`,
805
+ }],
806
+ structuredContent: {
807
+ status: "rejected",
808
+ tool_name: toolName,
809
+ artifact_written: false,
810
+ issues,
811
+ repair_instruction: repairInstruction,
812
+ },
813
+ isError: true,
814
+ };
815
+ }
816
+ function acceptedArtifactReceipt(reference) {
817
+ return {
818
+ artifact_written: true,
819
+ artifact_receipt: {
820
+ schema_version: 1,
821
+ path: reference.path,
822
+ report_path: reference.reportPath,
823
+ content_hash: reference.contentHash,
824
+ },
825
+ };
826
+ }
725
827
  function toolDefinition(name, phase, qaGate = null, decomposeGate = null) {
726
828
  return {
727
829
  name,
@@ -19,6 +19,9 @@ export function toolDescription(phase, qaGate = null, decomposeGate = null) {
19
19
  if (phase === "ci_repair") {
20
20
  return "Complete Forge CI repair by writing ci-repair.handoff.json with failed-check, root-cause, scoped-change, workflow-equivalence, validation, provider-state, and push-readiness evidence.";
21
21
  }
22
+ if (phase === "comment_repair") {
23
+ return "Complete Forge pull-request comment repair by recording one evidence-backed disposition for every actionable finding in the dispatch snapshot.";
24
+ }
22
25
  if (phase === "self_review") {
23
26
  return "Complete Forge self-review by writing self-review.report.json with diff review, findings, security, edge-case, design, deterministic-comparison, fixes, and remaining-risk evidence.";
24
27
  }
@@ -146,6 +149,42 @@ export function toolInputSchema(phase, qaGate = null, decomposeGate = null) {
146
149
  }
147
150
  }, ["status", "summary"], "For status pass, include failed_checks, root_cause, changed_files or no_code_action, narrow_validation, workflow_validation, broader_validation, remaining_risks, provider_state, and ready_for_push: true.");
148
151
  }
152
+ if (phase === "comment_repair") {
153
+ return objectToolSchema({
154
+ ...baseProperties,
155
+ repair_head_sha: { type: "string", description: "Exact pull-request head from the dispatch snapshot." },
156
+ repair_issue_key: { type: "string", description: "Exact review issue key from the dispatch snapshot." },
157
+ dispositions: {
158
+ type: "array",
159
+ description: "Exactly one outcome for every actionable dispatch finding; conversation-only comments are omitted.",
160
+ items: evidenceObjectSchema(["claim_id", "kind", "body_sha256", "disposition", "evidence"], {
161
+ claim_id: { type: "string" },
162
+ kind: { type: "string", enum: ["review_thread", "review_body", "issue_comment"] },
163
+ body_sha256: { type: "string" },
164
+ disposition: { type: "string", enum: ["fixed", "rejected", "already_addressed", "needs_decision", "accepted_transfer", "owned_elsewhere"] },
165
+ accepted_transfer: { type: "object", properties: {
166
+ amendment_id: { type: "string" }, version: { type: "integer", minimum: 1 }, content_hash: { type: "string" },
167
+ target_work_item_id: { type: "string" }, obligation_reference: { type: "string" },
168
+ }, required: ["amendment_id", "version", "content_hash", "target_work_item_id", "obligation_reference"], additionalProperties: false },
169
+ owned_elsewhere: { type: "object", description: "Another open Story in this Work Order whose accepted acceptance criteria already require the requested change.", properties: {
170
+ target_work_item_id: { type: "string" },
171
+ quoted_obligation: { type: "string", description: "Verbatim passage (at least 40 characters) from that Story's acceptance criteria that covers the finding." },
172
+ }, required: ["target_work_item_id", "quoted_obligation"], additionalProperties: false },
173
+ evidence: { type: "string" }
174
+ })
175
+ },
176
+ changed_files: {
177
+ type: "array",
178
+ description: "Files changed for fixed findings. May be empty when every finding is rejected, already addressed, or needs a decision.",
179
+ items: evidenceObjectSchema(["path", "summary"], { path: { type: "string" }, summary: { type: "string" } })
180
+ },
181
+ validation: {
182
+ type: "array",
183
+ description: "Scoped verification evidence. May be empty only when no source change was made.",
184
+ items: commandEvidenceSchema()
185
+ }
186
+ }, ["status", "summary", "repair_head_sha", "repair_issue_key", "dispositions", "changed_files", "validation"], "Use status pass after every actionable finding has an evidence-backed disposition. Rejected and needs_decision are legitimate outcomes and must not be disguised as code changes.");
187
+ }
149
188
  if (phase === "implementation") {
150
189
  return objectToolSchema({
151
190
  ...baseProperties,
@@ -221,7 +260,10 @@ export function toolInputSchema(phase, qaGate = null, decomposeGate = null) {
221
260
  file: { type: "string" }
222
261
  })
223
262
  },
224
- security_review: reviewSectionSchema(),
263
+ security_review: {
264
+ ...reviewSectionSchema(),
265
+ description: "Required for pass. Use pass after review, or not_applicable only with notes explaining why the change has no security-relevant surface."
266
+ },
225
267
  edge_case_review: reviewSectionSchema(),
226
268
  design_review: {
227
269
  ...reviewSectionSchema(),
@@ -719,6 +761,11 @@ function qaScopeSchema(description) {
719
761
  schema_version: { type: "number", enum: [1] },
720
762
  surface: { type: "array", items: { type: "string", enum: [...qaScopeSurfaces] } },
721
763
  required_evidence: { type: "array", items: { type: "string", enum: [...qaScopeEvidence] } },
764
+ required_capabilities: {
765
+ type: "array",
766
+ description: "Runner facilities required by implementation and validation. Use docker_engine when tests or project services require access to a Docker daemon, even when the agent itself runs in a managed host worktree.",
767
+ items: { type: "string", enum: ["docker_engine"] }
768
+ },
722
769
  not_applicable: { type: "array", items: { type: "string", enum: [...qaScopeEvidence] } },
723
770
  browser_required: { type: "boolean" },
724
771
  api_required: { type: "boolean" },
@@ -743,7 +790,7 @@ function qaScopeSchema(description) {
743
790
  function commandEvidenceSchema() {
744
791
  return evidenceObjectSchema(["command", "exit_code", "status", "summary"], {
745
792
  command: { type: "string" },
746
- managed_command_id: { type: "string", description: "Receipt ID returned by forge.start_managed_command and completed through one forge.wait_managed_command call for managed-host evidence." },
793
+ managed_command_id: { type: "string", description: "Receipt ID returned by forge.start_managed_command after forge.wait_managed_command reports a terminal receipt. A transport-safe wait can return command_continues=true; wait again after retry_after_ms before submitting the gate." },
747
794
  exit_code: { type: ["number", "null"], description: "Process exit code, or null when the managed runner timed out or could not launch the process." },
748
795
  status: { type: "string" },
749
796
  summary: { type: "string" },
@@ -2,6 +2,7 @@ import { type CompletionPhase, type DebugCompletionPhase, type QaCompletionGate
2
2
  export declare function normalizeCompletionStatus(value: string, phase?: CompletionPhase): string;
3
3
  export declare function normalizeQaGateStatus(value: string): string;
4
4
  export declare function validatePayloadShape(phase: CompletionPhase, payload: Record<string, unknown>): void;
5
+ export declare function validateCommentRepairPayloadShape(payload: Record<string, unknown>): void;
5
6
  export declare function validateCiRepairPayloadShape(payload: Record<string, unknown>): void;
6
7
  export declare function validateDebugPayloadShape(phase: DebugCompletionPhase, payload: Record<string, unknown>): void;
7
8
  export declare function validateQaGatePayloadShape(gate: QaCompletionGate, payload: Record<string, unknown>, status: string): void;
@@ -1,4 +1,4 @@
1
- import { qaBlockerReasons, qaCandidateBugActions, qaFindingScopes, qaFindingSeverities, qaGateToolNames, qaScopeAuthorities, qaScopeConfidence, qaScopeEvidence, qaScopeFields, qaScopeSources, qaScopeSurfaces } from "./phase-completion.js";
1
+ import { qaBlockerReasons, qaCandidateBugActions, qaFindingScopes, qaFindingSeverities, qaGateToolNames, qaScopeAuthorities, qaScopeCapabilities, qaScopeConfidence, qaScopeEvidence, qaScopeFields, qaScopeSources, qaScopeSurfaces } from "./phase-completion.js";
2
2
  export function normalizeCompletionStatus(value, phase) {
3
3
  const normalized = value.trim().toLowerCase().replace(/-/g, "_");
4
4
  if (phase === "qa") {
@@ -55,6 +55,10 @@ export function validatePayloadShape(phase, payload) {
55
55
  validateCiRepairPayloadShape(payload);
56
56
  return;
57
57
  }
58
+ if (phase === "comment_repair") {
59
+ validateCommentRepairPayloadShape(payload);
60
+ return;
61
+ }
58
62
  if (isDebugCompletionPhase(phase)) {
59
63
  validateDebugPayloadShape(phase, payload);
60
64
  return;
@@ -101,6 +105,65 @@ export function validatePayloadShape(phase, payload) {
101
105
  requirePassingCommands(payload);
102
106
  requireSatisfiedAcceptanceCriteria(payload);
103
107
  }
108
+ export function validateCommentRepairPayloadShape(payload) {
109
+ requireNonEmptyStringRecord(payload, "repair_head_sha", "");
110
+ requireNonEmptyStringRecord(payload, "repair_issue_key", "");
111
+ requireArray(payload, "dispositions");
112
+ requireNonEmptyArray(payload, "dispositions");
113
+ const seen = new Set();
114
+ let hasFixed = false;
115
+ for (const [index, entry] of payload.dispositions.entries()) {
116
+ requireEvidenceRecord(entry, `dispositions[${index}]`);
117
+ const record = entry;
118
+ for (const key of ["claim_id", "kind", "body_sha256", "disposition", "evidence"]) {
119
+ requireNonEmptyStringRecord(record, key, `dispositions[${index}].`);
120
+ }
121
+ requireKnownStringRecord(record, "kind", new Set(["review_thread", "review_body", "issue_comment"]), `dispositions[${index}].`);
122
+ requireKnownStringRecord(record, "disposition", new Set(["fixed", "rejected", "already_addressed", "needs_decision", "accepted_transfer", "owned_elsewhere"]), `dispositions[${index}].`);
123
+ if (record.disposition === "owned_elsewhere") {
124
+ const receipt = record.owned_elsewhere;
125
+ if (!receipt || typeof receipt !== "object" || Array.isArray(receipt))
126
+ throw new Error("owned_elsewhere requires the owning Story and its quoted obligation; server verification remains mandatory.");
127
+ const owner = receipt;
128
+ if (typeof owner.target_work_item_id !== "string" || !owner.target_work_item_id.trim())
129
+ throw new Error("owned_elsewhere.target_work_item_id is required.");
130
+ if (typeof owner.quoted_obligation !== "string" || owner.quoted_obligation.trim().length < 40)
131
+ throw new Error("owned_elsewhere.quoted_obligation must quote at least 40 characters of the owning Story's acceptance criteria.");
132
+ }
133
+ if (record.disposition === "accepted_transfer") {
134
+ const receipt = record.accepted_transfer;
135
+ if (!receipt || typeof receipt !== "object" || Array.isArray(receipt))
136
+ throw new Error("accepted_transfer requires an approved amendment receipt; server verification remains mandatory.");
137
+ const transfer = receipt;
138
+ for (const key of ["amendment_id", "content_hash", "target_work_item_id", "obligation_reference"]) {
139
+ if (typeof transfer[key] !== "string" || !transfer[key].trim())
140
+ throw new Error(`accepted_transfer.${key} is required.`);
141
+ }
142
+ if (!Number.isInteger(transfer.version) || Number(transfer.version) < 1)
143
+ throw new Error("accepted_transfer.version must be positive.");
144
+ }
145
+ const identity = `${normalizedString(optionalString(record, "kind"))}:${optionalString(record, "claim_id")}:${normalizedString(optionalString(record, "body_sha256"))}`;
146
+ if (seen.has(identity))
147
+ throw new Error(`dispositions[${index}] duplicates finding ${identity}.`);
148
+ seen.add(identity);
149
+ hasFixed ||= normalizedString(optionalString(record, "disposition")) === "fixed";
150
+ }
151
+ requireArray(payload, "changed_files");
152
+ requireArray(payload, "validation");
153
+ for (const [index, changedFile] of payload.changed_files.entries()) {
154
+ requireEvidenceRecord(changedFile, `changed_files[${index}]`);
155
+ const record = changedFile;
156
+ requireNonEmptyStringRecord(record, "path", `changed_files[${index}].`);
157
+ requireNonEmptyStringRecord(record, "summary", `changed_files[${index}].`);
158
+ }
159
+ if (hasFixed && arrayLength(payload, "changed_files") === 0) {
160
+ throw new Error("fixed comment dispositions require changed_files evidence.");
161
+ }
162
+ if (arrayLength(payload, "changed_files") > 0) {
163
+ requireNonEmptyArray(payload, "validation");
164
+ validateQaGatePassingCommands(payload.validation);
165
+ }
166
+ }
104
167
  export function validateCiRepairPayloadShape(payload) {
105
168
  requireArray(payload, "failed_checks");
106
169
  requireNonEmptyArray(payload, "failed_checks");
@@ -252,29 +315,43 @@ export function validateQaGatePayloadShape(gate, payload, status) {
252
315
  }
253
316
  }
254
317
  export function validateQaGatePassingCommands(commands, options = {}) {
318
+ const issues = [];
255
319
  for (const [index, command] of commands.entries()) {
256
- requireEvidenceRecord(command, `commands[${index}]`);
320
+ if (!command || typeof command !== "object" || Array.isArray(command)) {
321
+ issues.push(`commands[${index}] must be an object.`);
322
+ continue;
323
+ }
257
324
  const record = command;
258
- requireNonEmptyStringRecord(record, "command", `commands[${index}].`);
259
- requireNonEmptyStringRecord(record, "status", `commands[${index}].`);
260
- requireNonEmptyStringRecord(record, "summary", `commands[${index}].`);
325
+ if (!optionalString(record, "command"))
326
+ issues.push(`commands[${index}].command is required.`);
327
+ if (!optionalString(record, "status"))
328
+ issues.push(`commands[${index}].status is required.`);
329
+ if (!optionalString(record, "summary"))
330
+ issues.push(`commands[${index}].summary is required.`);
261
331
  if (typeof record.exit_code !== "number" || !Number.isFinite(record.exit_code)) {
262
- throw new Error(`commands[${index}].exit_code is required.`);
332
+ issues.push(`commands[${index}].exit_code is required.`);
333
+ continue;
263
334
  }
264
335
  const passed = record.exit_code === 0 && normalizedString(record.status) === "pass";
265
336
  if (!passed
266
337
  && options.allowClassifiedNonBlockingFailures
267
338
  && optionalString(record, "classification")) {
268
- validateClassifiedNonBlockingQaFailure(record, index);
339
+ try {
340
+ validateClassifiedNonBlockingQaFailure(record, index);
341
+ }
342
+ catch (error) {
343
+ issues.push(error instanceof Error ? error.message : String(error));
344
+ }
269
345
  continue;
270
346
  }
271
347
  if (record.exit_code !== 0) {
272
- throw new Error(`commands[${index}].exit_code must be 0 when QA gate status is pass.`);
348
+ issues.push(`commands[${index}].exit_code must be 0 when QA gate status is pass.`);
273
349
  }
274
350
  if (normalizedString(record.status) !== "pass") {
275
- throw new Error(`commands[${index}].status must be pass when QA gate status is pass.`);
351
+ issues.push(`commands[${index}].status must be pass when QA gate status is pass.`);
276
352
  }
277
353
  }
354
+ throwCompletionPayloadIssues(issues);
278
355
  }
279
356
  function validateClassifiedNonBlockingQaFailure(record, index) {
280
357
  const classification = normalizedString(optionalString(record, "classification"));
@@ -318,18 +395,22 @@ export function validateArchitectPayloadShape(payload, status) {
318
395
  throw new Error("implementation_plan.tasks: expected non-empty array; correction: include at least one task with id and tests.");
319
396
  }
320
397
  const taskIds = new Set();
321
- for (const task of implementationPlan.tasks) {
398
+ const taskIndexById = new Map();
399
+ for (const [taskIndex, task] of implementationPlan.tasks.entries()) {
322
400
  if (!task || typeof task !== "object" || Array.isArray(task)) {
323
401
  throw new Error("implementation_plan.tasks[]: expected object entries; correction: each task must be an object with id and tests.");
324
402
  }
325
403
  const taskRecord = task;
326
404
  requireNonEmptyStringRecord(taskRecord, "id", "implementation_plan.tasks[].");
327
405
  const taskId = optionalString(taskRecord, "id") ?? "";
328
- if (taskIds.has(taskId)) {
329
- throw new Error(`implementation_plan.tasks: duplicate task id '${taskId}'.`);
406
+ const firstTaskIndex = taskIndexById.get(taskId);
407
+ if (firstTaskIndex !== undefined) {
408
+ throw new Error(`implementation_plan.tasks[${taskIndex}].id duplicates implementation_plan.tasks[${firstTaskIndex}].id '${taskId}'; correction: assign the duplicate a unique stable task id, update every phase/dependency reference to that task, and resubmit forge.complete_architect_plan.`);
330
409
  }
331
410
  taskIds.add(taskId);
411
+ taskIndexById.set(taskId, taskIndex);
332
412
  validateArchitectTaskDevelopScope(taskRecord, taskId);
413
+ requireNonEmptyStringArrayRecord(taskRecord, "acceptance_criteria", `implementation_plan.tasks[${taskId}].`);
333
414
  validateArchitectVerificationScope(taskRecord, taskId);
334
415
  requireArchitectArray(taskRecord, "tests", payload, { path: `implementation_plan.tasks[${optionalString(taskRecord, "id") ?? "?"}].tests` });
335
416
  if (arrayLength(taskRecord, "tests") === 0) {
@@ -373,6 +454,7 @@ export function validateQaScopeRecord(scope, pathLabel) {
373
454
  }
374
455
  const surface = qaScopeStringArray(scope, "surface", pathLabel, qaScopeSurfaces);
375
456
  const requiredEvidence = qaScopeStringArray(scope, "required_evidence", pathLabel, qaScopeEvidence);
457
+ qaScopeStringArray(scope, "required_capabilities", pathLabel, qaScopeCapabilities);
376
458
  const notApplicable = qaScopeStringArray(scope, "not_applicable", pathLabel, qaScopeEvidence);
377
459
  const requiredSet = new Set(requiredEvidence);
378
460
  for (const evidence of notApplicable) {
@@ -715,22 +797,30 @@ export function validateQaPayloadShape(payload, status) {
715
797
  throw new Error("QA pass/needs_rework requires at least one command, browser check, or evidence note.");
716
798
  }
717
799
  }
800
+ const commandIssues = [];
718
801
  for (const [index, command] of commands.entries()) {
719
- requireEvidenceRecord(command, `commands[${index}]`);
802
+ if (!command || typeof command !== "object" || Array.isArray(command)) {
803
+ commandIssues.push(`commands[${index}] must be an object.`);
804
+ continue;
805
+ }
720
806
  const record = command;
721
- requireNonEmptyStringRecord(record, "command", `commands[${index}].`);
722
- requireNonEmptyStringRecord(record, "status", `commands[${index}].`);
723
- requireNonEmptyStringRecord(record, "summary", `commands[${index}].`);
807
+ if (!optionalString(record, "command"))
808
+ commandIssues.push(`commands[${index}].command is required.`);
809
+ if (!optionalString(record, "status"))
810
+ commandIssues.push(`commands[${index}].status is required.`);
811
+ if (!optionalString(record, "summary"))
812
+ commandIssues.push(`commands[${index}].summary is required.`);
724
813
  if (typeof record.exit_code !== "number" || !Number.isFinite(record.exit_code)) {
725
- throw new Error(`commands[${index}].exit_code is required.`);
814
+ commandIssues.push(`commands[${index}].exit_code is required.`);
726
815
  }
727
- if (status === "pass" && record.exit_code !== 0) {
728
- throw new Error(`commands[${index}].exit_code must be 0 when QA status is pass.`);
816
+ else if (status === "pass" && record.exit_code !== 0) {
817
+ commandIssues.push(`commands[${index}].exit_code must be 0 when QA status is pass.`);
729
818
  }
730
819
  if (status === "pass" && normalizedString(record.status) !== "pass") {
731
- throw new Error(`commands[${index}].status must be pass when QA status is pass.`);
820
+ commandIssues.push(`commands[${index}].status must be pass when QA status is pass.`);
732
821
  }
733
822
  }
823
+ throwCompletionPayloadIssues(commandIssues);
734
824
  for (const [index, browserCheck] of browserChecks.entries()) {
735
825
  requireEvidenceRecord(browserCheck, `browser_checks[${index}]`);
736
826
  const record = browserCheck;
@@ -824,5 +914,5 @@ export function validateOptionalWorkItemId(payload, expectedWorkItemId) {
824
914
  throw new Error(`completion payload work item ${actual} does not match dispatch work item ${expectedWorkItemId}.`);
825
915
  }
826
916
  }
827
- import { requireField, requireNonPassEvidence, requireImplementationEvidence, requirePassingCommands, requireArray, requireNonEmptyArray, arrayLength, requireAllowedStatus, requireObject, requiredRecord, requireBoolean, requireNumericOne, requireSatisfiedAcceptanceCriteria, validateResolvedFindings, isDebugCompletionPhase, qaEvidenceSource, qaArray, qaObjectArray, qaPolicyWarnings, qaStringArray, requireEvidenceRecord, requireNonEmptyStringRecord, requireKnownStringRecord, requireNonEmptyStringArrayRecord, requireSubstantiveEvidenceRecord, normalizedString, requiredString, optionalString, stripUndefined, } from "./phase-completion.helpers.js";
917
+ import { requireField, requireNonPassEvidence, requireImplementationEvidence, requirePassingCommands, requireArray, requireNonEmptyArray, arrayLength, requireAllowedStatus, requireObject, requiredRecord, requireBoolean, requireNumericOne, requireSatisfiedAcceptanceCriteria, validateResolvedFindings, isDebugCompletionPhase, qaEvidenceSource, qaArray, qaObjectArray, qaPolicyWarnings, qaStringArray, requireEvidenceRecord, requireNonEmptyStringRecord, requireKnownStringRecord, requireNonEmptyStringArrayRecord, requireSubstantiveEvidenceRecord, normalizedString, requiredString, optionalString, stripUndefined, throwCompletionPayloadIssues, } from "./phase-completion.helpers.js";
828
918
  export * from "./phase-completion.helpers.js";