@loucompanion/forge-bridge 0.1.3-dev.6e59f152916f → 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,8 +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
|
-
decomposeCycle: isDecomposeDispatch(options.dispatch)
|
|
267
|
+
draftCycle: options.dispatch.execution.lane.trim().toLowerCase() === "draft" || options.dispatch.cycleRun.kind.trim().toLowerCase() === "draft"
|
|
313
268
|
});
|
|
314
269
|
const runtime = {
|
|
315
270
|
serverName: toolServerName,
|
|
@@ -354,9 +309,6 @@ export function buildCompletionToolServerArgs(config) {
|
|
|
354
309
|
if (config.draftCycle) {
|
|
355
310
|
args.push("--draft-cycle");
|
|
356
311
|
}
|
|
357
|
-
if (config.decomposeCycle) {
|
|
358
|
-
args.push("--decompose-cycle");
|
|
359
|
-
}
|
|
360
312
|
return args;
|
|
361
313
|
}
|
|
362
314
|
export function codexMcpConfigArgs(runtime) {
|
|
@@ -444,22 +396,6 @@ Complete the Draft cycle in order inside this same provider session:
|
|
|
444
396
|
7. Follow the returned proposal instruction, then call \`forge.complete_draft_proposal(...)\` with the final Work Order JSON in \`draft_json\`.
|
|
445
397
|
The final proposal tool writes \`draft.proposal.json\` under \`.forge/work-items/{workItemId}/artifacts/\`. Reporting Draft completion in prose is not enough.`;
|
|
446
398
|
}
|
|
447
|
-
export function completionPromptForDecomposeCycle(currentGate = "decompose_subject_intake") {
|
|
448
|
-
return `# Forge Decompose completion cycle
|
|
449
|
-
You must complete the Decompose gates in order using the Forge MCP tools. Reporting progress in prose is not enough.
|
|
450
|
-
|
|
451
|
-
Current gate: ${currentGate}
|
|
452
|
-
|
|
453
|
-
Required order:
|
|
454
|
-
1. Call \`forge.complete_decompose_subject_intake(...)\` after identifying the Work Order/Feature subject, scope, and non-goals.
|
|
455
|
-
2. Follow the returned context-review instruction, inspect injected project/repo/dependency context, then call \`forge.complete_decompose_context_review(...)\`.
|
|
456
|
-
3. Follow the returned breakdown-strategy instruction, choose safe Feature/Story boundaries, then call \`forge.complete_decompose_breakdown_strategy(...)\`.
|
|
457
|
-
4. Follow the returned child-items instruction, draft candidate child items, then call \`forge.complete_decompose_child_items(...)\`.
|
|
458
|
-
5. Follow the returned dependency-graph instruction, validate keys/dependencies/order, then call \`forge.complete_decompose_dependency_graph(...)\`.
|
|
459
|
-
6. Follow the returned critic instruction, critique scope, DAG safety, manual reconciliation risk, and missing acceptance detail, then call \`forge.complete_decompose_critic(...)\`.
|
|
460
|
-
7. Follow the returned proposal instruction, then call \`forge.complete_decompose_proposal(...)\` with final JSON in \`proposal_json\`.
|
|
461
|
-
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.`;
|
|
462
|
-
}
|
|
463
399
|
export function completionPromptForDebugCycle(currentGate = "debug_repro") {
|
|
464
400
|
return `Forge completion tools are available through the MCP server named \`${toolServerName}\`.
|
|
465
401
|
Do not write Debug artifacts by hand. The tool server enforces Debug gate order and starts this run at \`${currentGate}\`.
|
|
@@ -472,43 +408,38 @@ Each tool writes a canonical Debug artifact under \`.forge/work-items/{workItemI
|
|
|
472
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.`;
|
|
473
409
|
}
|
|
474
410
|
export function completionToolNamesForServer(config) {
|
|
475
|
-
return config.
|
|
476
|
-
?
|
|
477
|
-
: config.
|
|
478
|
-
?
|
|
479
|
-
: config.
|
|
480
|
-
?
|
|
481
|
-
: config.
|
|
482
|
-
?
|
|
483
|
-
: config.
|
|
484
|
-
?
|
|
485
|
-
: config.
|
|
486
|
-
? completionToolNamesForPhase("architect")
|
|
487
|
-
: 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);
|
|
488
422
|
}
|
|
489
423
|
export class CompletionToolServerSession {
|
|
490
424
|
config;
|
|
491
425
|
currentPhase;
|
|
492
426
|
currentQaGate = "qa_plan";
|
|
493
427
|
currentDraftGate = "draft_intent";
|
|
494
|
-
currentDecomposeGate = "decompose_subject_intake";
|
|
495
428
|
completed = false;
|
|
496
429
|
constructor(config) {
|
|
497
430
|
this.config = config;
|
|
498
431
|
this.currentPhase = config.phase;
|
|
499
432
|
}
|
|
500
433
|
get instructions() {
|
|
501
|
-
return this.config.
|
|
502
|
-
?
|
|
503
|
-
: this.config.
|
|
504
|
-
?
|
|
505
|
-
: this.config.
|
|
506
|
-
?
|
|
507
|
-
: this.config.
|
|
508
|
-
?
|
|
509
|
-
: this.
|
|
510
|
-
? completionPromptForDevelopCycle(this.currentPhase)
|
|
511
|
-
: 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);
|
|
512
443
|
}
|
|
513
444
|
toolDefinitions() {
|
|
514
445
|
return completionToolNamesForServer(this.config).map((name) => toolDefinition(name, phaseFromToolName(name), qaGateFromToolName(name)));
|
|
@@ -518,9 +449,6 @@ export class CompletionToolServerSession {
|
|
|
518
449
|
if (this.completed) {
|
|
519
450
|
throw new Error("Forge completion cycle is already complete; no more completion calls are accepted.");
|
|
520
451
|
}
|
|
521
|
-
if (this.config.decomposeCycle) {
|
|
522
|
-
return await this.callDecomposeGateTool(toolName, requestedPhase, input);
|
|
523
|
-
}
|
|
524
452
|
if (this.config.draftCycle) {
|
|
525
453
|
return await this.callDraftGateTool(toolName, requestedPhase, input);
|
|
526
454
|
}
|
|
@@ -564,38 +492,6 @@ export class CompletionToolServerSession {
|
|
|
564
492
|
}
|
|
565
493
|
};
|
|
566
494
|
}
|
|
567
|
-
async callDecomposeGateTool(toolName, requestedPhase, input) {
|
|
568
|
-
const requestedGate = decomposeGateFromToolName(toolName);
|
|
569
|
-
if (requestedPhase !== "decompose" || !requestedGate) {
|
|
570
|
-
throw new Error(`Tool ${toolName} is not available in Decompose-cycle mode. Current Forge Decompose gate is ${this.currentDecomposeGate}.`);
|
|
571
|
-
}
|
|
572
|
-
if (requestedGate !== this.currentDecomposeGate) {
|
|
573
|
-
throw new Error(`Tool ${toolName} is out of order. Current Forge Decompose gate is ${this.currentDecomposeGate}.`);
|
|
574
|
-
}
|
|
575
|
-
const completedGate = this.currentDecomposeGate;
|
|
576
|
-
const reference = await writeDecomposeGateCompletionArtifact(this.config, completedGate, toolName, input);
|
|
577
|
-
const status = reference.status.trim().toLowerCase();
|
|
578
|
-
const pass = status === "pass";
|
|
579
|
-
const nextGate = pass ? this.advanceAfterDecomposeGate(completedGate) : completedGate;
|
|
580
|
-
const nextPhase = pass
|
|
581
|
-
? (nextGate ? "decompose" : "sign_off_ready")
|
|
582
|
-
: "decompose";
|
|
583
|
-
const nextInstruction = completionToolNextInstructionForDecomposeGate(completedGate, status, nextGate);
|
|
584
|
-
const reason = pass
|
|
585
|
-
? nextGate
|
|
586
|
-
? `${completedGate} artifact accepted; continue to ${nextGate}.`
|
|
587
|
-
: `Decompose proposal artifact accepted with status ${status}.`
|
|
588
|
-
: `${completedGate} artifact recorded with status ${status}; rework is required before continuing.`;
|
|
589
|
-
return {
|
|
590
|
-
content: [{ type: "text", text: `Forge recorded ${completedGate} artifact ${path.relative(reference.repoPath, reference.path)} (${reference.status}, ${reference.contentHash}). ${nextInstruction}` }],
|
|
591
|
-
structuredContent: {
|
|
592
|
-
...reference,
|
|
593
|
-
gate: { phase: "decompose", decomposeGate: completedGate, status, allowed: pass, reason },
|
|
594
|
-
nextPhase,
|
|
595
|
-
nextInstruction
|
|
596
|
-
}
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
495
|
async callDraftGateTool(toolName, requestedPhase, input) {
|
|
600
496
|
const requestedGate = draftGateFromToolName(toolName);
|
|
601
497
|
if (requestedPhase !== "draft" || !requestedGate) {
|
|
@@ -716,16 +612,6 @@ export class CompletionToolServerSession {
|
|
|
716
612
|
this.completed = true;
|
|
717
613
|
return "dev_test_ready";
|
|
718
614
|
}
|
|
719
|
-
advanceAfterDecomposeGate(gate) {
|
|
720
|
-
const index = decomposeCompletionGateOrder.indexOf(gate);
|
|
721
|
-
const next = index >= 0 ? decomposeCompletionGateOrder[index + 1] ?? null : null;
|
|
722
|
-
if (next) {
|
|
723
|
-
this.currentDecomposeGate = next;
|
|
724
|
-
return next;
|
|
725
|
-
}
|
|
726
|
-
this.completed = true;
|
|
727
|
-
return null;
|
|
728
|
-
}
|
|
729
615
|
advanceAfterDraftGate(gate) {
|
|
730
616
|
const index = draftCompletionGateOrder.indexOf(gate);
|
|
731
617
|
const next = index >= 0 ? draftCompletionGateOrder[index + 1] ?? null : null;
|
|
@@ -757,40 +643,6 @@ export class CompletionToolServerSession {
|
|
|
757
643
|
return null;
|
|
758
644
|
}
|
|
759
645
|
}
|
|
760
|
-
async function writeDecomposeGateCompletionArtifact(config, gate, toolName, input) {
|
|
761
|
-
const payload = asRecord(input, "Decompose completion payload");
|
|
762
|
-
const status = normalizeDraftStatus(optionalString(payload, "status") ?? "pass");
|
|
763
|
-
if (gate === "decompose_proposal") {
|
|
764
|
-
const proposalJson = asRecord(payload["proposal_json"] ?? payload["decomposition_json"], "proposal_json");
|
|
765
|
-
if (!Array.isArray(proposalJson["candidates"])) {
|
|
766
|
-
throw new Error("proposal_json.candidates: expected array");
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
const redactedPayload = redactSecrets({ ...payload, status, phase: "decompose", gate, tool_name: toolName }, config.redactionSecrets ?? []);
|
|
770
|
-
const root = artifactRootFor(config.worktreePath, config.workItemId);
|
|
771
|
-
await fs.mkdir(root, { recursive: true });
|
|
772
|
-
const artifactPath = path.join(root, decomposeGateFileNames[gate]);
|
|
773
|
-
const reportPath = gate === "decompose_proposal" ? path.join(root, phaseFiles.decompose.reportFileName) : path.join(root, `${gate}.md`);
|
|
774
|
-
const verdictJson = jsonText(redactedPayload);
|
|
775
|
-
const report = optionalString(redactedPayload, "report_markdown") ?? `# ${gate.replace(/_/g, " ")}
|
|
776
|
-
|
|
777
|
-
${optionalString(redactedPayload, "summary") ?? "Decompose gate recorded."}
|
|
778
|
-
`;
|
|
779
|
-
await writeFileAtomic(artifactPath, verdictJson);
|
|
780
|
-
await writeFileAtomic(reportPath, report);
|
|
781
|
-
return {
|
|
782
|
-
phase: "decompose",
|
|
783
|
-
workItemId: config.workItemId,
|
|
784
|
-
ownerType: config.ownerType,
|
|
785
|
-
ownerId: config.ownerId,
|
|
786
|
-
repoPath: path.resolve(config.worktreePath),
|
|
787
|
-
path: artifactPath,
|
|
788
|
-
reportPath,
|
|
789
|
-
contentHash: sha256(verdictJson),
|
|
790
|
-
status,
|
|
791
|
-
producedBy: config.producedBy ?? "forge-completion-tool"
|
|
792
|
-
};
|
|
793
|
-
}
|
|
794
646
|
async function writeDraftGateCompletionArtifact(config, gate, toolName, input) {
|
|
795
647
|
const payload = asRecord(input, "Draft completion payload");
|
|
796
648
|
const status = normalizeDraftStatus(optionalString(payload, "status") ?? "pass");
|
|
@@ -1326,7 +1178,6 @@ export function parseCompletionMcpArgs(argv) {
|
|
|
1326
1178
|
let debugCycle = false;
|
|
1327
1179
|
let architectCycle = false;
|
|
1328
1180
|
let draftCycle = false;
|
|
1329
|
-
let decomposeCycle = false;
|
|
1330
1181
|
for (let index = 1; index < argv.length; index += 1) {
|
|
1331
1182
|
const arg = argv[index];
|
|
1332
1183
|
if (arg === "--worktree") {
|
|
@@ -1370,9 +1221,6 @@ export function parseCompletionMcpArgs(argv) {
|
|
|
1370
1221
|
else if (arg === "--draft-cycle") {
|
|
1371
1222
|
draftCycle = true;
|
|
1372
1223
|
}
|
|
1373
|
-
else if (arg === "--decompose-cycle") {
|
|
1374
|
-
decomposeCycle = true;
|
|
1375
|
-
}
|
|
1376
1224
|
else {
|
|
1377
1225
|
return { ok: false, error: `Unknown mcp argument: ${arg}` };
|
|
1378
1226
|
}
|
|
@@ -1380,7 +1228,7 @@ export function parseCompletionMcpArgs(argv) {
|
|
|
1380
1228
|
if (!worktreePath?.trim() || !workItemId?.trim()) {
|
|
1381
1229
|
return { ok: false, error: "mcp requires --worktree and --work-item-id." };
|
|
1382
1230
|
}
|
|
1383
|
-
if ([developCycle, qaCycle, debugCycle, architectCycle, draftCycle
|
|
1231
|
+
if ([developCycle, qaCycle, debugCycle, architectCycle, draftCycle].filter(Boolean).length > 1) {
|
|
1384
1232
|
return { ok: false, error: "mcp accepts only one cycle mode." };
|
|
1385
1233
|
}
|
|
1386
1234
|
if (developCycle && !isDevelopCompletionPhase(phase)) {
|
|
@@ -1398,9 +1246,6 @@ export function parseCompletionMcpArgs(argv) {
|
|
|
1398
1246
|
if (draftCycle && phase !== "draft") {
|
|
1399
1247
|
return { ok: false, error: "mcp --draft-cycle requires --phase draft." };
|
|
1400
1248
|
}
|
|
1401
|
-
if (decomposeCycle && phase !== "decompose") {
|
|
1402
|
-
return { ok: false, error: "mcp --decompose-cycle requires --phase decompose." };
|
|
1403
|
-
}
|
|
1404
1249
|
return {
|
|
1405
1250
|
ok: true,
|
|
1406
1251
|
config: {
|
|
@@ -1415,8 +1260,7 @@ export function parseCompletionMcpArgs(argv) {
|
|
|
1415
1260
|
qaCycle,
|
|
1416
1261
|
debugCycle,
|
|
1417
1262
|
architectCycle,
|
|
1418
|
-
draftCycle
|
|
1419
|
-
decomposeCycle
|
|
1263
|
+
draftCycle
|
|
1420
1264
|
}
|
|
1421
1265
|
};
|
|
1422
1266
|
}
|
|
@@ -1591,24 +1435,6 @@ function completionToolNextInstructionForQaGate(completedGate, status, nextGate)
|
|
|
1591
1435
|
: `${completedGate} accepted. Continue to ${nextGate}.`;
|
|
1592
1436
|
return `${prefix} ${qaGateInstruction(nextGate)}`;
|
|
1593
1437
|
}
|
|
1594
|
-
function completionToolNextInstructionForDecomposeGate(completedGate, status, nextGate) {
|
|
1595
|
-
if (status !== "pass") {
|
|
1596
|
-
return `Stay in ${completedGate}, address the recorded blockers/rework, then call ${expectedDecomposeCompletionToolName(completedGate)} again.`;
|
|
1597
|
-
}
|
|
1598
|
-
if (!nextGate) {
|
|
1599
|
-
return "Decompose proposal accepted. Forge has the structured child-item proposal JSON needed to populate the proposal row.";
|
|
1600
|
-
}
|
|
1601
|
-
const nextInstructions = {
|
|
1602
|
-
decompose_subject_intake: "Capture subject/scope, then call forge.complete_decompose_subject_intake.",
|
|
1603
|
-
decompose_context_review: "Review project/repo/dependency context, then call forge.complete_decompose_context_review.",
|
|
1604
|
-
decompose_breakdown_strategy: "Choose safe child boundaries, then call forge.complete_decompose_breakdown_strategy.",
|
|
1605
|
-
decompose_child_items: "Draft child candidates, then call forge.complete_decompose_child_items.",
|
|
1606
|
-
decompose_dependency_graph: "Validate dependency DAG, then call forge.complete_decompose_dependency_graph.",
|
|
1607
|
-
decompose_critic: "Critique the decomposition, then call forge.complete_decompose_critic.",
|
|
1608
|
-
decompose_proposal: "Submit final proposal_json through forge.complete_decompose_proposal."
|
|
1609
|
-
};
|
|
1610
|
-
return `${completedGate} accepted. ${nextInstructions[nextGate]}`;
|
|
1611
|
-
}
|
|
1612
1438
|
function completionToolNextInstructionForDebugGate(completedGate, status, nextGate) {
|
|
1613
1439
|
if (status !== "pass") {
|
|
1614
1440
|
return `Stay in ${completedGate}, address the recorded blockers, then call ${expectedDebugCompletionToolName(completedGate)} again.`;
|
|
@@ -2872,9 +2698,6 @@ function parseStrictCompletionPhase(value) {
|
|
|
2872
2698
|
if (normalized === "draft" || normalized === "drafting" || normalized === "draft_work_order") {
|
|
2873
2699
|
return "draft";
|
|
2874
2700
|
}
|
|
2875
|
-
if (normalized === "decompose" || normalized === "decomposition" || normalized === "work_order_decomposition" || normalized === "feature_decomposition") {
|
|
2876
|
-
return "decompose";
|
|
2877
|
-
}
|
|
2878
2701
|
return null;
|
|
2879
2702
|
}
|
|
2880
2703
|
function normalizeDebugCompletionPhase(value) {
|
|
@@ -2949,9 +2772,6 @@ function phaseFromToolName(toolName) {
|
|
|
2949
2772
|
if (draftGateFromToolName(toolName)) {
|
|
2950
2773
|
return "draft";
|
|
2951
2774
|
}
|
|
2952
|
-
if (decomposeGateFromToolName(toolName)) {
|
|
2953
|
-
return "decompose";
|
|
2954
|
-
}
|
|
2955
2775
|
const debugGate = debugGateFromToolName(toolName);
|
|
2956
2776
|
if (debugGate) {
|
|
2957
2777
|
return debugGate;
|
|
@@ -2971,22 +2791,6 @@ function draftGateFromToolName(toolName) {
|
|
|
2971
2791
|
}
|
|
2972
2792
|
return null;
|
|
2973
2793
|
}
|
|
2974
|
-
function decomposeGateFromToolName(toolName) {
|
|
2975
|
-
for (const gate of decomposeCompletionGateOrder) {
|
|
2976
|
-
const base = decomposeGateToolNames[gate];
|
|
2977
|
-
if (toolName === base || toolName === `forge.${base}`) {
|
|
2978
|
-
return gate;
|
|
2979
|
-
}
|
|
2980
|
-
}
|
|
2981
|
-
return null;
|
|
2982
|
-
}
|
|
2983
|
-
function isDecomposeDispatch(dispatch) {
|
|
2984
|
-
const draftKind = dispatch.draft?.kind?.trim().toLowerCase();
|
|
2985
|
-
return dispatch.execution.lane.trim().toLowerCase() === "decompose"
|
|
2986
|
-
|| dispatch.session.lane.trim().toLowerCase() === "decompose"
|
|
2987
|
-
|| draftKind === "work_order_decomposition"
|
|
2988
|
-
|| draftKind === "feature_decomposition";
|
|
2989
|
-
}
|
|
2990
2794
|
function debugGateFromToolName(toolName) {
|
|
2991
2795
|
const normalized = toolName.trim().toLowerCase();
|
|
2992
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);
|
|
@@ -255,19 +252,17 @@ export class ProcessBackedProviderCliService {
|
|
|
255
252
|
}, providerCancellation.signal);
|
|
256
253
|
let result;
|
|
257
254
|
let earlyProposalArtifact = null;
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
result = terminal.value;
|
|
270
|
-
}
|
|
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;
|
|
271
266
|
}
|
|
272
267
|
context.signal?.removeEventListener("abort", abortProvider);
|
|
273
268
|
if (result.exitCode !== 0 && !earlyProposalArtifact) {
|
|
@@ -280,30 +275,6 @@ export class ProcessBackedProviderCliService {
|
|
|
280
275
|
eventType: "provider_cli_completed"
|
|
281
276
|
});
|
|
282
277
|
await emitProviderUsageEvent(context.emit, dispatch, outputState);
|
|
283
|
-
if (decomposeDraft) {
|
|
284
|
-
await emitDecomposeGateArtifacts(context.emit, tempDir, dispatch.workItem.id);
|
|
285
|
-
const proposalArtifact = earlyProposalArtifact ?? await readCompletionArtifact(tempDir, dispatch.workItem.id, "decompose");
|
|
286
|
-
if (!proposalArtifact) {
|
|
287
|
-
throw new Error("Provider CLI Decompose completed but did not call forge.complete_decompose_proposal.");
|
|
288
|
-
}
|
|
289
|
-
const proposal = parseJsonLine(proposalArtifact.verdictJson) ?? {};
|
|
290
|
-
const proposalJson = proposal["proposal_json"] ?? proposal["decomposition_json"];
|
|
291
|
-
if (!proposalJson || typeof proposalJson !== "object" || Array.isArray(proposalJson)) {
|
|
292
|
-
throw new Error("Provider CLI Decompose completed but decompose.proposal.json did not contain proposal_json.");
|
|
293
|
-
}
|
|
294
|
-
const json = JSON.stringify(proposalJson);
|
|
295
|
-
await context.emit({
|
|
296
|
-
role: "assistant",
|
|
297
|
-
kind: "message",
|
|
298
|
-
content: json,
|
|
299
|
-
eventType: "decompose_proposal"
|
|
300
|
-
});
|
|
301
|
-
return {
|
|
302
|
-
summary: "Decomposition proposal generated through Forge Decompose completion cycle.",
|
|
303
|
-
artifact: proposalArtifact,
|
|
304
|
-
artifacts: [proposalArtifact]
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
278
|
await emitDraftGateArtifacts(context.emit, tempDir, dispatch.workItem.id);
|
|
308
279
|
const proposalArtifact = earlyProposalArtifact ?? await readCompletionArtifact(tempDir, dispatch.workItem.id, "draft");
|
|
309
280
|
if (!proposalArtifact) {
|
|
@@ -2064,47 +2035,6 @@ async function emitDraftGateArtifacts(emit, worktreePath, workItemId) {
|
|
|
2064
2035
|
}
|
|
2065
2036
|
}
|
|
2066
2037
|
}
|
|
2067
|
-
async function emitDecomposeGateArtifacts(emit, worktreePath, workItemId) {
|
|
2068
|
-
const decomposeArtifactFiles = {
|
|
2069
|
-
decompose_subject_intake: "decompose.subject-intake.json",
|
|
2070
|
-
decompose_context_review: "decompose.context-review.json",
|
|
2071
|
-
decompose_breakdown_strategy: "decompose.breakdown-strategy.json",
|
|
2072
|
-
decompose_child_items: "decompose.child-items.json",
|
|
2073
|
-
decompose_dependency_graph: "decompose.dependency-graph.json",
|
|
2074
|
-
decompose_critic: "decompose.critic.json",
|
|
2075
|
-
decompose_proposal: "decompose.proposal.json"
|
|
2076
|
-
};
|
|
2077
|
-
const gates = Object.keys(decomposeArtifactFiles);
|
|
2078
|
-
for (const gate of gates) {
|
|
2079
|
-
const actualPath = path.join(worktreePath, ".forge", "work-items", workItemId, "artifacts", decomposeArtifactFiles[gate]);
|
|
2080
|
-
if (!await fileExists(actualPath)) {
|
|
2081
|
-
continue;
|
|
2082
|
-
}
|
|
2083
|
-
const relativePath = path.relative(worktreePath, actualPath).split(path.sep).join("/");
|
|
2084
|
-
await emit({
|
|
2085
|
-
role: "tool",
|
|
2086
|
-
kind: "tool_result",
|
|
2087
|
-
content: `Forge Decompose completion tool recorded ${gate} artifact ${relativePath}.`,
|
|
2088
|
-
eventType: "completion_tool_artifact_recorded",
|
|
2089
|
-
data: JSON.stringify({ phase: "decompose", decompose_gate: gate, artifact_relative_paths: [relativePath] })
|
|
2090
|
-
});
|
|
2091
|
-
await emit({
|
|
2092
|
-
role: "hook",
|
|
2093
|
-
kind: "checkpoint",
|
|
2094
|
-
content: `phase_gate: ${gate} artifact accepted with status pass.`,
|
|
2095
|
-
eventType: "phase_gate"
|
|
2096
|
-
});
|
|
2097
|
-
const next = gates[gates.indexOf(gate) + 1];
|
|
2098
|
-
if (next) {
|
|
2099
|
-
await emit({
|
|
2100
|
-
role: "hook",
|
|
2101
|
-
kind: "checkpoint",
|
|
2102
|
-
content: `phase_progression: ${gate} -> ${next}`,
|
|
2103
|
-
eventType: "phase_gate"
|
|
2104
|
-
});
|
|
2105
|
-
}
|
|
2106
|
-
}
|
|
2107
|
-
}
|
|
2108
2038
|
async function fileExists(filePath) {
|
|
2109
2039
|
try {
|
|
2110
2040
|
await fs.access(filePath);
|
|
@@ -2119,7 +2049,6 @@ function buildDraftPrompt(dispatch) {
|
|
|
2119
2049
|
if (!draft) {
|
|
2120
2050
|
throw new Error("Draft prompt is missing.");
|
|
2121
2051
|
}
|
|
2122
|
-
const decomposeDraft = isDecomposeDraftDispatch(dispatch);
|
|
2123
2052
|
const draftArtifacts = dispatch.artifacts
|
|
2124
2053
|
.filter((artifact) => artifact.scope === "operational" && ["command", "skill", "hook"].includes(artifact.kind))
|
|
2125
2054
|
.map((artifact) => `## ${artifact.kind}: ${artifact.slug}\n${artifact.body.trim()}`)
|
|
@@ -2138,39 +2067,25 @@ Draft kind: ${draft.kind}
|
|
|
2138
2067
|
|
|
2139
2068
|
${draft.userPrompt}
|
|
2140
2069
|
|
|
2141
|
-
${
|
|
2142
|
-
? `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.
|
|
2143
|
-
|
|
2144
|
-
Complete these gates in order: decompose_subject_intake -> decompose_context_review -> decompose_breakdown_strategy -> decompose_child_items -> decompose_dependency_graph -> decompose_critic -> decompose_proposal.
|
|
2070
|
+
${completionPromptForDraftCycle("draft_intent")}
|
|
2145
2071
|
|
|
2146
|
-
|
|
2147
|
-
: `${completionPromptForDraftCycle("draft_intent")}
|
|
2148
|
-
|
|
2149
|
-
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.
|
|
2150
2073
|
|
|
2151
2074
|
Do not modify source files.`;
|
|
2152
2075
|
}
|
|
2153
|
-
function isDecomposeDraftKind(kind) {
|
|
2154
|
-
return kind === "work_order_decomposition" || kind === "feature_decomposition";
|
|
2155
|
-
}
|
|
2156
|
-
function isDecomposeDraftDispatch(dispatch) {
|
|
2157
|
-
return dispatch.execution.lane.trim().toLowerCase() === "decompose"
|
|
2158
|
-
|| dispatch.session.lane.trim().toLowerCase() === "decompose"
|
|
2159
|
-
|| isDecomposeDraftKind(dispatch.draft?.kind?.trim().toLowerCase());
|
|
2160
|
-
}
|
|
2161
2076
|
function summarizeStderr(stderr) {
|
|
2162
2077
|
const trimmed = stderr.trim();
|
|
2163
2078
|
return trimmed ? `: ${clip(trimmed, 1000)}` : "";
|
|
2164
2079
|
}
|
|
2165
|
-
async function
|
|
2080
|
+
async function waitForDraftProposalArtifact(worktreePath, workItemId, signal) {
|
|
2166
2081
|
while (!signal?.aborted) {
|
|
2167
|
-
const artifact = await readCompletionArtifact(worktreePath, workItemId,
|
|
2082
|
+
const artifact = await readCompletionArtifact(worktreePath, workItemId, "draft");
|
|
2168
2083
|
if (artifact) {
|
|
2169
2084
|
return artifact;
|
|
2170
2085
|
}
|
|
2171
2086
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2172
2087
|
}
|
|
2173
|
-
throw new Error(
|
|
2088
|
+
throw new Error("Draft proposal artifact wait was cancelled.");
|
|
2174
2089
|
}
|
|
2175
2090
|
function deterministicDraftPayload(dispatch) {
|
|
2176
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",
|