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