@loucompanion/forge-bridge 0.1.1-dev.8dcbc6245e51 → 0.1.1-dev.9219f8477b8a
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.
- package/dist/bridge/bridge.service.d.ts +1 -0
- package/dist/bridge/bridge.service.js +219 -25
- package/dist/bridge/bridge.types.d.ts +19 -2
- package/dist/cli/cli.service.d.ts +27 -2
- package/dist/cli/cli.service.js +630 -25
- package/dist/cli/local-config.d.ts +4 -0
- package/dist/cli/local-config.js +42 -4
- package/dist/config.js +1 -1
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +1 -0
- package/dist/session/internals.d.ts +5 -3
- package/dist/session/internals.js +2 -1
- package/dist/session/phase-completion.d.ts +126 -0
- package/dist/session/phase-completion.js +3233 -0
- package/dist/session/provider-cli/index.d.ts +2 -2
- package/dist/session/provider-cli/index.js +1 -1
- package/dist/session/provider-cli/provider-cli.base.d.ts +5 -3
- package/dist/session/provider-cli/provider-cli.service.d.ts +64 -4
- package/dist/session/provider-cli/provider-cli.service.js +2787 -51
- package/dist/session/provider-cli/provider-cli.types.d.ts +7 -0
- package/dist/session/provider-cli/provider-usage.d.ts +25 -0
- package/dist/session/provider-cli/provider-usage.js +64 -0
- package/dist/session/qa-sidecar-validation.d.ts +3 -0
- package/dist/session/qa-sidecar-validation.js +44 -0
- package/dist/session/session.service.js +62 -11
- package/dist/session/session.types.d.ts +132 -0
- package/dist/session/session.types.js +261 -2
- package/dist/shared/cursor-agent/cursor-agent.launch.d.ts +9 -0
- package/dist/shared/cursor-agent/cursor-agent.launch.js +66 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worktree/artifact-injector.d.ts +2 -1
- package/dist/worktree/artifact-injector.js +125 -9
- package/dist/worktree/worktree.service.d.ts +3 -0
- package/dist/worktree/worktree.service.js +239 -8
- package/dist/worktree/worktree.types.d.ts +8 -1
- package/package.json +8 -3
|
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { GitClient } from "../shared/git/git.js";
|
|
4
4
|
import { ensureDirectory, isSubPath, resolveExistingDirectoryPath, safeSegment } from "../shared/path/path.js";
|
|
5
|
-
import { injectWorktreeArtifacts } from "./artifact-injector.js";
|
|
5
|
+
import { filterArtifactsForLane, injectWorktreeArtifacts } from "./artifact-injector.js";
|
|
6
6
|
import { warmWorktreeDependencies } from "./dependency-warmer.js";
|
|
7
7
|
export class GitWorktreeService {
|
|
8
8
|
git;
|
|
@@ -17,9 +17,12 @@ export class GitWorktreeService {
|
|
|
17
17
|
await this.ensureBaseBranch(repoPath, baseBranch);
|
|
18
18
|
const shortId = request.workItem.id.replace(/-/g, "").slice(0, 12);
|
|
19
19
|
const kind = safeSegment(request.workItem.kind, "work-item");
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
20
|
+
const fallbackWorktreeKey = `${kind}/${shortId}`;
|
|
21
|
+
const worktreeKey = request.worktreeKey?.trim() || fallbackWorktreeKey;
|
|
22
|
+
const worktreeSegments = worktreeKeySegments(worktreeKey, kind, shortId);
|
|
23
|
+
const branchName = normalizeBranchName(worktreeSegments.join("/"), "work-item/unknown");
|
|
24
|
+
const worktreeRoot = resolveWorktreeRoot(allowedRoot, repoPath);
|
|
25
|
+
const worktreePath = path.join(worktreeRoot, ...worktreeSegments);
|
|
23
26
|
const resolvedWorktreeParent = await ensureDirectory(path.dirname(worktreePath));
|
|
24
27
|
if (!isSubPath(allowedRoot, resolvedWorktreeParent)) {
|
|
25
28
|
throw new Error(`Worktree path must stay under ${allowedRoot}.`);
|
|
@@ -35,10 +38,17 @@ export class GitWorktreeService {
|
|
|
35
38
|
if (!isSubPath(allowedRoot, resolvedWorktreePath)) {
|
|
36
39
|
throw new Error(`Worktree path must stay under ${allowedRoot}.`);
|
|
37
40
|
}
|
|
41
|
+
const currentBranch = await this.currentBranch(resolvedWorktreePath);
|
|
42
|
+
if (currentBranch !== branchName) {
|
|
43
|
+
throw new Error(`Existing worktree ${resolvedWorktreePath} is on branch '${currentBranch}', expected '${branchName}'.`);
|
|
44
|
+
}
|
|
38
45
|
}
|
|
39
46
|
await this.ensureGitIdentity(resolvedWorktreePath);
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
await clearRunArtifactRoots(resolvedWorktreePath, request);
|
|
48
|
+
const qaReworkPacketPath = await materializeQaReworkPacket(resolvedWorktreePath, request);
|
|
49
|
+
const artifacts = filterArtifactsForLane(request.artifacts, request.lane, request.phase);
|
|
50
|
+
if (artifacts.length > 0) {
|
|
51
|
+
await injectWorktreeArtifacts(resolvedWorktreePath, artifacts);
|
|
42
52
|
}
|
|
43
53
|
const dependencyWarm = await warmWorktreeDependencies({
|
|
44
54
|
baseCheckoutPath: repoPath,
|
|
@@ -52,7 +62,8 @@ export class GitWorktreeService {
|
|
|
52
62
|
branchName,
|
|
53
63
|
baseBranch,
|
|
54
64
|
cleanupWorktrees: request.cleanupWorktrees,
|
|
55
|
-
dependencyWarm
|
|
65
|
+
dependencyWarm,
|
|
66
|
+
qaReworkPacketPath
|
|
56
67
|
};
|
|
57
68
|
}
|
|
58
69
|
async release(handle) {
|
|
@@ -78,7 +89,11 @@ export class GitWorktreeService {
|
|
|
78
89
|
if (!repo.cloneOnDemand) {
|
|
79
90
|
throw new Error("Repo has no server-local path and cloneOnDemand is disabled.");
|
|
80
91
|
}
|
|
81
|
-
const
|
|
92
|
+
const discovered = await this.findExistingRepoPath(repo, allowedRoot);
|
|
93
|
+
if (discovered) {
|
|
94
|
+
return discovered;
|
|
95
|
+
}
|
|
96
|
+
const repoPath = path.join(managedRepoRoot(allowedRoot), `${safeSegment(repo.name, "repo")}-${repo.id.replace(/-/g, "")}`);
|
|
82
97
|
if (!(await gitMetadataExists(repoPath, allowedRoot))) {
|
|
83
98
|
const repoPathExists = await pathExists(repoPath);
|
|
84
99
|
if (repoPathExists) {
|
|
@@ -109,6 +124,46 @@ export class GitWorktreeService {
|
|
|
109
124
|
}
|
|
110
125
|
return resolved;
|
|
111
126
|
}
|
|
127
|
+
async findExistingRepoPath(repo, allowedRoot) {
|
|
128
|
+
const expectedRemote = normalizeRemote(repo.remoteUrl);
|
|
129
|
+
const rawName = repo.name.trim();
|
|
130
|
+
const nameCandidates = uniqueStrings([
|
|
131
|
+
isSinglePathSegment(rawName) ? path.join(allowedRoot, rawName) : "",
|
|
132
|
+
path.join(allowedRoot, safeSegment(repo.name, "repo"))
|
|
133
|
+
]);
|
|
134
|
+
for (const candidate of nameCandidates) {
|
|
135
|
+
const resolved = await this.resolveMatchingRepo(candidate, allowedRoot, expectedRemote);
|
|
136
|
+
if (resolved) {
|
|
137
|
+
return resolved;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (expectedRemote.length > 0) {
|
|
141
|
+
const entries = await fs.readdir(allowedRoot, { withFileTypes: true }).catch(() => []);
|
|
142
|
+
for (const entry of entries) {
|
|
143
|
+
if (!entry.isDirectory() || entry.name === ".forge-worktrees") {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const resolved = await this.resolveMatchingRepo(path.join(allowedRoot, entry.name), allowedRoot, expectedRemote);
|
|
147
|
+
if (resolved) {
|
|
148
|
+
return resolved;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
async resolveMatchingRepo(candidate, allowedRoot, expectedRemote) {
|
|
155
|
+
if (!(await gitMetadataExists(candidate, allowedRoot))) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
if (expectedRemote.length > 0) {
|
|
159
|
+
const origin = await this.git.run(candidate, ["remote", "get-url", "origin"]).catch(() => "");
|
|
160
|
+
if (normalizeRemote(origin) !== expectedRemote) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const resolved = await resolveExistingDirectoryPath(candidate);
|
|
165
|
+
return isSubPath(allowedRoot, resolved) ? resolved : null;
|
|
166
|
+
}
|
|
112
167
|
async ensureBaseBranch(repoPath, baseBranch) {
|
|
113
168
|
if (await this.gitRefExists(repoPath, `refs/heads/${baseBranch}`)) {
|
|
114
169
|
return;
|
|
@@ -127,11 +182,23 @@ export class GitWorktreeService {
|
|
|
127
182
|
async gitRefExists(repoPath, gitRef) {
|
|
128
183
|
return this.git.run(repoPath, ["show-ref", "--verify", "--quiet", gitRef]).then(() => true).catch(() => false);
|
|
129
184
|
}
|
|
185
|
+
async currentBranch(repoPath) {
|
|
186
|
+
return (await this.git.run(repoPath, ["rev-parse", "--abbrev-ref", "HEAD"])).trim();
|
|
187
|
+
}
|
|
130
188
|
async ensureGitIdentity(repoPath) {
|
|
131
189
|
await this.git.run(repoPath, ["config", "user.email", "forge-bridge@forge.local"]);
|
|
132
190
|
await this.git.run(repoPath, ["config", "user.name", "Forge Bridge"]);
|
|
133
191
|
}
|
|
134
192
|
}
|
|
193
|
+
function managedRepoRoot(allowedRoot) {
|
|
194
|
+
return path.join(allowedRoot, ".forge-worktrees", "_repos");
|
|
195
|
+
}
|
|
196
|
+
function resolveWorktreeRoot(allowedRoot, repoPath) {
|
|
197
|
+
const baseName = path.basename(repoPath);
|
|
198
|
+
return isSubPath(managedRepoRoot(allowedRoot), repoPath)
|
|
199
|
+
? path.join(allowedRoot, ".forge-worktrees", baseName)
|
|
200
|
+
: path.join(path.dirname(repoPath), ".forge-worktrees", baseName);
|
|
201
|
+
}
|
|
135
202
|
function isCloneableRemote(remoteUrl) {
|
|
136
203
|
const trimmed = remoteUrl?.trim() ?? "";
|
|
137
204
|
if (trimmed.length === 0 || trimmed.startsWith("local://")) {
|
|
@@ -139,6 +206,76 @@ function isCloneableRemote(remoteUrl) {
|
|
|
139
206
|
}
|
|
140
207
|
return trimmed.startsWith("git@") || trimmed.includes("://") || path.isAbsolute(trimmed);
|
|
141
208
|
}
|
|
209
|
+
async function clearRunArtifactRoots(worktreePath, request) {
|
|
210
|
+
const roots = new Set();
|
|
211
|
+
roots.add(workItemArtifactRoot(worktreePath, request.workItem.id));
|
|
212
|
+
const ownerType = normalizeOwnerType(request.ownerType);
|
|
213
|
+
const ownerId = request.ownerId?.trim();
|
|
214
|
+
if (ownerType && ownerId) {
|
|
215
|
+
roots.add(ownerArtifactRoot(worktreePath, ownerType, ownerId));
|
|
216
|
+
}
|
|
217
|
+
for (const root of roots) {
|
|
218
|
+
const resolvedRoot = path.resolve(root);
|
|
219
|
+
if (!isSubPath(worktreePath, resolvedRoot)) {
|
|
220
|
+
throw new Error(`Artifact root must stay under ${worktreePath}.`);
|
|
221
|
+
}
|
|
222
|
+
await ensureNoSymlinkPathSegments(worktreePath, resolvedRoot, "Artifact root must not traverse symlinked path segments.");
|
|
223
|
+
await fs.rm(resolvedRoot, { recursive: true, force: true });
|
|
224
|
+
await fs.mkdir(resolvedRoot, { recursive: true });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async function materializeQaReworkPacket(worktreePath, request) {
|
|
228
|
+
const qaRework = request.qaRework;
|
|
229
|
+
if (!qaRework) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
const artifactRoot = artifactRootForRequest(worktreePath, request);
|
|
233
|
+
const packetFileName = qaReworkPacketFileName(qaRework.packetPath);
|
|
234
|
+
const packetPath = path.join(artifactRoot, packetFileName);
|
|
235
|
+
const paths = new Set([packetPath, path.join(artifactRoot, "qa-rework-packet.json")]);
|
|
236
|
+
const packetJson = qaRework.packetJson.endsWith("\n") ? qaRework.packetJson : `${qaRework.packetJson}\n`;
|
|
237
|
+
for (const target of paths) {
|
|
238
|
+
const resolvedTarget = path.resolve(target);
|
|
239
|
+
if (!isSubPath(worktreePath, resolvedTarget)) {
|
|
240
|
+
throw new Error(`QA rework packet path must stay under ${worktreePath}.`);
|
|
241
|
+
}
|
|
242
|
+
await ensureNoSymlinkPathSegments(worktreePath, path.dirname(resolvedTarget), "QA rework packet path must not traverse symlinked path segments.");
|
|
243
|
+
await fs.mkdir(path.dirname(resolvedTarget), { recursive: true });
|
|
244
|
+
await fs.writeFile(resolvedTarget, packetJson, "utf8");
|
|
245
|
+
}
|
|
246
|
+
return path.resolve(packetPath);
|
|
247
|
+
}
|
|
248
|
+
function artifactRootForRequest(worktreePath, request) {
|
|
249
|
+
const ownerType = normalizeOwnerType(request.ownerType);
|
|
250
|
+
const ownerId = request.ownerId?.trim();
|
|
251
|
+
if (ownerType && ownerId) {
|
|
252
|
+
return ownerArtifactRoot(worktreePath, ownerType, ownerId);
|
|
253
|
+
}
|
|
254
|
+
return workItemArtifactRoot(worktreePath, request.workItem.id);
|
|
255
|
+
}
|
|
256
|
+
function workItemArtifactRoot(worktreePath, workItemId) {
|
|
257
|
+
return path.join(worktreePath, ".forge", "work-items", safeSegment(workItemId, "work item id"), "artifacts");
|
|
258
|
+
}
|
|
259
|
+
function ownerArtifactRoot(worktreePath, ownerType, ownerId) {
|
|
260
|
+
return path.join(worktreePath, ".forge", ownerType === "story" ? "stories" : "work-items", safeSegment(ownerId, "owner id"), "artifacts");
|
|
261
|
+
}
|
|
262
|
+
function normalizeOwnerType(value) {
|
|
263
|
+
const normalized = (value ?? "").trim().toLowerCase().replace(/-/g, "_");
|
|
264
|
+
if (normalized === "story") {
|
|
265
|
+
return "story";
|
|
266
|
+
}
|
|
267
|
+
if (normalized === "work_item" || normalized === "workitem" || normalized === "work item") {
|
|
268
|
+
return "work_item";
|
|
269
|
+
}
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
function qaReworkPacketFileName(packetPath) {
|
|
273
|
+
const normalized = packetPath.trim().replace(/\\/g, "/");
|
|
274
|
+
const fileName = normalized.split("/").filter(Boolean).at(-1) ?? "";
|
|
275
|
+
return /^qa-rework-packet(?:\.attempt-\d+)?\.json$/i.test(fileName)
|
|
276
|
+
? fileName
|
|
277
|
+
: "qa-rework-packet.json";
|
|
278
|
+
}
|
|
142
279
|
export function normalizeBranchName(value, fallback) {
|
|
143
280
|
const branch = (value?.trim() || fallback).trim();
|
|
144
281
|
if (branch.length === 0 ||
|
|
@@ -151,6 +288,7 @@ export function normalizeBranchName(value, fallback) {
|
|
|
151
288
|
branch.endsWith(".") ||
|
|
152
289
|
branch.toLowerCase().endsWith(".lock") ||
|
|
153
290
|
/\s/.test(branch) ||
|
|
291
|
+
// eslint-disable-next-line no-control-regex -- Git branch validation must reject ASCII control characters.
|
|
154
292
|
/[\u0000-\u001f~^:?*[\\]/.test(branch) ||
|
|
155
293
|
branch.split("/").some((part) => part.length === 0 || part.startsWith(".") || part.toLowerCase().endsWith(".lock"))) {
|
|
156
294
|
throw new Error("Git branch name must be valid, without whitespace, and cannot start with '-'.");
|
|
@@ -187,6 +325,39 @@ async function gitMetadataExists(repoPath, allowedRoot) {
|
|
|
187
325
|
const resolvedGitDir = await resolveExistingDirectoryPath(gitDirPath);
|
|
188
326
|
return isSubPath(allowedRoot, resolvedGitDir);
|
|
189
327
|
}
|
|
328
|
+
async function ensureNoSymlinkPathSegments(root, targetPath, errorMessage) {
|
|
329
|
+
const resolvedRoot = path.resolve(root);
|
|
330
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
331
|
+
const relative = path.relative(resolvedRoot, resolvedTarget);
|
|
332
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
333
|
+
throw new Error(errorMessage);
|
|
334
|
+
}
|
|
335
|
+
let current = resolvedRoot;
|
|
336
|
+
const rootStat = await fs.lstat(current);
|
|
337
|
+
if (rootStat.isSymbolicLink()) {
|
|
338
|
+
throw new Error(errorMessage);
|
|
339
|
+
}
|
|
340
|
+
if (!relative) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
for (const segment of relative.split(path.sep)) {
|
|
344
|
+
if (!segment) {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
current = path.join(current, segment);
|
|
348
|
+
const stat = await fs.lstat(current).catch((error) => {
|
|
349
|
+
if (error.code === "ENOENT")
|
|
350
|
+
return null;
|
|
351
|
+
throw error;
|
|
352
|
+
});
|
|
353
|
+
if (!stat) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (stat.isSymbolicLink()) {
|
|
357
|
+
throw new Error(errorMessage);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
190
361
|
async function pathExists(inputPath) {
|
|
191
362
|
return fs.stat(inputPath).then(() => true).catch(() => false);
|
|
192
363
|
}
|
|
@@ -194,3 +365,63 @@ async function directoryExists(inputPath) {
|
|
|
194
365
|
const stat = await fs.stat(inputPath).catch(() => null);
|
|
195
366
|
return stat?.isDirectory() ?? false;
|
|
196
367
|
}
|
|
368
|
+
function uniqueStrings(values) {
|
|
369
|
+
const seen = new Set();
|
|
370
|
+
const result = [];
|
|
371
|
+
for (const value of values) {
|
|
372
|
+
if (!value || seen.has(value)) {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
seen.add(value);
|
|
376
|
+
result.push(value);
|
|
377
|
+
}
|
|
378
|
+
return result;
|
|
379
|
+
}
|
|
380
|
+
function worktreeKeySegments(worktreeKey, fallbackKind, fallbackShortId) {
|
|
381
|
+
const segments = worktreeKey
|
|
382
|
+
.split("/")
|
|
383
|
+
.map((segment) => segment.trim())
|
|
384
|
+
.filter((segment) => segment.length > 0)
|
|
385
|
+
.map((segment) => safeSegment(segment, "worktree"));
|
|
386
|
+
return segments.length > 0 ? segments : [fallbackKind, fallbackShortId];
|
|
387
|
+
}
|
|
388
|
+
function isSinglePathSegment(value) {
|
|
389
|
+
return value.length > 0 && value !== "." && value !== ".." && !value.includes("/") && !value.includes("\\");
|
|
390
|
+
}
|
|
391
|
+
function normalizeRemote(remoteUrl) {
|
|
392
|
+
const trimmed = remoteUrl?.trim() ?? "";
|
|
393
|
+
if (trimmed.length === 0) {
|
|
394
|
+
return "";
|
|
395
|
+
}
|
|
396
|
+
const gitHubSsh = /^git@github\.com:(.+)$/i.exec(trimmed);
|
|
397
|
+
if (gitHubSsh) {
|
|
398
|
+
return stripGitSuffix(`https://github.com/${gitHubSsh[1]}`).toLowerCase();
|
|
399
|
+
}
|
|
400
|
+
if (path.isAbsolute(trimmed)) {
|
|
401
|
+
return normalizeLocalPath(trimmed);
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
const url = new URL(trimmed);
|
|
405
|
+
if (url.protocol === "file:") {
|
|
406
|
+
return normalizeLocalPath(normalizeFileUrlPath(url));
|
|
407
|
+
}
|
|
408
|
+
url.username = "";
|
|
409
|
+
url.password = "";
|
|
410
|
+
url.hash = "";
|
|
411
|
+
return stripGitSuffix(url.toString().replace(/\/$/, "")).toLowerCase();
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
return stripGitSuffix(trimmed.replace(/\\/g, "/")).toLowerCase();
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function stripGitSuffix(value) {
|
|
418
|
+
return value.replace(/\.git$/i, "").replace(/\/$/, "");
|
|
419
|
+
}
|
|
420
|
+
function normalizeFileUrlPath(url) {
|
|
421
|
+
const pathname = decodeURIComponent(url.pathname);
|
|
422
|
+
return /^\/[A-Za-z]:[\\/]/.test(pathname) ? pathname.slice(1) : pathname;
|
|
423
|
+
}
|
|
424
|
+
function normalizeLocalPath(value) {
|
|
425
|
+
const normalized = path.resolve(value).replace(/\\/g, "/");
|
|
426
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
427
|
+
}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import type { DispatchRepo, DispatchWorkItem, DispatchResolvedArtifact } from "../session/session.types.js";
|
|
1
|
+
import type { DispatchQaReworkContext, DispatchRepo, DispatchWorkItem, DispatchResolvedArtifact } from "../session/session.types.js";
|
|
2
2
|
import type { WarmDependenciesResult } from "./dependency-warmer.js";
|
|
3
3
|
export interface WorktreePrepareRequest {
|
|
4
4
|
readonly repo: DispatchRepo;
|
|
5
5
|
readonly workItem: DispatchWorkItem;
|
|
6
6
|
readonly baseBranch: string;
|
|
7
|
+
readonly worktreeKey?: string | null;
|
|
8
|
+
readonly ownerType?: string | null;
|
|
9
|
+
readonly ownerId?: string | null;
|
|
10
|
+
readonly lane: string;
|
|
11
|
+
readonly phase: string;
|
|
7
12
|
readonly allowedRepoRoot: string;
|
|
8
13
|
readonly cleanupWorktrees: boolean;
|
|
9
14
|
readonly artifacts: readonly DispatchResolvedArtifact[];
|
|
15
|
+
readonly qaRework?: DispatchQaReworkContext | null;
|
|
10
16
|
readonly warmPathOverrides?: readonly string[];
|
|
11
17
|
}
|
|
12
18
|
export interface WorktreeHandle {
|
|
@@ -16,4 +22,5 @@ export interface WorktreeHandle {
|
|
|
16
22
|
readonly baseBranch: string;
|
|
17
23
|
readonly cleanupWorktrees: boolean;
|
|
18
24
|
readonly dependencyWarm?: WarmDependenciesResult;
|
|
25
|
+
readonly qaReworkPacketPath?: string;
|
|
19
26
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loucompanion/forge-bridge",
|
|
3
|
-
"version": "0.1.1-dev.
|
|
3
|
+
"version": "0.1.1-dev.9219f8477b8a",
|
|
4
4
|
"description": "Forge runner bridge CLI for connecting local developer machines to Forge dispatch.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"url": "https://github.com/coleman399/Forge/issues"
|
|
31
31
|
},
|
|
32
32
|
"engines": {
|
|
33
|
-
"node": ">=22.
|
|
33
|
+
"node": ">=22.13.0"
|
|
34
34
|
},
|
|
35
35
|
"publishConfig": {
|
|
36
36
|
"access": "public"
|
|
@@ -39,14 +39,19 @@
|
|
|
39
39
|
"build": "tsc -p tsconfig.json",
|
|
40
40
|
"test": "vitest run",
|
|
41
41
|
"test:watch": "vitest",
|
|
42
|
-
"smoke:package": "npm run build && node scripts/smoke-package.mjs"
|
|
42
|
+
"smoke:package": "npm run build && node scripts/smoke-package.mjs",
|
|
43
|
+
"lint": "eslint \"src/**/*.ts\" \"scripts/**/*.mjs\" \"vitest.config.ts\""
|
|
43
44
|
},
|
|
44
45
|
"dependencies": {
|
|
45
46
|
"@microsoft/signalr": "^8.0.17"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
49
|
+
"@eslint/js": "^10.0.1",
|
|
48
50
|
"@types/node": "^22.15.32",
|
|
51
|
+
"eslint": "^10.5.0",
|
|
52
|
+
"globals": "^17.6.0",
|
|
49
53
|
"typescript": "^5.8.3",
|
|
54
|
+
"typescript-eslint": "^8.61.1",
|
|
50
55
|
"vitest": "^3.2.3"
|
|
51
56
|
}
|
|
52
57
|
}
|