@loucompanion/forge-bridge 0.1.3-dev.b6be5e7f9668 → 0.1.3-dev.ba7d36726eda

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 (141) hide show
  1. package/README.md +78 -0
  2. package/dist/bridge/bridge.base.d.ts +5 -1
  3. package/dist/bridge/bridge.service.d.ts +52 -3
  4. package/dist/bridge/bridge.service.js +796 -22
  5. package/dist/bridge/bridge.types.d.ts +135 -1
  6. package/dist/cleanup/cleanup.service.d.ts +13 -2
  7. package/dist/cleanup/cleanup.service.js +435 -3
  8. package/dist/cleanup/cleanup.types.d.ts +10 -0
  9. package/dist/cleanup/index.d.ts +1 -1
  10. package/dist/cleanup/index.js +1 -1
  11. package/dist/cleanup/internals.d.ts +2 -2
  12. package/dist/cleanup/internals.js +1 -1
  13. package/dist/cli/cli.help.d.ts +2 -0
  14. package/dist/cli/cli.help.js +217 -0
  15. package/dist/cli/cli.service.d.ts +42 -2
  16. package/dist/cli/cli.service.js +356 -205
  17. package/dist/cli/local-config.d.ts +6 -0
  18. package/dist/cli/local-config.js +45 -2
  19. package/dist/runtime/attempt-node-runtime-launcher.d.ts +15 -0
  20. package/dist/runtime/attempt-node-runtime-launcher.js +50 -0
  21. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  22. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  23. package/dist/runtime/attempt-process-supervisor.d.ts +74 -0
  24. package/dist/runtime/attempt-process-supervisor.js +290 -0
  25. package/dist/runtime/host-listener-inspector.d.ts +28 -0
  26. package/dist/runtime/host-listener-inspector.js +185 -0
  27. package/dist/runtime/host-port-broker.d.ts +76 -0
  28. package/dist/runtime/host-port-broker.js +333 -0
  29. package/dist/runtime/host-runtime-contract.d.ts +39 -0
  30. package/dist/runtime/host-runtime-contract.js +295 -0
  31. package/dist/runtime/host-runtime-lease.d.ts +122 -0
  32. package/dist/runtime/host-runtime-lease.js +1241 -0
  33. package/dist/runtime/host-runtime-promotion.d.ts +21 -0
  34. package/dist/runtime/host-runtime-promotion.js +128 -0
  35. package/dist/runtime/node-host-runtime-adapter.d.ts +44 -0
  36. package/dist/runtime/node-host-runtime-adapter.js +205 -0
  37. package/dist/runtime/playwright-host-runtime-adapter.d.ts +20 -0
  38. package/dist/runtime/playwright-host-runtime-adapter.js +136 -0
  39. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  40. package/dist/runtime/runtime-isolation-lease.js +158 -0
  41. package/dist/sandbox/command-runner.d.ts +4 -0
  42. package/dist/sandbox/command-runner.js +47 -0
  43. package/dist/sandbox/contract.d.ts +5 -0
  44. package/dist/sandbox/contract.js +276 -0
  45. package/dist/sandbox/event-budget.d.ts +11 -0
  46. package/dist/sandbox/event-budget.js +27 -0
  47. package/dist/sandbox/image-policy.d.ts +35 -0
  48. package/dist/sandbox/image-policy.js +247 -0
  49. package/dist/sandbox/index.d.ts +5 -0
  50. package/dist/sandbox/index.js +5 -0
  51. package/dist/sandbox/lease-watchdog.d.ts +18 -0
  52. package/dist/sandbox/lease-watchdog.js +67 -0
  53. package/dist/sandbox/preflight.d.ts +45 -0
  54. package/dist/sandbox/preflight.js +300 -0
  55. package/dist/sandbox/process.d.ts +20 -0
  56. package/dist/sandbox/process.js +209 -0
  57. package/dist/sandbox/provider-auth/codex.d.ts +26 -0
  58. package/dist/sandbox/provider-auth/codex.js +212 -0
  59. package/dist/sandbox/repository-lock.d.ts +5 -0
  60. package/dist/sandbox/repository-lock.js +119 -0
  61. package/dist/sandbox/runtime.d.ts +297 -0
  62. package/dist/sandbox/runtime.js +2338 -0
  63. package/dist/sandbox/sandbox.types.d.ts +86 -0
  64. package/dist/sandbox/sandbox.types.js +1 -0
  65. package/dist/sandbox/smoke.d.ts +76 -0
  66. package/dist/sandbox/smoke.js +380 -0
  67. package/dist/sandbox/worker-supervisor.d.ts +13 -0
  68. package/dist/sandbox/worker-supervisor.js +36 -0
  69. package/dist/sandbox/worker.d.ts +1 -0
  70. package/dist/sandbox/worker.js +225 -0
  71. package/dist/sandbox/workspace.service.d.ts +13 -0
  72. package/dist/sandbox/workspace.service.js +67 -0
  73. package/dist/session/phase-completion.artifacts.d.ts +10 -0
  74. package/dist/session/phase-completion.artifacts.js +620 -0
  75. package/dist/session/phase-completion.d.ts +32 -44
  76. package/dist/session/phase-completion.helpers.d.ts +112 -0
  77. package/dist/session/phase-completion.helpers.js +665 -0
  78. package/dist/session/phase-completion.js +28 -3485
  79. package/dist/session/phase-completion.prompts.d.ts +14 -0
  80. package/dist/session/phase-completion.prompts.js +220 -0
  81. package/dist/session/phase-completion.runtime.d.ts +36 -0
  82. package/dist/session/phase-completion.runtime.js +611 -0
  83. package/dist/session/phase-completion.schemas.d.ts +3 -0
  84. package/dist/session/phase-completion.schemas.js +707 -0
  85. package/dist/session/phase-completion.validation.d.ts +34 -0
  86. package/dist/session/phase-completion.validation.js +724 -0
  87. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +93 -0
  88. package/dist/session/provider-cli/provider-cli.artifacts.js +778 -0
  89. package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
  90. package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
  91. package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
  92. package/dist/session/provider-cli/provider-cli.deterministic.d.ts +14 -0
  93. package/dist/session/provider-cli/provider-cli.deterministic.js +329 -0
  94. package/dist/session/provider-cli/provider-cli.process.d.ts +34 -0
  95. package/dist/session/provider-cli/provider-cli.process.js +316 -0
  96. package/dist/session/provider-cli/provider-cli.prompts.d.ts +47 -0
  97. package/dist/session/provider-cli/provider-cli.prompts.js +573 -0
  98. package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
  99. package/dist/session/provider-cli/provider-cli.qa-sidecars.js +65 -0
  100. package/dist/session/provider-cli/provider-cli.runtime.d.ts +9 -0
  101. package/dist/session/provider-cli/provider-cli.runtime.js +41 -0
  102. package/dist/session/provider-cli/provider-cli.service.d.ts +9 -53
  103. package/dist/session/provider-cli/provider-cli.service.js +182 -2536
  104. package/dist/session/provider-cli/provider-cli.types.d.ts +3 -0
  105. package/dist/session/provider-cli/provider-output-state.d.ts +20 -0
  106. package/dist/session/provider-cli/provider-output-state.js +445 -0
  107. package/dist/session/session-redaction.d.ts +3 -0
  108. package/dist/session/session-redaction.js +23 -0
  109. package/dist/session/session.base.d.ts +4 -0
  110. package/dist/session/session.service.d.ts +6 -1
  111. package/dist/session/session.service.js +202 -53
  112. package/dist/session/session.types.d.ts +123 -1
  113. package/dist/session/session.types.js +301 -3
  114. package/dist/shared/path/path.js +3 -1
  115. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  116. package/dist/shared/process/host-process-recovery.js +78 -0
  117. package/dist/shared/process/host-process-supervisor.d.ts +40 -0
  118. package/dist/shared/process/host-process-supervisor.js +195 -0
  119. package/dist/shared/process/windows-launch.d.ts +8 -0
  120. package/dist/shared/process/windows-launch.js +41 -0
  121. package/dist/shared/provider-capabilities.d.ts +1 -0
  122. package/dist/shared/provider-capabilities.js +3 -0
  123. package/dist/shared/redaction/redaction.js +10 -3
  124. package/dist/version.d.ts +1 -1
  125. package/dist/version.js +1 -1
  126. package/dist/worktree/artifact-injector.js +39 -6
  127. package/dist/worktree/dependency-warmer.d.ts +11 -0
  128. package/dist/worktree/dependency-warmer.js +45 -1
  129. package/dist/worktree/index.d.ts +1 -0
  130. package/dist/worktree/index.js +1 -0
  131. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  132. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  133. package/dist/worktree/worktree-paths.d.ts +4 -0
  134. package/dist/worktree/worktree-paths.js +30 -0
  135. package/dist/worktree/worktree.base.d.ts +3 -0
  136. package/dist/worktree/worktree.service.d.ts +8 -3
  137. package/dist/worktree/worktree.service.js +89 -52
  138. package/dist/worktree/worktree.types.d.ts +3 -1
  139. package/package.json +6 -3
  140. package/runtime/windows-job-supervisor.ps1 +287 -0
  141. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -0,0 +1,611 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { completionPhaseFromDispatch, completionPhaseOrder, completionRedactionSecretsEnv, completionToolNamesForDebugGate, completionToolNamesForDecomposeGate, completionToolNamesForDraftGate, completionToolNamesForPhase, completionToolNamesForQaGate, debugCompletionPhaseOrder, decomposeCompletionGateOrder, draftCompletionGateOrder, qaCompletionGateOrder, toolServerName, usesArchitectCycleCompletion, usesDebugCycleCompletion, usesDevelopCycleCompletion, usesQaCycleCompletion } from "./phase-completion.js";
