@loucompanion/forge-bridge 0.1.1-dev.9219f8477b8a → 0.1.1-dev.a7b57476b69b.dirty1

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { describe, expect, it } from "vitest";
4
+ import { defaultForgeBridgeConfig } from "./config.js";
5
+ describe("defaultForgeBridgeConfig", () => {
6
+ it("uses a non-phase-numbered repo root", () => {
7
+ expect(defaultForgeBridgeConfig().defaultRepoRoot).toBe(path.join(os.tmpdir(), "forge-repos"));
8
+ });
9
+ });
@@ -235,7 +235,15 @@ export class ProcessBackedProviderCliService {
235
235
  }
236
236
  const outputState = createProviderOutputState(dispatch.execution.provider);
237
237
  try {
238
- const result = await this.processRunner.run({
238
+ const providerCancellation = new AbortController();
239
+ const abortProvider = () => providerCancellation.abort();
240
+ if (context.signal?.aborted) {
241
+ abortProvider();
242
+ }
243
+ else {
244
+ context.signal?.addEventListener("abort", abortProvider, { once: true });
245
+ }
246
+ const runPromise = this.processRunner.run({
239
247
  command: command.command,
240
248
  args: command.args,
241
249
  cwd: tempDir,
@@ -246,8 +254,28 @@ export class ProcessBackedProviderCliService {
246
254
  await context.emit(event);
247
255
  }
248
256
  }
249
- }, context.signal);
250
- if (result.exitCode !== 0) {
257
+ }, providerCancellation.signal);
258
+ let result;
259
+ let earlyProposalArtifact = null;
260
+ if (completionTools && !isDecomposePrompt) {
261
+ const terminal = await Promise.race([
262
+ runPromise.then(value => ({ kind: "process", value })),
263
+ waitForDraftProposalArtifact(tempDir, dispatch.workItem.id, context.signal).then(value => ({ kind: "artifact", value }))
264
+ ]);
265
+ if (terminal.kind === "artifact") {
266
+ earlyProposalArtifact = terminal.value;
267
+ providerCancellation.abort();
268
+ result = await runPromise.catch(() => ({ exitCode: 0, stdout: "", stderr: "" }));
269
+ }
270
+ else {
271
+ result = terminal.value;
272
+ }
273
+ }
274
+ else {
275
+ result = await runPromise;
276
+ }
277
+ context.signal?.removeEventListener("abort", abortProvider);
278
+ if (result.exitCode !== 0 && !earlyProposalArtifact) {
251
279
  throw new Error(`Provider CLI exited with code ${result.exitCode}${summarizeStderr(result.stderr)}.`);
252
280
  }
253
281
  await context.emit({
@@ -273,7 +301,7 @@ export class ProcessBackedProviderCliService {
273
301
  };
274
302
  }
275
303
  await emitDraftGateArtifacts(context.emit, tempDir, dispatch.workItem.id);
276
- const proposalArtifact = await readCompletionArtifact(tempDir, dispatch.workItem.id, "draft");
304
+ const proposalArtifact = earlyProposalArtifact ?? await readCompletionArtifact(tempDir, dispatch.workItem.id, "draft");
277
305
  if (!proposalArtifact) {
278
306
  throw new Error("Provider CLI Draft completed but did not call forge.complete_draft_proposal.");
279
307
  }
@@ -417,6 +445,16 @@ export class ProcessBackedProviderCliService {
417
445
  }
418
446
  async runArchitect(context) {
419
447
  const { dispatch, worktree } = context;
448
+ const metadataRoot = path.join(worktree.worktreePath, ".forge", "work-items", dispatch.workItem.id);
449
+ await fs.mkdir(metadataRoot, { recursive: true });
450
+ const laneCommand = selectLaneCommand(dispatch.artifacts, dispatch.execution.lane, dispatch.cycleRun.phase);
451
+ if (!laneCommand) {
452
+ throw new Error(`Lane command for lane '${dispatch.execution.lane}' phase '${dispatch.cycleRun.phase}' was not resolved.`);
453
+ }
454
+ const specBody = buildSpecBody(laneCommand, dispatch.workItem, "architect");
455
+ const specPath = path.join(metadataRoot, "spec.md");
456
+ await fs.writeFile(specPath, specBody, "utf8");
457
+ await fs.writeFile(path.join(metadataRoot, "spec.meta.json"), JSON.stringify({ schema_version: 1, status: "pending_approval", produced_by: "forge-lane-command" }) + "\n", "utf8");
420
458
  const completionTools = await prepareCompletionToolRuntime({ dispatch, worktree, phase: "architect" });
421
459
  const command = buildProviderCliCommand(dispatch, {
422
460
  worktreePath: worktree.worktreePath,
@@ -483,15 +521,13 @@ export class ProcessBackedProviderCliService {
483
521
  content: outputState.finalMessage || `${dispatch.execution.provider} CLI produced an Architect plan for ${worktree.branchName}.`,
484
522
  eventType: "architect_complete"
485
523
  });
486
- const specPath = path.join(worktree.worktreePath, ".forge", "work-items", dispatch.workItem.id, "spec.md");
487
- const specBody = await fs.readFile(specPath, "utf8").catch(() => `# ${dispatch.workItem.title}\n\nArchitect plan pending approval.\n`);
488
524
  return {
489
525
  summary: `${dispatch.execution.provider} CLI Architect plan produced.`,
490
526
  spec: {
491
527
  repoPath: worktree.worktreePath,
492
528
  path: specPath,
493
529
  body: specBody,
494
- producedBy: `${dispatch.execution.provider}-cli`,
530
+ producedBy: "forge-lane-command",
495
531
  status: "pending_approval"
496
532
  },
497
533
  artifact: architectArtifact,
@@ -2072,11 +2108,85 @@ function summarizeStderr(stderr) {
2072
2108
  const trimmed = stderr.trim();
2073
2109
  return trimmed ? `: ${clip(trimmed, 1000)}` : "";
2074
2110
  }
2111
+ async function waitForDraftProposalArtifact(worktreePath, workItemId, signal) {
2112
+ while (!signal?.aborted) {
2113
+ const artifact = await readCompletionArtifact(worktreePath, workItemId, "draft");
2114
+ if (artifact) {
2115
+ return artifact;
2116
+ }
2117
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2118
+ }
2119
+ throw new Error("Draft proposal artifact wait was cancelled.");
2120
+ }
2075
2121
  function isDecomposeDraftKind(kind) {
2076
2122
  return kind === "work_order_decomposition" || kind === "feature_decomposition";
2077
2123
  }
2078
2124
  function deterministicDraftPayload(dispatch) {
2079
2125
  const kind = dispatch.draft?.kind ?? "work_item";
2126
+ if (kind === "work_order_decomposition") {
2127
+ return {
2128
+ summary: "Fake decompose proposed a Feature DAG for the Work Order.",
2129
+ blockedReason: "",
2130
+ candidates: [
2131
+ {
2132
+ origin: "agent",
2133
+ key: "intake-submission",
2134
+ title: "Customer intake submission",
2135
+ summary: "Collect, validate, de-duplicate, and confirm incoming customer requests.",
2136
+ planningPacket: "# Customer intake submission\n\nPlan Stories for request form validation, attachment support, duplicate prevention, and submission confirmation.",
2137
+ sortOrder: 10,
2138
+ dependsOn: []
2139
+ },
2140
+ {
2141
+ origin: "agent",
2142
+ key: "review-queue",
2143
+ title: "Reviewer triage queue",
2144
+ summary: "Provide reviewer triage, assignment, notes, status transitions, and audit trail.",
2145
+ planningPacket: "# Reviewer triage queue\n\nPlan Stories for queue filtering, assignment, reviewer notes, status changes, and immutable audit trail.",
2146
+ sortOrder: 20,
2147
+ dependsOn: ["intake-submission"]
2148
+ }
2149
+ ]
2150
+ };
2151
+ }
2152
+ if (kind === "feature_decomposition") {
2153
+ return {
2154
+ summary: "Fake decompose proposed a Story DAG for the Feature.",
2155
+ blockedReason: "",
2156
+ candidates: [
2157
+ {
2158
+ origin: "agent",
2159
+ key: "foundation",
2160
+ title: "Build the feature foundation",
2161
+ description: "Create the persistence, API, and UI shell needed by the feature.",
2162
+ acceptanceCriteria: "Scenario: Foundation exists\nGiven the Feature is selected\nWhen the foundation is delivered\nThen downstream Stories can build on stable contracts",
2163
+ points: 3,
2164
+ sortOrder: 10,
2165
+ dependsOn: []
2166
+ },
2167
+ {
2168
+ origin: "agent",
2169
+ key: "workflow-behavior",
2170
+ title: "Implement the primary workflow behavior",
2171
+ description: "Implement the main user-visible behavior for the feature after the foundation is ready.",
2172
+ acceptanceCriteria: "Scenario: Workflow behavior works\nGiven the foundation Story is complete\nWhen the user performs the primary workflow\nThen the expected state change and feedback are visible",
2173
+ points: 5,
2174
+ sortOrder: 20,
2175
+ dependsOn: ["foundation"]
2176
+ },
2177
+ {
2178
+ origin: "agent",
2179
+ key: "audit-and-tests",
2180
+ title: "Add audit coverage and regression tests",
2181
+ description: "Add audit/evidence behavior and automated regression coverage for the feature.",
2182
+ acceptanceCriteria: "Scenario: Feature is verifiable\nGiven the workflow behavior is complete\nWhen validation runs\nThen audit evidence and tests cover the expected outcomes",
2183
+ points: 3,
2184
+ sortOrder: 30,
2185
+ dependsOn: ["workflow-behavior"]
2186
+ }
2187
+ ]
2188
+ };
2189
+ }
2080
2190
  if (kind === "work_order") {
2081
2191
  return {
2082
2192
  title: "Deterministic runner Work Order draft",
@@ -2465,11 +2575,11 @@ class CursorStreamJsonState {
2465
2575
  if (type === "assistant") {
2466
2576
  const text = assistantMessageText(event);
2467
2577
  if (text) {
2468
- this.finalMessage = clip(text, 4000);
2578
+ this.finalMessage = text;
2469
2579
  return [{
2470
2580
  role: "assistant",
2471
2581
  kind: "message",
2472
- content: this.finalMessage,
2582
+ content: clip(text, 4000),
2473
2583
  eventType: "provider_message"
2474
2584
  }];
2475
2585
  }
@@ -2503,7 +2613,7 @@ class CursorStreamJsonState {
2503
2613
  if (type === "result" && stringField(event, "subtype") === "success") {
2504
2614
  const result = stringField(event, "result");
2505
2615
  if (result) {
2506
- this.finalMessage = clip(result, 4000);
2616
+ this.finalMessage = result;
2507
2617
  }
2508
2618
  this.accumulateUsage(event);
2509
2619
  }
@@ -2684,11 +2794,11 @@ class CodexJsonState {
2684
2794
  if (itemType === "agent_message") {
2685
2795
  const text = stringField(item, "text");
2686
2796
  if (text) {
2687
- this.finalMessage = clip(text, 4000);
2797
+ this.finalMessage = text;
2688
2798
  return [{
2689
2799
  role: "assistant",
2690
2800
  kind: "message",
2691
- content: this.finalMessage,
2801
+ content: clip(text, 4000),
2692
2802
  eventType: "provider_message"
2693
2803
  }];
2694
2804
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const forgeBridgeVersion = "0.1.1-dev.9219f8477b8a";
1
+ export declare const forgeBridgeVersion = "0.1.1-dev.a7b57476b69b.dirty1";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const forgeBridgeVersion = "0.1.1-dev.9219f8477b8a";
1
+ export const forgeBridgeVersion = "0.1.1-dev.a7b57476b69b.dirty1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loucompanion/forge-bridge",
3
- "version": "0.1.1-dev.9219f8477b8a",
3
+ "version": "0.1.1-dev.a7b57476b69b.dirty1",
4
4
  "description": "Forge runner bridge CLI for connecting local developer machines to Forge dispatch.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",