@loucompanion/forge-bridge 0.1.3-dev.62c25087ca89 → 0.1.3-dev.7b1dc795c37f

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.
@@ -411,7 +411,9 @@ function decomposeGateToolInputSchema(gate, baseProperties) {
411
411
  estimatedQaScope: qaScopeSchema("Work Order-to-Feature planning QA estimate. This is not dispatch-authoritative QA scope."),
412
412
  estimated_qa_scope: qaScopeSchema("Work Order-to-Feature planning QA estimate. Snake_case alias for estimatedQaScope."),
413
413
  qaScope: qaScopeSchema("Feature-to-Story QA scope that becomes Story-level QA scope after proposal approval."),
414
- qa_scope: qaScopeSchema("Feature-to-Story QA scope. Snake_case alias for qaScope.")
414
+ qa_scope: qaScopeSchema("Feature-to-Story QA scope. Snake_case alias for qaScope."),
415
+ designerRefCandidates: designerRefCandidatesSchema("Designer packet refs that justify this candidate. Omit to inherit all parent refs unchanged."),
416
+ designer_ref_candidates: designerRefCandidatesSchema("Snake_case alias for designerRefCandidates.")
415
417
  }
416
418
  };
417
419
  const proposalSchema = {
@@ -440,7 +442,27 @@ function decomposeGateToolInputSchema(gate, baseProperties) {
440
442
  ...proposalSchema,
441
443
  description: "Alias for proposal_json when a client cannot send proposal_json."
442
444
  }
443
- }, ["status", "summary"], "For the final Decompose proposal, include proposal_json with a candidates array. Work Order-to-Feature candidates may include estimatedQaScope/estimated_qa_scope; Feature-to-Story candidates may include qaScope/qa_scope.");
445
+ }, ["status", "summary"], "For the final Decompose proposal, include proposal_json with a candidates array. Work Order-to-Feature candidates may include estimatedQaScope/estimated_qa_scope; Feature-to-Story candidates may include qaScope/qa_scope. Both levels may include designerRefCandidates/designer_ref_candidates.");
446
+ }
447
+ function designerRefCandidatesSchema(description) {
448
+ return {
449
+ type: "array",
450
+ description,
451
+ items: {
452
+ type: "object",
453
+ additionalProperties: false,
454
+ properties: {
455
+ designerRefId: { type: "string" },
456
+ designer_ref_id: { type: "string" },
457
+ refKey: { type: "string" },
458
+ ref_key: { type: "string" },
459
+ path: { type: "string" },
460
+ anchor: { type: "string" },
461
+ reason: { type: "string" },
462
+ required: { type: "boolean" }
463
+ }
464
+ }
465
+ };
444
466
  }
