@loucompanion/forge-bridge 0.1.3-dev.f66395c3ac21 → 0.1.4-dev.04a1e5b9e484

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 (223) hide show
  1. package/README.md +165 -1
  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 +9 -1
  5. package/dist/bridge/bridge.service.d.ts +129 -3
  6. package/dist/bridge/bridge.service.js +2427 -70
  7. package/dist/bridge/bridge.types.d.ts +218 -2
  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 +14 -2
  21. package/dist/cleanup/cleanup.service.js +496 -3
  22. package/dist/cleanup/cleanup.types.d.ts +10 -0
  23. package/dist/cleanup/index.d.ts +1 -1
  24. package/dist/cleanup/index.js +1 -1
  25. package/dist/cleanup/internals.d.ts +2 -2
  26. package/dist/cleanup/internals.js +1 -1
  27. package/dist/cli/cli.help.d.ts +2 -0
  28. package/dist/cli/cli.help.js +217 -0
  29. package/dist/cli/cli.service.d.ts +55 -7
  30. package/dist/cli/cli.service.js +429 -219
  31. package/dist/cli/local-config.d.ts +7 -0
  32. package/dist/cli/local-config.js +51 -3
  33. package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
  34. package/dist/runtime/aspnet-runtime-prebuild.js +341 -0
  35. package/dist/runtime/attempt-database-broker.d.ts +89 -0
  36. package/dist/runtime/attempt-database-broker.js +390 -0
  37. package/dist/runtime/attempt-node-runtime-launcher.d.ts +18 -0
  38. package/dist/runtime/attempt-node-runtime-launcher.js +140 -0
  39. package/dist/runtime/attempt-path-operation.d.ts +21 -0
  40. package/dist/runtime/attempt-path-operation.js +94 -0
  41. package/dist/runtime/attempt-private-directory.d.ts +6 -0
  42. package/dist/runtime/attempt-private-directory.js +54 -0
  43. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  44. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  45. package/dist/runtime/attempt-process-supervisor.d.ts +102 -0
  46. package/dist/runtime/attempt-process-supervisor.js +421 -0
  47. package/dist/runtime/canonical-source-file.d.ts +3 -0
  48. package/dist/runtime/canonical-source-file.js +54 -0
  49. package/dist/runtime/docker-engine-probe.d.ts +90 -0
  50. package/dist/runtime/docker-engine-probe.js +333 -0
  51. package/dist/runtime/generated-output-reset.d.ts +20 -0
  52. package/dist/runtime/generated-output-reset.js +157 -0
  53. package/dist/runtime/host-database-capability-boundary.d.ts +16 -0
  54. package/dist/runtime/host-database-capability-boundary.js +62 -0
  55. package/dist/runtime/host-diagnostics.d.ts +11 -0
  56. package/dist/runtime/host-diagnostics.js +192 -0
  57. package/dist/runtime/host-execution-root.d.ts +4 -0
  58. package/dist/runtime/host-execution-root.js +39 -0
  59. package/dist/runtime/host-listener-inspector.d.ts +51 -0
  60. package/dist/runtime/host-listener-inspector.js +385 -0
  61. package/dist/runtime/host-port-broker.d.ts +76 -0
  62. package/dist/runtime/host-port-broker.js +333 -0
  63. package/dist/runtime/host-runtime-contract.d.ts +58 -0
  64. package/dist/runtime/host-runtime-contract.js +813 -0
  65. package/dist/runtime/host-runtime-lease.d.ts +217 -0
  66. package/dist/runtime/host-runtime-lease.js +2487 -0
  67. package/dist/runtime/host-runtime-promotion.d.ts +32 -0
  68. package/dist/runtime/host-runtime-promotion.js +206 -0
  69. package/dist/runtime/memory-capacity.d.ts +70 -0
  70. package/dist/runtime/memory-capacity.js +123 -0
  71. package/dist/runtime/native-test-postgres-fallback.d.ts +25 -0
  72. package/dist/runtime/native-test-postgres-fallback.js +91 -0
  73. package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
  74. package/dist/runtime/node-host-runtime-adapter.js +286 -0
  75. package/dist/runtime/pinned-directory-process.d.ts +9 -0
  76. package/dist/runtime/pinned-directory-process.js +61 -0
  77. package/dist/runtime/pinned-source-directory.d.ts +17 -0
  78. package/dist/runtime/pinned-source-directory.js +94 -0
  79. package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
  80. package/dist/runtime/playwright-host-runtime-adapter.js +268 -0
  81. package/dist/runtime/prepared-runtime-identity.d.ts +6 -0
  82. package/dist/runtime/prepared-runtime-identity.js +218 -0
  83. package/dist/runtime/prepared-runtime-readiness.d.ts +30 -0
  84. package/dist/runtime/prepared-runtime-readiness.js +285 -0
  85. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  86. package/dist/runtime/runtime-isolation-lease.js +161 -0
  87. package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
  88. package/dist/runtime/workspace-resource-estimator.js +174 -0
  89. package/dist/sandbox/command-runner.d.ts +4 -0
  90. package/dist/sandbox/command-runner.js +47 -0
  91. package/dist/sandbox/contract.d.ts +5 -0
  92. package/dist/sandbox/contract.js +276 -0
  93. package/dist/sandbox/event-budget.d.ts +11 -0
  94. package/dist/sandbox/event-budget.js +27 -0
  95. package/dist/sandbox/image-policy.d.ts +35 -0
  96. package/dist/sandbox/image-policy.js +247 -0
  97. package/dist/sandbox/index.d.ts +5 -0
  98. package/dist/sandbox/index.js +5 -0
  99. package/dist/sandbox/lease-watchdog.d.ts +18 -0
  100. package/dist/sandbox/lease-watchdog.js +67 -0
  101. package/dist/sandbox/preflight.d.ts +84 -0
  102. package/dist/sandbox/preflight.js +521 -0
  103. package/dist/sandbox/process.d.ts +20 -0
  104. package/dist/sandbox/process.js +209 -0
  105. package/dist/sandbox/provider-auth/codex.d.ts +26 -0
  106. package/dist/sandbox/provider-auth/codex.js +212 -0
  107. package/dist/sandbox/repository-lock.d.ts +6 -0
  108. package/dist/sandbox/repository-lock.js +129 -0
  109. package/dist/sandbox/runtime.d.ts +302 -0
  110. package/dist/sandbox/runtime.js +2368 -0
  111. package/dist/sandbox/sandbox.types.d.ts +93 -0
  112. package/dist/sandbox/sandbox.types.js +1 -0
  113. package/dist/sandbox/smoke.d.ts +76 -0
  114. package/dist/sandbox/smoke.js +380 -0
  115. package/dist/sandbox/worker-supervisor.d.ts +13 -0
  116. package/dist/sandbox/worker-supervisor.js +36 -0
  117. package/dist/sandbox/worker.d.ts +1 -0
  118. package/dist/sandbox/worker.js +225 -0
  119. package/dist/sandbox/workspace.service.d.ts +13 -0
  120. package/dist/sandbox/workspace.service.js +74 -0
  121. package/dist/session/managed-attempt-tools.d.ts +28 -0
  122. package/dist/session/managed-attempt-tools.js +1016 -0
  123. package/dist/session/managed-build-coordinator.d.ts +5 -0
  124. package/dist/session/managed-build-coordinator.js +106 -0
  125. package/dist/session/managed-build-receipt.d.ts +21 -0
  126. package/dist/session/managed-build-receipt.js +337 -0
  127. package/dist/session/managed-verification-timeouts.d.ts +8 -0
  128. package/dist/session/managed-verification-timeouts.js +17 -0
  129. package/dist/session/phase-completion.artifacts.d.ts +27 -0
  130. package/dist/session/phase-completion.artifacts.js +829 -0
  131. package/dist/session/phase-completion.d.ts +53 -42
  132. package/dist/session/phase-completion.helpers.d.ts +117 -0
  133. package/dist/session/phase-completion.helpers.js +707 -0
  134. package/dist/session/phase-completion.js +125 -3007
  135. package/dist/session/phase-completion.prompts.d.ts +14 -0
  136. package/dist/session/phase-completion.prompts.js +233 -0
  137. package/dist/session/phase-completion.runtime.d.ts +49 -0
  138. package/dist/session/phase-completion.runtime.js +840 -0
  139. package/dist/session/phase-completion.schemas.d.ts +3 -0
  140. package/dist/session/phase-completion.schemas.js +859 -0
  141. package/dist/session/phase-completion.validation.d.ts +38 -0
  142. package/dist/session/phase-completion.validation.js +918 -0
  143. package/dist/session/pre-publication-validation.d.ts +75 -0
  144. package/dist/session/pre-publication-validation.js +661 -0
  145. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.d.ts +72 -0
  146. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.js +462 -0
  147. package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
  148. package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
  149. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +95 -0
  150. package/dist/session/provider-cli/provider-cli.artifacts.js +812 -0
  151. package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
  152. package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
  153. package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
  154. package/dist/session/provider-cli/provider-cli.deterministic.d.ts +14 -0
  155. package/dist/session/provider-cli/provider-cli.deterministic.js +407 -0
  156. package/dist/session/provider-cli/provider-cli.process.d.ts +99 -0
  157. package/dist/session/provider-cli/provider-cli.process.js +874 -0
  158. package/dist/session/provider-cli/provider-cli.prompts.d.ts +52 -0
  159. package/dist/session/provider-cli/provider-cli.prompts.js +715 -0
  160. package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
  161. package/dist/session/provider-cli/provider-cli.qa-sidecars.js +66 -0
  162. package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
  163. package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
  164. package/dist/session/provider-cli/provider-cli.runtime.d.ts +22 -0
  165. package/dist/session/provider-cli/provider-cli.runtime.js +92 -0
  166. package/dist/session/provider-cli/provider-cli.service.d.ts +47 -53
  167. package/dist/session/provider-cli/provider-cli.service.js +1258 -2497
  168. package/dist/session/provider-cli/provider-cli.types.d.ts +8 -0
  169. package/dist/session/provider-cli/provider-output-state.d.ts +32 -0
  170. package/dist/session/provider-cli/provider-output-state.js +817 -0
  171. package/dist/session/provider-cli/provider-usage.d.ts +7 -0
  172. package/dist/session/provider-cli/provider-usage.js +28 -5
  173. package/dist/session/session-redaction.d.ts +3 -0
  174. package/dist/session/session-redaction.js +448 -0
  175. package/dist/session/session.base.d.ts +6 -1
  176. package/dist/session/session.service.d.ts +8 -2
  177. package/dist/session/session.service.js +586 -59
  178. package/dist/session/session.types.d.ts +324 -3
  179. package/dist/session/session.types.js +939 -10
  180. package/dist/session/unreferenced-exports.d.ts +12 -0
  181. package/dist/session/unreferenced-exports.js +59 -0
  182. package/dist/shared/format/format-bytes.d.ts +1 -0
  183. package/dist/shared/format/format-bytes.js +9 -0
  184. package/dist/shared/git/git.js +2 -1
  185. package/dist/shared/path/path.d.ts +1 -0
  186. package/dist/shared/path/path.js +21 -1
  187. package/dist/shared/process/dotnet-toolchain.d.ts +17 -0
  188. package/dist/shared/process/dotnet-toolchain.js +307 -0
  189. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  190. package/dist/shared/process/host-process-recovery.js +79 -0
  191. package/dist/shared/process/host-process-supervisor.d.ts +59 -0
  192. package/dist/shared/process/host-process-supervisor.js +331 -0
  193. package/dist/shared/process/process.d.ts +3 -0
  194. package/dist/shared/process/process.js +13 -7
  195. package/dist/shared/process/windows-launch.d.ts +8 -0
  196. package/dist/shared/process/windows-launch.js +41 -0
  197. package/dist/shared/provider-capabilities.d.ts +4 -0
  198. package/dist/shared/provider-capabilities.js +88 -0
  199. package/dist/shared/redaction/redaction.d.ts +1 -0
  200. package/dist/shared/redaction/redaction.js +47 -4
  201. package/dist/shared/time/monotonic.d.ts +1 -0
  202. package/dist/shared/time/monotonic.js +8 -0
  203. package/dist/version.d.ts +1 -1
  204. package/dist/version.js +1 -1
  205. package/dist/worktree/artifact-injector.d.ts +3 -2
  206. package/dist/worktree/artifact-injector.js +82 -10
  207. package/dist/worktree/dependency-warmer.d.ts +53 -1
  208. package/dist/worktree/dependency-warmer.js +619 -11
  209. package/dist/worktree/index.d.ts +2 -1
  210. package/dist/worktree/index.js +1 -0
  211. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  212. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  213. package/dist/worktree/story-qa-reference-hydrator.d.ts +4 -0
  214. package/dist/worktree/story-qa-reference-hydrator.js +253 -0
  215. package/dist/worktree/worktree-paths.d.ts +4 -0
  216. package/dist/worktree/worktree-paths.js +30 -0
  217. package/dist/worktree/worktree.base.d.ts +3 -0
  218. package/dist/worktree/worktree.service.d.ts +16 -3
  219. package/dist/worktree/worktree.service.js +334 -54
  220. package/dist/worktree/worktree.types.d.ts +9 -1
  221. package/package.json +12 -7
  222. package/runtime/windows-job-supervisor.ps1 +722 -0
  223. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -0,0 +1,840 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { attemptDatabaseCapabilityEnvironment, attemptDatabaseCapabilitySecrets, } from "../runtime/attempt-database-broker.js";
