@loucompanion/forge-bridge 0.1.1-dev.caea7216568f → 0.1.1-dev.e646c8fd6b9b
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 +213 -25
- package/dist/bridge/bridge.types.d.ts +13 -2
- package/dist/cli/cli.service.js +225 -10
- package/dist/cli/local-config.d.ts +1 -0
- package/dist/cli/local-config.js +36 -4
- package/dist/config.js +1 -1
- package/dist/session/internals.d.ts +1 -1
- package/dist/session/phase-completion.d.ts +34 -5
- package/dist/session/phase-completion.js +1498 -33
- package/dist/session/provider-cli/provider-cli.base.d.ts +3 -1
- package/dist/session/provider-cli/provider-cli.service.d.ts +9 -1
- package/dist/session/provider-cli/provider-cli.service.js +1353 -217
- package/dist/session/provider-cli/provider-cli.types.d.ts +5 -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 +47 -5
- package/dist/session/session.types.d.ts +123 -0
- package/dist/session/session.types.js +250 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worktree/artifact-injector.d.ts +1 -0
- package/dist/worktree/artifact-injector.js +78 -1
- package/dist/worktree/worktree.service.d.ts +1 -0
- package/dist/worktree/worktree.service.js +132 -6
- package/dist/worktree/worktree.types.d.ts +8 -1
- package/package.json +8 -3
|
@@ -6,6 +6,11 @@ export interface ProviderCliRunContext {
|
|
|
6
6
|
readonly signal?: AbortSignal;
|
|
7
7
|
emit(event: RunnerDispatchEvent): Promise<void>;
|
|
8
8
|
}
|
|
9
|
+
export interface ProviderCliDraftContext {
|
|
10
|
+
readonly dispatch: RunOnceDispatchPacket;
|
|
11
|
+
readonly signal?: AbortSignal;
|
|
12
|
+
emit(event: RunnerDispatchEvent): Promise<void>;
|
|
13
|
+
}
|
|
9
14
|
export interface ProviderCliRunResult {
|
|
10
15
|
readonly summary: string;
|
|
11
16
|
readonly spec?: RunnerDispatchSpec | null;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function isPathUnderRoot(root: string, target: string): boolean;
|
|
2
|
+
export declare function ensureNoSymbolicLinkPathSegments(root: string, targetDirectory: string, errorMessage: string): Promise<void>;
|
|
3
|
+
export declare function sha256File(filePath: string): Promise<string>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
export function isPathUnderRoot(root, target) {
|
|
6
|
+
const relative = path.relative(path.resolve(root), path.resolve(target));
|
|
7
|
+
return Boolean(relative)
|
|
8
|
+
&& relative !== ".."
|
|
9
|
+
&& !relative.startsWith(`..${path.sep}`)
|
|
10
|
+
&& !path.isAbsolute(relative);
|
|
11
|
+
}
|
|
12
|
+
export async function ensureNoSymbolicLinkPathSegments(root, targetDirectory, errorMessage) {
|
|
13
|
+
const resolvedRoot = path.resolve(root);
|
|
14
|
+
const resolvedTargetDirectory = path.resolve(targetDirectory);
|
|
15
|
+
const relative = path.relative(resolvedRoot, resolvedTargetDirectory);
|
|
16
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
17
|
+
throw new Error(errorMessage);
|
|
18
|
+
}
|
|
19
|
+
let current = resolvedRoot;
|
|
20
|
+
const rootStat = await fs.lstat(current);
|
|
21
|
+
if (rootStat.isSymbolicLink()) {
|
|
22
|
+
throw new Error(errorMessage);
|
|
23
|
+
}
|
|
24
|
+
if (!relative) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
for (const segment of relative.split(path.sep)) {
|
|
28
|
+
if (!segment) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
current = path.join(current, segment);
|
|
32
|
+
const stat = await fs.lstat(current);
|
|
33
|
+
if (stat.isSymbolicLink()) {
|
|
34
|
+
throw new Error(errorMessage);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function sha256File(filePath) {
|
|
39
|
+
const hash = crypto.createHash("sha256");
|
|
40
|
+
for await (const chunk of createReadStream(filePath)) {
|
|
41
|
+
hash.update(chunk);
|
|
42
|
+
}
|
|
43
|
+
return hash.digest("hex");
|
|
44
|
+
}
|
|
@@ -13,7 +13,9 @@ export class BridgeSessionService {
|
|
|
13
13
|
const redactionSecrets = exactSecrets(dispatch);
|
|
14
14
|
const events = [];
|
|
15
15
|
const emit = async (event) => {
|
|
16
|
-
|
|
16
|
+
if (signal?.aborted) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
17
19
|
const redacted = redactSecrets(event, redactionSecrets);
|
|
18
20
|
events.push(redacted);
|
|
19
21
|
await sink.emit(redacted);
|
|
@@ -25,15 +27,39 @@ export class BridgeSessionService {
|
|
|
25
27
|
content: "forge-bridge accepted run-once dispatch.",
|
|
26
28
|
eventType: "dispatch_accepted"
|
|
27
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
|
+
}
|
|
28
47
|
const worktree = await this.worktrees.prepare({
|
|
29
48
|
repo: dispatch.repo,
|
|
30
49
|
workItem: dispatch.workItem,
|
|
31
50
|
baseBranch: dispatch.cycleRun.baseBranch || dispatch.cycleRun.requestedBase,
|
|
51
|
+
worktreeKey: dispatch.lease.worktreeKey,
|
|
52
|
+
ownerType: dispatch.cycleRun.ownerType ?? dispatch.lease.ownerType ?? null,
|
|
53
|
+
ownerId: dispatch.cycleRun.ownerId ?? dispatch.lease.ownerId ?? null,
|
|
54
|
+
lane: dispatch.execution.lane,
|
|
55
|
+
phase: dispatch.cycleRun.phase,
|
|
32
56
|
allowedRepoRoot: dispatch.serverLocal.repoRoot,
|
|
33
57
|
cleanupWorktrees: dispatch.serverLocal.cleanupWorktrees,
|
|
34
58
|
artifacts: dispatch.artifacts,
|
|
59
|
+
qaRework: dispatch.qaRework,
|
|
35
60
|
warmPathOverrides: dispatch.warmPathOverrides
|
|
36
61
|
});
|
|
62
|
+
const providerDispatch = withPreparedQaReworkPacket(dispatch, worktree.qaReworkPacketPath);
|
|
37
63
|
throwIfAborted(signal);
|
|
38
64
|
await emit({
|
|
39
65
|
role: "runner",
|
|
@@ -49,18 +75,22 @@ export class BridgeSessionService {
|
|
|
49
75
|
const lane = dispatch.execution.lane.toLowerCase();
|
|
50
76
|
const role = dispatch.execution.role.toLowerCase();
|
|
51
77
|
const isQa = role === "qa" || lane === "qa";
|
|
78
|
+
const isArchitect = role === "architect" || lane === "architect" || dispatch.cycleRun.kind.toLowerCase() === "architect";
|
|
52
79
|
const isConflictResolve = role === "conflict_resolve" || lane === "conflict_resolve";
|
|
53
80
|
const context = {
|
|
54
|
-
dispatch,
|
|
81
|
+
dispatch: providerDispatch,
|
|
55
82
|
worktree,
|
|
56
83
|
emit,
|
|
57
84
|
signal
|
|
58
85
|
};
|
|
59
86
|
const run = isQa
|
|
60
87
|
? await this.providerCli.runQa(context)
|
|
61
|
-
:
|
|
62
|
-
? await this.providerCli.
|
|
63
|
-
:
|
|
88
|
+
: isArchitect
|
|
89
|
+
? await this.providerCli.runArchitect(context)
|
|
90
|
+
: isConflictResolve
|
|
91
|
+
? await this.providerCli.runConflictResolve(context)
|
|
92
|
+
: await this.providerCli.runImplementation(context);
|
|
93
|
+
throwIfAborted(signal);
|
|
64
94
|
const phaseArtifacts = run.artifacts?.length ? run.artifacts : run.artifact ? [run.artifact] : [];
|
|
65
95
|
return redactSecrets({
|
|
66
96
|
schemaVersion: 1,
|
|
@@ -104,6 +134,18 @@ function validateApiKeySecretGrant(dispatch) {
|
|
|
104
134
|
function exactSecrets(dispatch) {
|
|
105
135
|
return dispatch.apiKeySecretGrant?.secret ? [dispatch.apiKeySecretGrant.secret] : [];
|
|
106
136
|
}
|
|
137
|
+
function withPreparedQaReworkPacket(dispatch, packetPath) {
|
|
138
|
+
if (!dispatch.qaRework || !packetPath) {
|
|
139
|
+
return dispatch;
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
...dispatch,
|
|
143
|
+
qaRework: {
|
|
144
|
+
...dispatch.qaRework,
|
|
145
|
+
packetPath
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
107
149
|
async function emitDependencyWarmEvents(warm, emit) {
|
|
108
150
|
if (warm.linked.length > 0) {
|
|
109
151
|
await emit({
|
|
@@ -8,9 +8,120 @@ export interface RunOnceDispatchPacket {
|
|
|
8
8
|
readonly lease: DispatchLease;
|
|
9
9
|
readonly execution: DispatchExecution;
|
|
10
10
|
readonly serverLocal: DispatchServerLocalConfig;
|
|
11
|
+
readonly pullRequest?: DispatchPullRequestContext | null;
|
|
11
12
|
readonly apiKeySecretGrant?: ApiKeySecretGrant | null;
|
|
12
13
|
readonly artifacts: readonly DispatchResolvedArtifact[];
|
|
13
14
|
readonly warmPathOverrides?: readonly string[];
|
|
15
|
+
readonly qaBrowser?: DispatchQaBrowserContext | null;
|
|
16
|
+
readonly qaRework?: DispatchQaReworkContext | null;
|
|
17
|
+
readonly architectRevision?: DispatchArchitectRevisionContext | null;
|
|
18
|
+
readonly storyDevelop?: DispatchStoryDevelopContext | null;
|
|
19
|
+
readonly storyQa?: DispatchStoryQaContext | null;
|
|
20
|
+
readonly draft?: DispatchDraftPrompt | null;
|
|
21
|
+
}
|
|
22
|
+
export interface DispatchDraftPrompt {
|
|
23
|
+
readonly kind: string;
|
|
24
|
+
readonly systemPrompt: string;
|
|
25
|
+
readonly userPrompt: string;
|
|
26
|
+
}
|
|
27
|
+
export interface DispatchQaBrowserContext {
|
|
28
|
+
readonly required: boolean;
|
|
29
|
+
readonly launchTarget?: DispatchQaAppLaunchTarget | null;
|
|
30
|
+
readonly artifactsPath: string;
|
|
31
|
+
readonly missingTargetReason?: string | null;
|
|
32
|
+
}
|
|
33
|
+
export interface DispatchQaAppLaunchTarget {
|
|
34
|
+
readonly url?: string | null;
|
|
35
|
+
readonly command?: string | null;
|
|
36
|
+
readonly working_directory?: string | null;
|
|
37
|
+
readonly ready_url?: string | null;
|
|
38
|
+
readonly routes: readonly string[];
|
|
39
|
+
readonly notes?: string | null;
|
|
40
|
+
}
|
|
41
|
+
export interface DispatchQaReworkContext {
|
|
42
|
+
readonly attempt: number;
|
|
43
|
+
readonly maxAttempts: number;
|
|
44
|
+
readonly packetPath: string;
|
|
45
|
+
readonly packetJson: string;
|
|
46
|
+
readonly findingIds: readonly string[];
|
|
47
|
+
readonly findings: readonly Record<string, unknown>[];
|
|
48
|
+
}
|
|
49
|
+
export interface DispatchArchitectRevisionContext {
|
|
50
|
+
readonly priorPlanId: string;
|
|
51
|
+
readonly priorContentHash: string;
|
|
52
|
+
readonly feedback: string;
|
|
53
|
+
}
|
|
54
|
+
export interface DispatchStoryDevelopContext {
|
|
55
|
+
readonly storyWorkItemId: string;
|
|
56
|
+
readonly planId: string;
|
|
57
|
+
readonly planContentHash: string;
|
|
58
|
+
readonly architectTaskId: string;
|
|
59
|
+
readonly phaseId: string;
|
|
60
|
+
readonly dependencyWorkItemIds: readonly string[];
|
|
61
|
+
}
|
|
62
|
+
export interface DispatchStoryQaContext {
|
|
63
|
+
readonly storyWorkItemId: string;
|
|
64
|
+
readonly planId: string;
|
|
65
|
+
readonly planContentHash: string;
|
|
66
|
+
readonly worktreeKey: string;
|
|
67
|
+
readonly branch: string;
|
|
68
|
+
readonly implementationPlanJson?: string | null;
|
|
69
|
+
readonly qaPlanJson?: string | null;
|
|
70
|
+
readonly finalQaChecks: readonly string[];
|
|
71
|
+
readonly childTasks: readonly DispatchStoryQaChildTaskContext[];
|
|
72
|
+
readonly dependencies: readonly DispatchStoryQaDependencyContext[];
|
|
73
|
+
}
|
|
74
|
+
export interface DispatchStoryQaChildTaskContext {
|
|
75
|
+
readonly workItemId: string;
|
|
76
|
+
readonly title: string;
|
|
77
|
+
readonly architectTaskId: string;
|
|
78
|
+
readonly phaseId: string;
|
|
79
|
+
readonly phaseOrder: number;
|
|
80
|
+
readonly dependencyWorkItemIds: readonly string[];
|
|
81
|
+
readonly spec?: DispatchStoryQaChildSpecContext | null;
|
|
82
|
+
readonly implementationArtifact?: DispatchStoryQaArtifactContext | null;
|
|
83
|
+
readonly selfReviewArtifact?: DispatchStoryQaArtifactContext | null;
|
|
84
|
+
readonly validationArtifact?: DispatchStoryQaArtifactContext | null;
|
|
85
|
+
readonly qaArtifact?: DispatchStoryQaArtifactContext | null;
|
|
86
|
+
}
|
|
87
|
+
export interface DispatchStoryQaChildSpecContext {
|
|
88
|
+
readonly version: number;
|
|
89
|
+
readonly status: string;
|
|
90
|
+
readonly path: string;
|
|
91
|
+
readonly contentHash: string;
|
|
92
|
+
readonly body: string;
|
|
93
|
+
}
|
|
94
|
+
export interface DispatchStoryQaArtifactContext {
|
|
95
|
+
readonly artifactId: string;
|
|
96
|
+
readonly cycleRunId: string;
|
|
97
|
+
readonly phase: string;
|
|
98
|
+
readonly status: string;
|
|
99
|
+
readonly producedAt: string;
|
|
100
|
+
readonly path?: string | null;
|
|
101
|
+
readonly verdictJson?: string | null;
|
|
102
|
+
readonly evidenceJson?: string | null;
|
|
103
|
+
readonly findingsJson?: string | null;
|
|
104
|
+
readonly report?: string | null;
|
|
105
|
+
}
|
|
106
|
+
export interface DispatchStoryQaDependencyContext {
|
|
107
|
+
readonly fromWorkItemId: string;
|
|
108
|
+
readonly toWorkItemId: string;
|
|
109
|
+
}
|
|
110
|
+
export interface DispatchPullRequestContext {
|
|
111
|
+
readonly number: number;
|
|
112
|
+
readonly provider: string;
|
|
113
|
+
readonly title: string;
|
|
114
|
+
readonly sourceBranch: string;
|
|
115
|
+
readonly targetBranch: string;
|
|
116
|
+
readonly url: string;
|
|
117
|
+
readonly state: string;
|
|
118
|
+
readonly checkState: string;
|
|
119
|
+
readonly mergeState: string;
|
|
120
|
+
readonly requiredChecksPassed: number;
|
|
121
|
+
readonly requiredChecksTotal: number;
|
|
122
|
+
readonly unresolvedThreads: number;
|
|
123
|
+
readonly humanApproved: boolean;
|
|
124
|
+
readonly hasMergeConflicts: boolean;
|
|
14
125
|
}
|
|
15
126
|
export interface DispatchResolvedArtifact {
|
|
16
127
|
readonly kind: string;
|
|
@@ -41,6 +152,9 @@ export interface DispatchRepo {
|
|
|
41
152
|
}
|
|
42
153
|
export interface DispatchCycleRun {
|
|
43
154
|
readonly id: string;
|
|
155
|
+
readonly ownerType?: string | null;
|
|
156
|
+
readonly ownerId?: string | null;
|
|
157
|
+
readonly workItemId?: string | null;
|
|
44
158
|
readonly kind: string;
|
|
45
159
|
readonly phase: string;
|
|
46
160
|
readonly requestedBase: string;
|
|
@@ -56,6 +170,9 @@ export interface DispatchSession {
|
|
|
56
170
|
}
|
|
57
171
|
export interface DispatchLease {
|
|
58
172
|
readonly id: string;
|
|
173
|
+
readonly ownerType?: string | null;
|
|
174
|
+
readonly ownerId?: string | null;
|
|
175
|
+
readonly workItemId?: string | null;
|
|
59
176
|
readonly worktreeKey: string;
|
|
60
177
|
readonly lane: string;
|
|
61
178
|
readonly fencingToken: string;
|
|
@@ -71,6 +188,7 @@ export interface DispatchExecution {
|
|
|
71
188
|
readonly role: string;
|
|
72
189
|
readonly lane: string;
|
|
73
190
|
readonly requiresApiKeySecretGrant: boolean;
|
|
191
|
+
readonly runnerRepoLocalPath?: string | null;
|
|
74
192
|
}
|
|
75
193
|
export interface ApiKeySecretGrant {
|
|
76
194
|
readonly grantId: string;
|
|
@@ -103,6 +221,11 @@ export interface RunnerDispatchArtifact {
|
|
|
103
221
|
readonly repoPath: string;
|
|
104
222
|
readonly path: string;
|
|
105
223
|
readonly verdictJson: string;
|
|
224
|
+
readonly planJson?: string | null;
|
|
225
|
+
readonly evidenceJson?: string | null;
|
|
226
|
+
readonly findingsJson?: string | null;
|
|
227
|
+
readonly implementationPlanJson?: string | null;
|
|
228
|
+
readonly qaPlanJson?: string | null;
|
|
106
229
|
readonly report: string;
|
|
107
230
|
readonly producedBy: string;
|
|
108
231
|
readonly status: string;
|
|
@@ -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 [];
|
|
@@ -189,6 +427,16 @@ function requiredBoolean(source, key) {
|
|
|
189
427
|
}
|
|
190
428
|
return value;
|
|
191
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
|
+
}
|
|
192
440
|
function optionalPositiveInteger(source, key) {
|
|
193
441
|
const value = source[key];
|
|
194
442
|
if (value === undefined || value === null) {
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const forgeBridgeVersion = "0.1.1-dev.
|
|
1
|
+
export declare const forgeBridgeVersion = "0.1.1-dev.e646c8fd6b9b";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const forgeBridgeVersion = "0.1.1-dev.
|
|
1
|
+
export const forgeBridgeVersion = "0.1.1-dev.e646c8fd6b9b";
|
|
@@ -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;
|