@loucompanion/forge-bridge 0.1.1-dev.03b8ba168599 → 0.1.1-dev.2bc1e7f19b3a

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.
@@ -10,4 +10,5 @@ export interface ProviderCliRunResult {
10
10
  readonly summary: string;
11
11
  readonly spec?: RunnerDispatchSpec | null;
12
12
  readonly artifact?: RunnerDispatchArtifact | null;
13
+ readonly artifacts?: readonly RunnerDispatchArtifact[] | null;
13
14
  }
@@ -65,13 +65,15 @@ export class BridgeSessionService {
65
65
  emit,
66
66
  signal
67
67
  });
68
+ const phaseArtifacts = run.artifacts?.length ? run.artifacts : run.artifact ? [run.artifact] : [];
68
69
  return redactSecrets({
69
70
  schemaVersion: 1,
70
71
  exitCode: 0,
71
72
  summary: run.summary,
72
73
  events,
73
74
  spec: run.spec ?? null,
74
- artifact: run.artifact ?? null
75
+ artifact: run.artifact ?? null,
76
+ artifacts: phaseArtifacts
75
77
  }, redactionSecrets);
76
78
  }
77
79
  finally {
@@ -18,6 +18,7 @@ export interface DispatchResolvedArtifact {
18
18
  readonly scope: string;
19
19
  readonly body: string;
20
20
  readonly contentHash: string;
21
+ readonly version: number;
21
22
  readonly lane?: string | null;
22
23
  readonly event?: string | null;
23
24
  }
@@ -101,6 +102,7 @@ export interface RunnerDispatchArtifact {
101
102
  readonly report: string;
102
103
  readonly producedBy: string;
103
104
  readonly status: string;
105
+ readonly phase?: string | null;
104
106
  }
105
107
  export interface RunOnceResultPacket {
106
108
  readonly schemaVersion: 1;
@@ -109,6 +111,7 @@ export interface RunOnceResultPacket {
109
111
  readonly events: readonly RunnerDispatchEvent[];
110
112
  readonly spec?: RunnerDispatchSpec | null;
111
113
  readonly artifact?: RunnerDispatchArtifact | null;
114
+ readonly artifacts?: readonly RunnerDispatchArtifact[] | null;
112
115
  }
113
116
  export interface RunnerEventLine {
114
117
  readonly type: "runner_event";
@@ -153,6 +153,7 @@ function parseArtifacts(value) {
153
153
  scope: requiredString(artifact, "scope"),
154
154
  body: optionalString(artifact, "body") ?? "",
155
155
  contentHash: requiredString(artifact, "contentHash"),
156
+ version: optionalPositiveInteger(artifact, "version") ?? 1,
156
157
  lane: optionalString(artifact, "lane"),
157
158
  event: optionalString(artifact, "event")
158
159
  };
@@ -188,6 +189,16 @@ function requiredBoolean(source, key) {
188
189
  }
189
190
  return value;
190
191
  }
192
+ function optionalPositiveInteger(source, key) {
193
+ const value = source[key];
194
+ if (value === undefined || value === null) {
195
+ return null;
196
+ }
197
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
198
+ throw new Error(`${key} must be a positive integer when provided.`);
199
+ }
200
+ return value;
201
+ }
191
202
  function requiredNumber(source, key) {
192
203
  const value = source[key];
193
204
  if (typeof value !== "number" || !Number.isFinite(value)) {
@@ -0,0 +1,9 @@
1
+ export interface CursorAgentLaunch {
2
+ readonly command: string;
3
+ readonly args: readonly string[];
4
+ }
5
+ export declare function isCursorAgentCommand(command: string): boolean;
6
+ export declare function resolveCursorAgentDirectLaunch(command: string, args: readonly string[]): Promise<CursorAgentLaunch | null>;
7
+ export declare function findLatestCursorAgentVersionDir(versionsRoot: string): Promise<string | null>;
8
+ export declare function compareCursorAgentVersionDirNames(left: string, right: string): number;
9
+ export declare function parseCursorAgentVersionSortKey(name: string): number;
@@ -0,0 +1,66 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ const cursorAgentVersionDirPattern = /^\d{4}\.\d{1,2}\.\d{1,2}-.+/;
4
+ export function isCursorAgentCommand(command) {
5
+ const normalized = command.trim().toLowerCase();
6
+ return normalized === "cursor-agent" || normalized === "agent";
7
+ }
8
+ export async function resolveCursorAgentDirectLaunch(command, args) {
9
+ if (!isCursorAgentCommand(command)) {
10
+ return null;
11
+ }
12
+ const localAppData = process.env.LOCALAPPDATA;
13
+ if (!localAppData) {
14
+ return null;
15
+ }
16
+ const versionDir = await findLatestCursorAgentVersionDir(path.join(localAppData, "cursor-agent", "versions"));
17
+ if (!versionDir) {
18
+ return null;
19
+ }
20
+ const nodePath = path.join(versionDir, "node.exe");
21
+ const indexPath = path.join(versionDir, "index.js");
22
+ if (!(await fileExists(nodePath)) || !(await fileExists(indexPath))) {
23
+ return null;
24
+ }
25
+ return {
26
+ command: nodePath,
27
+ args: [indexPath, ...args]
28
+ };
29
+ }
30
+ export async function findLatestCursorAgentVersionDir(versionsRoot) {
31
+ let entries;
32
+ try {
33
+ entries = await fs.readdir(versionsRoot);
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ const latestName = entries
39
+ .filter((name) => cursorAgentVersionDirPattern.test(name))
40
+ .sort(compareCursorAgentVersionDirNames)
41
+ .at(-1);
42
+ return latestName ? path.join(versionsRoot, latestName) : null;
43
+ }
44
+ export function compareCursorAgentVersionDirNames(left, right) {
45
+ const leftKey = parseCursorAgentVersionSortKey(left);
46
+ const rightKey = parseCursorAgentVersionSortKey(right);
47
+ if (leftKey !== rightKey) {
48
+ return leftKey - rightKey;
49
+ }
50
+ return left.localeCompare(right);
51
+ }
52
+ export function parseCursorAgentVersionSortKey(name) {
53
+ const datePart = name.split("-")[0] ?? "";
54
+ const parts = datePart.split(".");
55
+ if (parts.length !== 3) {
56
+ return 0;
57
+ }
58
+ const year = parts[0] ?? "";
59
+ const month = (parts[1] ?? "").padStart(2, "0");
60
+ const day = (parts[2] ?? "").padStart(2, "0");
61
+ const numeric = Number(`${year}${month}${day}`);
62
+ return Number.isFinite(numeric) ? numeric : 0;
63
+ }
64
+ async function fileExists(inputPath) {
65
+ return fs.stat(inputPath).then((stat) => stat.isFile()).catch(() => false);
66
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const forgeBridgeVersion = "0.1.1-dev.03b8ba168599";
1
+ export declare const forgeBridgeVersion = "0.1.1-dev.2bc1e7f19b3a";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const forgeBridgeVersion = "0.1.1-dev.03b8ba168599";
1
+ export const forgeBridgeVersion = "0.1.1-dev.2bc1e7f19b3a";
@@ -8,4 +8,4 @@ export declare function buildSpecBody(laneCommand: DispatchResolvedArtifact, wor
8
8
  readonly title: string;
9
9
  readonly description: string;
10
10
  readonly acceptanceCriteria: string;
11
- }): string;
11
+ }, phase?: string): string;
@@ -15,11 +15,13 @@ export async function writeArtifact(worktreePath, artifact) {
15
15
  const scope = safeArtifactSegment("scope", artifact.scope);
16
16
  const slug = safeArtifactSegment("slug", artifact.slug);
17
17
  const forgeRoot = path.resolve(worktreePath, ".forge");
18
- const targetDir = path.resolve(forgeRoot, kindFolder, scope);
19
- assertInside(forgeRoot, targetDir, "Artifact directory");
20
- await fs.mkdir(targetDir, { recursive: true });
21
- const filePath = path.resolve(targetDir, `${slug}.md`);
18
+ const scopeDir = path.resolve(forgeRoot, kindFolder, scope);
19
+ assertInside(forgeRoot, scopeDir, "Artifact directory");
20
+ const filePath = normalizedArtifactKind(artifact.kind) === "skill"
21
+ ? path.resolve(scopeDir, slug, "SKILL.md")
22
+ : path.resolve(scopeDir, `${slug}.md`);
22
23
  assertInside(forgeRoot, filePath, "Artifact file");
24
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
23
25
  await fs.writeFile(filePath, artifact.body, "utf8");
24
26
  }
25
27
  export function selectLaneCommand(artifacts, lane, phase) {
@@ -27,10 +29,7 @@ export function selectLaneCommand(artifacts, lane, phase) {
27
29
  return artifacts.find((artifact) => normalizedArtifactKind(artifact.kind) === "command" && artifact.slug.trim().toLowerCase() === slug);
28
30
  }
29
31
  export function resolveCommandSlug(lane, phase) {
30
- const normalizedPhase = phase.trim().toLowerCase();
31
- if (normalizedPhase === "self_review") {
32
- return "self-review-prompt";
33
- }
32
+ const normalizedPhase = phase.trim().toLowerCase().replace(/-/g, "_");
34
33
  if (normalizedPhase === "rework") {
35
34
  return "rework-prompt";
36
35
  }
@@ -43,12 +42,13 @@ export function resolveCommandSlug(lane, phase) {
43
42
  return "dev-prompt";
44
43
  }
45
44
  }
46
- export function buildSpecBody(laneCommand, workItem) {
45
+ export function buildSpecBody(laneCommand, workItem, phase = "implementation") {
47
46
  const prompt = laneCommand.body.replaceAll("{workItemId}", workItem.id);
48
47
  return `---
49
48
  work_item_id: ${workItem.id}
50
49
  status: approved
51
50
  lane: ${laneCommand.lane ?? "develop"}
51
+ phase: ${phase}
52
52
  command_slug: ${laneCommand.slug}
53
53
  ---
54
54
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loucompanion/forge-bridge",
3
- "version": "0.1.1-dev.03b8ba168599",
3
+ "version": "0.1.1-dev.2bc1e7f19b3a",
4
4
  "description": "Forge runner bridge CLI for connecting local developer machines to Forge dispatch.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",