@loucompanion/forge-bridge 0.1.3-dev.04a84914da09 → 0.1.3-dev.f66395c3ac21
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.
|
@@ -4,11 +4,9 @@ export type DevelopCompletionPhase = "implementation" | "self_review" | "validat
|
|
|
4
4
|
export type DebugCompletionPhase = "debug_repro" | "debug_root_cause" | "debug_fix" | "debug_regression";
|
|
5
5
|
export type ArchitectCompletionPhase = "architect";
|
|
6
6
|
export type DraftCompletionPhase = "draft";
|
|
7
|
-
export type
|
|
8
|
-
export type CompletionPhase = DevelopCompletionPhase | "qa" | DebugCompletionPhase | ArchitectCompletionPhase | DraftCompletionPhase | DecomposeCompletionPhase;
|
|
7
|
+
export type CompletionPhase = DevelopCompletionPhase | "qa" | DebugCompletionPhase | ArchitectCompletionPhase | DraftCompletionPhase;
|
|
9
8
|
export type QaCompletionGate = "qa_plan" | "qa_regression" | "qa_api" | "qa_manual_web" | "qa_bug_repro" | "qa_findings" | "qa_verdict";
|
|
10
9
|
export type DraftCompletionGate = "draft_intent" | "draft_repo_context" | "draft_graph_context" | "draft_product_scope" | "draft_work_order_plan" | "draft_critic" | "draft_proposal";
|
|
11
|
-
export type DecomposeCompletionGate = "decompose_subject_intake" | "decompose_context_review" | "decompose_breakdown_strategy" | "decompose_child_items" | "decompose_dependency_graph" | "decompose_critic" | "decompose_proposal";
|
|
12
10
|
export interface CompletionToolRuntime {
|
|
13
11
|
readonly serverName: string;
|
|
14
12
|
readonly command: string;
|
|
@@ -35,7 +33,6 @@ export interface CompletionToolServerConfig {
|
|
|
35
33
|
readonly debugCycle?: boolean;
|
|
36
34
|
readonly architectCycle?: boolean;
|
|
37
35
|
readonly draftCycle?: boolean;
|
|
38
|
-
readonly decomposeCycle?: boolean;
|
|
39
36
|
}
|
|
40
37
|
export interface CompletionToolCallResult {
|
|
41
38
|
readonly content: readonly {
|
|
@@ -47,7 +44,6 @@ export interface CompletionToolCallResult {
|
|
|
47
44
|
readonly phase: CompletionPhase;
|
|
48
45
|
readonly qaGate?: QaCompletionGate;
|
|
49
46
|
readonly draftGate?: DraftCompletionGate;
|
|
50
|
-
readonly decomposeGate?: DecomposeCompletionGate;
|
|
51
47
|
readonly status: string;
|
|
52
48
|
readonly allowed: boolean;
|
|
53
49
|
readonly reason: string;
|
|
@@ -71,7 +67,6 @@ export interface CompletionArtifactReference {
|
|
|
71
67
|
export declare const debugCompletionPhaseOrder: readonly DebugCompletionPhase[];
|
|
72
68
|
export declare const qaCompletionGateOrder: readonly QaCompletionGate[];
|
|
73
69
|
export declare const draftCompletionGateOrder: readonly DraftCompletionGate[];
|
|
74
|
-
export declare const decomposeCompletionGateOrder: readonly DecomposeCompletionGate[];
|
|
75
70
|
export declare function completionPhaseFromDispatch(dispatch: RunOnceDispatchPacket): CompletionPhase;
|
|
76
71
|
export declare function normalizeCompletionPhase(value: string | null | undefined): CompletionPhase;
|
|
77
72
|
export declare function usesDevelopCycleCompletion(dispatch: RunOnceDispatchPacket): boolean;
|
|
@@ -82,12 +77,10 @@ export declare function completionToolNamesForPhase(phase: CompletionPhase): rea
|
|
|
82
77
|
export declare function completionToolNamesForQaGate(gate: QaCompletionGate): readonly string[];
|
|
83
78
|
export declare function completionToolNamesForDebugGate(gate: DebugCompletionPhase): readonly string[];
|
|
84
79
|
export declare function completionToolNamesForDraftGate(gate: DraftCompletionGate): readonly string[];
|
|
85
|
-
export declare function completionToolNamesForDecomposeGate(gate: DecomposeCompletionGate): readonly string[];
|
|
86
80
|
export declare function expectedCompletionToolName(phase: CompletionPhase): string;
|
|
87
81
|
export declare function expectedQaCompletionToolName(gate: QaCompletionGate): string;
|
|
88
82
|
export declare function expectedDebugCompletionToolName(gate: DebugCompletionPhase): string;
|
|
89
83
|
export declare function expectedDraftCompletionToolName(gate: DraftCompletionGate): string;
|
|
90
|
-
export declare function expectedDecomposeCompletionToolName(gate: DecomposeCompletionGate): string;
|
|
91
84
|
export declare function artifactRootFor(worktreePath: string, workItemId: string): string;
|
|
92
85
|
export declare function artifactRootForOwner(worktreePath: string, ownerType: string, ownerId: string): string;
|
|
93
86
|
export declare function completionArtifactJsonPath(worktreePath: string, workItemId: string, phase: CompletionPhase, ownerType?: string | null, ownerId?: string | null): string;
|
|
@@ -100,7 +93,6 @@ export declare function completionPromptForPhase(phase: CompletionPhase): string
|
|
|
100
93
|
export declare function completionPromptForDevelopCycle(phase: CompletionPhase): string;
|
|
101
94
|
export declare function completionPromptForQaCycle(currentGate?: QaCompletionGate): string;
|
|
102
95
|
export declare function completionPromptForDraftCycle(currentGate?: DraftCompletionGate): string;
|
|
103
|
-
export declare function completionPromptForDecomposeCycle(currentGate?: DecomposeCompletionGate): string;
|
|
104
96
|
export declare function completionPromptForDebugCycle(currentGate?: DebugCompletionPhase): string;
|
|
105
97
|
export declare function completionToolNamesForServer(config: CompletionToolServerConfig): readonly string[];
|
|
106
98
|
export declare class CompletionToolServerSession {
|
|
@@ -108,18 +100,15 @@ export declare class CompletionToolServerSession {
|
|
|
108
100
|
private currentPhase;
|
|
109
101
|
private currentQaGate;
|
|
110
102
|
private currentDraftGate;
|
|
111
|
-
private currentDecomposeGate;
|
|
112
103
|
private completed;
|
|
113
104
|
constructor(config: CompletionToolServerConfig);
|
|
114
105
|
get instructions(): string;
|
|
115
106
|
toolDefinitions(): readonly unknown[];
|
|
116
107
|
callTool(toolName: string, input: unknown): Promise<CompletionToolCallResult>;
|
|
117
|
-
private callDecomposeGateTool;
|
|
118
108
|
private callDraftGateTool;
|
|
119
109
|
private callDebugGateTool;
|
|
120
110
|
private callQaGateTool;
|
|
121
111
|
private advanceAfterPass;
|
|
122
|
-
private advanceAfterDecomposeGate;
|
|
123
112
|
private advanceAfterDraftGate;
|
|
124
113
|
private advanceAfterQaGate;
|
|
125
114
|
private advanceAfterDebugGate;
|
|
@@ -31,33 +31,6 @@ export const draftCompletionGateOrder = [
|
|
|
31
31
|
"draft_critic",
|
|
32
32
|
"draft_proposal"
|
|
33
33
|
];
|
|
34
|
-
export const decomposeCompletionGateOrder = [
|
|
35
|
-
"decompose_subject_intake",
|
|
36
|
-
"decompose_context_review",
|
|
37
|
-
"decompose_breakdown_strategy",
|
|
38
|
-
"decompose_child_items",
|
|
39
|
-
"decompose_dependency_graph",
|
|
40
|
-
"decompose_critic",
|
|
41
|
-
"decompose_proposal"
|
|
42
|
-
];
|
|
43
|
-
const decomposeGateFileNames = {
|
|
44
|
-
decompose_subject_intake: "decompose.subject-intake.json",
|
|
45
|
-
decompose_context_review: "decompose.context-review.json",
|
|
46
|
-
decompose_breakdown_strategy: "decompose.breakdown-strategy.json",
|
|
47
|
-
decompose_child_items: "decompose.child-items.json",
|
|
48
|
-
decompose_dependency_graph: "decompose.dependency-graph.json",
|
|
49
|
-
decompose_critic: "decompose.critic.json",
|
|
50
|
-
decompose_proposal: "decompose.proposal.json"
|
|
51
|
-
};
|
|
52
|
-
const decomposeGateToolNames = {
|
|
53
|
-
decompose_subject_intake: "complete_decompose_subject_intake",
|
|
54
|
-
decompose_context_review: "complete_decompose_context_review",
|
|
55
|
-
decompose_breakdown_strategy: "complete_decompose_breakdown_strategy",
|
|
56
|
-
decompose_child_items: "complete_decompose_child_items",
|
|
57
|
-
decompose_dependency_graph: "complete_decompose_dependency_graph",
|
|
58
|
-
decompose_critic: "complete_decompose_critic",
|
|
59
|
-
decompose_proposal: "complete_decompose_proposal"
|
|
60
|
-
};
|
|
61
34
|
const draftGateFileNames = {
|
|
62
35
|
draft_intent: "draft.intent.json",
|
|
63
36
|
draft_repo_context: "draft.repo-context.json",
|
|
@@ -153,10 +126,6 @@ const phaseFiles = {
|
|
|
153
126
|
draft: {
|
|
154
127
|
jsonFileName: "draft.proposal.json",
|
|
155
128
|
reportFileName: "draft.report.md"
|
|
156
|
-
},
|
|
157
|
-
decompose: {
|
|
158
|
-
jsonFileName: "decompose.proposal.json",
|
|
159
|
-
reportFileName: "decompose.report.md"
|
|
160
129
|
}
|
|
161
130
|
};
|
|
162
131
|
export function completionPhaseFromDispatch(dispatch) {
|
|
@@ -183,9 +152,6 @@ export function normalizeCompletionPhase(value) {
|
|
|
183
152
|
if (normalized === "draft" || normalized === "drafting" || normalized === "draft_work_order") {
|
|
184
153
|
return "draft";
|
|
185
154
|
}
|
|
186
|
-
if (normalized === "decompose" || normalized === "decomposition" || normalized === "work_order_decomposition" || normalized === "feature_decomposition") {
|
|
187
|
-
return "decompose";
|
|
188
|
-
}
|
|
189
155
|
return "implementation";
|
|
190
156
|
}
|
|
191
157
|
export function usesDevelopCycleCompletion(dispatch) {
|
|
@@ -225,9 +191,6 @@ export function completionToolNamesForPhase(phase) {
|
|
|
225
191
|
if (phase === "draft") {
|
|
226
192
|
return completionToolNamesForDraftGate("draft_proposal");
|
|
227
193
|
}
|
|
228
|
-
if (phase === "decompose") {
|
|
229
|
-
return completionToolNamesForDecomposeGate("decompose_proposal");
|
|
230
|
-
}
|
|
231
194
|
if (isDebugCompletionPhase(phase)) {
|
|
232
195
|
return completionToolNamesForDebugGate(phase);
|
|
233
196
|
}
|
|
@@ -254,10 +217,6 @@ export function completionToolNamesForDraftGate(gate) {
|
|
|
254
217
|
const base = draftGateToolNames[gate];
|
|
255
218
|
return [`forge.${base}`, base];
|
|
256
219
|
}
|
|
257
|
-
export function completionToolNamesForDecomposeGate(gate) {
|
|
258
|
-
const base = decomposeGateToolNames[gate];
|
|
259
|
-
return [`forge.${base}`, base];
|
|
260
|
-
}
|
|
261
220
|
export function expectedCompletionToolName(phase) {
|
|
262
221
|
return completionToolNamesForPhase(phase)[0];
|
|
263
222
|
}
|
|
@@ -270,9 +229,6 @@ export function expectedDebugCompletionToolName(gate) {
|
|
|
270
229
|
export function expectedDraftCompletionToolName(gate) {
|
|
271
230
|
return completionToolNamesForDraftGate(gate)[0];
|
|
272
231
|
}
|
|
273
|
-
export function expectedDecomposeCompletionToolName(gate) {
|
|
274
|
-
return completionToolNamesForDecomposeGate(gate)[0];
|
|
275
|
-
}
|
|
276
232
|
export function artifactRootFor(worktreePath, workItemId) {
|
|
277
233
|
return path.join(worktreePath, ".forge", "work-items", safePathSegment(workItemId, "work item id"), "artifacts");
|
|
278
234
|
}
|
|
@@ -308,11 +264,7 @@ export async function prepareCompletionToolRuntime(options) {
|
|
|
308
264
|
qaCycle: usesQaCycleCompletion(options.dispatch),
|
|
309
265
|
debugCycle: usesDebugCycleCompletion(options.dispatch),
|
|
310
266
|
architectCycle: usesArchitectCycleCompletion(options.dispatch),
|
|
311
|
-
draftCycle: (
|
|
312
|
-
|| options.dispatch.execution.lane.trim().toLowerCase() === "draft"
|
|
313
|
-
|| options.dispatch.cycleRun.kind.trim().toLowerCase() === "draft")
|
|
314
|
-
&& !isDecomposeDispatch(options.dispatch),
|
|
315
|
-
decomposeCycle: isDecomposeDispatch(options.dispatch)
|
|
267
|
+
draftCycle: options.dispatch.execution.lane.trim().toLowerCase() === "draft" || options.dispatch.cycleRun.kind.trim().toLowerCase() === "draft"
|
|
316
268
|
});
|
|
317
269
|
const runtime = {
|
|
318
270
|
serverName: toolServerName,
|
|
@@ -357,9 +309,6 @@ export function buildCompletionToolServerArgs(config) {
|
|
|
357
309
|
if (config.draftCycle) {
|
|
358
310
|
args.push("--draft-cycle");
|
|
359
311
|
}
|
|
360
|
-
if (config.decomposeCycle) {
|
|
361
|
-
args.push("--decompose-cycle");
|
|
362
|
-
}
|
|
363
312
|
return args;
|
|
364
313
|
}
|
|
365
314
|
export function codexMcpConfigArgs(runtime) {
|
|
@@ -442,27 +391,11 @@ Complete the Draft cycle in order inside this same provider session:
|
|
|
442
391
|
2. Follow the returned repo-context instruction, inspect selected project/repo assumptions, then call \`forge.complete_draft_repo_context(...)\`.
|
|
443
392
|
3. Follow the returned graph-context instruction, account for DAG/dependency placement, then call \`forge.complete_draft_graph_context(...)\`.
|
|
444
393
|
4. Follow the returned product-scope instruction, choose product scope for the selected project, then call \`forge.complete_draft_product_scope(...)\`.
|
|
445
|
-
5. Follow the returned work-order-plan instruction, draft
|
|
394
|
+
5. Follow the returned work-order-plan instruction, draft Work Order fields and candidate feature boundaries, then call \`forge.complete_draft_work_order_plan(...)\`.
|
|
446
395
|
6. Follow the returned critic instruction, critique literalism, wrong-project scope, and decomposition quality, then call \`forge.complete_draft_critic(...)\`.
|
|
447
|
-
7. Follow the returned proposal instruction, then call \`forge.complete_draft_proposal(...)\` with the final
|
|
396
|
+
7. Follow the returned proposal instruction, then call \`forge.complete_draft_proposal(...)\` with the final Work Order JSON in \`draft_json\`.
|
|
448
397
|
The final proposal tool writes \`draft.proposal.json\` under \`.forge/work-items/{workItemId}/artifacts/\`. Reporting Draft completion in prose is not enough.`;
|
|
449
398
|
}
|
|
450
|
-
export function completionPromptForDecomposeCycle(currentGate = "decompose_subject_intake") {
|
|
451
|
-
return `# Forge Decompose completion cycle
|
|
452
|
-
You must complete the Decompose gates in order using the Forge MCP tools. Reporting progress in prose is not enough.
|
|
453
|
-
|
|
454
|
-
Current gate: ${currentGate}
|
|
455
|
-
|
|
456
|
-
Required order:
|
|
457
|
-
1. Call \`forge.complete_decompose_subject_intake(...)\` after identifying the Work Order/Feature subject, scope, and non-goals.
|
|
458
|
-
2. Follow the returned context-review instruction, inspect injected project/repo/dependency context, then call \`forge.complete_decompose_context_review(...)\`.
|
|
459
|
-
3. Follow the returned breakdown-strategy instruction, choose safe Feature/Story boundaries, then call \`forge.complete_decompose_breakdown_strategy(...)\`.
|
|
460
|
-
4. Follow the returned child-items instruction, draft candidate child items, then call \`forge.complete_decompose_child_items(...)\`.
|
|
461
|
-
5. Follow the returned dependency-graph instruction, validate keys/dependencies/order, then call \`forge.complete_decompose_dependency_graph(...)\`.
|
|
462
|
-
6. Follow the returned critic instruction, critique scope, DAG safety, manual reconciliation risk, and missing acceptance detail, then call \`forge.complete_decompose_critic(...)\`.
|
|
463
|
-
7. Follow the returned proposal instruction, then call \`forge.complete_decompose_proposal(...)\` with final JSON in \`proposal_json\`.
|
|
464
|
-
The final proposal tool writes \`decompose.proposal.json\` under \`.forge/work-items/{workItemId}/artifacts/\`. The proposal_json object must contain summary, optional blockedReason, and candidates.`;
|
|
465
|
-
}
|
|
466
399
|
export function completionPromptForDebugCycle(currentGate = "debug_repro") {
|
|
467
400
|
return `Forge completion tools are available through the MCP server named \`${toolServerName}\`.
|
|
468
401
|
Do not write Debug artifacts by hand. The tool server enforces Debug gate order and starts this run at \`${currentGate}\`.
|
|
@@ -475,43 +408,38 @@ Each tool writes a canonical Debug artifact under \`.forge/work-items/{workItemI
|
|
|
475
408
|
Use a non-pass status with explicit blockers when the current Debug gate cannot pass. Reporting Debug completion in prose is not enough; the relevant MCP completion tool is the transition request.`;
|
|
476
409
|
}
|
|
477
410
|
export function completionToolNamesForServer(config) {
|
|
478
|
-
return config.
|
|
479
|
-
?
|
|
480
|
-
: config.
|
|
481
|
-
?
|
|
482
|
-
: config.
|
|
483
|
-
?
|
|
484
|
-
: config.
|
|
485
|
-
?
|
|
486
|
-
: config.
|
|
487
|
-
?
|
|
488
|
-
: config.
|
|
489
|
-
? completionToolNamesForPhase("architect")
|
|
490
|
-
: completionToolNamesForPhase(config.phase);
|
|
411
|
+
return config.draftCycle
|
|
412
|
+
? draftCompletionGateOrder.flatMap((gate) => completionToolNamesForDraftGate(gate))
|
|
413
|
+
: config.debugCycle
|
|
414
|
+
? debugCompletionPhaseOrder.flatMap((gate) => completionToolNamesForDebugGate(gate))
|
|
415
|
+
: config.qaCycle
|
|
416
|
+
? qaCompletionGateOrder.flatMap((gate) => completionToolNamesForQaGate(gate))
|
|
417
|
+
: config.developCycle
|
|
418
|
+
? completionPhaseOrder.flatMap((phase) => completionToolNamesForPhase(phase))
|
|
419
|
+
: config.architectCycle
|
|
420
|
+
? completionToolNamesForPhase("architect")
|
|
421
|
+
: completionToolNamesForPhase(config.phase);
|
|
491
422
|
}
|
|
492
423
|
export class CompletionToolServerSession {
|
|
493
424
|
config;
|
|
494
425
|
currentPhase;
|
|
495
426
|
currentQaGate = "qa_plan";
|
|
496
427
|
currentDraftGate = "draft_intent";
|
|
497
|
-
currentDecomposeGate = "decompose_subject_intake";
|
|
498
428
|
completed = false;
|
|
499
429
|
constructor(config) {
|
|
500
430
|
this.config = config;
|
|
501
431
|
this.currentPhase = config.phase;
|
|
502
432
|
}
|
|
503
433
|
get instructions() {
|
|
504
|
-
return this.config.
|
|
505
|
-
?
|
|
506
|
-
: this.config.
|
|
507
|
-
?
|
|
508
|
-
: this.config.
|
|
509
|
-
?
|
|
510
|
-
: this.config.
|
|
511
|
-
?
|
|
512
|
-
: this.
|
|
513
|
-
? completionPromptForDevelopCycle(this.currentPhase)
|
|
514
|
-
: completionPromptForPhase(this.currentPhase);
|
|
434
|
+
return this.config.draftCycle
|
|
435
|
+
? completionPromptForDraftCycle(this.currentDraftGate)
|
|
436
|
+
: this.config.debugCycle
|
|
437
|
+
? completionPromptForDebugCycle(asDebugCompletionPhase(this.currentPhase))
|
|
438
|
+
: this.config.qaCycle
|
|
439
|
+
? completionPromptForQaCycle(this.currentQaGate)
|
|
440
|
+
: this.config.developCycle
|
|
441
|
+
? completionPromptForDevelopCycle(this.currentPhase)
|
|
442
|
+
: completionPromptForPhase(this.currentPhase);
|
|
515
443
|
}
|
|
516
444
|
toolDefinitions() {
|
|
517
445
|
return completionToolNamesForServer(this.config).map((name) => toolDefinition(name, phaseFromToolName(name), qaGateFromToolName(name)));
|
|
@@ -521,9 +449,6 @@ export class CompletionToolServerSession {
|
|
|
521
449
|
if (this.completed) {
|
|
522
450
|
throw new Error("Forge completion cycle is already complete; no more completion calls are accepted.");
|
|
523
451
|
}
|
|
524
|
-
if (this.config.decomposeCycle) {
|
|
525
|
-
return await this.callDecomposeGateTool(toolName, requestedPhase, input);
|
|
526
|
-
}
|
|
527
452
|
if (this.config.draftCycle) {
|
|
528
453
|
return await this.callDraftGateTool(toolName, requestedPhase, input);
|
|
529
454
|
}
|
|
@@ -567,38 +492,6 @@ export class CompletionToolServerSession {
|
|
|
567
492
|
}
|
|
568
493
|
};
|
|
569
494
|
}
|
|
570
|
-
async callDecomposeGateTool(toolName, requestedPhase, input) {
|
|
571
|
-
const requestedGate = decomposeGateFromToolName(toolName);
|
|
572
|
-
if (requestedPhase !== "decompose" || !requestedGate) {
|
|
573
|
-
throw new Error(`Tool ${toolName} is not available in Decompose-cycle mode. Current Forge Decompose gate is ${this.currentDecomposeGate}.`);
|
|
574
|
-
}
|
|
575
|
-
if (requestedGate !== this.currentDecomposeGate) {
|
|
576
|
-
throw new Error(`Tool ${toolName} is out of order. Current Forge Decompose gate is ${this.currentDecomposeGate}.`);
|
|
577
|
-
}
|
|
578
|
-
const completedGate = this.currentDecomposeGate;
|
|
579
|
-
const reference = await writeDecomposeGateCompletionArtifact(this.config, completedGate, toolName, input);
|
|
580
|
-
const status = reference.status.trim().toLowerCase();
|
|
581
|
-
const pass = status === "pass";
|
|
582
|
-
const nextGate = pass ? this.advanceAfterDecomposeGate(completedGate) : completedGate;
|
|
583
|
-
const nextPhase = pass
|
|
584
|
-
? (nextGate ? "decompose" : "sign_off_ready")
|
|
585
|
-
: "decompose";
|
|
586
|
-
const nextInstruction = completionToolNextInstructionForDecomposeGate(completedGate, status, nextGate);
|
|
587
|
-
const reason = pass
|
|
588
|
-
? nextGate
|
|
589
|
-
? `${completedGate} artifact accepted; continue to ${nextGate}.`
|
|
590
|
-
: `Decompose proposal artifact accepted with status ${status}.`
|
|
591
|
-
: `${completedGate} artifact recorded with status ${status}; rework is required before continuing.`;
|
|
592
|
-
return {
|
|
593
|
-
content: [{ type: "text", text: `Forge recorded ${completedGate} artifact ${path.relative(reference.repoPath, reference.path)} (${reference.status}, ${reference.contentHash}). ${nextInstruction}` }],
|
|
594
|
-
structuredContent: {
|
|
595
|
-
...reference,
|
|
596
|
-
gate: { phase: "decompose", decomposeGate: completedGate, status, allowed: pass, reason },
|
|
597
|
-
nextPhase,
|
|
598
|
-
nextInstruction
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
495
|
async callDraftGateTool(toolName, requestedPhase, input) {
|
|
603
496
|
const requestedGate = draftGateFromToolName(toolName);
|
|
604
497
|
if (requestedPhase !== "draft" || !requestedGate) {
|
|
@@ -719,16 +612,6 @@ export class CompletionToolServerSession {
|
|
|
719
612
|
this.completed = true;
|
|
720
613
|
return "dev_test_ready";
|
|
721
614
|
}
|
|
722
|
-
advanceAfterDecomposeGate(gate) {
|
|
723
|
-
const index = decomposeCompletionGateOrder.indexOf(gate);
|
|
724
|
-
const next = index >= 0 ? decomposeCompletionGateOrder[index + 1] ?? null : null;
|
|
725
|
-
if (next) {
|
|
726
|
-
this.currentDecomposeGate = next;
|
|
727
|
-
return next;
|
|
728
|
-
}
|
|
729
|
-
this.completed = true;
|
|
730
|
-
return null;
|
|
731
|
-
}
|
|
732
615
|
advanceAfterDraftGate(gate) {
|
|
733
616
|
const index = draftCompletionGateOrder.indexOf(gate);
|
|
734
617
|
const next = index >= 0 ? draftCompletionGateOrder[index + 1] ?? null : null;
|
|
@@ -760,46 +643,16 @@ export class CompletionToolServerSession {
|
|
|
760
643
|
return null;
|
|
761
644
|
}
|
|
762
645
|
}
|
|
763
|
-
async function writeDecomposeGateCompletionArtifact(config, gate, toolName, input) {
|
|
764
|
-
const payload = asRecord(input, "Decompose completion payload");
|
|
765
|
-
const status = normalizeDraftStatus(optionalString(payload, "status") ?? "pass");
|
|
766
|
-
if (gate === "decompose_proposal") {
|
|
767
|
-
const proposalJson = asRecord(payload["proposal_json"] ?? payload["decomposition_json"], "proposal_json");
|
|
768
|
-
if (!Array.isArray(proposalJson["candidates"])) {
|
|
769
|
-
throw new Error("proposal_json.candidates: expected array");
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
const redactedPayload = redactSecrets({ ...payload, status, phase: "decompose", gate, tool_name: toolName }, config.redactionSecrets ?? []);
|
|
773
|
-
const root = artifactRootFor(config.worktreePath, config.workItemId);
|
|
774
|
-
await fs.mkdir(root, { recursive: true });
|
|
775
|
-
const artifactPath = path.join(root, decomposeGateFileNames[gate]);
|
|
776
|
-
const reportPath = gate === "decompose_proposal" ? path.join(root, phaseFiles.decompose.reportFileName) : path.join(root, `${gate}.md`);
|
|
777
|
-
const verdictJson = jsonText(redactedPayload);
|
|
778
|
-
const report = optionalString(redactedPayload, "report_markdown") ?? `# ${gate.replace(/_/g, " ")}
|
|
779
|
-
|
|
780
|
-
${optionalString(redactedPayload, "summary") ?? "Decompose gate recorded."}
|
|
781
|
-
`;
|
|
782
|
-
await writeFileAtomic(artifactPath, verdictJson);
|
|
783
|
-
await writeFileAtomic(reportPath, report);
|
|
784
|
-
return {
|
|
785
|
-
phase: "decompose",
|
|
786
|
-
workItemId: config.workItemId,
|
|
787
|
-
ownerType: config.ownerType,
|
|
788
|
-
ownerId: config.ownerId,
|
|
789
|
-
repoPath: path.resolve(config.worktreePath),
|
|
790
|
-
path: artifactPath,
|
|
791
|
-
reportPath,
|
|
792
|
-
contentHash: sha256(verdictJson),
|
|
793
|
-
status,
|
|
794
|
-
producedBy: config.producedBy ?? "forge-completion-tool"
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
646
|
async function writeDraftGateCompletionArtifact(config, gate, toolName, input) {
|
|
798
647
|
const payload = asRecord(input, "Draft completion payload");
|
|
799
648
|
const status = normalizeDraftStatus(optionalString(payload, "status") ?? "pass");
|
|
800
649
|
if (gate === "draft_proposal") {
|
|
801
650
|
const draftJson = asRecord(payload["draft_json"], "draft_json");
|
|
802
|
-
|
|
651
|
+
for (const key of ["title", "summary", "objective", "planningPacket", "decompositionContext"]) {
|
|
652
|
+
if (typeof draftJson[key] !== "string" || draftJson[key].trim().length === 0) {
|
|
653
|
+
throw new Error(`draft_json.${key}: expected non-empty string`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
803
656
|
}
|
|
804
657
|
const redactedPayload = redactSecrets({ ...payload, status, phase: "draft", gate, tool_name: toolName }, config.redactionSecrets ?? []);
|
|
805
658
|
const root = artifactRootFor(config.worktreePath, config.workItemId);
|
|
@@ -827,22 +680,6 @@ function normalizeDraftStatus(value) {
|
|
|
827
680
|
const normalized = value.trim().toLowerCase();
|
|
828
681
|
return normalized === "blocked" || normalized === "needs_revision" || normalized === "needs_rework" ? normalized : "pass";
|
|
829
682
|
}
|
|
830
|
-
function validateDraftProposalJson(draftJson) {
|
|
831
|
-
if (isDecompositionDraftJson(draftJson)) {
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
for (const key of ["title", "summary", "objective", "planningPacket", "decompositionContext"]) {
|
|
835
|
-
if (typeof draftJson[key] !== "string" || draftJson[key].trim().length === 0) {
|
|
836
|
-
throw new Error(`draft_json.${key}: expected non-empty string`);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
function isDecompositionDraftJson(draftJson) {
|
|
841
|
-
const summary = draftJson["summary"];
|
|
842
|
-
return typeof summary === "string"
|
|
843
|
-
&& summary.trim().length > 0
|
|
844
|
-
&& Array.isArray(draftJson["candidates"]);
|
|
845
|
-
}
|
|
846
683
|
export async function writeCompletionArtifact(config, toolName, input) {
|
|
847
684
|
const phase = phaseFromToolName(toolName);
|
|
848
685
|
if (phase !== config.phase) {
|
|
@@ -1341,7 +1178,6 @@ export function parseCompletionMcpArgs(argv) {
|
|
|
1341
1178
|
let debugCycle = false;
|
|
1342
1179
|
let architectCycle = false;
|
|
1343
1180
|
let draftCycle = false;
|
|
1344
|
-
let decomposeCycle = false;
|
|
1345
1181
|
for (let index = 1; index < argv.length; index += 1) {
|
|
1346
1182
|
const arg = argv[index];
|
|
1347
1183
|
if (arg === "--worktree") {
|
|
@@ -1385,9 +1221,6 @@ export function parseCompletionMcpArgs(argv) {
|
|
|
1385
1221
|
else if (arg === "--draft-cycle") {
|
|
1386
1222
|
draftCycle = true;
|
|
1387
1223
|
}
|
|
1388
|
-
else if (arg === "--decompose-cycle") {
|
|
1389
|
-
decomposeCycle = true;
|
|
1390
|
-
}
|
|
1391
1224
|
else {
|
|
1392
1225
|
return { ok: false, error: `Unknown mcp argument: ${arg}` };
|
|
1393
1226
|
}
|
|
@@ -1395,7 +1228,7 @@ export function parseCompletionMcpArgs(argv) {
|
|
|
1395
1228
|
if (!worktreePath?.trim() || !workItemId?.trim()) {
|
|
1396
1229
|
return { ok: false, error: "mcp requires --worktree and --work-item-id." };
|
|
1397
1230
|
}
|
|
1398
|
-
if ([developCycle, qaCycle, debugCycle, architectCycle, draftCycle
|
|
1231
|
+
if ([developCycle, qaCycle, debugCycle, architectCycle, draftCycle].filter(Boolean).length > 1) {
|
|
1399
1232
|
return { ok: false, error: "mcp accepts only one cycle mode." };
|
|
1400
1233
|
}
|
|
1401
1234
|
if (developCycle && !isDevelopCompletionPhase(phase)) {
|
|
@@ -1413,9 +1246,6 @@ export function parseCompletionMcpArgs(argv) {
|
|
|
1413
1246
|
if (draftCycle && phase !== "draft") {
|
|
1414
1247
|
return { ok: false, error: "mcp --draft-cycle requires --phase draft." };
|
|
1415
1248
|
}
|
|
1416
|
-
if (decomposeCycle && phase !== "decompose") {
|
|
1417
|
-
return { ok: false, error: "mcp --decompose-cycle requires --phase decompose." };
|
|
1418
|
-
}
|
|
1419
1249
|
return {
|
|
1420
1250
|
ok: true,
|
|
1421
1251
|
config: {
|
|
@@ -1430,8 +1260,7 @@ export function parseCompletionMcpArgs(argv) {
|
|
|
1430
1260
|
qaCycle,
|
|
1431
1261
|
debugCycle,
|
|
1432
1262
|
architectCycle,
|
|
1433
|
-
draftCycle
|
|
1434
|
-
decomposeCycle
|
|
1263
|
+
draftCycle
|
|
1435
1264
|
}
|
|
1436
1265
|
};
|
|
1437
1266
|
}
|
|
@@ -1606,24 +1435,6 @@ function completionToolNextInstructionForQaGate(completedGate, status, nextGate)
|
|
|
1606
1435
|
: `${completedGate} accepted. Continue to ${nextGate}.`;
|
|
1607
1436
|
return `${prefix} ${qaGateInstruction(nextGate)}`;
|
|
1608
1437
|
}
|
|
1609
|
-
function completionToolNextInstructionForDecomposeGate(completedGate, status, nextGate) {
|
|
1610
|
-
if (status !== "pass") {
|
|
1611
|
-
return `Stay in ${completedGate}, address the recorded blockers/rework, then call ${expectedDecomposeCompletionToolName(completedGate)} again.`;
|
|
1612
|
-
}
|
|
1613
|
-
if (!nextGate) {
|
|
1614
|
-
return "Decompose proposal accepted. Forge has the structured child-item proposal JSON needed to populate the proposal row.";
|
|
1615
|
-
}
|
|
1616
|
-
const nextInstructions = {
|
|
1617
|
-
decompose_subject_intake: "Capture subject/scope, then call forge.complete_decompose_subject_intake.",
|
|
1618
|
-
decompose_context_review: "Review project/repo/dependency context, then call forge.complete_decompose_context_review.",
|
|
1619
|
-
decompose_breakdown_strategy: "Choose safe child boundaries, then call forge.complete_decompose_breakdown_strategy.",
|
|
1620
|
-
decompose_child_items: "Draft child candidates, then call forge.complete_decompose_child_items.",
|
|
1621
|
-
decompose_dependency_graph: "Validate dependency DAG, then call forge.complete_decompose_dependency_graph.",
|
|
1622
|
-
decompose_critic: "Critique the decomposition, then call forge.complete_decompose_critic.",
|
|
1623
|
-
decompose_proposal: "Submit final proposal_json through forge.complete_decompose_proposal."
|
|
1624
|
-
};
|
|
1625
|
-
return `${completedGate} accepted. ${nextInstructions[nextGate]}`;
|
|
1626
|
-
}
|
|
1627
1438
|
function completionToolNextInstructionForDebugGate(completedGate, status, nextGate) {
|
|
1628
1439
|
if (status !== "pass") {
|
|
1629
1440
|
return `Stay in ${completedGate}, address the recorded blockers, then call ${expectedDebugCompletionToolName(completedGate)} again.`;
|
|
@@ -2887,9 +2698,6 @@ function parseStrictCompletionPhase(value) {
|
|
|
2887
2698
|
if (normalized === "draft" || normalized === "drafting" || normalized === "draft_work_order") {
|
|
2888
2699
|
return "draft";
|
|
2889
2700
|
}
|
|
2890
|
-
if (normalized === "decompose" || normalized === "decomposition" || normalized === "work_order_decomposition" || normalized === "feature_decomposition") {
|
|
2891
|
-
return "decompose";
|
|
2892
|
-
}
|
|
2893
2701
|
return null;
|
|
2894
2702
|
}
|
|
2895
2703
|
function normalizeDebugCompletionPhase(value) {
|
|
@@ -2964,9 +2772,6 @@ function phaseFromToolName(toolName) {
|
|
|
2964
2772
|
if (draftGateFromToolName(toolName)) {
|
|
2965
2773
|
return "draft";
|
|
2966
2774
|
}
|
|
2967
|
-
if (decomposeGateFromToolName(toolName)) {
|
|
2968
|
-
return "decompose";
|
|
2969
|
-
}
|
|
2970
2775
|
const debugGate = debugGateFromToolName(toolName);
|
|
2971
2776
|
if (debugGate) {
|
|
2972
2777
|
return debugGate;
|
|
@@ -2986,22 +2791,6 @@ function draftGateFromToolName(toolName) {
|
|
|
2986
2791
|
}
|
|
2987
2792
|
return null;
|
|
2988
2793
|
}
|
|
2989
|
-
function decomposeGateFromToolName(toolName) {
|
|
2990
|
-
for (const gate of decomposeCompletionGateOrder) {
|
|
2991
|
-
const base = decomposeGateToolNames[gate];
|
|
2992
|
-
if (toolName === base || toolName === `forge.${base}`) {
|
|
2993
|
-
return gate;
|
|
2994
|
-
}
|
|
2995
|
-
}
|
|
2996
|
-
return null;
|
|
2997
|
-
}
|
|
2998
|
-
function isDecomposeDispatch(dispatch) {
|
|
2999
|
-
const draftKind = dispatch.draft?.kind?.trim().toLowerCase();
|
|
3000
|
-
return dispatch.execution.lane.trim().toLowerCase() === "decompose"
|
|
3001
|
-
|| dispatch.session.lane.trim().toLowerCase() === "decompose"
|
|
3002
|
-
|| draftKind === "work_order_decomposition"
|
|
3003
|
-
|| draftKind === "feature_decomposition";
|
|
3004
|
-
}
|
|
3005
2794
|
function debugGateFromToolName(toolName) {
|
|
3006
2795
|
const normalized = toolName.trim().toLowerCase();
|
|
3007
2796
|
for (const gate of debugCompletionPhaseOrder) {
|
|
@@ -200,8 +200,7 @@ export class ProcessBackedProviderCliService {
|
|
|
200
200
|
await fs.mkdir(metadataRoot, { recursive: true });
|
|
201
201
|
await fs.writeFile(path.join(metadataRoot, "draft-input.json"), JSON.stringify(dispatch.draft, null, 2) + "\n", "utf8");
|
|
202
202
|
await fs.writeFile(path.join(metadataRoot, "spec.md"), buildDraftSpec(dispatch), "utf8");
|
|
203
|
-
const
|
|
204
|
-
const completionTools = await prepareCompletionToolRuntime({ dispatch, worktree: draftWorktree, phase: decomposeDraft ? "decompose" : "draft" });
|
|
203
|
+
const completionTools = await prepareCompletionToolRuntime({ dispatch, worktree: draftWorktree, phase: "draft" });
|
|
205
204
|
const command = buildProviderCliCommand(dispatch, {
|
|
206
205
|
worktreePath: tempDir,
|
|
207
206
|
prompt: buildDraftPrompt(dispatch),
|
|
@@ -226,9 +225,7 @@ export class ProcessBackedProviderCliService {
|
|
|
226
225
|
await context.emit({
|
|
227
226
|
role: "runner",
|
|
228
227
|
kind: "checkpoint",
|
|
229
|
-
content:
|
|
230
|
-
? "Forge Decompose completion tools configured for decompose_subject_intake -> decompose_context_review -> decompose_breakdown_strategy -> decompose_child_items -> decompose_dependency_graph -> decompose_critic -> decompose_proposal; provider must call each MCP completion tool before exit."
|
|
231
|
-
: "Forge Draft completion tools configured for draft_intent -> draft_repo_context -> draft_graph_context -> draft_product_scope -> draft_work_order_plan -> draft_critic -> draft_proposal; provider must call each MCP completion tool before exit.",
|
|
228
|
+
content: "Forge Draft completion tools configured for draft_intent -> draft_repo_context -> draft_graph_context -> draft_product_scope -> draft_work_order_plan -> draft_critic -> draft_proposal; provider must call each MCP completion tool before exit.",
|
|
232
229
|
eventType: "completion_tools_configured"
|
|
233
230
|
});
|
|
234
231
|
const outputState = createProviderOutputState(dispatch.execution.provider);
|
|
@@ -241,110 +238,65 @@ export class ProcessBackedProviderCliService {
|
|
|
241
238
|
else {
|
|
242
239
|
context.signal?.addEventListener("abort", abortProvider, { once: true });
|
|
243
240
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
await context.emit(event);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}, providerCancellation.signal);
|
|
257
|
-
const artifactWaitCancellation = new AbortController();
|
|
258
|
-
const abortArtifactWait = () => artifactWaitCancellation.abort();
|
|
259
|
-
if (context.signal?.aborted) {
|
|
260
|
-
abortArtifactWait();
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
context.signal?.addEventListener("abort", abortArtifactWait, { once: true });
|
|
264
|
-
}
|
|
265
|
-
const artifactWaitPromise = waitForCompletionArtifact(tempDir, dispatch.workItem.id, decomposeDraft ? "decompose" : "draft", artifactWaitCancellation.signal);
|
|
266
|
-
let result;
|
|
267
|
-
let earlyProposalArtifact = null;
|
|
268
|
-
try {
|
|
269
|
-
const terminal = await Promise.race([
|
|
270
|
-
runPromise.then(value => ({ kind: "process", value })),
|
|
271
|
-
artifactWaitPromise.then(value => ({ kind: "artifact", value }))
|
|
272
|
-
]);
|
|
273
|
-
artifactWaitCancellation.abort();
|
|
274
|
-
await artifactWaitPromise.catch(() => undefined);
|
|
275
|
-
if (terminal.kind === "artifact") {
|
|
276
|
-
earlyProposalArtifact = terminal.value;
|
|
277
|
-
providerCancellation.abort();
|
|
278
|
-
result = await runPromise.catch(() => ({ exitCode: 0, stdout: "", stderr: "" }));
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
result = terminal.value;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
finally {
|
|
285
|
-
artifactWaitCancellation.abort();
|
|
286
|
-
context.signal?.removeEventListener("abort", abortArtifactWait);
|
|
287
|
-
}
|
|
288
|
-
if (result.exitCode !== 0 && !earlyProposalArtifact) {
|
|
289
|
-
throw new Error(`Provider CLI exited with code ${result.exitCode}${summarizeStderr(result.stderr)}.`);
|
|
290
|
-
}
|
|
291
|
-
await context.emit({
|
|
292
|
-
role: "runner",
|
|
293
|
-
kind: "checkpoint",
|
|
294
|
-
content: `Provider CLI Draft completed: ${command.command} exit ${result.exitCode}${outputState.sessionLabel ? ` ${outputState.sessionLabel}` : ""}.`,
|
|
295
|
-
eventType: "provider_cli_completed"
|
|
296
|
-
});
|
|
297
|
-
await emitProviderUsageEvent(context.emit, dispatch, outputState);
|
|
298
|
-
if (decomposeDraft) {
|
|
299
|
-
await emitDecomposeGateArtifacts(context.emit, tempDir, dispatch.workItem.id);
|
|
300
|
-
const proposalArtifact = earlyProposalArtifact ?? await readCompletionArtifact(tempDir, dispatch.workItem.id, "decompose");
|
|
301
|
-
if (!proposalArtifact) {
|
|
302
|
-
throw new Error("Provider CLI Decompose completed but did not call forge.complete_decompose_proposal.");
|
|
303
|
-
}
|
|
304
|
-
const proposal = parseJsonLine(proposalArtifact.verdictJson) ?? {};
|
|
305
|
-
const proposalJson = proposal["proposal_json"] ?? proposal["decomposition_json"];
|
|
306
|
-
if (!proposalJson || typeof proposalJson !== "object" || Array.isArray(proposalJson)) {
|
|
307
|
-
throw new Error("Provider CLI Decompose completed but decompose.proposal.json did not contain proposal_json.");
|
|
241
|
+
const runPromise = this.processRunner.run({
|
|
242
|
+
command: command.command,
|
|
243
|
+
args: command.args,
|
|
244
|
+
cwd: tempDir,
|
|
245
|
+
env: providerProcessEnv(dispatch)
|
|
246
|
+
}, {
|
|
247
|
+
onStdoutLine: async (line) => {
|
|
248
|
+
for (const event of outputState.consume(line)) {
|
|
249
|
+
await context.emit(event);
|
|
308
250
|
}
|
|
309
|
-
const json = JSON.stringify(proposalJson);
|
|
310
|
-
await context.emit({
|
|
311
|
-
role: "assistant",
|
|
312
|
-
kind: "message",
|
|
313
|
-
content: json,
|
|
314
|
-
eventType: "decompose_proposal"
|
|
315
|
-
});
|
|
316
|
-
return {
|
|
317
|
-
summary: "Decomposition proposal generated through Forge Decompose completion cycle.",
|
|
318
|
-
artifact: proposalArtifact,
|
|
319
|
-
artifacts: [proposalArtifact]
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
await emitDraftGateArtifacts(context.emit, tempDir, dispatch.workItem.id);
|
|
323
|
-
const proposalArtifact = earlyProposalArtifact ?? await readCompletionArtifact(tempDir, dispatch.workItem.id, "draft");
|
|
324
|
-
if (!proposalArtifact) {
|
|
325
|
-
throw new Error("Provider CLI Draft completed but did not call forge.complete_draft_proposal.");
|
|
326
251
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
252
|
+
}, providerCancellation.signal);
|
|
253
|
+
let result;
|
|
254
|
+
let earlyProposalArtifact = null;
|
|
255
|
+
const terminal = await Promise.race([
|
|
256
|
+
runPromise.then(value => ({ kind: "process", value })),
|
|
257
|
+
waitForDraftProposalArtifact(tempDir, dispatch.workItem.id, context.signal).then(value => ({ kind: "artifact", value }))
|
|
258
|
+
]);
|
|
259
|
+
if (terminal.kind === "artifact") {
|
|
260
|
+
earlyProposalArtifact = terminal.value;
|
|
261
|
+
providerCancellation.abort();
|
|
262
|
+
result = await runPromise.catch(() => ({ exitCode: 0, stdout: "", stderr: "" }));
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
result = terminal.value;
|
|
266
|
+
}
|
|
267
|
+
context.signal?.removeEventListener("abort", abortProvider);
|
|
268
|
+
if (result.exitCode !== 0 && !earlyProposalArtifact) {
|
|
269
|
+
throw new Error(`Provider CLI exited with code ${result.exitCode}${summarizeStderr(result.stderr)}.`);
|
|
270
|
+
}
|
|
271
|
+
await context.emit({
|
|
272
|
+
role: "runner",
|
|
273
|
+
kind: "checkpoint",
|
|
274
|
+
content: `Provider CLI Draft completed: ${command.command} exit ${result.exitCode}${outputState.sessionLabel ? ` ${outputState.sessionLabel}` : ""}.`,
|
|
275
|
+
eventType: "provider_cli_completed"
|
|
276
|
+
});
|
|
277
|
+
await emitProviderUsageEvent(context.emit, dispatch, outputState);
|
|
278
|
+
await emitDraftGateArtifacts(context.emit, tempDir, dispatch.workItem.id);
|
|
279
|
+
const proposalArtifact = earlyProposalArtifact ?? await readCompletionArtifact(tempDir, dispatch.workItem.id, "draft");
|
|
280
|
+
if (!proposalArtifact) {
|
|
281
|
+
throw new Error("Provider CLI Draft completed but did not call forge.complete_draft_proposal.");
|
|
344
282
|
}
|
|
345
|
-
|
|
346
|
-
|
|
283
|
+
const proposal = parseJsonLine(proposalArtifact.verdictJson) ?? {};
|
|
284
|
+
const draftJson = proposal["draft_json"];
|
|
285
|
+
if (!draftJson || typeof draftJson !== "object" || Array.isArray(draftJson)) {
|
|
286
|
+
throw new Error("Provider CLI Draft completed but draft.proposal.json did not contain draft_json.");
|
|
347
287
|
}
|
|
288
|
+
const json = JSON.stringify(draftJson);
|
|
289
|
+
await context.emit({
|
|
290
|
+
role: "assistant",
|
|
291
|
+
kind: "message",
|
|
292
|
+
content: json,
|
|
293
|
+
eventType: "draft_complete"
|
|
294
|
+
});
|
|
295
|
+
return {
|
|
296
|
+
summary: "Draft generated through Forge Draft completion cycle.",
|
|
297
|
+
artifact: proposalArtifact,
|
|
298
|
+
artifacts: [proposalArtifact]
|
|
299
|
+
};
|
|
348
300
|
}
|
|
349
301
|
finally {
|
|
350
302
|
await cleanupCompletionTools(completionTools, context.emit);
|
|
@@ -2083,47 +2035,6 @@ async function emitDraftGateArtifacts(emit, worktreePath, workItemId) {
|
|
|
2083
2035
|
}
|
|
2084
2036
|
}
|
|
2085
2037
|
}
|
|
2086
|
-
async function emitDecomposeGateArtifacts(emit, worktreePath, workItemId) {
|
|
2087
|
-
const decomposeArtifactFiles = {
|
|
2088
|
-
decompose_subject_intake: "decompose.subject-intake.json",
|
|
2089
|
-
decompose_context_review: "decompose.context-review.json",
|
|
2090
|
-
decompose_breakdown_strategy: "decompose.breakdown-strategy.json",
|
|
2091
|
-
decompose_child_items: "decompose.child-items.json",
|
|
2092
|
-
decompose_dependency_graph: "decompose.dependency-graph.json",
|
|
2093
|
-
decompose_critic: "decompose.critic.json",
|
|
2094
|
-
decompose_proposal: "decompose.proposal.json"
|
|
2095
|
-
};
|
|
2096
|
-
const gates = Object.keys(decomposeArtifactFiles);
|
|
2097
|
-
for (const gate of gates) {
|
|
2098
|
-
const actualPath = path.join(worktreePath, ".forge", "work-items", workItemId, "artifacts", decomposeArtifactFiles[gate]);
|
|
2099
|
-
if (!await fileExists(actualPath)) {
|
|
2100
|
-
continue;
|
|
2101
|
-
}
|
|
2102
|
-
const relativePath = path.relative(worktreePath, actualPath).split(path.sep).join("/");
|
|
2103
|
-
await emit({
|
|
2104
|
-
role: "tool",
|
|
2105
|
-
kind: "tool_result",
|
|
2106
|
-
content: `Forge Decompose completion tool recorded ${gate} artifact ${relativePath}.`,
|
|
2107
|
-
eventType: "completion_tool_artifact_recorded",
|
|
2108
|
-
data: JSON.stringify({ phase: "decompose", decompose_gate: gate, artifact_relative_paths: [relativePath] })
|
|
2109
|
-
});
|
|
2110
|
-
await emit({
|
|
2111
|
-
role: "hook",
|
|
2112
|
-
kind: "checkpoint",
|
|
2113
|
-
content: `phase_gate: ${gate} artifact accepted with status pass.`,
|
|
2114
|
-
eventType: "phase_gate"
|
|
2115
|
-
});
|
|
2116
|
-
const next = gates[gates.indexOf(gate) + 1];
|
|
2117
|
-
if (next) {
|
|
2118
|
-
await emit({
|
|
2119
|
-
role: "hook",
|
|
2120
|
-
kind: "checkpoint",
|
|
2121
|
-
content: `phase_progression: ${gate} -> ${next}`,
|
|
2122
|
-
eventType: "phase_gate"
|
|
2123
|
-
});
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
2038
|
async function fileExists(filePath) {
|
|
2128
2039
|
try {
|
|
2129
2040
|
await fs.access(filePath);
|
|
@@ -2138,7 +2049,6 @@ function buildDraftPrompt(dispatch) {
|
|
|
2138
2049
|
if (!draft) {
|
|
2139
2050
|
throw new Error("Draft prompt is missing.");
|
|
2140
2051
|
}
|
|
2141
|
-
const decomposeDraft = isDecomposeDraftDispatch(dispatch);
|
|
2142
2052
|
const draftArtifacts = dispatch.artifacts
|
|
2143
2053
|
.filter((artifact) => artifact.scope === "operational" && ["command", "skill", "hook"].includes(artifact.kind))
|
|
2144
2054
|
.map((artifact) => `## ${artifact.kind}: ${artifact.slug}\n${artifact.body.trim()}`)
|
|
@@ -2157,62 +2067,25 @@ Draft kind: ${draft.kind}
|
|
|
2157
2067
|
|
|
2158
2068
|
${draft.userPrompt}
|
|
2159
2069
|
|
|
2160
|
-
${
|
|
2161
|
-
? `Use the injected command/skills/hooks as lane instructions. You must call each Forge Decompose MCP completion tool in order. Do not claim Decompose is complete until forge.complete_decompose_proposal accepts the final proposal_json.
|
|
2162
|
-
|
|
2163
|
-
Complete these gates in order: decompose_subject_intake -> decompose_context_review -> decompose_breakdown_strategy -> decompose_child_items -> decompose_dependency_graph -> decompose_critic -> decompose_proposal.
|
|
2164
|
-
|
|
2165
|
-
The final proposal_json object must contain summary, optional blockedReason, and candidates.`
|
|
2166
|
-
: `${completionPromptForDraftCycle("draft_intent")}
|
|
2070
|
+
${completionPromptForDraftCycle("draft_intent")}
|
|
2167
2071
|
|
|
2168
|
-
Use the injected command/skills/hooks as lane instructions. You must call each Forge Draft MCP completion tool in order. Do not claim the Draft is complete until forge.complete_draft_proposal accepts the final draft_json
|
|
2072
|
+
Use the injected command/skills/hooks as lane instructions. You must call each Forge Draft MCP completion tool in order. Do not claim the Draft is complete until forge.complete_draft_proposal accepts the final draft_json.
|
|
2169
2073
|
|
|
2170
2074
|
Do not modify source files.`;
|
|
2171
2075
|
}
|
|
2172
|
-
function isDecomposeDraftKind(kind) {
|
|
2173
|
-
return kind === "work_order_decomposition" || kind === "feature_decomposition";
|
|
2174
|
-
}
|
|
2175
|
-
function isDecomposeDraftDispatch(dispatch) {
|
|
2176
|
-
return dispatch.execution.lane.trim().toLowerCase() === "decompose"
|
|
2177
|
-
|| dispatch.session.lane.trim().toLowerCase() === "decompose"
|
|
2178
|
-
|| isDecomposeDraftKind(dispatch.draft?.kind?.trim().toLowerCase());
|
|
2179
|
-
}
|
|
2180
2076
|
function summarizeStderr(stderr) {
|
|
2181
2077
|
const trimmed = stderr.trim();
|
|
2182
2078
|
return trimmed ? `: ${clip(trimmed, 1000)}` : "";
|
|
2183
2079
|
}
|
|
2184
|
-
async function
|
|
2080
|
+
async function waitForDraftProposalArtifact(worktreePath, workItemId, signal) {
|
|
2185
2081
|
while (!signal?.aborted) {
|
|
2186
|
-
const artifact = await readCompletionArtifact(worktreePath, workItemId,
|
|
2082
|
+
const artifact = await readCompletionArtifact(worktreePath, workItemId, "draft");
|
|
2187
2083
|
if (artifact) {
|
|
2188
2084
|
return artifact;
|
|
2189
2085
|
}
|
|
2190
|
-
await
|
|
2086
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2191
2087
|
}
|
|
2192
|
-
throw new Error(
|
|
2193
|
-
}
|
|
2194
|
-
async function delay(ms, signal) {
|
|
2195
|
-
if (!signal) {
|
|
2196
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2197
|
-
return;
|
|
2198
|
-
}
|
|
2199
|
-
const abortSignal = signal;
|
|
2200
|
-
if (abortSignal.aborted) {
|
|
2201
|
-
return;
|
|
2202
|
-
}
|
|
2203
|
-
await new Promise((resolve) => {
|
|
2204
|
-
const timeout = setTimeout(done, ms);
|
|
2205
|
-
const abort = () => done();
|
|
2206
|
-
function done() {
|
|
2207
|
-
clearTimeout(timeout);
|
|
2208
|
-
abortSignal.removeEventListener("abort", abort);
|
|
2209
|
-
resolve();
|
|
2210
|
-
}
|
|
2211
|
-
abortSignal.addEventListener("abort", abort, { once: true });
|
|
2212
|
-
if (abortSignal.aborted) {
|
|
2213
|
-
done();
|
|
2214
|
-
}
|
|
2215
|
-
});
|
|
2088
|
+
throw new Error("Draft proposal artifact wait was cancelled.");
|
|
2216
2089
|
}
|
|
2217
2090
|
function deterministicDraftPayload(dispatch) {
|
|
2218
2091
|
const kind = dispatch.draft?.kind ?? "work_item";
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const forgeBridgeVersion = "0.1.3-dev.
|
|
1
|
+
export declare const forgeBridgeVersion = "0.1.3-dev.f66395c3ac21";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const forgeBridgeVersion = "0.1.3-dev.
|
|
1
|
+
export const forgeBridgeVersion = "0.1.3-dev.f66395c3ac21";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loucompanion/forge-bridge",
|
|
3
|
-
"version": "0.1.3-dev.
|
|
3
|
+
"version": "0.1.3-dev.f66395c3ac21",
|
|
4
4
|
"description": "Forge runner bridge CLI for connecting local developer machines to Forge dispatch.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|