@loucompanion/forge-bridge 0.1.3-dev.04a84914da09 → 0.1.3-dev.6e59f152916f
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.
|
@@ -308,10 +308,7 @@ export async function prepareCompletionToolRuntime(options) {
|
|
|
308
308
|
qaCycle: usesQaCycleCompletion(options.dispatch),
|
|
309
309
|
debugCycle: usesDebugCycleCompletion(options.dispatch),
|
|
310
310
|
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),
|
|
311
|
+
draftCycle: (options.dispatch.execution.lane.trim().toLowerCase() === "draft" || options.dispatch.cycleRun.kind.trim().toLowerCase() === "draft") && !isDecomposeDispatch(options.dispatch),
|
|
315
312
|
decomposeCycle: isDecomposeDispatch(options.dispatch)
|
|
316
313
|
});
|
|
317
314
|
const runtime = {
|
|
@@ -442,9 +439,9 @@ Complete the Draft cycle in order inside this same provider session:
|
|
|
442
439
|
2. Follow the returned repo-context instruction, inspect selected project/repo assumptions, then call \`forge.complete_draft_repo_context(...)\`.
|
|
443
440
|
3. Follow the returned graph-context instruction, account for DAG/dependency placement, then call \`forge.complete_draft_graph_context(...)\`.
|
|
444
441
|
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
|
|
442
|
+
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
443
|
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
|
|
444
|
+
7. Follow the returned proposal instruction, then call \`forge.complete_draft_proposal(...)\` with the final Work Order JSON in \`draft_json\`.
|
|
448
445
|
The final proposal tool writes \`draft.proposal.json\` under \`.forge/work-items/{workItemId}/artifacts/\`. Reporting Draft completion in prose is not enough.`;
|
|
449
446
|
}
|
|
450
447
|
export function completionPromptForDecomposeCycle(currentGate = "decompose_subject_intake") {
|
|
@@ -799,7 +796,11 @@ async function writeDraftGateCompletionArtifact(config, gate, toolName, input) {
|
|
|
799
796
|
const status = normalizeDraftStatus(optionalString(payload, "status") ?? "pass");
|
|
800
797
|
if (gate === "draft_proposal") {
|
|
801
798
|
const draftJson = asRecord(payload["draft_json"], "draft_json");
|
|
802
|
-
|
|
799
|
+
for (const key of ["title", "summary", "objective", "planningPacket", "decompositionContext"]) {
|
|
800
|
+
if (typeof draftJson[key] !== "string" || draftJson[key].trim().length === 0) {
|
|
801
|
+
throw new Error(`draft_json.${key}: expected non-empty string`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
803
804
|
}
|
|
804
805
|
const redactedPayload = redactSecrets({ ...payload, status, phase: "draft", gate, tool_name: toolName }, config.redactionSecrets ?? []);
|
|
805
806
|
const root = artifactRootFor(config.worktreePath, config.workItemId);
|
|
@@ -827,22 +828,6 @@ function normalizeDraftStatus(value) {
|
|
|
827
828
|
const normalized = value.trim().toLowerCase();
|
|
828
829
|
return normalized === "blocked" || normalized === "needs_revision" || normalized === "needs_rework" ? normalized : "pass";
|
|
829
830
|
}
|
|
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
831
|
export async function writeCompletionArtifact(config, toolName, input) {
|
|
847
832
|
const phase = phaseFromToolName(toolName);
|
|
848
833
|
if (phase !== config.phase) {
|
|
@@ -241,110 +241,91 @@ export class ProcessBackedProviderCliService {
|
|
|
241
241
|
else {
|
|
242
242
|
context.signal?.addEventListener("abort", abortProvider, { once: true });
|
|
243
243
|
}
|
|
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;
|
|
244
|
+
const runPromise = this.processRunner.run({
|
|
245
|
+
command: command.command,
|
|
246
|
+
args: command.args,
|
|
247
|
+
cwd: tempDir,
|
|
248
|
+
env: providerProcessEnv(dispatch)
|
|
249
|
+
}, {
|
|
250
|
+
onStdoutLine: async (line) => {
|
|
251
|
+
for (const event of outputState.consume(line)) {
|
|
252
|
+
await context.emit(event);
|
|
282
253
|
}
|
|
283
254
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
255
|
+
}, providerCancellation.signal);
|
|
256
|
+
let result;
|
|
257
|
+
let earlyProposalArtifact = null;
|
|
258
|
+
{
|
|
259
|
+
const terminal = await Promise.race([
|
|
260
|
+
runPromise.then(value => ({ kind: "process", value })),
|
|
261
|
+
waitForCompletionArtifact(tempDir, dispatch.workItem.id, decomposeDraft ? "decompose" : "draft", context.signal).then(value => ({ kind: "artifact", value }))
|
|
262
|
+
]);
|
|
263
|
+
if (terminal.kind === "artifact") {
|
|
264
|
+
earlyProposalArtifact = terminal.value;
|
|
265
|
+
providerCancellation.abort();
|
|
266
|
+
result = await runPromise.catch(() => ({ exitCode: 0, stdout: "", stderr: "" }));
|
|
290
267
|
}
|
|
291
|
-
|
|
292
|
-
|
|
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.");
|
|
308
|
-
}
|
|
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
|
-
};
|
|
268
|
+
else {
|
|
269
|
+
result = terminal.value;
|
|
321
270
|
}
|
|
322
|
-
|
|
323
|
-
|
|
271
|
+
}
|
|
272
|
+
context.signal?.removeEventListener("abort", abortProvider);
|
|
273
|
+
if (result.exitCode !== 0 && !earlyProposalArtifact) {
|
|
274
|
+
throw new Error(`Provider CLI exited with code ${result.exitCode}${summarizeStderr(result.stderr)}.`);
|
|
275
|
+
}
|
|
276
|
+
await context.emit({
|
|
277
|
+
role: "runner",
|
|
278
|
+
kind: "checkpoint",
|
|
279
|
+
content: `Provider CLI Draft completed: ${command.command} exit ${result.exitCode}${outputState.sessionLabel ? ` ${outputState.sessionLabel}` : ""}.`,
|
|
280
|
+
eventType: "provider_cli_completed"
|
|
281
|
+
});
|
|
282
|
+
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");
|
|
324
286
|
if (!proposalArtifact) {
|
|
325
|
-
throw new Error("Provider CLI
|
|
287
|
+
throw new Error("Provider CLI Decompose completed but did not call forge.complete_decompose_proposal.");
|
|
326
288
|
}
|
|
327
289
|
const proposal = parseJsonLine(proposalArtifact.verdictJson) ?? {};
|
|
328
|
-
const
|
|
329
|
-
if (!
|
|
330
|
-
throw new Error("Provider CLI
|
|
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.");
|
|
331
293
|
}
|
|
332
|
-
const json = JSON.stringify(
|
|
294
|
+
const json = JSON.stringify(proposalJson);
|
|
333
295
|
await context.emit({
|
|
334
296
|
role: "assistant",
|
|
335
297
|
kind: "message",
|
|
336
298
|
content: json,
|
|
337
|
-
eventType: "
|
|
299
|
+
eventType: "decompose_proposal"
|
|
338
300
|
});
|
|
339
301
|
return {
|
|
340
|
-
summary: "
|
|
302
|
+
summary: "Decomposition proposal generated through Forge Decompose completion cycle.",
|
|
341
303
|
artifact: proposalArtifact,
|
|
342
304
|
artifacts: [proposalArtifact]
|
|
343
305
|
};
|
|
344
306
|
}
|
|
345
|
-
|
|
346
|
-
|
|
307
|
+
await emitDraftGateArtifacts(context.emit, tempDir, dispatch.workItem.id);
|
|
308
|
+
const proposalArtifact = earlyProposalArtifact ?? await readCompletionArtifact(tempDir, dispatch.workItem.id, "draft");
|
|
309
|
+
if (!proposalArtifact) {
|
|
310
|
+
throw new Error("Provider CLI Draft completed but did not call forge.complete_draft_proposal.");
|
|
347
311
|
}
|
|
312
|
+
const proposal = parseJsonLine(proposalArtifact.verdictJson) ?? {};
|
|
313
|
+
const draftJson = proposal["draft_json"];
|
|
314
|
+
if (!draftJson || typeof draftJson !== "object" || Array.isArray(draftJson)) {
|
|
315
|
+
throw new Error("Provider CLI Draft completed but draft.proposal.json did not contain draft_json.");
|
|
316
|
+
}
|
|
317
|
+
const json = JSON.stringify(draftJson);
|
|
318
|
+
await context.emit({
|
|
319
|
+
role: "assistant",
|
|
320
|
+
kind: "message",
|
|
321
|
+
content: json,
|
|
322
|
+
eventType: "draft_complete"
|
|
323
|
+
});
|
|
324
|
+
return {
|
|
325
|
+
summary: "Draft generated through Forge Draft completion cycle.",
|
|
326
|
+
artifact: proposalArtifact,
|
|
327
|
+
artifacts: [proposalArtifact]
|
|
328
|
+
};
|
|
348
329
|
}
|
|
349
330
|
finally {
|
|
350
331
|
await cleanupCompletionTools(completionTools, context.emit);
|
|
@@ -2187,33 +2168,10 @@ async function waitForCompletionArtifact(worktreePath, workItemId, phase, signal
|
|
|
2187
2168
|
if (artifact) {
|
|
2188
2169
|
return artifact;
|
|
2189
2170
|
}
|
|
2190
|
-
await
|
|
2171
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2191
2172
|
}
|
|
2192
2173
|
throw new Error(`${phase} proposal artifact wait was cancelled.`);
|
|
2193
2174
|
}
|
|
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
|
-
});
|
|
2216
|
-
}
|
|
2217
2175
|
function deterministicDraftPayload(dispatch) {
|
|
2218
2176
|
const kind = dispatch.draft?.kind ?? "work_item";
|
|
2219
2177
|
if (kind === "work_order_decomposition") {
|
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.6e59f152916f";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const forgeBridgeVersion = "0.1.3-dev.
|
|
1
|
+
export const forgeBridgeVersion = "0.1.3-dev.6e59f152916f";
|
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.6e59f152916f",
|
|
4
4
|
"description": "Forge runner bridge CLI for connecting local developer machines to Forge dispatch.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|