445
467
  function debugToolInputSchema(phase, baseProperties) {
446
468
  if (phase === "debug_repro") {
@@ -16,6 +16,7 @@ export declare function buildProviderCliCommand(dispatch: RunOnceDispatchPacket,
16
16
  export declare function codexSandboxModeForPlatform(platform?: NodeJS.Platform): "workspace-write" | "danger-full-access";
17
17
  export declare function buildArchitectPrompt(dispatch: RunOnceDispatchPacket): string;
18
18
  export declare function architectDependencyInstruction(dispatch: RunOnceDispatchPacket): string;
19
+ export declare function designerRefInstruction(dispatch: RunOnceDispatchPacket): string;
19
20
  export declare function architectRevisionInstruction(dispatch: RunOnceDispatchPacket): string;
20
21
  export declare function buildImplementationPrompt(dispatch: RunOnceDispatchPacket, phase?: import("../phase-completion.js").CompletionPhase): string;
21
22
  export declare function buildDevelopCyclePrompt(dispatch: RunOnceDispatchPacket, phase?: import("../phase-completion.js").CompletionPhase): string;
@@ -114,6 +114,7 @@ Read injected Forge context under .forge plus the repository shape, then produce
114
114
  Implementation DAG tasks are Develop child tasks only: include focused repo/product implementation changes. Do not create child Develop tasks for parent-story QA, sign-off, materialized child inventory, child-count evidence, parent plan artifact checks, or final integrated acceptance proof; put those checks in qa_plan.final_qa.
115
115
  ${architectDependencyInstruction(dispatch)}
116
116
  ${architectRevisionInstruction(dispatch)}
117
+ ${designerRefInstruction(dispatch)}
117
118
  Forge owns protocol files: write Architect artifacts only through the completion MCP tool.
118
119
 
119
120
  ${completionPromptForPhase("architect")}
@@ -153,6 +154,29 @@ ${dependencies}
153
154
 
154
155
  Use this predecessor output as foundation. Do not redesign or recreate predecessor scope. If the expected output is unavailable in the checkout/base branch, stop and report dependency_output_unavailable.`;
155
156
  }
157
+ export function designerRefInstruction(dispatch) {
158
+ if (!dispatch.designerRefs || dispatch.designerRefs.refs.length === 0) {
159
+ return "";
160
+ }
161
+ const refs = dispatch.designerRefs.refs
162
+ .map((attachment, index) => {
163
+ const ref = attachment.ref;
164
+ const anchor = ref.anchor ? `#${ref.anchor}` : "";
165
+ const required = attachment.required ? "required" : "optional";
166
+ return `${index + 1}. ${ref.refKey} (${ref.kind}, ${required}, ${attachment.lineageKind})
167
+ - Path: ${ref.path}${anchor}
168
+ - Hash/commit: ${ref.contentHash} @ ${ref.commitSha}
169
+ - Reason: ${attachment.reason || "No reason recorded."}`;
170
+ })
171
+ .join("\n");
172
+ return `
173
+ Designer packet context:
174
+ This ${dispatch.designerRefs.ownerType} inherits approved Designer packet refs. Use these refs as implementation planning constraints and cite the relevant paths in tasks when they shape scope.
175
+
176
+ ${refs}
177
+
178
+ If a required Designer ref is missing or cannot be inspected in the pinned commit/worktree, stop and report design_ref_unavailable instead of inventing replacement behavior.`;
179
+ }
156
180
  export function architectRevisionInstruction(dispatch) {
157
181
  if (!dispatch.architectRevision) {
158
182
  return "";
@@ -1,5 +1,4 @@
1
1
  import fs from "node:fs/promises";
2
- import os from "node:os";
3
2
  import path from "node:path";
4
3
  import { GitClient } from "../../shared/git/git.js";
5
4
  import { buildSpecBody, selectLaneCommand } from "../../worktree/artifact-injector.js";
@@ -60,25 +59,18 @@ export class ProcessBackedProviderCliService {
60
59
  if (!dispatch.draft) {
61
60
  throw new Error("Draft dispatch context is missing.");
62
61
  }
63
- const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "forge-bridge-draft-"));
64
- const draftWorktree = {
65
- repoPath: tempDir,
66
- worktreePath: tempDir,
67
- branchName: "draft",
68
- baseBranch: dispatch.cycleRun.baseBranch || dispatch.repo.defaultBranch || "draft",
69
- cleanupWorktrees: true
70
- };
71
- const metadataRoot = path.join(tempDir, ".forge", "work-items", dispatch.workItem.id);
62
+ const { worktree } = context;
63
+ const metadataRoot = path.join(worktree.worktreePath, ".forge", "work-items", dispatch.workItem.id);
72
64
  await fs.mkdir(metadataRoot, { recursive: true });
73
65
  await fs.writeFile(path.join(metadataRoot, "draft-input.json"), JSON.stringify(dispatch.draft, null, 2) + "\n", "utf8");
74
66
  await fs.writeFile(path.join(metadataRoot, "spec.md"), buildDraftSpec(dispatch), "utf8");
75
67
  const decomposeDraft = isDecomposeDraftDispatch(dispatch);
76
- const completionTools = await prepareCompletionToolRuntime({ dispatch, worktree: draftWorktree, phase: decomposeDraft ? "decompose" : "draft" });
68
+ const completionTools = await prepareCompletionToolRuntime({ dispatch, worktree, phase: decomposeDraft ? "decompose" : "draft" });
77
69
  const command = buildProviderCliCommand(dispatch, {
78
- worktreePath: tempDir,
70
+ worktreePath: worktree.worktreePath,
79
71
  prompt: buildDraftPrompt(dispatch),
80
72
  completionTools: completionTools ?? undefined,
81
- skipGitRepoCheck: true
73
+ skipGitRepoCheck: false
82
74
  });
83
75
  const draftArtifactSlugs = dispatch.artifacts
84
76
  .filter((artifact) => artifact.scope === "operational" && ["command", "skill", "hook"].includes(artifact.kind))
@@ -117,7 +109,7 @@ export class ProcessBackedProviderCliService {
117
109
  const runPromise = this.processRunner.run({
118
110
  command: command.command,
119
111
  args: command.args,
120
- cwd: tempDir,
112
+ cwd: worktree.worktreePath,
121
113
  env: providerProcessEnv(dispatch)
122
114
  }, {
123
115
  onStdoutLine: async (line) => {
@@ -134,7 +126,7 @@ export class ProcessBackedProviderCliService {
134
126
  else {
135
127
  context.signal?.addEventListener("abort", abortArtifactWait, { once: true });
136
128
  }
137
- const artifactWaitPromise = waitForCompletionArtifact(tempDir, dispatch.workItem.id, decomposeDraft ? "decompose" : "draft", artifactWaitCancellation.signal);
129
+ const artifactWaitPromise = waitForCompletionArtifact(worktree.worktreePath, dispatch.workItem.id, decomposeDraft ? "decompose" : "draft", artifactWaitCancellation.signal);
138
130
  let result;
139
131
  let earlyProposalArtifact = null;
140
132
  try {
@@ -168,8 +160,8 @@ export class ProcessBackedProviderCliService {
168
160
  });
169
161
  await emitProviderUsageEvent(context.emit, dispatch, outputState);
170
162
  if (decomposeDraft) {
171
- await emitDecomposeGateArtifacts(context.emit, tempDir, dispatch.workItem.id);
172
- const proposalArtifact = earlyProposalArtifact ?? await readCompletionArtifact(tempDir, dispatch.workItem.id, "decompose");
163
+ await emitDecomposeGateArtifacts(context.emit, worktree.worktreePath, dispatch.workItem.id);
164
+ const proposalArtifact = earlyProposalArtifact ?? await readCompletionArtifact(worktree.worktreePath, dispatch.workItem.id, "decompose");
173
165
  if (!proposalArtifact) {
174
166
  throw new Error("Provider CLI Decompose completed but did not call forge.complete_decompose_proposal.");
175
167
  }
@@ -191,8 +183,8 @@ export class ProcessBackedProviderCliService {
191
183
  artifacts: [proposalArtifact]
192
184
  };
193
185
  }
194
- await emitDraftGateArtifacts(context.emit, tempDir, dispatch.workItem.id);
195
- const proposalArtifact = earlyProposalArtifact ?? await readCompletionArtifact(tempDir, dispatch.workItem.id, "draft");
186
+ await emitDraftGateArtifacts(context.emit, worktree.worktreePath, dispatch.workItem.id);
187
+ const proposalArtifact = earlyProposalArtifact ?? await readCompletionArtifact(worktree.worktreePath, dispatch.workItem.id, "draft");
196
188
  if (!proposalArtifact) {
197
189
  throw new Error("Provider CLI Draft completed but did not call forge.complete_draft_proposal.");
198
190
  }
@@ -220,7 +212,6 @@ export class ProcessBackedProviderCliService {
220
212
  }
221
213
  finally {
222
214
  await cleanupCompletionTools(completionTools, context.emit);
223
- await fs.rm(tempDir, { recursive: true, force: true });
224
215
  }
225
216
  }
226
217
  async runImplementation(context) {
@@ -8,6 +8,7 @@ export interface ProviderCliRunContext {
8
8
  }
9
9
  export interface ProviderCliDraftContext {
10
10
  readonly dispatch: RunOnceDispatchPacket;
11
+ readonly worktree: WorktreeHandle;
11
12
  readonly signal?: AbortSignal;
12
13
  emit(event: RunnerDispatchEvent): Promise<void>;
13
14
  }
@@ -27,23 +27,6 @@ export class BridgeSessionService {
27
27
  content: "forge-bridge accepted run-once dispatch.",
28
28
  eventType: "dispatch_accepted"
29
29
  });
30
- if (dispatch.draft) {
31
- const run = await this.providerCli.runDraft({
32
- dispatch,
33
- emit,
34
- signal
35
- });
36
- throwIfAborted(signal);
37
- return redactSecrets({
38
- schemaVersion: 1,
39
- exitCode: 0,
40
- summary: run.summary,
41
- events,
42
- spec: run.spec ?? null,
43
- artifact: run.artifact ?? null,
44
- artifacts: run.artifacts?.length ? run.artifacts : run.artifact ? [run.artifact] : []
45
- }, redactionSecrets);
46
- }
47
30
  const worktree = await this.worktrees.prepare({
48
31
  repo: dispatch.repo,
49
32
  workItem: dispatch.workItem,
@@ -71,6 +54,26 @@ export class BridgeSessionService {
71
54
  await emitDependencyWarmEvents(worktree.dependencyWarm, emit);
72
55
  }
73
56
  try {
57
+ if (dispatch.draft) {
58
+ throwIfAborted(signal);
59
+ const run = await this.providerCli.runDraft({
60
+ dispatch: providerDispatch,
61
+ worktree,
62
+ emit,
63
+ signal
64
+ });
65
+ throwIfAborted(signal);
66
+ const draftArtifacts = run.artifacts?.length ? run.artifacts : run.artifact ? [run.artifact] : [];
67
+ return redactSecrets({
68
+ schemaVersion: 1,
69
+ exitCode: 0,
70
+ summary: run.summary,
71
+ events,
72
+ spec: run.spec ?? null,
73
+ artifact: run.artifact ?? null,
74
+ artifacts: draftArtifacts
75
+ }, redactionSecrets);
76
+ }
74
77
  throwIfAborted(signal);
75
78
  const lane = dispatch.execution.lane.toLowerCase();
76
79
  const role = dispatch.execution.role.toLowerCase();
@@ -19,6 +19,7 @@ export interface RunOnceDispatchPacket {
19
19
  readonly architectDependencies?: DispatchArchitectDependencyContext | null;
20
20
  readonly storyDevelop?: DispatchStoryDevelopContext | null;
21
21
  readonly storyQa?: DispatchStoryQaContext | null;
22
+ readonly designerRefs?: DispatchDesignerRefHandoffContext | null;
22
23
  readonly draft?: DispatchDraftPrompt | null;
23
24
  }
24
25
  export interface DispatchDraftPrompt {
@@ -142,6 +143,42 @@ export interface DispatchStoryQaDependencyContext {
142
143
  readonly fromWorkItemId: string;
143
144
  readonly toWorkItemId: string;
144
145
  }
146
+ export interface DispatchDesignerRefHandoffContext {
147
+ readonly schemaVersion: number;
148
+ readonly kind: string;
149
+ readonly ownerType: string;
150
+ readonly ownerId: string;
151
+ readonly projectId: string;
152
+ readonly repoId: string;
153
+ readonly title: string;
154
+ readonly refs: readonly DispatchDesignerRefAttachment[];
155
+ }
156
+ export interface DispatchDesignerRefAttachment {
157
+ readonly ref: DispatchDesignerRef;
158
+ readonly reason: string;
159
+ readonly required: boolean;
160
+ readonly source: string;
161
+ readonly lineageKind: string;
162
+ readonly inheritedFromItemType?: string | null;
163
+ readonly inheritedFromItemId?: string | null;
164
+ readonly inheritedFromDesignerRefId?: string | null;
165
+ readonly addedByLane: string;
166
+ readonly sortOrder: number;
167
+ readonly createdAt: string;
168
+ }
169
+ export interface DispatchDesignerRef {
170
+ readonly id: string;
171
+ readonly projectId: string;
172
+ readonly repoId: string;
173
+ readonly refKey: string;
174
+ readonly kind: string;
175
+ readonly path: string;
176
+ readonly anchor?: string | null;
177
+ readonly title: string;
178
+ readonly contentHash: string;
179
+ readonly commitSha: string;
180
+ readonly createdAt: string;
181
+ }
145
182
  export interface DispatchPullRequestContext {
146
183
  readonly number: number;
147
184
  readonly provider: string;
@@ -26,6 +26,7 @@ export function parseRunOnceDispatchPacket(value) {
26
26
  architectDependencies: parseArchitectDependencies(packet.architectDependencies),
27
27
  storyDevelop: parseStoryDevelop(packet.storyDevelop),
28
28
  storyQa: parseStoryQa(packet.storyQa),
29
+ designerRefs: parseDesignerRefs(packet.designerRefs ?? packet.designer_refs),
29
30
  draft: parseDraftPrompt(packet.draft)
30
31
  };
31
32
  }
@@ -416,6 +417,58 @@ function parseStoryQaDependencies(value) {
416
417
  };
417
418
  });
418
419
  }
420
+ function parseDesignerRefs(value) {
421
+ if (value === undefined || value === null) {
422
+ return null;
423
+ }
424
+ const context = asObject(value, "designerRefs");
425
+ const refs = context.refs;
426
+ if (!Array.isArray(refs)) {
427
+ throw new Error("designerRefs.refs must be an array.");
428
+ }
429
+ return {
430
+ schemaVersion: requiredNumber(context, "schemaVersion"),
431
+ kind: requiredString(context, "kind"),
432
+ ownerType: requiredString(context, "ownerType"),
433
+ ownerId: requiredString(context, "ownerId"),
434
+ projectId: requiredString(context, "projectId"),
435
+ repoId: requiredString(context, "repoId"),
436
+ title: requiredString(context, "title"),
437
+ refs: refs.map((entry, index) => parseDesignerRefAttachment(entry, index))
438
+ };
439
+ }
440
+ function parseDesignerRefAttachment(value, index) {
441
+ const attachment = asObject(value, `designerRefs.refs[${index}]`);
442
+ return {
443
+ ref: parseDesignerRef(attachment.ref, index),
444
+ reason: optionalString(attachment, "reason") ?? "",
445
+ required: requiredBoolean(attachment, "required"),
446
+ source: optionalString(attachment, "source") ?? "",
447
+ lineageKind: optionalString(attachment, "lineageKind") ?? optionalString(attachment, "lineage_kind") ?? "",
448
+ inheritedFromItemType: optionalString(attachment, "inheritedFromItemType") ?? optionalString(attachment, "inherited_from_item_type"),
449
+ inheritedFromItemId: optionalString(attachment, "inheritedFromItemId") ?? optionalString(attachment, "inherited_from_item_id"),
450
+ inheritedFromDesignerRefId: optionalString(attachment, "inheritedFromDesignerRefId") ?? optionalString(attachment, "inherited_from_designer_ref_id"),
451
+ addedByLane: optionalString(attachment, "addedByLane") ?? optionalString(attachment, "added_by_lane") ?? "",
452
+ sortOrder: requiredNumber(attachment, "sortOrder"),
453
+ createdAt: requiredString(attachment, "createdAt")
454
+ };
455
+ }
456
+ function parseDesignerRef(value, index) {
457
+ const designerRef = asObject(value, `designerRefs.refs[${index}].ref`);
458
+ return {
459
+ id: requiredString(designerRef, "id"),
460
+ projectId: requiredString(designerRef, "projectId"),
461
+ repoId: requiredString(designerRef, "repoId"),
462
+ refKey: requiredString(designerRef, "refKey"),
463
+ kind: requiredString(designerRef, "kind"),
464
+ path: requiredString(designerRef, "path"),
465
+ anchor: optionalString(designerRef, "anchor"),
466
+ title: optionalString(designerRef, "title") ?? "",
467
+ contentHash: requiredString(designerRef, "contentHash"),
468
+ commitSha: requiredString(designerRef, "commitSha"),
469
+ createdAt: requiredString(designerRef, "createdAt")
470
+ };
471
+ }
419
472
  function parseStringArray(value, label) {
420
473
  if (!Array.isArray(value)) {
421
474
  throw new Error(`${label} must be an array.`);
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const forgeBridgeVersion = "0.1.3-dev.62c25087ca89";
1
+ export declare const forgeBridgeVersion = "0.1.3-dev.7b1dc795c37f";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const forgeBridgeVersion = "0.1.3-dev.62c25087ca89";
1
+ export const forgeBridgeVersion = "0.1.3-dev.7b1dc795c37f";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loucompanion/forge-bridge",
3
- "version": "0.1.3-dev.62c25087ca89",
3
+ "version": "0.1.3-dev.7b1dc795c37f",
4
4
  "description": "Forge runner bridge CLI for connecting local developer machines to Forge dispatch.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",