4
+ import { asDebugCompletionPhase, asJsonRpcMessage, asMutableRecord, asRecord, decomposeGateFromToolName, draftGateFromToolName, errorMessage, forgeBridgeCliBinPath, isDebugCompletionPhase, isDecomposeDispatch, isDevelopCompletionPhase, normalizedOwnerType, parseCompletionRedactionSecrets, parseOptionalJsonObject, parseStrictCompletionPhase, phaseFromToolName, qaGateFromToolName, requiredString, rpcError, tomlString, tomlStringArray, writeFileAtomic, writeJsonRpc } from "./phase-completion.validation.js";
5
+ import { completionPromptForDebugCycle, completionPromptForDecomposeCycle, completionPromptForDevelopCycle, completionPromptForDraftCycle, completionPromptForPhase, completionPromptForQaCycle, completionToolNextInstruction, completionToolNextInstructionForDebugGate, completionToolNextInstructionForDecomposeGate, completionToolNextInstructionForDraftGate, completionToolNextInstructionForQaGate, nextDebugPhaseAfter, nextPhaseAfter } from "./phase-completion.prompts.js";
6
+ import { toolDescription, toolInputSchema } from "./phase-completion.schemas.js";
7
+ import { writeCompletionArtifact, writeDecomposeGateCompletionArtifact, writeDraftGateCompletionArtifact } from "./phase-completion.artifacts.js";
8
+ export async function prepareCompletionToolRuntime(options) {
9
+ const phase = options.phase ?? completionPhaseFromDispatch(options.dispatch);
10
+ const worktreePath = path.resolve(options.worktree.worktreePath);
11
+ const workItemId = options.dispatch.workItem.id;
12
+ const ownerType = normalizedOwnerType(options.dispatch.cycleRun.ownerType) ?? "work_item";
13
+ const ownerId = options.dispatch.cycleRun.ownerId ?? workItemId;
14
+ const mcpArgs = buildCompletionToolServerArgs({
15
+ worktreePath,
16
+ workItemId,
17
+ ownerType,
18
+ ownerId,
19
+ phase,
20
+ producedBy: `${options.dispatch.execution.provider}-cli`,
21
+ developCycle: usesDevelopCycleCompletion(options.dispatch),
22
+ qaCycle: usesQaCycleCompletion(options.dispatch),
23
+ debugCycle: usesDebugCycleCompletion(options.dispatch),
24
+ architectCycle: usesArchitectCycleCompletion(options.dispatch),
25
+ draftCycle: (phase === "draft"
26
+ || options.dispatch.execution.lane.trim().toLowerCase() === "draft"
27
+ || options.dispatch.cycleRun.kind.trim().toLowerCase() === "draft")
28
+ && !isDecomposeDispatch(options.dispatch),
29
+ decomposeCycle: isDecomposeDispatch(options.dispatch)
30
+ });
31
+ const runtime = {
32
+ serverName: toolServerName,
33
+ command: process.execPath,
34
+ args: [forgeBridgeCliBinPath(), ...mcpArgs],
35
+ env: {},
36
+ cleanup: async () => undefined
37
+ };
38
+ if (options.dispatch.execution.provider.trim().toLowerCase() !== "cursor") {
39
+ return runtime;
40
+ }
41
+ return await prepareCursorCompletionToolRuntime(worktreePath, runtime);
42
+ }
43
+ export function buildCompletionToolServerArgs(config) {
44
+ const args = [
45
+ "mcp",
46
+ "--worktree",
47
+ path.resolve(config.worktreePath),
48
+ "--work-item-id",
49
+ config.workItemId,
50
+ "--phase",
51
+ config.phase,
52
+ "--produced-by",
53
+ config.producedBy ?? "forge-completion-tool"
54
+ ];
55
+ if (config.ownerType || config.ownerId) {
56
+ args.push("--owner-type", normalizedOwnerType(config.ownerType) ?? "work_item");
57
+ args.push("--owner-id", config.ownerId ?? config.workItemId);
58
+ }
59
+ if (config.developCycle) {
60
+ args.push("--develop-cycle");
61
+ }
62
+ if (config.qaCycle) {
63
+ args.push("--qa-cycle");
64
+ }
65
+ if (config.debugCycle) {
66
+ args.push("--debug-cycle");
67
+ }
68
+ if (config.architectCycle) {
69
+ args.push("--architect-cycle");
70
+ }
71
+ if (config.draftCycle) {
72
+ args.push("--draft-cycle");
73
+ }
74
+ if (config.decomposeCycle) {
75
+ args.push("--decompose-cycle");
76
+ }
77
+ return args;
78
+ }
79
+ export function codexMcpConfigArgs(runtime) {
80
+ const prefix = `mcp_servers.${runtime.serverName}`;
81
+ const args = [
82
+ "-c",
83
+ `${prefix}.command=${tomlString(runtime.command)}`,
84
+ "-c",
85
+ `${prefix}.args=${tomlStringArray(runtime.args)}`,
86
+ "-c",
87
+ `${prefix}.default_tools_approval_mode=${tomlString("approve")}`
88
+ ];
89
+ for (const [key, value] of Object.entries(runtime.env)) {
90
+ args.push("-c", `${prefix}.env.${key}=${tomlString(value)}`);
91
+ }
92
+ return args;
93
+ }
94
+ export function cursorMcpServerConfig(runtime) {
95
+ return {
96
+ command: runtime.command,
97
+ args: [...runtime.args],
98
+ env: runtime.env
99
+ };
100
+ }
101
+ export function completionToolNamesForServer(config) {
102
+ return config.decomposeCycle
103
+ ? decomposeCompletionGateOrder.flatMap((gate) => completionToolNamesForDecomposeGate(gate))
104
+ : config.draftCycle
105
+ ? draftCompletionGateOrder.flatMap((gate) => completionToolNamesForDraftGate(gate))
106
+ : config.debugCycle
107
+ ? debugCompletionPhaseOrder.flatMap((gate) => completionToolNamesForDebugGate(gate))
108
+ : config.qaCycle
109
+ ? qaCompletionGateOrder.flatMap((gate) => completionToolNamesForQaGate(gate))
110
+ : config.developCycle
111
+ ? completionPhaseOrder.flatMap((phase) => completionToolNamesForPhase(phase))
112
+ : config.architectCycle
113
+ ? completionToolNamesForPhase("architect")
114
+ : completionToolNamesForPhase(config.phase);
115
+ }
116
+ export class CompletionToolServerSession {
117
+ config;
118
+ currentPhase;
119
+ currentQaGate = "qa_plan";
120
+ currentDraftGate = "draft_intent";
121
+ currentDecomposeGate = "decompose_subject_intake";
122
+ completed = false;
123
+ constructor(config) {
124
+ this.config = config;
125
+ this.currentPhase = config.phase;
126
+ }
127
+ get instructions() {
128
+ return this.config.decomposeCycle
129
+ ? completionPromptForDecomposeCycle(this.currentDecomposeGate)
130
+ : this.config.draftCycle
131
+ ? completionPromptForDraftCycle(this.currentDraftGate)
132
+ : this.config.debugCycle
133
+ ? completionPromptForDebugCycle(asDebugCompletionPhase(this.currentPhase))
134
+ : this.config.qaCycle
135
+ ? completionPromptForQaCycle(this.currentQaGate)
136
+ : this.config.developCycle
137
+ ? completionPromptForDevelopCycle(this.currentPhase)
138
+ : completionPromptForPhase(this.currentPhase);
139
+ }
140
+ toolDefinitions() {
141
+ return completionToolNamesForServer(this.config).map((name) => toolDefinition(name, phaseFromToolName(name), qaGateFromToolName(name), decomposeGateFromToolName(name)));
142
+ }
143
+ async callTool(toolName, input) {
144
+ const requestedPhase = phaseFromToolName(toolName);
145
+ if (this.completed) {
146
+ throw new Error("Forge completion cycle is already complete; no more completion calls are accepted.");
147
+ }
148
+ if (this.config.decomposeCycle) {
149
+ return await this.callDecomposeGateTool(toolName, requestedPhase, input);
150
+ }
151
+ if (this.config.draftCycle) {
152
+ return await this.callDraftGateTool(toolName, requestedPhase, input);
153
+ }
154
+ if (this.config.debugCycle) {
155
+ return await this.callDebugGateTool(toolName, requestedPhase, input);
156
+ }
157
+ if (this.config.qaCycle) {
158
+ return await this.callQaGateTool(toolName, requestedPhase, input);
159
+ }
160
+ if (requestedPhase !== this.currentPhase) {
161
+ throw new Error(`Tool ${toolName} is out of order. Current Forge phase is ${this.currentPhase}.`);
162
+ }
163
+ const completedPhase = this.currentPhase;
164
+ const reference = await writeCompletionArtifact({ ...this.config, phase: completedPhase }, toolName, input);
165
+ const status = reference.status.trim().toLowerCase();
166
+ const pass = status === "pass";
167
+ const nextPhase = pass
168
+ ? this.advanceAfterPass(completedPhase)
169
+ : completedPhase;
170
+ const nextInstruction = completionToolNextInstruction(completedPhase, status, nextPhase);
171
+ const reason = pass
172
+ ? `${completedPhase} completion artifact accepted.`
173
+ : `${completedPhase} completion artifact recorded with status ${status}; rework is required before phase progression.`;
174
+ return {
175
+ content: [
176
+ {
177
+ type: "text",
178
+ text: `Forge recorded ${reference.phase} completion artifact ${path.relative(reference.repoPath, reference.path)} (${reference.status}, ${reference.contentHash}). ${nextInstruction}`
179
+ }
180
+ ],
181
+ structuredContent: {
182
+ ...reference,
183
+ gate: {
184
+ phase: completedPhase,
185
+ status,
186
+ allowed: pass,
187
+ reason
188
+ },
189
+ nextPhase,
190
+ nextInstruction
191
+ }
192
+ };
193
+ }
194
+ async callDecomposeGateTool(toolName, requestedPhase, input) {
195
+ const requestedGate = decomposeGateFromToolName(toolName);
196
+ if (requestedPhase !== "decompose" || !requestedGate) {
197
+ throw new Error(`Tool ${toolName} is not available in Decompose-cycle mode. Current Forge Decompose gate is ${this.currentDecomposeGate}.`);
198
+ }
199
+ if (requestedGate !== this.currentDecomposeGate) {
200
+ throw new Error(`Tool ${toolName} is out of order. Current Forge Decompose gate is ${this.currentDecomposeGate}.`);
201
+ }
202
+ const completedGate = this.currentDecomposeGate;
203
+ const reference = await writeDecomposeGateCompletionArtifact(this.config, completedGate, toolName, input);
204
+ const status = reference.status.trim().toLowerCase();
205
+ const pass = status === "pass";
206
+ const nextGate = pass ? this.advanceAfterDecomposeGate(completedGate) : completedGate;
207
+ const nextPhase = pass
208
+ ? (nextGate ? "decompose" : "sign_off_ready")
209
+ : "decompose";
210
+ const nextInstruction = completionToolNextInstructionForDecomposeGate(completedGate, status, nextGate);
211
+ const reason = pass
212
+ ? nextGate
213
+ ? `${completedGate} artifact accepted; continue to ${nextGate}.`
214
+ : `Decompose proposal artifact accepted with status ${status}.`
215
+ : `${completedGate} artifact recorded with status ${status}; rework is required before continuing.`;
216
+ return {
217
+ content: [{ type: "text", text: `Forge recorded ${completedGate} artifact ${path.relative(reference.repoPath, reference.path)} (${reference.status}, ${reference.contentHash}). ${nextInstruction}` }],
218
+ structuredContent: {
219
+ ...reference,
220
+ gate: { phase: "decompose", decomposeGate: completedGate, status, allowed: pass, reason },
221
+ nextPhase,
222
+ nextInstruction
223
+ }
224
+ };
225
+ }
226
+ async callDraftGateTool(toolName, requestedPhase, input) {
227
+ const requestedGate = draftGateFromToolName(toolName);
228
+ if (requestedPhase !== "draft" || !requestedGate) {
229
+ throw new Error(`Tool ${toolName} is not available in Draft-cycle mode. Current Forge Draft gate is ${this.currentDraftGate}.`);
230
+ }
231
+ if (requestedGate !== this.currentDraftGate) {
232
+ throw new Error(`Tool ${toolName} is out of order. Current Forge Draft gate is ${this.currentDraftGate}.`);
233
+ }
234
+ const completedGate = this.currentDraftGate;
235
+ const reference = await writeDraftGateCompletionArtifact(this.config, completedGate, toolName, input);
236
+ const status = reference.status.trim().toLowerCase();
237
+ const nextGate = this.advanceAfterDraftGate(completedGate);
238
+ const nextPhase = nextGate ? "draft" : "sign_off_ready";
239
+ const nextInstruction = completionToolNextInstructionForDraftGate(completedGate, status, nextGate);
240
+ return {
241
+ content: [{ type: "text", text: `Forge recorded ${completedGate} artifact ${path.relative(reference.repoPath, reference.path)} (${reference.status}, ${reference.contentHash}). ${nextInstruction}` }],
242
+ structuredContent: {
243
+ ...reference,
244
+ gate: { phase: "draft", draftGate: completedGate, status, allowed: status !== "blocked", reason: nextGate ? `${completedGate} artifact accepted; continue to ${nextGate}.` : `Draft proposal artifact accepted with status ${status}.` },
245
+ nextPhase,
246
+ nextInstruction
247
+ }
248
+ };
249
+ }
250
+ async callDebugGateTool(toolName, requestedPhase, input) {
251
+ const currentDebugGate = asDebugCompletionPhase(this.currentPhase);
252
+ if (!isDebugCompletionPhase(requestedPhase)) {
253
+ throw new Error(`Tool ${toolName} is not available in Debug-cycle mode. Current Forge Debug gate is ${currentDebugGate}.`);
254
+ }
255
+ if (requestedPhase !== currentDebugGate) {
256
+ throw new Error(`Tool ${toolName} is out of order. Current Forge Debug gate is ${currentDebugGate}.`);
257
+ }
258
+ const completedGate = currentDebugGate;
259
+ const reference = await writeCompletionArtifact({ ...this.config, phase: completedGate }, toolName, input);
260
+ const status = reference.status.trim().toLowerCase();
261
+ const pass = status === "pass";
262
+ const nextGate = pass ? this.advanceAfterDebugGate(completedGate) : completedGate;
263
+ const nextPhase = nextGate ?? "qa_ready";
264
+ const nextInstruction = completionToolNextInstructionForDebugGate(completedGate, status, nextGate);
265
+ const reason = pass
266
+ ? `${completedGate} artifact accepted${nextGate ? `; continue to ${nextGate}.` : "; Debug regression is QA-ready."}`
267
+ : `${completedGate} artifact recorded with status ${status}; rework is required before Debug progression.`;
268
+ return {
269
+ content: [
270
+ {
271
+ type: "text",
272
+ text: `Forge recorded ${completedGate} artifact ${path.relative(reference.repoPath, reference.path)} (${reference.status}, ${reference.contentHash}). ${nextInstruction}`
273
+ }
274
+ ],
275
+ structuredContent: {
276
+ ...reference,
277
+ gate: {
278
+ phase: completedGate,
279
+ status,
280
+ allowed: pass,
281
+ reason
282
+ },
283
+ nextPhase,
284
+ nextInstruction
285
+ }
286
+ };
287
+ }
288
+ async callQaGateTool(toolName, requestedPhase, input) {
289
+ const requestedGate = qaGateFromToolName(toolName);
290
+ if (requestedPhase !== "qa" || !requestedGate) {
291
+ throw new Error(`Tool ${toolName} is not available in QA-cycle mode. Current Forge QA gate is ${this.currentQaGate}.`);
292
+ }
293
+ if (requestedGate !== this.currentQaGate) {
294
+ throw new Error(`Tool ${toolName} is out of order. Current Forge QA gate is ${this.currentQaGate}.`);
295
+ }
296
+ const completedGate = this.currentQaGate;
297
+ const reference = await writeCompletionArtifact({ ...this.config, phase: "qa" }, toolName, input);
298
+ const status = reference.status.trim().toLowerCase();
299
+ const nextGate = this.advanceAfterQaGate(completedGate);
300
+ const nextPhase = nextGate ? "qa" : "sign_off_ready";
301
+ const nextInstruction = completionToolNextInstructionForQaGate(completedGate, status, nextGate);
302
+ const allowed = status !== "blocked";
303
+ const reason = nextGate
304
+ ? `${completedGate} artifact accepted; continue to ${nextGate}.`
305
+ : `QA verdict artifact accepted with status ${status}.`;
306
+ return {
307
+ content: [
308
+ {
309
+ type: "text",
310
+ text: `Forge recorded ${completedGate} artifact ${path.relative(reference.repoPath, reference.path)} (${reference.status}, ${reference.contentHash}). ${nextInstruction}`
311
+ }
312
+ ],
313
+ structuredContent: {
314
+ ...reference,
315
+ gate: {
316
+ phase: "qa",
317
+ qaGate: completedGate,
318
+ status,
319
+ allowed,
320
+ reason
321
+ },
322
+ nextPhase,
323
+ nextInstruction
324
+ }
325
+ };
326
+ }
327
+ advanceAfterPass(phase) {
328
+ if (phase === "qa") {
329
+ this.completed = true;
330
+ return "sign_off_ready";
331
+ }
332
+ if (isDebugCompletionPhase(phase)) {
333
+ return nextDebugPhaseAfter(phase) ?? "qa_ready";
334
+ }
335
+ const next = nextPhaseAfter(phase);
336
+ if (!this.config.developCycle) {
337
+ return next ?? "dev_test_ready";
338
+ }
339
+ if (next) {
340
+ this.currentPhase = next;
341
+ return next;
342
+ }
343
+ this.completed = true;
344
+ return "dev_test_ready";
345
+ }
346
+ advanceAfterDecomposeGate(gate) {
347
+ const index = decomposeCompletionGateOrder.indexOf(gate);
348
+ const next = index >= 0 ? decomposeCompletionGateOrder[index + 1] ?? null : null;
349
+ if (next) {
350
+ this.currentDecomposeGate = next;
351
+ return next;
352
+ }
353
+ this.completed = true;
354
+ return null;
355
+ }
356
+ advanceAfterDraftGate(gate) {
357
+ const index = draftCompletionGateOrder.indexOf(gate);
358
+ const next = index >= 0 ? draftCompletionGateOrder[index + 1] ?? null : null;
359
+ if (next) {
360
+ this.currentDraftGate = next;
361
+ return next;
362
+ }
363
+ this.completed = true;
364
+ return null;
365
+ }
366
+ advanceAfterQaGate(gate) {
367
+ const index = qaCompletionGateOrder.indexOf(gate);
368
+ const next = index >= 0 ? qaCompletionGateOrder[index + 1] ?? null : null;
369
+ if (next) {
370
+ this.currentQaGate = next;
371
+ return next;
372
+ }
373
+ this.completed = true;
374
+ return null;
375
+ }
376
+ advanceAfterDebugGate(gate) {
377
+ const index = debugCompletionPhaseOrder.indexOf(gate);
378
+ const next = index >= 0 ? debugCompletionPhaseOrder[index + 1] ?? null : null;
379
+ if (next) {
380
+ this.currentPhase = next;
381
+ return next;
382
+ }
383
+ this.completed = true;
384
+ return null;
385
+ }
386
+ }
387
+ export async function runCompletionMcpServer(config) {
388
+ const session = new CompletionToolServerSession(config);
389
+ process.stdin.setEncoding("utf8");
390
+ let buffer = "";
391
+ for await (const chunk of process.stdin) {
392
+ buffer += chunk;
393
+ let newlineIndex = buffer.indexOf("\n");
394
+ while (newlineIndex >= 0) {
395
+ const rawLine = buffer.slice(0, newlineIndex).trim();
396
+ buffer = buffer.slice(newlineIndex + 1);
397
+ if (rawLine.length > 0) {
398
+ await handleJsonRpcLine(session, rawLine);
399
+ }
400
+ newlineIndex = buffer.indexOf("\n");
401
+ }
402
+ }
403
+ const trailing = buffer.trim();
404
+ if (trailing.length > 0) {
405
+ await handleJsonRpcLine(session, trailing);
406
+ }
407
+ }
408
+ export function parseCompletionMcpArgs(argv) {
409
+ let worktreePath = null;
410
+ let workItemId = null;
411
+ let phase = "implementation";
412
+ let ownerType;
413
+ let ownerId;
414
+ let producedBy;
415
+ let developCycle = false;
416
+ let qaCycle = false;
417
+ let debugCycle = false;
418
+ let architectCycle = false;
419
+ let draftCycle = false;
420
+ let decomposeCycle = false;
421
+ for (let index = 1; index < argv.length; index += 1) {
422
+ const arg = argv[index];
423
+ if (arg === "--worktree") {
424
+ worktreePath = argv[++index] ?? null;
425
+ }
426
+ else if (arg === "--work-item-id") {
427
+ workItemId = argv[++index] ?? null;
428
+ }
429
+ else if (arg === "--owner-type") {
430
+ ownerType = normalizedOwnerType(argv[++index]);
431
+ if (!ownerType) {
432
+ return { ok: false, error: "mcp --owner-type must be work_item or story." };
433
+ }
434
+ }
435
+ else if (arg === "--owner-id") {
436
+ ownerId = argv[++index] ?? undefined;
437
+ }
438
+ else if (arg === "--phase") {
439
+ const rawPhase = argv[++index];
440
+ const parsedPhase = parseStrictCompletionPhase(rawPhase);
441
+ if (!parsedPhase) {
442
+ return { ok: false, error: "mcp --phase must be one of: implementation, self-review, validation, qa, architect, draft, debug-repro, debug-root-cause, debug-fix, debug-regression." };
443
+ }
444
+ phase = parsedPhase;
445
+ }
446
+ else if (arg === "--produced-by") {
447
+ producedBy = argv[++index] ?? undefined;
448
+ }
449
+ else if (arg === "--develop-cycle") {
450
+ developCycle = true;
451
+ }
452
+ else if (arg === "--qa-cycle") {
453
+ qaCycle = true;
454
+ }
455
+ else if (arg === "--debug-cycle") {
456
+ debugCycle = true;
457
+ }
458
+ else if (arg === "--architect-cycle") {
459
+ architectCycle = true;
460
+ }
461
+ else if (arg === "--draft-cycle") {
462
+ draftCycle = true;
463
+ }
464
+ else if (arg === "--decompose-cycle") {
465
+ decomposeCycle = true;
466
+ }
467
+ else {
468
+ return { ok: false, error: `Unknown mcp argument: ${arg}` };
469
+ }
470
+ }
471
+ if (!worktreePath?.trim() || !workItemId?.trim()) {
472
+ return { ok: false, error: "mcp requires --worktree and --work-item-id." };
473
+ }
474
+ if ([developCycle, qaCycle, debugCycle, architectCycle, draftCycle, decomposeCycle].filter(Boolean).length > 1) {
475
+ return { ok: false, error: "mcp accepts only one cycle mode." };
476
+ }
477
+ if (developCycle && !isDevelopCompletionPhase(phase)) {
478
+ return { ok: false, error: "mcp --develop-cycle requires --phase implementation, self-review, or validation." };
479
+ }
480
+ if (qaCycle && phase !== "qa") {
481
+ return { ok: false, error: "mcp --qa-cycle requires --phase qa." };
482
+ }
483
+ if (debugCycle && !isDebugCompletionPhase(phase)) {
484
+ return { ok: false, error: "mcp --debug-cycle requires --phase debug-repro, debug-root-cause, debug-fix, or debug-regression." };
485
+ }
486
+ if (architectCycle && phase !== "architect") {
487
+ return { ok: false, error: "mcp --architect-cycle requires --phase architect." };
488
+ }
489
+ if (draftCycle && phase !== "draft") {
490
+ return { ok: false, error: "mcp --draft-cycle requires --phase draft." };
491
+ }
492
+ if (decomposeCycle && phase !== "decompose") {
493
+ return { ok: false, error: "mcp --decompose-cycle requires --phase decompose." };
494
+ }
495
+ return {
496
+ ok: true,
497
+ config: {
498
+ worktreePath: path.resolve(worktreePath),
499
+ workItemId,
500
+ ownerType,
501
+ ownerId,
502
+ phase,
503
+ producedBy,
504
+ redactionSecrets: parseCompletionRedactionSecrets(process.env[completionRedactionSecretsEnv]),
505
+ developCycle,
506
+ qaCycle,
507
+ debugCycle,
508
+ architectCycle,
509
+ draftCycle,
510
+ decomposeCycle
511
+ }
512
+ };
513
+ }
514
+ export function completionRedactionEnv(secrets) {
515
+ const filtered = secrets.filter((secret) => secret.length > 0);
516
+ return filtered.length > 0
517
+ ? { [completionRedactionSecretsEnv]: JSON.stringify(filtered) }
518
+ : {};
519
+ }
520
+ async function prepareCursorCompletionToolRuntime(worktreePath, runtime) {
521
+ const cursorDir = path.join(worktreePath, ".cursor");
522
+ const configPath = path.join(cursorDir, "mcp.json");
523
+ const original = await fs.readFile(configPath, "utf8").then((content) => ({ existed: true, content }), () => ({ existed: false, content: "" }));
524
+ const existing = original.existed ? parseOptionalJsonObject(original.content, configPath) : {};
525
+ const servers = asMutableRecord(existing.mcpServers);
526
+ servers[runtime.serverName] = cursorMcpServerConfig(runtime);
527
+ const next = {
528
+ ...existing,
529
+ mcpServers: servers
530
+ };
531
+ await fs.mkdir(cursorDir, { recursive: true });
532
+ await writeFileAtomic(configPath, `${JSON.stringify(next, null, 2)}\n`);
533
+ return {
534
+ ...runtime,
535
+ cursorMcpConfigPath: configPath,
536
+ cleanup: async () => {
537
+ if (original.existed) {
538
+ await writeFileAtomic(configPath, original.content);
539
+ }
540
+ else {
541
+ await fs.rm(configPath, { force: true });
542
+ await fs.rmdir(cursorDir).catch(() => undefined);
543
+ }
544
+ }
545
+ };
546
+ }
547
+ async function handleJsonRpcLine(session, rawLine) {
548
+ let message;
549
+ try {
550
+ message = JSON.parse(rawLine);
551
+ }
552
+ catch (error) {
553
+ writeJsonRpc({ jsonrpc: "2.0", id: null, error: rpcError(-32700, errorMessage(error)) });
554
+ return;
555
+ }
556
+ const request = asJsonRpcMessage(message);
557
+ if (!request) {
558
+ writeJsonRpc({ jsonrpc: "2.0", id: null, error: rpcError(-32600, "Invalid JSON-RPC message.") });
559
+ return;
560
+ }
561
+ if (request.id === undefined) {
562
+ return;
563
+ }
564
+ try {
565
+ const result = await handleJsonRpcRequest(session, request.method, request.params);
566
+ writeJsonRpc({ jsonrpc: "2.0", id: request.id, result });
567
+ }
568
+ catch (error) {
569
+ writeJsonRpc({ jsonrpc: "2.0", id: request.id, error: rpcError(-32000, errorMessage(error)) });
570
+ }
571
+ }
572
+ async function handleJsonRpcRequest(session, method, params) {
573
+ if (method === "initialize") {
574
+ const initializeParams = params && typeof params === "object" && !Array.isArray(params)
575
+ ? params
576
+ : {};
577
+ return {
578
+ protocolVersion: typeof initializeParams.protocolVersion === "string" ? initializeParams.protocolVersion : "2025-06-18",
579
+ capabilities: {
580
+ tools: {}
581
+ },
582
+ serverInfo: {
583
+ name: "forge-completion-tools",
584
+ version: "0.1.0"
585
+ },
586
+ instructions: session.instructions
587
+ };
588
+ }
589
+ if (method === "ping") {
590
+ return {};
591
+ }
592
+ if (method === "tools/list") {
593
+ return {
594
+ tools: session.toolDefinitions()
595
+ };
596
+ }
597
+ if (method === "tools/call") {
598
+ const call = asRecord(params, "tools/call params");
599
+ const name = requiredString(call, "name");
600
+ const args = call.arguments ?? {};
601
+ return await session.callTool(name, args);
602
+ }
603
+ throw new Error(`Unsupported MCP method: ${method}`);
604
+ }
605
+ function toolDefinition(name, phase, qaGate = null, decomposeGate = null) {
606
+ return {
607
+ name,
608
+ description: toolDescription(phase, qaGate, decomposeGate),
609
+ inputSchema: toolInputSchema(phase, qaGate, decomposeGate)
610
+ };
611
+ }
@@ -0,0 +1,3 @@
1
+ import { type CompletionPhase, type DecomposeCompletionGate, type QaCompletionGate } from "./phase-completion.js";
2
+ export declare function toolDescription(phase: CompletionPhase, qaGate?: QaCompletionGate | null, decomposeGate?: DecomposeCompletionGate | null): string;
3
+ export declare function toolInputSchema(phase: CompletionPhase, qaGate?: QaCompletionGate | null, decomposeGate?: DecomposeCompletionGate | null): unknown;