@loucompanion/forge-bridge 0.1.3-dev.bd0c67bbf352 → 0.1.3-dev.f2b8822ce953

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