@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: (phase === "draft"
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 the requested final JSON shape and any candidate boundaries, then call \`forge.complete_draft_work_order_plan(...)\`.
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 Draft JSON payload in \`draft_json\`.
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
- validateDraftProposalJson(draftJson);
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
- try {
245
- const runPromise = this.processRunner.run({
246
- command: command.command,
247
- args: command.args,
248
- cwd: tempDir,
249
- env: providerProcessEnv(dispatch)
250
- }, {
251
- onStdoutLine: async (line) => {
252
- for (const event of outputState.consume(line)) {
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
- 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)}.`);
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
- 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.");
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
- await emitDraftGateArtifacts(context.emit, tempDir, dispatch.workItem.id);
323
- const proposalArtifact = earlyProposalArtifact ?? await readCompletionArtifact(tempDir, dispatch.workItem.id, "draft");
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 Draft completed but did not call forge.complete_draft_proposal.");
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 draftJson = proposal["draft_json"];
329
- if (!draftJson || typeof draftJson !== "object" || Array.isArray(draftJson)) {
330
- throw new Error("Provider CLI Draft completed but draft.proposal.json did not contain draft_json.");
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(draftJson);
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: "draft_complete"
299
+ eventType: "decompose_proposal"
338
300
  });
339
301
  return {
340
- summary: "Draft generated through Forge Draft completion cycle.",
302
+ summary: "Decomposition proposal generated through Forge Decompose completion cycle.",
341
303
  artifact: proposalArtifact,
342
304
  artifacts: [proposalArtifact]
343
305
  };
344
306
  }
345
- finally {
346
- context.signal?.removeEventListener("abort", abortProvider);
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 delay(1000, signal);
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.04a84914da09";
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.04a84914da09";
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.04a84914da09",
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",