5
+ import { completionPhaseFromDispatch, completionPhaseOrder, completionRedactionSecretsEnv, completionToolNamesForDebugGate, completionToolNamesForDecomposeGate, completionToolNamesForDraftGate, completionToolNamesForPhase, completionToolNamesForQaGate, debugCompletionPhaseOrder, decomposeCompletionGateOrder, draftCompletionGateOrder, qaCompletionGateOrder, toolServerName, usesArchitectCycleCompletion, usesDebugCycleCompletion, usesDevelopCycleCompletion, usesQaCycleCompletion } from "./phase-completion.js";
6
+ 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";
7
+ import { completionPromptForDebugCycle, completionPromptForDecomposeCycle, completionPromptForDevelopCycle, completionPromptForDraftCycle, completionPromptForPhase, completionPromptForQaCycle, completionToolNextInstruction, completionToolNextInstructionForDebugGate, completionToolNextInstructionForDecomposeGate, completionToolNextInstructionForDraftGate, completionToolNextInstructionForQaGate, nextDebugPhaseAfter, nextPhaseAfter } from "./phase-completion.prompts.js";
8
+ import { configuredNativeTestPostgresFallbackPath, nativeTestPostgresFallbackConfigEnvironment, } from "../runtime/native-test-postgres-fallback.js";
9
+ import { toolDescription, toolInputSchema } from "./phase-completion.schemas.js";
10
+ import { writeCompletionArtifact, writeDecomposeGateCompletionArtifact, writeDraftGateCompletionArtifact } from "./phase-completion.artifacts.js";
11
+ import { callManagedAttemptTool, isManagedAttemptTool, managedAttemptToolDefinition, managedAttemptToolNames, } from "./managed-attempt-tools.js";
12
+ export async function prepareCompletionToolRuntime(options) {
13
+ const phase = options.phase ?? completionPhaseFromDispatch(options.dispatch);
14
+ const worktreePath = path.resolve(options.worktree.worktreePath);
15
+ const workItemId = options.dispatch.workItem.id;
16
+ const ownerType = normalizedOwnerType(options.dispatch.cycleRun.ownerType) ?? "work_item";
17
+ const ownerId = options.dispatch.cycleRun.ownerId ?? workItemId;
18
+ const mcpArgs = buildCompletionToolServerArgs({
19
+ worktreePath,
20
+ workItemId,
21
+ ownerType,
22
+ ownerId,
23
+ phase,
24
+ producedBy: `${options.dispatch.execution.provider}-cli`,
25
+ developCycle: usesDevelopCycleCompletion(options.dispatch),
26
+ qaCycle: usesQaCycleCompletion(options.dispatch),
27
+ debugCycle: usesDebugCycleCompletion(options.dispatch),
28
+ architectCycle: usesArchitectCycleCompletion(options.dispatch),
29
+ draftCycle: (phase === "draft"
30
+ || options.dispatch.execution.lane.trim().toLowerCase() === "draft"
31
+ || options.dispatch.cycleRun.kind.trim().toLowerCase() === "draft")
32
+ && !isDecomposeDispatch(options.dispatch),
33
+ decomposeCycle: isDecomposeDispatch(options.dispatch)
34
+ });
35
+ const runtime = {
36
+ serverName: toolServerName,
37
+ command: process.execPath,
38
+ args: [forgeBridgeCliBinPath(), ...mcpArgs],
39
+ env: completionToolRuntimeEnvironment(options.runtimeEnvironment, configuredNativeTestPostgresFallbackPath()),
40
+ cleanup: async () => undefined
41
+ };
42
+ const provider = options.dispatch.execution.provider.trim().toLowerCase();
43
+ if (provider === "cursor") {
44
+ return await prepareCursorCompletionToolRuntime(worktreePath, runtime);
45
+ }
46
+ if (provider === "claude") {
47
+ return await prepareClaudeCompletionToolRuntime(runtime);
48
+ }
49
+ return runtime;
50
+ }
51
+ export function buildCompletionToolServerArgs(config) {
52
+ const args = [
53
+ "mcp",
54
+ "--worktree",
55
+ path.resolve(config.worktreePath),
56
+ "--work-item-id",
57
+ config.workItemId,
58
+ "--phase",
59
+ config.phase,
60
+ "--produced-by",
61
+ config.producedBy ?? "forge-completion-tool"
62
+ ];
63
+ if (config.ownerType || config.ownerId) {
64
+ args.push("--owner-type", normalizedOwnerType(config.ownerType) ?? "work_item");
65
+ args.push("--owner-id", config.ownerId ?? config.workItemId);
66
+ }
67
+ if (config.developCycle) {
68
+ args.push("--develop-cycle");
69
+ }
70
+ if (config.qaCycle) {
71
+ args.push("--qa-cycle");
72
+ }
73
+ if (config.debugCycle) {
74
+ args.push("--debug-cycle");
75
+ }
76
+ if (config.architectCycle) {
77
+ args.push("--architect-cycle");
78
+ }
79
+ if (config.draftCycle) {
80
+ args.push("--draft-cycle");
81
+ }
82
+ if (config.decomposeCycle) {
83
+ args.push("--decompose-cycle");
84
+ }
85
+ return args;
86
+ }
87
+ export function codexMcpConfigArgs(runtime) {
88
+ const prefix = `mcp_servers.${runtime.serverName}`;
89
+ const args = [
90
+ "-c",
91
+ `${prefix}.command=${tomlString(runtime.command)}`,
92
+ "-c",
93
+ `${prefix}.args=${tomlStringArray(runtime.args)}`,
94
+ "-c",
95
+ `${prefix}.default_tools_approval_mode=${tomlString("approve")}`,
96
+ "-c",
97
+ `${prefix}.tool_timeout_sec=7200`
98
+ ];
99
+ for (const [key, value] of Object.entries(runtime.env)) {
100
+ args.push("-c", `${prefix}.env.${key}=${tomlString(value)}`);
101
+ }
102
+ return args;
103
+ }
104
+ export function cursorMcpServerConfig(runtime) {
105
+ return {
106
+ command: runtime.command,
107
+ args: [...runtime.args],
108
+ env: runtime.env
109
+ };
110
+ }
111
+ export function claudeMcpServerConfig(runtime) {
112
+ return {
113
+ type: "stdio",
114
+ command: runtime.command,
115
+ args: [...runtime.args],
116
+ env: runtime.env
117
+ };
118
+ }
119
+ export function completionToolNamesForServer(config) {
120
+ return config.decomposeCycle
121
+ ? decomposeCompletionGateOrder.flatMap((gate) => completionToolNamesForDecomposeGate(gate))
122
+ : config.draftCycle
123
+ ? draftCompletionGateOrder.flatMap((gate) => completionToolNamesForDraftGate(gate))
124
+ : config.debugCycle
125
+ ? debugCompletionPhaseOrder.flatMap((gate) => completionToolNamesForDebugGate(gate))
126
+ : config.qaCycle
127
+ ? qaCompletionGateOrder.flatMap((gate) => completionToolNamesForQaGate(gate))
128
+ : config.developCycle
129
+ ? completionPhaseOrder.flatMap((phase) => completionToolNamesForPhase(phase))
130
+ : config.architectCycle
131
+ ? completionToolNamesForPhase("architect")
132
+ : completionToolNamesForPhase(config.phase);
133
+ }
134
+ export class CompletionToolServerSession {
135
+ config;
136
+ currentPhase;
137
+ currentQaGate = "qa_plan";
138
+ currentDraftGate = "draft_intent";
139
+ currentDecomposeGate = "decompose_subject_intake";
140
+ managedCommandReceipts = [];
141
+ completed = false;
142
+ constructor(config) {
143
+ this.config = config;
144
+ this.currentPhase = config.phase;
145
+ }
146
+ get instructions() {
147
+ return this.config.decomposeCycle
148
+ ? completionPromptForDecomposeCycle(this.currentDecomposeGate)
149
+ : this.config.draftCycle
150
+ ? completionPromptForDraftCycle(this.currentDraftGate)
151
+ : this.config.debugCycle
152
+ ? completionPromptForDebugCycle(asDebugCompletionPhase(this.currentPhase))
153
+ : this.config.qaCycle
154
+ ? completionPromptForQaCycle(this.currentQaGate, artifactOwnerRelativeRoot(this.config.ownerType, this.config.ownerId, this.config.workItemId))
155
+ : this.config.developCycle
156
+ ? completionPromptForDevelopCycle(this.currentPhase)
157
+ : completionPromptForPhase(this.currentPhase, artifactOwnerRelativeRoot(this.config.ownerType, this.config.ownerId, this.config.workItemId));
158
+ }
159
+ toolDefinitions() {
160
+ const completionTools = completionToolNamesForServer(this.config).map((name) => toolDefinition(name, phaseFromToolName(name), qaGateFromToolName(name), decomposeGateFromToolName(name)));
161
+ return managedAttemptToolsAvailable()
162
+ ? [...completionTools, ...managedAttemptToolNames.map(managedAttemptToolDefinition)]
163
+ : completionTools;
164
+ }
165
+ async callTool(toolName, input) {
166
+ if (isManagedAttemptTool(toolName)) {
167
+ if (!managedAttemptToolsAvailable()) {
168
+ throw new Error("Forge managed-attempt tools require an active managed host runtime.");
169
+ }
170
+ const result = await callManagedAttemptTool(toolName, input, this.config.worktreePath);
171
+ if (Array.isArray(result.structuredContent.command)) {
172
+ this.managedCommandReceipts.push(result.structuredContent);
173
+ }
174
+ return result;
175
+ }
176
+ const requestedPhase = phaseFromToolName(toolName);
177
+ if (this.completed) {
178
+ throw new Error("Forge completion cycle is already complete; no more completion calls are accepted.");
179
+ }
180
+ if (this.config.decomposeCycle) {
181
+ return await this.callDecomposeGateTool(toolName, requestedPhase, input);
182
+ }
183
+ if (this.config.draftCycle) {
184
+ return await this.callDraftGateTool(toolName, requestedPhase, input);
185
+ }
186
+ if (this.config.debugCycle) {
187
+ return await this.callDebugGateTool(toolName, requestedPhase, input);
188
+ }
189
+ if (this.config.qaCycle) {
190
+ return await this.callQaGateTool(toolName, requestedPhase, input);
191
+ }
192
+ if (requestedPhase !== this.currentPhase) {
193
+ throw new Error(`Tool ${toolName} is out of order. Current Forge phase is ${this.currentPhase}.`);
194
+ }
195
+ const completedPhase = this.currentPhase;
196
+ const reference = await writeCompletionArtifact({ ...this.config, phase: completedPhase }, toolName, input);
197
+ const status = reference.status.trim().toLowerCase();
198
+ const pass = status === "pass";
199
+ const nextPhase = pass
200
+ ? this.advanceAfterPass(completedPhase)
201
+ : completedPhase;
202
+ const nextInstruction = completionToolNextInstruction(completedPhase, status, nextPhase);
203
+ const reason = completedPhase === "qa"
204
+ ? `QA artifact recorded locally with status ${status}; API ingestion and acceptance are pending.`
205
+ : pass
206
+ ? `${completedPhase} completion artifact accepted.`
207
+ : `${completedPhase} completion artifact recorded with status ${status}; rework is required before phase progression.`;
208
+ return {
209
+ content: [
210
+ {
211
+ type: "text",
212
+ text: `Forge recorded ${reference.phase} completion artifact ${path.relative(reference.repoPath, reference.path)} (${reference.status}, ${reference.contentHash}). ${nextInstruction}`
213
+ }
214
+ ],
215
+ structuredContent: {
216
+ ...reference,
217
+ ...acceptedArtifactReceipt(reference),
218
+ gate: {
219
+ phase: completedPhase,
220
+ status,
221
+ allowed: pass,
222
+ reason
223
+ },
224
+ nextPhase,
225
+ nextInstruction
226
+ }
227
+ };
228
+ }
229
+ async callDecomposeGateTool(toolName, requestedPhase, input) {
230
+ const requestedGate = decomposeGateFromToolName(toolName);
231
+ if (requestedPhase !== "decompose" || !requestedGate) {
232
+ throw new Error(`Tool ${toolName} is not available in Decompose-cycle mode. Current Forge Decompose gate is ${this.currentDecomposeGate}.`);
233
+ }
234
+ if (requestedGate !== this.currentDecomposeGate) {
235
+ throw new Error(`Tool ${toolName} is out of order. Current Forge Decompose gate is ${this.currentDecomposeGate}.`);
236
+ }
237
+ const completedGate = this.currentDecomposeGate;
238
+ const reference = await writeDecomposeGateCompletionArtifact(this.config, completedGate, toolName, input);
239
+ const status = reference.status.trim().toLowerCase();
240
+ const pass = status === "pass";
241
+ const nextGate = pass ? this.advanceAfterDecomposeGate(completedGate) : completedGate;
242
+ const nextPhase = pass
243
+ ? (nextGate ? "decompose" : "sign_off_ready")
244
+ : "decompose";
245
+ const nextInstruction = completionToolNextInstructionForDecomposeGate(completedGate, status, nextGate);
246
+ const reason = pass
247
+ ? nextGate
248
+ ? `${completedGate} artifact accepted; continue to ${nextGate}.`
249
+ : `Decompose proposal artifact accepted with status ${status}.`
250
+ : `${completedGate} artifact recorded with status ${status}; rework is required before continuing.`;
251
+ return {
252
+ content: [{ type: "text", text: `Forge recorded ${completedGate} artifact ${path.relative(reference.repoPath, reference.path)} (${reference.status}, ${reference.contentHash}). ${nextInstruction}` }],
253
+ structuredContent: {
254
+ ...reference,
255
+ ...acceptedArtifactReceipt(reference),
256
+ gate: { phase: "decompose", decomposeGate: completedGate, status, allowed: pass, reason },
257
+ nextPhase,
258
+ nextInstruction
259
+ }
260
+ };
261
+ }
262
+ async callDraftGateTool(toolName, requestedPhase, input) {
263
+ const requestedGate = draftGateFromToolName(toolName);
264
+ if (requestedPhase !== "draft" || !requestedGate) {
265
+ throw new Error(`Tool ${toolName} is not available in Draft-cycle mode. Current Forge Draft gate is ${this.currentDraftGate}.`);
266
+ }
267
+ if (requestedGate !== this.currentDraftGate) {
268
+ throw new Error(`Tool ${toolName} is out of order. Current Forge Draft gate is ${this.currentDraftGate}.`);
269
+ }
270
+ const completedGate = this.currentDraftGate;
271
+ const reference = await writeDraftGateCompletionArtifact(this.config, completedGate, toolName, input);
272
+ const status = reference.status.trim().toLowerCase();
273
+ const nextGate = this.advanceAfterDraftGate(completedGate);
274
+ const nextPhase = nextGate ? "draft" : "sign_off_ready";
275
+ const nextInstruction = completionToolNextInstructionForDraftGate(completedGate, status, nextGate);
276
+ return {
277
+ content: [{ type: "text", text: `Forge recorded ${completedGate} artifact ${path.relative(reference.repoPath, reference.path)} (${reference.status}, ${reference.contentHash}). ${nextInstruction}` }],
278
+ structuredContent: {
279
+ ...reference,
280
+ ...acceptedArtifactReceipt(reference),
281
+ gate: { phase: "draft", draftGate: completedGate, status, allowed: status !== "blocked", reason: nextGate ? `${completedGate} artifact accepted; continue to ${nextGate}.` : `Draft proposal artifact accepted with status ${status}.` },
282
+ nextPhase,
283
+ nextInstruction
284
+ }
285
+ };
286
+ }
287
+ async callDebugGateTool(toolName, requestedPhase, input) {
288
+ const currentDebugGate = asDebugCompletionPhase(this.currentPhase);
289
+ if (!isDebugCompletionPhase(requestedPhase)) {
290
+ throw new Error(`Tool ${toolName} is not available in Debug-cycle mode. Current Forge Debug gate is ${currentDebugGate}.`);
291
+ }
292
+ if (requestedPhase !== currentDebugGate) {
293
+ throw new Error(`Tool ${toolName} is out of order. Current Forge Debug gate is ${currentDebugGate}.`);
294
+ }
295
+ const completedGate = currentDebugGate;
296
+ const reference = await writeCompletionArtifact({ ...this.config, phase: completedGate }, toolName, input);
297
+ const status = reference.status.trim().toLowerCase();
298
+ const pass = status === "pass";
299
+ const nextGate = pass ? this.advanceAfterDebugGate(completedGate) : completedGate;
300
+ const nextPhase = nextGate ?? "qa_ready";
301
+ const nextInstruction = completionToolNextInstructionForDebugGate(completedGate, status, nextGate);
302
+ const reason = pass
303
+ ? `${completedGate} artifact accepted${nextGate ? `; continue to ${nextGate}.` : "; Debug regression is QA-ready."}`
304
+ : `${completedGate} artifact recorded with status ${status}; rework is required before Debug progression.`;
305
+ return {
306
+ content: [
307
+ {
308
+ type: "text",
309
+ text: `Forge recorded ${completedGate} artifact ${path.relative(reference.repoPath, reference.path)} (${reference.status}, ${reference.contentHash}). ${nextInstruction}`
310
+ }
311
+ ],
312
+ structuredContent: {
313
+ ...reference,
314
+ ...acceptedArtifactReceipt(reference),
315
+ gate: {
316
+ phase: completedGate,
317
+ status,
318
+ allowed: pass,
319
+ reason
320
+ },
321
+ nextPhase,
322
+ nextInstruction
323
+ }
324
+ };
325
+ }
326
+ async callQaGateTool(toolName, requestedPhase, input) {
327
+ const requestedGate = qaGateFromToolName(toolName);
328
+ if (requestedPhase !== "qa" || !requestedGate) {
329
+ throw new Error(`Tool ${toolName} is not available in QA-cycle mode. Current Forge QA gate is ${this.currentQaGate}.`);
330
+ }
331
+ if (requestedGate !== this.currentQaGate) {
332
+ throw new Error(`Tool ${toolName} is out of order. Current Forge QA gate is ${this.currentQaGate}.`);
333
+ }
334
+ const completedGate = this.currentQaGate;
335
+ if (completedGate === "qa_regression" && managedAttemptToolsAvailable()) {
336
+ assertManagedQaRegressionEvidence(input, this.managedCommandReceipts);
337
+ }
338
+ const reference = await writeCompletionArtifact({ ...this.config, phase: "qa" }, toolName, input);
339
+ const status = reference.status.trim().toLowerCase();
340
+ const nextGate = this.advanceAfterQaGate(completedGate);
341
+ const nextPhase = nextGate ? "qa" : "sign_off_ready";
342
+ const nextInstruction = completionToolNextInstructionForQaGate(completedGate, status, nextGate);
343
+ const allowed = status !== "blocked";
344
+ const reason = nextGate
345
+ ? `${completedGate} artifact accepted locally; continue to ${nextGate}.`
346
+ : `QA verdict artifact recorded locally with status ${status}; API ingestion and acceptance are pending.`;
347
+ return {
348
+ content: [
349
+ {
350
+ type: "text",
351
+ text: `Forge recorded ${completedGate} artifact ${path.relative(reference.repoPath, reference.path)} (${reference.status}, ${reference.contentHash}). ${nextInstruction}`
352
+ }
353
+ ],
354
+ structuredContent: {
355
+ ...reference,
356
+ ...acceptedArtifactReceipt(reference),
357
+ gate: {
358
+ phase: "qa",
359
+ qaGate: completedGate,
360
+ status,
361
+ allowed,
362
+ reason
363
+ },
364
+ nextPhase,
365
+ nextInstruction
366
+ }
367
+ };
368
+ }
369
+ advanceAfterPass(phase) {
370
+ if (phase === "qa") {
371
+ this.completed = true;
372
+ return "sign_off_ready";
373
+ }
374
+ if (phase === "ci_repair") {
375
+ this.completed = true;
376
+ return "provider_checks_ready";
377
+ }
378
+ if (isDebugCompletionPhase(phase)) {
379
+ return nextDebugPhaseAfter(phase) ?? "qa_ready";
380
+ }
381
+ const next = nextPhaseAfter(phase);
382
+ if (!this.config.developCycle) {
383
+ return next ?? "dev_test_ready";
384
+ }
385
+ if (next) {
386
+ this.currentPhase = next;
387
+ return next;
388
+ }
389
+ this.completed = true;
390
+ return "dev_test_ready";
391
+ }
392
+ advanceAfterDecomposeGate(gate) {
393
+ const index = decomposeCompletionGateOrder.indexOf(gate);
394
+ const next = index >= 0 ? decomposeCompletionGateOrder[index + 1] ?? null : null;
395
+ if (next) {
396
+ this.currentDecomposeGate = next;
397
+ return next;
398
+ }
399
+ this.completed = true;
400
+ return null;
401
+ }
402
+ advanceAfterDraftGate(gate) {
403
+ const index = draftCompletionGateOrder.indexOf(gate);
404
+ const next = index >= 0 ? draftCompletionGateOrder[index + 1] ?? null : null;
405
+ if (next) {
406
+ this.currentDraftGate = next;
407
+ return next;
408
+ }
409
+ this.completed = true;
410
+ return null;
411
+ }
412
+ advanceAfterQaGate(gate) {
413
+ const index = qaCompletionGateOrder.indexOf(gate);
414
+ const next = index >= 0 ? qaCompletionGateOrder[index + 1] ?? null : null;
415
+ if (next) {
416
+ this.currentQaGate = next;
417
+ return next;
418
+ }
419
+ this.completed = true;
420
+ return null;
421
+ }
422
+ advanceAfterDebugGate(gate) {
423
+ const index = debugCompletionPhaseOrder.indexOf(gate);
424
+ const next = index >= 0 ? debugCompletionPhaseOrder[index + 1] ?? null : null;
425
+ if (next) {
426
+ this.currentPhase = next;
427
+ return next;
428
+ }
429
+ this.completed = true;
430
+ return null;
431
+ }
432
+ }
433
+ const completionToolRuntimeEnvironmentNames = [
434
+ "FORGE_RUNTIME_MODE",
435
+ "FORGE_RESOURCE_LEASE_ID",
436
+ "FORGE_DISPATCH_ATTEMPT_ID",
437
+ "FORGE_E2E_OWNERSHIP_MODE",
438
+ "FORGE_E2E_OWNERSHIP_TOKEN",
439
+ "FORGE_WORKTREE_PATH",
440
+ "FORGE_LOG_DIR",
441
+ "FORGE_CACHE_DIR",
442
+ "FORGE_TEST_OUTPUT_DIR",
443
+ "FORGE_ATTEMPT_DATABASE_SCHEMA",
444
+ "FORGE_ATTEMPT_DATABASE_READINESS_PATH",
445
+ "FORGE_ATTEMPT_DATABASE_CAPABILITY",
446
+ "FORGE_CI_REPAIR_READINESS_PATH",
447
+ "FORGE_CI_REPAIR_HANDOFF_PATH",
448
+ "FORGE_CI_REPAIR_PROVIDER_ONLY_SPECS",
449
+ "FORGE_PLAYWRIGHT_CONFIG",
450
+ "FORGE_API_URL",
451
+ "FORGE_WEB_URL",
452
+ "FORGE_E2E_API_URL",
453
+ "FORGE_E2E_WEB_URL",
454
+ "PLAYWRIGHT_BROWSERS_PATH",
455
+ "FORGE_TMPDIR",
456
+ "NPM_CONFIG_CACHE",
457
+ "TMPDIR",
458
+ "TMP",
459
+ "TEMP",
460
+ ];
461
+ export function completionToolRuntimeEnvironment(runtimeEnvironment, nativeTestPostgresFallbackPath) {
462
+ if (!runtimeEnvironment)
463
+ return {};
464
+ const env = {};
465
+ for (const name of completionToolRuntimeEnvironmentNames) {
466
+ const value = runtimeEnvironment[name]?.trim();
467
+ if (value)
468
+ env[name] = value;
469
+ }
470
+ // Managed verification reads the operator's native test cluster only through
471
+ // this explicit path, and only while Docker is unavailable.
472
+ if (nativeTestPostgresFallbackPath) {
473
+ env[nativeTestPostgresFallbackConfigEnvironment] = nativeTestPostgresFallbackPath;
474
+ }
475
+ return env;
476
+ }
477
+ const minimumManagedSolutionTestTimeoutMs = 45 * 60 * 1000;
478
+ /**
479
+ * A solution-wide Forge test is long enough that a generic provider shell can
480
+ * expire while VSTest is still healthy. On managed host attempts, only the
481
+ * attempt-owned command runner can prove the exact process outcome and clean
482
+ * up its process tree. Keep this check at the completion gate so prompt drift
483
+ * cannot turn an arbitrary shell timeout into a QA blocker.
484
+ */
485
+ export function assertManagedQaRegressionEvidence(input, receipts) {
486
+ const payload = asRecord(input, "QA regression completion payload");
487
+ const nestedEvidence = asMutableRecord(payload.evidence);
488
+ const commands = payload.commands ?? nestedEvidence.commands;
489
+ if (!Array.isArray(commands))
490
+ return;
491
+ for (const [index, entry] of commands.entries()) {
492
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
493
+ continue;
494
+ const command = entry.command;
495
+ if (typeof command !== "string" || !isForgeSolutionTestCommand(command))
496
+ continue;
497
+ const managedCommandId = entry.managed_command_id;
498
+ if (typeof managedCommandId !== "string" || !managedCommandId.trim()) {
499
+ throw new Error(`qa_regression_managed_command_id_required: commands[${index}] runs the solution-wide Forge test. `
500
+ + "Run it with forge.start_managed_command, continue independent work, then call forge.wait_managed_command for a transport-safe receipt. "
501
+ + "If command_continues=true, continue useful work or wait again after retry_after_ms. Include the terminal managed_command_id in this command evidence.");
502
+ }
503
+ const matchingReceipts = receipts.filter((receipt) => {
504
+ const tokens = receipt.command;
505
+ return receipt.command_id === managedCommandId
506
+ && Array.isArray(tokens)
507
+ && tokens.every((token) => typeof token === "string")
508
+ && isForgeSolutionTestCommand(tokens.join(" "));
509
+ });
510
+ if (matchingReceipts.length === 0) {
511
+ throw new Error(`qa_regression_managed_command_required: commands[${index}] runs the solution-wide Forge test. `
512
+ + "Run it with forge.start_managed_command, continue independent work, then call forge.wait_managed_command for a transport-safe receipt. "
513
+ + "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.");
514
+ }
515
+ const terminalReceipts = matchingReceipts.filter((receipt) => typeof receipt.status === "string" && [
516
+ "pass",
517
+ "fail",
518
+ "timed_out",
519
+ "timeout_termination_failed",
520
+ "runner_error",
521
+ ].includes(receipt.status));
522
+ if (terminalReceipts.length === 0) {
523
+ throw new Error(`qa_regression_managed_command_in_progress: commands[${index}] has not produced a terminal managed receipt. `
524
+ + "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.");
525
+ }
526
+ const adequateReceipt = terminalReceipts.some((receipt) => {
527
+ if (receipt.timed_out !== true)
528
+ return true;
529
+ return typeof receipt.timeout_ms === "number"
530
+ && receipt.timeout_ms >= minimumManagedSolutionTestTimeoutMs
531
+ && receipt.process_tree_terminated === true;
532
+ });
533
+ if (!adequateReceipt) {
534
+ throw new Error(`qa_regression_managed_timeout_too_short: commands[${index}] timed out before the minimum `
535
+ + `${minimumManagedSolutionTestTimeoutMs}ms solution-test budget. Rerun it with forge.start_managed_command `
536
+ + "using at least a 45-minute timeout and resubmit this gate.");
537
+ }
538
+ }
539
+ }
540
+ function isForgeSolutionTestCommand(command) {
541
+ const normalized = command.trim().toLowerCase().replaceAll("\\", "/");
542
+ return normalized.includes("dotnet")
543
+ && /(?:^|\s)test(?:\s|$)/u.test(normalized)
544
+ && /(?:^|[\s/"'])forge\.sln(?:[\s"']|$)/u.test(normalized);
545
+ }
546
+ function managedAttemptToolsAvailable() {
547
+ return process.env.FORGE_RUNTIME_MODE === "host"
548
+ && Boolean(process.env.FORGE_RESOURCE_LEASE_ID?.trim())
549
+ && Boolean(process.env.FORGE_DISPATCH_ATTEMPT_ID?.trim())
550
+ && Boolean(process.env.FORGE_LOG_DIR?.trim())
551
+ && Boolean(process.env.FORGE_TEST_OUTPUT_DIR?.trim());
552
+ }
553
+ export async function runCompletionMcpServer(config) {
554
+ const session = new CompletionToolServerSession(config);
555
+ process.stdin.setEncoding("utf8");
556
+ let buffer = "";
557
+ for await (const chunk of process.stdin) {
558
+ buffer += chunk;
559
+ let newlineIndex = buffer.indexOf("\n");
560
+ while (newlineIndex >= 0) {
561
+ const rawLine = buffer.slice(0, newlineIndex).trim();
562
+ buffer = buffer.slice(newlineIndex + 1);
563
+ if (rawLine.length > 0) {
564
+ await handleJsonRpcLine(session, rawLine);
565
+ }
566
+ newlineIndex = buffer.indexOf("\n");
567
+ }
568
+ }
569
+ const trailing = buffer.trim();
570
+ if (trailing.length > 0) {
571
+ await handleJsonRpcLine(session, trailing);
572
+ }
573
+ }
574
+ export function parseCompletionMcpArgs(argv) {
575
+ let worktreePath = null;
576
+ let workItemId = null;
577
+ let phase = "implementation";
578
+ let ownerType;
579
+ let ownerId;
580
+ let producedBy;
581
+ let developCycle = false;
582
+ let qaCycle = false;
583
+ let debugCycle = false;
584
+ let architectCycle = false;
585
+ let draftCycle = false;
586
+ let decomposeCycle = false;
587
+ for (let index = 1; index < argv.length; index += 1) {
588
+ const arg = argv[index];
589
+ if (arg === "--worktree") {
590
+ worktreePath = argv[++index] ?? null;
591
+ }
592
+ else if (arg === "--work-item-id") {
593
+ workItemId = argv[++index] ?? null;
594
+ }
595
+ else if (arg === "--owner-type") {
596
+ ownerType = normalizedOwnerType(argv[++index]);
597
+ if (!ownerType) {
598
+ return { ok: false, error: "mcp --owner-type must be work_item or story." };
599
+ }
600
+ }
601
+ else if (arg === "--owner-id") {
602
+ ownerId = argv[++index] ?? undefined;
603
+ }
604
+ else if (arg === "--phase") {
605
+ const rawPhase = argv[++index];
606
+ const parsedPhase = parseStrictCompletionPhase(rawPhase);
607
+ if (!parsedPhase) {
608
+ 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." };
609
+ }
610
+ phase = parsedPhase;
611
+ }
612
+ else if (arg === "--produced-by") {
613
+ producedBy = argv[++index] ?? undefined;
614
+ }
615
+ else if (arg === "--develop-cycle") {
616
+ developCycle = true;
617
+ }
618
+ else if (arg === "--qa-cycle") {
619
+ qaCycle = true;
620
+ }
621
+ else if (arg === "--debug-cycle") {
622
+ debugCycle = true;
623
+ }
624
+ else if (arg === "--architect-cycle") {
625
+ architectCycle = true;
626
+ }
627
+ else if (arg === "--draft-cycle") {
628
+ draftCycle = true;
629
+ }
630
+ else if (arg === "--decompose-cycle") {
631
+ decomposeCycle = true;
632
+ }
633
+ else {
634
+ return { ok: false, error: `Unknown mcp argument: ${arg}` };
635
+ }
636
+ }
637
+ if (!worktreePath?.trim() || !workItemId?.trim()) {
638
+ return { ok: false, error: "mcp requires --worktree and --work-item-id." };
639
+ }
640
+ if ([developCycle, qaCycle, debugCycle, architectCycle, draftCycle, decomposeCycle].filter(Boolean).length > 1) {
641
+ return { ok: false, error: "mcp accepts only one cycle mode." };
642
+ }
643
+ if (developCycle && !isDevelopCompletionPhase(phase)) {
644
+ return { ok: false, error: "mcp --develop-cycle requires --phase implementation, self-review, or validation." };
645
+ }
646
+ if (qaCycle && phase !== "qa") {
647
+ return { ok: false, error: "mcp --qa-cycle requires --phase qa." };
648
+ }
649
+ if (debugCycle && !isDebugCompletionPhase(phase)) {
650
+ return { ok: false, error: "mcp --debug-cycle requires --phase debug-repro, debug-root-cause, debug-fix, or debug-regression." };
651
+ }
652
+ if (architectCycle && phase !== "architect") {
653
+ return { ok: false, error: "mcp --architect-cycle requires --phase architect." };
654
+ }
655
+ if (draftCycle && phase !== "draft") {
656
+ return { ok: false, error: "mcp --draft-cycle requires --phase draft." };
657
+ }
658
+ if (decomposeCycle && phase !== "decompose") {
659
+ return { ok: false, error: "mcp --decompose-cycle requires --phase decompose." };
660
+ }
661
+ return {
662
+ ok: true,
663
+ config: {
664
+ worktreePath: path.resolve(worktreePath),
665
+ workItemId,
666
+ ownerType,
667
+ ownerId,
668
+ phase,
669
+ producedBy,
670
+ redactionSecrets: [
671
+ ...parseCompletionRedactionSecrets(process.env[completionRedactionSecretsEnv]),
672
+ ...attemptDatabaseCapabilitySecrets(process.env[attemptDatabaseCapabilityEnvironment]),
673
+ ],
674
+ developCycle,
675
+ qaCycle,
676
+ debugCycle,
677
+ architectCycle,
678
+ draftCycle,
679
+ decomposeCycle
680
+ }
681
+ };
682
+ }
683
+ export function completionRedactionEnv(secrets) {
684
+ const filtered = secrets.filter((secret) => secret.length > 0);
685
+ return filtered.length > 0
686
+ ? { [completionRedactionSecretsEnv]: JSON.stringify(filtered) }
687
+ : {};
688
+ }
689
+ async function prepareCursorCompletionToolRuntime(worktreePath, runtime) {
690
+ const cursorDir = path.join(worktreePath, ".cursor");
691
+ const configPath = path.join(cursorDir, "mcp.json");
692
+ const original = await fs.readFile(configPath, "utf8").then((content) => ({ existed: true, content }), () => ({ existed: false, content: "" }));
693
+ const existing = original.existed ? parseOptionalJsonObject(original.content, configPath) : {};
694
+ const servers = asMutableRecord(existing.mcpServers);
695
+ servers[runtime.serverName] = cursorMcpServerConfig(runtime);
696
+ const next = {
697
+ ...existing,
698
+ mcpServers: servers
699
+ };
700
+ await fs.mkdir(cursorDir, { recursive: true });
701
+ await writeFileAtomic(configPath, `${JSON.stringify(next, null, 2)}\n`);
702
+ return {
703
+ ...runtime,
704
+ cursorMcpConfigPath: configPath,
705
+ cleanup: async () => {
706
+ if (original.existed) {
707
+ await writeFileAtomic(configPath, original.content);
708
+ }
709
+ else {
710
+ await fs.rm(configPath, { force: true });
711
+ await fs.rmdir(cursorDir).catch(() => undefined);
712
+ }
713
+ }
714
+ };
715
+ }
716
+ /**
717
+ * Claude Code takes MCP servers from `--mcp-config <file>`. The file lives in an attempt-owned
718
+ * temp directory (never the worktree) because its env block can carry redaction secrets, and
719
+ * passing the JSON inline would not survive Windows shim quoting.
720
+ */
721
+ async function prepareClaudeCompletionToolRuntime(runtime) {
722
+ const configDir = await fs.mkdtemp(path.join(os.tmpdir(), "forge-claude-mcp-"));
723
+ const configPath = path.join(configDir, "mcp.json");
724
+ const config = {
725
+ mcpServers: {
726
+ [runtime.serverName]: claudeMcpServerConfig(runtime)
727
+ }
728
+ };
729
+ await fs.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
730
+ return {
731
+ ...runtime,
732
+ claudeMcpConfigPath: configPath,
733
+ cleanup: async () => {
734
+ await fs.rm(configDir, { recursive: true, force: true });
735
+ }
736
+ };
737
+ }
738
+ async function handleJsonRpcLine(session, rawLine) {
739
+ let message;
740
+ try {
741
+ message = JSON.parse(rawLine);
742
+ }
743
+ catch (error) {
744
+ writeJsonRpc({ jsonrpc: "2.0", id: null, error: rpcError(-32700, errorMessage(error)) });
745
+ return;
746
+ }
747
+ const request = asJsonRpcMessage(message);
748
+ if (!request) {
749
+ writeJsonRpc({ jsonrpc: "2.0", id: null, error: rpcError(-32600, "Invalid JSON-RPC message.") });
750
+ return;
751
+ }
752
+ if (request.id === undefined) {
753
+ return;
754
+ }
755
+ try {
756
+ const result = await handleJsonRpcRequest(session, request.method, request.params);
757
+ writeJsonRpc({ jsonrpc: "2.0", id: request.id, result });
758
+ }
759
+ catch (error) {
760
+ writeJsonRpc({ jsonrpc: "2.0", id: request.id, error: rpcError(-32000, errorMessage(error)) });
761
+ }
762
+ }
763
+ async function handleJsonRpcRequest(session, method, params) {
764
+ if (method === "initialize") {
765
+ const initializeParams = params && typeof params === "object" && !Array.isArray(params)
766
+ ? params
767
+ : {};
768
+ return {
769
+ protocolVersion: typeof initializeParams.protocolVersion === "string" ? initializeParams.protocolVersion : "2025-06-18",
770
+ capabilities: {
771
+ tools: {}
772
+ },
773
+ serverInfo: {
774
+ name: "forge-completion-tools",
775
+ version: "0.1.0"
776
+ },
777
+ instructions: session.instructions
778
+ };
779
+ }
780
+ if (method === "ping") {
781
+ return {};
782
+ }
783
+ if (method === "tools/list") {
784
+ return {
785
+ tools: session.toolDefinitions()
786
+ };
787
+ }
788
+ if (method === "tools/call") {
789
+ const call = asRecord(params, "tools/call params");
790
+ const name = requiredString(call, "name");
791
+ const args = call.arguments ?? {};
792
+ try {
793
+ return await session.callTool(name, args);
794
+ }
795
+ catch (error) {
796
+ if (isManagedAttemptTool(name))
797
+ throw error;
798
+ return completionToolRejectedResult(name, error);
799
+ }
800
+ }
801
+ throw new Error(`Unsupported MCP method: ${method}`);
802
+ }
803
+ export function completionToolRejectedResult(toolName, error) {
804
+ const issues = error instanceof CompletionPayloadValidationError
805
+ ? [...error.issues]
806
+ : [errorMessage(error)];
807
+ 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.";
808
+ return {
809
+ content: [{
810
+ type: "text",
811
+ text: `Forge rejected ${toolName}. No artifact was written.\n${issues.map(issue => `- ${issue}`).join("\n")}\n${repairInstruction}`,
812
+ }],
813
+ structuredContent: {
814
+ status: "rejected",
815
+ tool_name: toolName,
816
+ artifact_written: false,
817
+ issues,
818
+ repair_instruction: repairInstruction,
819
+ },
820
+ isError: true,
821
+ };
822
+ }
823
+ function acceptedArtifactReceipt(reference) {
824
+ return {
825
+ artifact_written: true,
826
+ artifact_receipt: {
827
+ schema_version: 1,
828
+ path: reference.path,
829
+ report_path: reference.reportPath,
830
+ content_hash: reference.contentHash,
831
+ },
832
+ };
833
+ }
834
+ function toolDefinition(name, phase, qaGate = null, decomposeGate = null) {
835
+ return {
836
+ name,
837
+ description: toolDescription(phase, qaGate, decomposeGate),
838
+ inputSchema: toolInputSchema(phase, qaGate, decomposeGate)
839
+ };
840
+ }