@loucompanion/forge-bridge 0.1.1-dev.32d4dd5874ee → 0.1.1-dev.40fbc494669b

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.
@@ -15,9 +15,16 @@ export function parseRunOnceDispatchPacket(value) {
15
15
  lease: parseLease(packet.lease),
16
16
  execution: parseExecution(packet.execution),
17
17
  serverLocal: parseServerLocal(packet.serverLocal),
18
+ pullRequest: parsePullRequest(packet.pullRequest),
18
19
  apiKeySecretGrant: parseApiKeySecretGrant(packet.apiKeySecretGrant),
19
20
  artifacts: parseArtifacts(packet.artifacts),
20
- warmPathOverrides: parseWarmPathOverrides(packet.warmPathOverrides)
21
+ warmPathOverrides: parseWarmPathOverrides(packet.warmPathOverrides),
22
+ qaBrowser: parseQaBrowser(packet.qaBrowser),
23
+ qaRework: parseQaRework(packet.qaRework),
24
+ architectRevision: parseArchitectRevision(packet.architectRevision),
25
+ storyDevelop: parseStoryDevelop(packet.storyDevelop),
26
+ storyQa: parseStoryQa(packet.storyQa),
27
+ draft: parseDraftPrompt(packet.draft)
21
28
  };
22
29
  }
23
30
  export function failureResult(summary, events = []) {
@@ -55,6 +62,9 @@ function parseCycleRun(value) {
55
62
  const cycle = asObject(value, "cycleRun");
56
63
  return {
57
64
  id: requiredString(cycle, "id"),
65
+ ownerType: optionalString(cycle, "ownerType"),
66
+ ownerId: optionalString(cycle, "ownerId"),
67
+ workItemId: optionalString(cycle, "workItemId"),
58
68
  kind: requiredString(cycle, "kind"),
59
69
  phase: requiredString(cycle, "phase"),
60
70
  requestedBase: optionalString(cycle, "requestedBase") ?? "develop",
@@ -76,6 +86,9 @@ function parseLease(value) {
76
86
  const lease = asObject(value, "lease");
77
87
  return {
78
88
  id: requiredString(lease, "id"),
89
+ ownerType: optionalString(lease, "ownerType"),
90
+ ownerId: optionalString(lease, "ownerId"),
91
+ workItemId: optionalString(lease, "workItemId"),
79
92
  worktreeKey: requiredString(lease, "worktreeKey"),
80
93
  lane: requiredString(lease, "lane"),
81
94
  fencingToken: requiredString(lease, "fencingToken"),
@@ -93,7 +106,19 @@ function parseExecution(value) {
93
106
  repoId: requiredString(execution, "repoId"),
94
107
  role: requiredString(execution, "role"),
95
108
  lane: requiredString(execution, "lane"),
96
- requiresApiKeySecretGrant: requiredBoolean(execution, "requiresApiKeySecretGrant")
109
+ requiresApiKeySecretGrant: requiredBoolean(execution, "requiresApiKeySecretGrant"),
110
+ runnerRepoLocalPath: optionalString(execution, "runnerRepoLocalPath")
111
+ };
112
+ }
113
+ function parseDraftPrompt(value) {
114
+ if (value === undefined || value === null) {
115
+ return null;
116
+ }
117
+ const draft = asObject(value, "draft");
118
+ return {
119
+ kind: requiredString(draft, "kind"),
120
+ systemPrompt: requiredString(draft, "systemPrompt"),
121
+ userPrompt: requiredString(draft, "userPrompt")
97
122
  };
98
123
  }
99
124
  function parseApiKeySecretGrant(value) {
@@ -115,6 +140,28 @@ function parseServerLocal(value) {
115
140
  cleanupWorktrees: requiredBoolean(serverLocal, "cleanupWorktrees")
116
141
  };
117
142
  }
143
+ function parsePullRequest(value) {
144
+ if (value === undefined || value === null) {
145
+ return null;
146
+ }
147
+ const pullRequest = asObject(value, "pullRequest");
148
+ return {
149
+ number: requiredNumber(pullRequest, "number"),
150
+ provider: requiredString(pullRequest, "provider"),
151
+ title: optionalString(pullRequest, "title") ?? "",
152
+ sourceBranch: requiredString(pullRequest, "sourceBranch"),
153
+ targetBranch: requiredString(pullRequest, "targetBranch"),
154
+ url: optionalString(pullRequest, "url") ?? "",
155
+ state: requiredString(pullRequest, "state"),
156
+ checkState: requiredString(pullRequest, "checkState"),
157
+ mergeState: requiredString(pullRequest, "mergeState"),
158
+ requiredChecksPassed: requiredNumber(pullRequest, "requiredChecksPassed"),
159
+ requiredChecksTotal: requiredNumber(pullRequest, "requiredChecksTotal"),
160
+ unresolvedThreads: requiredNumber(pullRequest, "unresolvedThreads"),
161
+ humanApproved: requiredBoolean(pullRequest, "humanApproved"),
162
+ hasMergeConflicts: requiredBoolean(pullRequest, "hasMergeConflicts")
163
+ };
164
+ }
118
165
  function parseWarmPathOverrides(value) {
119
166
  if (value === undefined || value === null) {
120
167
  return [];
@@ -138,6 +185,197 @@ function parseWarmPathOverride(entry, index) {
138
185
  }
139
186
  return segments.join("/");
140
187
  }
188
+ function parseQaBrowser(value) {
189
+ if (value === undefined || value === null) {
190
+ return null;
191
+ }
192
+ const qaBrowser = asObject(value, "qaBrowser");
193
+ return {
194
+ required: optionalBoolean(qaBrowser, "required") ?? false,
195
+ launchTarget: parseQaAppLaunchTarget(qaBrowser.launchTarget ?? qaBrowser.launch_target),
196
+ artifactsPath: optionalString(qaBrowser, "artifactsPath") ?? optionalString(qaBrowser, "artifacts_path") ?? "",
197
+ missingTargetReason: optionalString(qaBrowser, "missingTargetReason") ?? optionalString(qaBrowser, "missing_target_reason")
198
+ };
199
+ }
200
+ function parseQaAppLaunchTarget(value) {
201
+ if (value === undefined || value === null) {
202
+ return null;
203
+ }
204
+ const launchTarget = asObject(value, "qaBrowser.launchTarget");
205
+ const routes = launchTarget.routes;
206
+ if (routes !== undefined && routes !== null && !Array.isArray(routes)) {
207
+ throw new Error("qaBrowser.launchTarget.routes must be an array when provided.");
208
+ }
209
+ return {
210
+ url: optionalString(launchTarget, "url"),
211
+ command: optionalString(launchTarget, "command"),
212
+ working_directory: optionalString(launchTarget, "working_directory"),
213
+ ready_url: optionalString(launchTarget, "ready_url"),
214
+ routes: Array.isArray(routes)
215
+ ? routes.map((entry, index) => {
216
+ if (typeof entry !== "string") {
217
+ throw new Error(`qaBrowser.launchTarget.routes[${index}] must be a string.`);
218
+ }
219
+ return entry;
220
+ }).filter((entry) => entry.trim().length > 0)
221
+ : [],
222
+ notes: optionalString(launchTarget, "notes")
223
+ };
224
+ }
225
+ function parseQaRework(value) {
226
+ if (value === undefined || value === null) {
227
+ return null;
228
+ }
229
+ const qaRework = asObject(value, "qaRework");
230
+ const findingIdsValue = qaRework.findingIds;
231
+ if (!Array.isArray(findingIdsValue)) {
232
+ throw new Error("qaRework.findingIds must be an array.");
233
+ }
234
+ const findingsValue = qaRework.findings;
235
+ if (!Array.isArray(findingsValue)) {
236
+ throw new Error("qaRework.findings must be an array.");
237
+ }
238
+ return {
239
+ attempt: requiredNumber(qaRework, "attempt"),
240
+ maxAttempts: requiredNumber(qaRework, "maxAttempts"),
241
+ packetPath: requiredString(qaRework, "packetPath"),
242
+ packetJson: requiredString(qaRework, "packetJson"),
243
+ findingIds: findingIdsValue.map((entry, index) => {
244
+ if (typeof entry !== "string" || entry.trim().length === 0) {
245
+ throw new Error(`qaRework.findingIds[${index}] must be a non-empty string.`);
246
+ }
247
+ return entry;
248
+ }),
249
+ findings: findingsValue.map((entry, index) => asObject(entry, `qaRework.findings[${index}]`))
250
+ };
251
+ }
252
+ function parseArchitectRevision(value) {
253
+ if (value === undefined || value === null) {
254
+ return null;
255
+ }
256
+ const revision = asObject(value, "architectRevision");
257
+ return {
258
+ priorPlanId: requiredString(revision, "priorPlanId"),
259
+ priorContentHash: requiredString(revision, "priorContentHash"),
260
+ feedback: requiredString(revision, "feedback")
261
+ };
262
+ }
263
+ function parseStoryDevelop(value) {
264
+ if (value === undefined || value === null) {
265
+ return null;
266
+ }
267
+ const storyDevelop = asObject(value, "storyDevelop");
268
+ const dependencyWorkItemIds = storyDevelop.dependencyWorkItemIds;
269
+ if (!Array.isArray(dependencyWorkItemIds)) {
270
+ throw new Error("storyDevelop.dependencyWorkItemIds must be an array.");
271
+ }
272
+ return {
273
+ storyWorkItemId: requiredString(storyDevelop, "storyWorkItemId"),
274
+ planId: requiredString(storyDevelop, "planId"),
275
+ planContentHash: requiredString(storyDevelop, "planContentHash"),
276
+ architectTaskId: requiredString(storyDevelop, "architectTaskId"),
277
+ phaseId: requiredString(storyDevelop, "phaseId"),
278
+ dependencyWorkItemIds: dependencyWorkItemIds.map((entry, index) => {
279
+ if (typeof entry !== "string" || entry.trim().length === 0) {
280
+ throw new Error(`storyDevelop.dependencyWorkItemIds[${index}] must be a non-empty string.`);
281
+ }
282
+ return entry.trim();
283
+ })
284
+ };
285
+ }
286
+ function parseStoryQa(value) {
287
+ if (value === undefined || value === null) {
288
+ return null;
289
+ }
290
+ const storyQa = asObject(value, "storyQa");
291
+ return {
292
+ storyWorkItemId: requiredString(storyQa, "storyWorkItemId"),
293
+ planId: requiredString(storyQa, "planId"),
294
+ planContentHash: requiredString(storyQa, "planContentHash"),
295
+ worktreeKey: requiredString(storyQa, "worktreeKey"),
296
+ branch: requiredString(storyQa, "branch"),
297
+ implementationPlanJson: optionalString(storyQa, "implementationPlanJson"),
298
+ qaPlanJson: optionalString(storyQa, "qaPlanJson"),
299
+ finalQaChecks: parseStringArray(storyQa.finalQaChecks, "storyQa.finalQaChecks"),
300
+ childTasks: parseStoryQaChildTasks(storyQa.childTasks),
301
+ dependencies: parseStoryQaDependencies(storyQa.dependencies)
302
+ };
303
+ }
304
+ function parseStoryQaChildTasks(value) {
305
+ if (!Array.isArray(value)) {
306
+ throw new Error("storyQa.childTasks must be an array.");
307
+ }
308
+ return value.map((entry, index) => {
309
+ const child = asObject(entry, `storyQa.childTasks[${index}]`);
310
+ return {
311
+ workItemId: requiredString(child, "workItemId"),
312
+ title: requiredString(child, "title"),
313
+ architectTaskId: requiredString(child, "architectTaskId"),
314
+ phaseId: requiredString(child, "phaseId"),
315
+ phaseOrder: requiredNumber(child, "phaseOrder"),
316
+ dependencyWorkItemIds: parseStringArray(child.dependencyWorkItemIds, `storyQa.childTasks[${index}].dependencyWorkItemIds`),
317
+ spec: parseStoryQaSpec(child.spec, `storyQa.childTasks[${index}].spec`),
318
+ implementationArtifact: parseStoryQaArtifact(child.implementationArtifact, `storyQa.childTasks[${index}].implementationArtifact`),
319
+ selfReviewArtifact: parseStoryQaArtifact(child.selfReviewArtifact, `storyQa.childTasks[${index}].selfReviewArtifact`),
320
+ validationArtifact: parseStoryQaArtifact(child.validationArtifact, `storyQa.childTasks[${index}].validationArtifact`),
321
+ qaArtifact: parseStoryQaArtifact(child.qaArtifact, `storyQa.childTasks[${index}].qaArtifact`)
322
+ };
323
+ });
324
+ }
325
+ function parseStoryQaSpec(value, label) {
326
+ if (value === undefined || value === null) {
327
+ return null;
328
+ }
329
+ const spec = asObject(value, label);
330
+ return {
331
+ version: requiredNumber(spec, "version"),
332
+ status: requiredString(spec, "status"),
333
+ path: requiredString(spec, "path"),
334
+ contentHash: requiredString(spec, "contentHash"),
335
+ body: requiredString(spec, "body")
336
+ };
337
+ }
338
+ function parseStoryQaArtifact(value, label) {
339
+ if (value === undefined || value === null) {
340
+ return null;
341
+ }
342
+ const artifact = asObject(value, label);
343
+ return {
344
+ artifactId: requiredString(artifact, "artifactId"),
345
+ cycleRunId: requiredString(artifact, "cycleRunId"),
346
+ phase: requiredString(artifact, "phase"),
347
+ status: requiredString(artifact, "status"),
348
+ producedAt: requiredString(artifact, "producedAt"),
349
+ path: optionalString(artifact, "path"),
350
+ verdictJson: optionalString(artifact, "verdictJson"),
351
+ evidenceJson: optionalString(artifact, "evidenceJson"),
352
+ findingsJson: optionalString(artifact, "findingsJson"),
353
+ report: optionalString(artifact, "report")
354
+ };
355
+ }
356
+ function parseStoryQaDependencies(value) {
357
+ if (!Array.isArray(value)) {
358
+ throw new Error("storyQa.dependencies must be an array.");
359
+ }
360
+ return value.map((entry, index) => {
361
+ const dependency = asObject(entry, `storyQa.dependencies[${index}]`);
362
+ return {
363
+ fromWorkItemId: requiredString(dependency, "fromWorkItemId").trim(),
364
+ toWorkItemId: requiredString(dependency, "toWorkItemId").trim()
365
+ };
366
+ });
367
+ }
368
+ function parseStringArray(value, label) {
369
+ if (!Array.isArray(value)) {
370
+ throw new Error(`${label} must be an array.`);
371
+ }
372
+ return value.map((entry, index) => {
373
+ if (typeof entry !== "string" || entry.trim().length === 0) {
374
+ throw new Error(`${label}[${index}] must be a non-empty string.`);
375
+ }
376
+ return entry.trim();
377
+ });
378
+ }
141
379
  function parseArtifacts(value) {
142
380
  if (value === undefined || value === null) {
143
381
  return [];
@@ -153,6 +391,7 @@ function parseArtifacts(value) {
153
391
  scope: requiredString(artifact, "scope"),
154
392
  body: optionalString(artifact, "body") ?? "",
155
393
  contentHash: requiredString(artifact, "contentHash"),
394
+ version: optionalPositiveInteger(artifact, "version") ?? 1,
156
395
  lane: optionalString(artifact, "lane"),
157
396
  event: optionalString(artifact, "event")
158
397
  };
@@ -188,6 +427,26 @@ function requiredBoolean(source, key) {
188
427
  }
189
428
  return value;
190
429
  }
430
+ function optionalBoolean(source, key) {
431
+ const value = source[key];
432
+ if (value === undefined || value === null) {
433
+ return null;
434
+ }
435
+ if (typeof value !== "boolean") {
436
+ throw new Error(`${key} must be a boolean when provided.`);
437
+ }
438
+ return value;
439
+ }
440
+ function optionalPositiveInteger(source, key) {
441
+ const value = source[key];
442
+ if (value === undefined || value === null) {
443
+ return null;
444
+ }
445
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
446
+ throw new Error(`${key} must be a positive integer when provided.`);
447
+ }
448
+ return value;
449
+ }
191
450
  function requiredNumber(source, key) {
192
451
  const value = source[key];
193
452
  if (typeof value !== "number" || !Number.isFinite(value)) {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const forgeBridgeVersion = "0.1.1-dev.32d4dd5874ee";
1
+ export declare const forgeBridgeVersion = "0.1.1-dev.40fbc494669b";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const forgeBridgeVersion = "0.1.1-dev.32d4dd5874ee";
1
+ export const forgeBridgeVersion = "0.1.1-dev.40fbc494669b";
@@ -1,5 +1,6 @@
1
1
  import type { DispatchResolvedArtifact } from "../session/session.types.js";
2
2
  export declare function injectWorktreeArtifacts(worktreePath: string, artifacts: readonly DispatchResolvedArtifact[]): Promise<void>;
3
+ export declare function filterArtifactsForLane(artifacts: readonly DispatchResolvedArtifact[], lane: string, phase: string): readonly DispatchResolvedArtifact[];
3
4
  export declare function writeArtifact(worktreePath: string, artifact: DispatchResolvedArtifact): Promise<void>;
4
5
  export declare function selectLaneCommand(artifacts: readonly DispatchResolvedArtifact[], lane: string, phase: string): DispatchResolvedArtifact | undefined;
5
6
  export declare function resolveCommandSlug(lane: string, phase: string): string;
@@ -1,11 +1,73 @@
1
1
  import crypto from "node:crypto";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
+ const DEBUG_OPERATIONAL_SKILLS = new Set(["debug-repro", "debug-root-cause", "debug-fix", "debug-regression"]);
5
+ const DEBUG_OPERATIONAL_HOOKS = new Set(["debug-gate", "debug-tool-hook", "debug-rework"]);
6
+ const ARCHITECT_OPERATIONAL_SKILLS = new Set([
7
+ "architect-repo-map",
8
+ "architect-acceptance-map",
9
+ "architect-contract-designer",
10
+ "architect-dag-planner",
11
+ "architect-test-plan-designer",
12
+ "architect-risk-review",
13
+ "architect-integration-strategy",
14
+ "architect-handoff-writer",
15
+ "architect-mcp-contract",
16
+ "architect-semantic-validator"
17
+ ]);
18
+ const ARCHITECT_OPERATIONAL_HOOKS = new Set([
19
+ "architect-preflight",
20
+ "architect-context",
21
+ "architect-repo-map",
22
+ "architect-acceptance-map",
23
+ "architect-contracts",
24
+ "architect-plan",
25
+ "architect-qa-seed",
26
+ "architect-integration",
27
+ "architect-mcp-contract",
28
+ "architect-validate-plan",
29
+ "architect-risk-gate",
30
+ "architect-mcp-complete",
31
+ "architect-complete",
32
+ "architect-revise"
33
+ ]);
34
+ const DRAFT_OPERATIONAL_SKILLS = new Set([
35
+ "draft-intent",
36
+ "draft-repo-context",
37
+ "draft-graph-context",
38
+ "draft-product-scope",
39
+ "draft-work-order-plan",
40
+ "draft-critic",
41
+ "draft-proposal"
42
+ ]);
43
+ const DRAFT_OPERATIONAL_HOOKS = new Set([
44
+ "draft-intent",
45
+ "draft-repo-context",
46
+ "draft-graph-context",
47
+ "draft-product-scope",
48
+ "draft-work-order-plan",
49
+ "draft-critic",
50
+ "draft-proposal"
51
+ ]);
4
52
  export async function injectWorktreeArtifacts(worktreePath, artifacts) {
5
53
  for (const artifact of artifacts) {
6
54
  await writeArtifact(worktreePath, artifact);
7
55
  }
8
56
  }
57
+ export function filterArtifactsForLane(artifacts, lane, phase) {
58
+ const normalizedLane = lane.trim().toLowerCase();
59
+ const commandSlug = resolveCommandSlug(lane, phase).toLowerCase();
60
+ if (normalizedLane === "debug") {
61
+ return artifacts.filter((artifact) => shouldInjectDebugArtifact(artifact, commandSlug));
62
+ }
63
+ if (normalizedLane === "architect") {
64
+ return artifacts.filter((artifact) => shouldInjectArchitectArtifact(artifact, commandSlug));
65
+ }
66
+ if (normalizedLane === "draft") {
67
+ return artifacts.filter((artifact) => shouldInjectDraftArtifact(artifact, commandSlug));
68
+ }
69
+ return artifacts;
70
+ }
9
71
  export async function writeArtifact(worktreePath, artifact) {
10
72
  const writtenHash = sha256(artifact.body);
11
73
  if (writtenHash !== artifact.contentHash.toLowerCase()) {
@@ -29,14 +91,17 @@ export function selectLaneCommand(artifacts, lane, phase) {
29
91
  return artifacts.find((artifact) => normalizedArtifactKind(artifact.kind) === "command" && artifact.slug.trim().toLowerCase() === slug);
30
92
  }
31
93
  export function resolveCommandSlug(lane, phase) {
32
- const normalizedPhase = phase.trim().toLowerCase();
33
- if (normalizedPhase === "self_review") {
34
- return "self-review-prompt";
94
+ const normalizedLane = lane.trim().toLowerCase();
95
+ if (normalizedLane === "debug") {
96
+ return "debug-prompt";
35
97
  }
98
+ const normalizedPhase = phase.trim().toLowerCase().replace(/-/g, "_");
36
99
  if (normalizedPhase === "rework") {
37
100
  return "rework-prompt";
38
101
  }
39
- switch (lane.trim().toLowerCase()) {
102
+ switch (normalizedLane) {
103
+ case "draft":
104
+ return "draft-prompt";
40
105
  case "architect":
41
106
  return "architect-prompt";
42
107
  case "qa":
@@ -45,6 +110,54 @@ export function resolveCommandSlug(lane, phase) {
45
110
  return "dev-prompt";
46
111
  }
47
112
  }
113
+ function shouldInjectDebugArtifact(artifact, commandSlug) {
114
+ if (artifact.scope.trim().toLowerCase() !== "operational") {
115
+ return false;
116
+ }
117
+ const slug = artifact.slug.trim().toLowerCase();
118
+ switch (normalizedArtifactKind(artifact.kind)) {
119
+ case "command":
120
+ return slug === commandSlug;
121
+ case "skill":
122
+ return DEBUG_OPERATIONAL_SKILLS.has(slug);
123
+ case "hook":
124
+ return DEBUG_OPERATIONAL_HOOKS.has(slug);
125
+ default:
126
+ return true;
127
+ }
128
+ }
129
+ function shouldInjectArchitectArtifact(artifact, commandSlug) {
130
+ if (artifact.scope.trim().toLowerCase() !== "operational") {
131
+ return false;
132
+ }
133
+ const slug = artifact.slug.trim().toLowerCase();
134
+ switch (normalizedArtifactKind(artifact.kind)) {
135
+ case "command":
136
+ return slug === commandSlug;
137
+ case "skill":
138
+ return ARCHITECT_OPERATIONAL_SKILLS.has(slug);
139
+ case "hook":
140
+ return ARCHITECT_OPERATIONAL_HOOKS.has(slug);
141
+ default:
142
+ return false;
143
+ }
144
+ }
145
+ function shouldInjectDraftArtifact(artifact, commandSlug) {
146
+ if (artifact.scope.trim().toLowerCase() !== "operational") {
147
+ return false;
148
+ }
149
+ const slug = artifact.slug.trim().toLowerCase();
150
+ switch (normalizedArtifactKind(artifact.kind)) {
151
+ case "command":
152
+ return slug === commandSlug;
153
+ case "skill":
154
+ return DRAFT_OPERATIONAL_SKILLS.has(slug);
155
+ case "hook":
156
+ return DRAFT_OPERATIONAL_HOOKS.has(slug);
157
+ default:
158
+ return false;
159
+ }
160
+ }
48
161
  export function buildSpecBody(laneCommand, workItem, phase = "implementation") {
49
162
  const prompt = laneCommand.body.replaceAll("{workItemId}", workItem.id);
50
163
  return `---
@@ -11,6 +11,7 @@ export declare class GitWorktreeService implements WorktreeService {
11
11
  private resolveMatchingRepo;
12
12
  private ensureBaseBranch;
13
13
  private gitRefExists;
14
+ private currentBranch;
14
15
  private ensureGitIdentity;
15
16
  }
16
17
  export declare function normalizeBranchName(value: string | null | undefined, fallback: string): string;