@loucompanion/forge-bridge 0.1.3-dev.d1ca67076aa9 → 0.1.3-dev.e7b62d8fdd33
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/README.md +127 -0
- package/dist/bridge/bridge.base.d.ts +8 -1
- package/dist/bridge/bridge.service.d.ts +93 -3
- package/dist/bridge/bridge.service.js +1358 -47
- package/dist/bridge/bridge.types.d.ts +202 -1
- package/dist/bridge/runner-host-capabilities.d.ts +15 -0
- package/dist/bridge/runner-host-capabilities.js +93 -0
- package/dist/cleanup/cleanup.service.d.ts +14 -2
- package/dist/cleanup/cleanup.service.js +496 -3
- package/dist/cleanup/cleanup.types.d.ts +10 -0
- package/dist/cleanup/index.d.ts +1 -1
- package/dist/cleanup/index.js +1 -1
- package/dist/cleanup/internals.d.ts +2 -2
- package/dist/cleanup/internals.js +1 -1
- package/dist/cli/cli.help.js +47 -1
- package/dist/cli/cli.service.d.ts +45 -2
- package/dist/cli/cli.service.js +378 -26
- package/dist/cli/local-config.d.ts +7 -0
- package/dist/cli/local-config.js +51 -3
- package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
- package/dist/runtime/aspnet-runtime-prebuild.js +340 -0
- package/dist/runtime/attempt-node-runtime-launcher.d.ts +17 -0
- package/dist/runtime/attempt-node-runtime-launcher.js +105 -0
- package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
- package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
- package/dist/runtime/attempt-process-supervisor.d.ts +97 -0
- package/dist/runtime/attempt-process-supervisor.js +386 -0
- package/dist/runtime/docker-engine-probe.d.ts +83 -0
- package/dist/runtime/docker-engine-probe.js +300 -0
- package/dist/runtime/generated-output-reset.d.ts +10 -0
- package/dist/runtime/generated-output-reset.js +103 -0
- package/dist/runtime/host-listener-inspector.d.ts +46 -0
- package/dist/runtime/host-listener-inspector.js +349 -0
- package/dist/runtime/host-port-broker.d.ts +76 -0
- package/dist/runtime/host-port-broker.js +333 -0
- package/dist/runtime/host-runtime-contract.d.ts +47 -0
- package/dist/runtime/host-runtime-contract.js +700 -0
- package/dist/runtime/host-runtime-lease.d.ts +197 -0
- package/dist/runtime/host-runtime-lease.js +2203 -0
- package/dist/runtime/host-runtime-promotion.d.ts +23 -0
- package/dist/runtime/host-runtime-promotion.js +159 -0
- package/dist/runtime/memory-capacity.d.ts +68 -0
- package/dist/runtime/memory-capacity.js +121 -0
- package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
- package/dist/runtime/node-host-runtime-adapter.js +284 -0
- package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
- package/dist/runtime/playwright-host-runtime-adapter.js +231 -0
- package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
- package/dist/runtime/runtime-isolation-lease.js +158 -0
- package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
- package/dist/runtime/workspace-resource-estimator.js +174 -0
- package/dist/sandbox/command-runner.d.ts +4 -0
- package/dist/sandbox/command-runner.js +47 -0
- package/dist/sandbox/contract.d.ts +5 -0
- package/dist/sandbox/contract.js +276 -0
- package/dist/sandbox/event-budget.d.ts +11 -0
- package/dist/sandbox/event-budget.js +27 -0
- package/dist/sandbox/image-policy.d.ts +35 -0
- package/dist/sandbox/image-policy.js +247 -0
- package/dist/sandbox/index.d.ts +5 -0
- package/dist/sandbox/index.js +5 -0
- package/dist/sandbox/lease-watchdog.d.ts +18 -0
- package/dist/sandbox/lease-watchdog.js +67 -0
- package/dist/sandbox/preflight.d.ts +84 -0
- package/dist/sandbox/preflight.js +521 -0
- package/dist/sandbox/process.d.ts +20 -0
- package/dist/sandbox/process.js +209 -0
- package/dist/sandbox/provider-auth/codex.d.ts +26 -0
- package/dist/sandbox/provider-auth/codex.js +212 -0
- package/dist/sandbox/repository-lock.d.ts +5 -0
- package/dist/sandbox/repository-lock.js +121 -0
- package/dist/sandbox/runtime.d.ts +297 -0
- package/dist/sandbox/runtime.js +2338 -0
- package/dist/sandbox/sandbox.types.d.ts +93 -0
- package/dist/sandbox/sandbox.types.js +1 -0
- package/dist/sandbox/smoke.d.ts +76 -0
- package/dist/sandbox/smoke.js +380 -0
- package/dist/sandbox/worker-supervisor.d.ts +13 -0
- package/dist/sandbox/worker-supervisor.js +36 -0
- package/dist/sandbox/worker.d.ts +1 -0
- package/dist/sandbox/worker.js +225 -0
- package/dist/sandbox/workspace.service.d.ts +13 -0
- package/dist/sandbox/workspace.service.js +72 -0
- package/dist/session/managed-attempt-tools.d.ts +21 -0
- package/dist/session/managed-attempt-tools.js +912 -0
- package/dist/session/managed-build-coordinator.d.ts +5 -0
- package/dist/session/managed-build-coordinator.js +105 -0
- package/dist/session/managed-build-receipt.d.ts +21 -0
- package/dist/session/managed-build-receipt.js +335 -0
- package/dist/session/phase-completion.artifacts.d.ts +17 -0
- package/dist/session/phase-completion.artifacts.js +256 -26
- package/dist/session/phase-completion.d.ts +12 -2
- package/dist/session/phase-completion.helpers.d.ts +5 -0
- package/dist/session/phase-completion.helpers.js +51 -10
- package/dist/session/phase-completion.js +14 -3
- package/dist/session/phase-completion.prompts.d.ts +3 -3
- package/dist/session/phase-completion.prompts.js +24 -8
- package/dist/session/phase-completion.runtime.d.ts +13 -1
- package/dist/session/phase-completion.runtime.js +189 -8
- package/dist/session/phase-completion.schemas.js +117 -4
- package/dist/session/phase-completion.validation.d.ts +4 -1
- package/dist/session/phase-completion.validation.js +153 -22
- package/dist/session/pre-publication-validation.d.ts +46 -0
- package/dist/session/pre-publication-validation.js +208 -0
- package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
- package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
- package/dist/session/provider-cli/provider-cli.artifacts.d.ts +3 -2
- package/dist/session/provider-cli/provider-cli.artifacts.js +65 -7
- package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
- package/dist/session/provider-cli/provider-cli.deterministic.d.ts +2 -0
- package/dist/session/provider-cli/provider-cli.deterministic.js +68 -9
- package/dist/session/provider-cli/provider-cli.process.d.ts +26 -0
- package/dist/session/provider-cli/provider-cli.process.js +474 -23
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +16 -2
- package/dist/session/provider-cli/provider-cli.prompts.js +224 -24
- package/dist/session/provider-cli/provider-cli.qa-sidecars.js +1 -0
- package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
- package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
- package/dist/session/provider-cli/provider-cli.runtime.d.ts +1 -1
- package/dist/session/provider-cli/provider-cli.runtime.js +6 -3
- package/dist/session/provider-cli/provider-cli.service.d.ts +26 -3
- package/dist/session/provider-cli/provider-cli.service.js +650 -108
- package/dist/session/provider-cli/provider-cli.types.d.ts +6 -0
- package/dist/session/provider-cli/provider-output-state.d.ts +2 -0
- package/dist/session/provider-cli/provider-output-state.js +91 -16
- package/dist/session/provider-cli/provider-usage.d.ts +7 -0
- package/dist/session/provider-cli/provider-usage.js +28 -5
- package/dist/session/session-redaction.d.ts +3 -0
- package/dist/session/session-redaction.js +38 -0
- package/dist/session/session.base.d.ts +6 -1
- package/dist/session/session.service.d.ts +8 -2
- package/dist/session/session.service.js +549 -58
- package/dist/session/session.types.d.ts +124 -1
- package/dist/session/session.types.js +231 -4
- package/dist/shared/format/format-bytes.d.ts +1 -0
- package/dist/shared/format/format-bytes.js +9 -0
- package/dist/shared/path/path.d.ts +1 -0
- package/dist/shared/path/path.js +21 -1
- package/dist/shared/process/dotnet-toolchain.d.ts +14 -0
- package/dist/shared/process/dotnet-toolchain.js +179 -0
- package/dist/shared/process/host-process-recovery.d.ts +12 -0
- package/dist/shared/process/host-process-recovery.js +78 -0
- package/dist/shared/process/host-process-supervisor.d.ts +59 -0
- package/dist/shared/process/host-process-supervisor.js +330 -0
- package/dist/shared/process/process.d.ts +1 -0
- package/dist/shared/process/process.js +1 -0
- package/dist/shared/process/windows-launch.d.ts +1 -1
- package/dist/shared/process/windows-launch.js +6 -3
- package/dist/shared/provider-capabilities.d.ts +1 -0
- package/dist/shared/provider-capabilities.js +3 -0
- package/dist/shared/redaction/redaction.js +16 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worktree/artifact-injector.d.ts +3 -2
- package/dist/worktree/artifact-injector.js +43 -4
- package/dist/worktree/dependency-warmer.d.ts +39 -1
- package/dist/worktree/dependency-warmer.js +351 -8
- package/dist/worktree/index.d.ts +2 -1
- package/dist/worktree/index.js +1 -0
- package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
- package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
- package/dist/worktree/worktree-paths.d.ts +4 -0
- package/dist/worktree/worktree-paths.js +30 -0
- package/dist/worktree/worktree.base.d.ts +3 -0
- package/dist/worktree/worktree.service.d.ts +13 -2
- package/dist/worktree/worktree.service.js +238 -47
- package/dist/worktree/worktree.types.d.ts +5 -1
- package/package.json +8 -4
- package/runtime/windows-job-supervisor.ps1 +444 -0
- package/runtime/windows-listener-owner.ps1 +33 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { redactSecrets } from "../shared/redaction/redaction.js";
|
|
@@ -12,8 +13,11 @@ export async function writeDecomposeGateCompletionArtifact(config, gate, toolNam
|
|
|
12
13
|
validateDecomposeProposalJson(proposalJson);
|
|
13
14
|
}
|
|
14
15
|
const redactedPayload = redactSecrets({ ...payload, status, phase: "decompose", gate, tool_name: toolName }, config.redactionSecrets ?? []);
|
|
16
|
+
assertStructuralFieldsUnchanged(payload, redactedPayload);
|
|
17
|
+
if (gate === "decompose_proposal") {
|
|
18
|
+
validatePostRedaction(() => validateDecomposeProposalJson(asRecord(redactedPayload["proposal_json"] ?? redactedPayload["decomposition_json"], "proposal_json")));
|
|
19
|
+
}
|
|
15
20
|
const root = artifactRootFor(config.worktreePath, config.workItemId);
|
|
16
|
-
await fs.mkdir(root, { recursive: true });
|
|
17
21
|
const artifactPath = path.join(root, decomposeGateFileNames[gate]);
|
|
18
22
|
const reportPath = gate === "decompose_proposal" ? path.join(root, phaseFiles.decompose.reportFileName) : path.join(root, `${gate}.md`);
|
|
19
23
|
const verdictJson = jsonText(redactedPayload);
|
|
@@ -21,8 +25,10 @@ export async function writeDecomposeGateCompletionArtifact(config, gate, toolNam
|
|
|
21
25
|
|
|
22
26
|
${optionalString(redactedPayload, "summary") ?? "Decompose gate recorded."}
|
|
23
27
|
`;
|
|
24
|
-
await
|
|
25
|
-
|
|
28
|
+
await publishArtifactBundleAtomic(root, [
|
|
29
|
+
{ fileName: path.basename(reportPath), content: report },
|
|
30
|
+
{ fileName: path.basename(artifactPath), content: verdictJson }
|
|
31
|
+
], path.basename(artifactPath));
|
|
26
32
|
return {
|
|
27
33
|
phase: "decompose",
|
|
28
34
|
workItemId: config.workItemId,
|
|
@@ -44,14 +50,19 @@ export async function writeDraftGateCompletionArtifact(config, gate, toolName, i
|
|
|
44
50
|
validateDraftProposalJson(draftJson);
|
|
45
51
|
}
|
|
46
52
|
const redactedPayload = redactSecrets({ ...payload, status, phase: "draft", gate, tool_name: toolName }, config.redactionSecrets ?? []);
|
|
53
|
+
assertStructuralFieldsUnchanged(payload, redactedPayload);
|
|
54
|
+
if (gate === "draft_proposal") {
|
|
55
|
+
validatePostRedaction(() => validateDraftProposalJson(asRecord(redactedPayload["draft_json"], "draft_json")));
|
|
56
|
+
}
|
|
47
57
|
const root = artifactRootFor(config.worktreePath, config.workItemId);
|
|
48
|
-
await fs.mkdir(root, { recursive: true });
|
|
49
58
|
const artifactPath = path.join(root, draftGateFileNames[gate]);
|
|
50
59
|
const reportPath = gate === "draft_proposal" ? path.join(root, phaseFiles.draft.reportFileName) : path.join(root, `${gate}.md`);
|
|
51
60
|
const verdictJson = jsonText(redactedPayload);
|
|
52
61
|
const report = optionalString(redactedPayload, "report_markdown") ?? `# ${gate.replace(/_/g, " ")}\n\n${optionalString(redactedPayload, "summary") ?? "Draft gate recorded."}\n`;
|
|
53
|
-
await
|
|
54
|
-
|
|
62
|
+
await publishArtifactBundleAtomic(root, [
|
|
63
|
+
{ fileName: path.basename(reportPath), content: report },
|
|
64
|
+
{ fileName: path.basename(artifactPath), content: verdictJson }
|
|
65
|
+
], path.basename(artifactPath));
|
|
55
66
|
return {
|
|
56
67
|
phase: "draft",
|
|
57
68
|
workItemId: config.workItemId,
|
|
@@ -148,6 +159,11 @@ export async function writeCompletionArtifact(config, toolName, input) {
|
|
|
148
159
|
validatePayloadShape(phase, validationPayload);
|
|
149
160
|
validateOptionalWorkItemId(validationPayload, config.workItemId);
|
|
150
161
|
const redactedPayload = redactSecrets(validationPayload, config.redactionSecrets ?? []);
|
|
162
|
+
assertStructuralFieldsUnchanged(validationPayload, redactedPayload);
|
|
163
|
+
validatePostRedaction(() => {
|
|
164
|
+
validatePayloadShape(phase, redactedPayload);
|
|
165
|
+
validateOptionalWorkItemId(redactedPayload, config.workItemId);
|
|
166
|
+
});
|
|
151
167
|
const producedBy = optionalString(redactedPayload, "produced_by")
|
|
152
168
|
?? optionalString(redactedPayload, "producedBy")
|
|
153
169
|
?? config.producedBy
|
|
@@ -175,9 +191,10 @@ export async function writeCompletionArtifact(config, toolName, input) {
|
|
|
175
191
|
const report = reportMarkdown(redactedPayload, phase, status, producedBy, contentHash);
|
|
176
192
|
const artifactPath = completionArtifactJsonPath(config.worktreePath, config.workItemId, phase);
|
|
177
193
|
const reportPath = completionArtifactReportPath(config.worktreePath, config.workItemId, phase);
|
|
178
|
-
await
|
|
179
|
-
|
|
180
|
-
|
|
194
|
+
await publishArtifactBundleAtomic(path.dirname(artifactPath), [
|
|
195
|
+
{ fileName: path.basename(reportPath), content: report },
|
|
196
|
+
{ fileName: path.basename(artifactPath), content: artifactJson }
|
|
197
|
+
], path.basename(artifactPath));
|
|
181
198
|
return {
|
|
182
199
|
phase,
|
|
183
200
|
workItemId: config.workItemId,
|
|
@@ -215,11 +232,12 @@ export async function writeArchitectCompletionArtifact(config, payload, status,
|
|
|
215
232
|
...artifactWithoutHash,
|
|
216
233
|
content_hash: contentHash
|
|
217
234
|
};
|
|
218
|
-
await
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
235
|
+
await publishArtifactBundleAtomic(artifactRoot, [
|
|
236
|
+
{ fileName: path.basename(implementationPlanPath), content: jsonText(implementationPlan) },
|
|
237
|
+
{ fileName: path.basename(qaPlanPath), content: jsonText(qaPlan) },
|
|
238
|
+
{ fileName: path.basename(reportPath), content: `${report.trim()}\n` },
|
|
239
|
+
{ fileName: path.basename(verdictPath), content: jsonText(verdict) }
|
|
240
|
+
], path.basename(verdictPath));
|
|
223
241
|
return {
|
|
224
242
|
phase: "architect",
|
|
225
243
|
workItemId: config.workItemId,
|
|
@@ -237,12 +255,29 @@ export async function writeQaGateCompletionArtifact(config, gate, input) {
|
|
|
237
255
|
const status = normalizeQaGateStatus(requiredString(payload, "status"));
|
|
238
256
|
validateQaGatePayloadShape(gate, payload, status);
|
|
239
257
|
validateOptionalWorkItemId(payload, config.workItemId);
|
|
240
|
-
const
|
|
258
|
+
const ownerFields = artifactOwnerFields(config);
|
|
259
|
+
const validatedSidecars = gate === "qa_manual_web"
|
|
260
|
+
? await validateQaSidecarReferences(config.worktreePath, config.workItemId, qaArray(payload, "sidecars", qaEvidenceSource(payload)), ownerFields.owner_type, ownerFields.owner_id)
|
|
261
|
+
: null;
|
|
262
|
+
const redactedPayload = redactSecrets({
|
|
263
|
+
...payload,
|
|
264
|
+
status,
|
|
265
|
+
...(validatedSidecars ? { sidecars: validatedSidecars } : {})
|
|
266
|
+
}, config.redactionSecrets ?? []);
|
|
267
|
+
assertStructuralFieldsUnchanged(payload, redactedPayload);
|
|
268
|
+
validatePostRedaction(() => {
|
|
269
|
+
validateQaGatePayloadShape(gate, redactedPayload, status);
|
|
270
|
+
validateOptionalWorkItemId(redactedPayload, config.workItemId);
|
|
271
|
+
});
|
|
272
|
+
if (gate === "qa_manual_web") {
|
|
273
|
+
await validateQaSidecarReferences(config.worktreePath, config.workItemId, qaArray(redactedPayload, "sidecars", qaEvidenceSource(redactedPayload)), ownerFields.owner_type, ownerFields.owner_id).catch((error) => {
|
|
274
|
+
throw postRedactionValidationError(error);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
241
277
|
const producedBy = optionalString(redactedPayload, "produced_by")
|
|
242
278
|
?? optionalString(redactedPayload, "producedBy")
|
|
243
279
|
?? config.producedBy
|
|
244
280
|
?? "forge-completion-tool";
|
|
245
|
-
const ownerFields = artifactOwnerFields(config);
|
|
246
281
|
const artifactRoot = ownerFields.owner_type && ownerFields.owner_id
|
|
247
282
|
? artifactRootForOwner(config.worktreePath, ownerFields.owner_type, ownerFields.owner_id)
|
|
248
283
|
: artifactRootFor(config.worktreePath, config.workItemId);
|
|
@@ -262,7 +297,6 @@ export async function writeQaGateCompletionArtifact(config, gate, input) {
|
|
|
262
297
|
...artifactWithoutHash,
|
|
263
298
|
content_hash: contentHash
|
|
264
299
|
};
|
|
265
|
-
await fs.mkdir(artifactRoot, { recursive: true });
|
|
266
300
|
await writeFileAtomic(artifactPath, jsonText(artifact));
|
|
267
301
|
return {
|
|
268
302
|
phase: "qa",
|
|
@@ -371,12 +405,13 @@ export async function writeQaCompletionArtifact(config, payload, status, produce
|
|
|
371
405
|
content_hash: contentHash
|
|
372
406
|
});
|
|
373
407
|
const finalReport = reportMarkdown(payload, "qa", status, producedBy, contentHash);
|
|
374
|
-
await
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
408
|
+
await publishArtifactBundleAtomic(artifactRoot, [
|
|
409
|
+
{ fileName: path.basename(planPath), content: planJson },
|
|
410
|
+
{ fileName: path.basename(evidencePath), content: evidenceJson },
|
|
411
|
+
{ fileName: path.basename(findingsPath), content: findingsJson },
|
|
412
|
+
{ fileName: path.basename(reportPath), content: finalReport },
|
|
413
|
+
{ fileName: path.basename(verdictPath), content: verdictJson }
|
|
414
|
+
], path.basename(verdictPath));
|
|
380
415
|
return {
|
|
381
416
|
phase: "qa",
|
|
382
417
|
workItemId: config.workItemId,
|
|
@@ -424,7 +459,7 @@ async function synthesizeQaCycleVerdictPayload(config, payload, status) {
|
|
|
424
459
|
]);
|
|
425
460
|
merged.browser_checks = dedupeUnknownArray([
|
|
426
461
|
...qaArray(payload, "browser_checks", payloadEvidence),
|
|
427
|
-
...evidenceGates.flatMap((gate) => qaArray(gate, "browser_checks"))
|
|
462
|
+
...evidenceGates.flatMap((gate) => qaArray(gate, "browser_checks").map(normalizeInheritedQaBrowserCheck))
|
|
428
463
|
]);
|
|
429
464
|
merged.sidecars = dedupeUnknownArray([
|
|
430
465
|
...qaArray(payload, "sidecars", payloadEvidence),
|
|
@@ -516,6 +551,27 @@ function dedupeStrings(values) {
|
|
|
516
551
|
}
|
|
517
552
|
return deduped;
|
|
518
553
|
}
|
|
554
|
+
function normalizeInheritedQaBrowserCheck(browserCheck) {
|
|
555
|
+
if (!browserCheck || typeof browserCheck !== "object" || Array.isArray(browserCheck)) {
|
|
556
|
+
return browserCheck;
|
|
557
|
+
}
|
|
558
|
+
const record = browserCheck;
|
|
559
|
+
if (optionalString(record, "summary")) {
|
|
560
|
+
return record;
|
|
561
|
+
}
|
|
562
|
+
const target = optionalString(record, "url") ?? optionalString(record, "route") ?? "the configured browser target";
|
|
563
|
+
const status = optionalString(record, "status") ?? "recorded";
|
|
564
|
+
return {
|
|
565
|
+
...record,
|
|
566
|
+
summary: `Inherited browser check ${status} for ${target}.`
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
function normalizeSha256ContentHash(contentHash) {
|
|
570
|
+
const normalized = contentHash.trim().toLowerCase();
|
|
571
|
+
return normalized.startsWith("sha256:")
|
|
572
|
+
? normalized.slice("sha256:".length)
|
|
573
|
+
: normalized;
|
|
574
|
+
}
|
|
519
575
|
async function validateQaSidecarReferences(worktreePath, workItemId, sidecars, ownerType, ownerId) {
|
|
520
576
|
if (sidecars.length === 0) {
|
|
521
577
|
return [];
|
|
@@ -525,6 +581,7 @@ async function validateQaSidecarReferences(worktreePath, workItemId, sidecars, o
|
|
|
525
581
|
: artifactRootFor(worktreePath, workItemId);
|
|
526
582
|
const qaSidecarRoot = path.resolve(artifactRoot, "qa");
|
|
527
583
|
const validated = [];
|
|
584
|
+
const validatedByPath = new Map();
|
|
528
585
|
for (const [index, sidecar] of sidecars.entries()) {
|
|
529
586
|
if (!sidecar || typeof sidecar !== "object" || Array.isArray(sidecar)) {
|
|
530
587
|
throw new Error(`sidecars[${index}] must be an object.`);
|
|
@@ -557,12 +614,22 @@ async function validateQaSidecarReferences(worktreePath, workItemId, sidecars, o
|
|
|
557
614
|
throw new Error(`sidecars[${index}].bytes does not match the referenced file.`);
|
|
558
615
|
}
|
|
559
616
|
const actualHash = await sha256File(fullPath);
|
|
560
|
-
if (actualHash !== contentHash
|
|
617
|
+
if (actualHash !== normalizeSha256ContentHash(contentHash)) {
|
|
561
618
|
throw new Error(`sidecars[${index}].content_hash does not match the referenced file.`);
|
|
562
619
|
}
|
|
620
|
+
const canonicalPath = path.relative(worktreePath, fullPath).split(path.sep).join("/");
|
|
621
|
+
const duplicateKey = canonicalPath.toLowerCase();
|
|
622
|
+
const existing = validatedByPath.get(duplicateKey);
|
|
623
|
+
if (existing) {
|
|
624
|
+
if (existing.contentHash !== actualHash || existing.bytes !== stat.size) {
|
|
625
|
+
throw new Error(`sidecars[${index}] conflicts with another payload for ${canonicalPath}.`);
|
|
626
|
+
}
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
validatedByPath.set(duplicateKey, { contentHash: actualHash, bytes: stat.size });
|
|
563
630
|
validated.push({
|
|
564
631
|
...record,
|
|
565
|
-
path:
|
|
632
|
+
path: canonicalPath,
|
|
566
633
|
content_hash: actualHash,
|
|
567
634
|
bytes: stat.size
|
|
568
635
|
});
|
|
@@ -597,3 +664,166 @@ export async function readCompletionArtifact(worktreePath, workItemId, phase, ow
|
|
|
597
664
|
phase
|
|
598
665
|
};
|
|
599
666
|
}
|
|
667
|
+
/**
|
|
668
|
+
* Stages a complete phase bundle outside the live artifact directory, then
|
|
669
|
+
* publishes its canonical verdict/marker last. Consumers treat the marker as
|
|
670
|
+
* the bundle's commit point, so they can observe either the old complete bundle
|
|
671
|
+
* or the new complete bundle, never a partially written set. A failed or
|
|
672
|
+
* cancelled publication rolls every touched file back before rejecting.
|
|
673
|
+
*/
|
|
674
|
+
export async function publishArtifactBundleAtomic(artifactRoot, files, commitFileName, options = {}) {
|
|
675
|
+
validateArtifactBundleFiles(files, commitFileName);
|
|
676
|
+
throwIfPublicationCancelled(options.signal);
|
|
677
|
+
const parent = path.dirname(artifactRoot);
|
|
678
|
+
const transactionId = `${process.pid}.${crypto.randomUUID()}`;
|
|
679
|
+
const stagingRoot = path.join(parent, `.${path.basename(artifactRoot)}.${transactionId}.publish`);
|
|
680
|
+
const backupRoot = path.join(stagingRoot, ".previous");
|
|
681
|
+
const orderedFiles = [
|
|
682
|
+
...files.filter((file) => file.fileName !== commitFileName),
|
|
683
|
+
files.find((file) => file.fileName === commitFileName)
|
|
684
|
+
];
|
|
685
|
+
const backedUp = new Set();
|
|
686
|
+
const published = new Set();
|
|
687
|
+
try {
|
|
688
|
+
await fs.mkdir(parent, { recursive: true });
|
|
689
|
+
await fs.mkdir(stagingRoot, { recursive: false });
|
|
690
|
+
for (const file of orderedFiles) {
|
|
691
|
+
throwIfPublicationCancelled(options.signal);
|
|
692
|
+
const handle = await fs.open(path.join(stagingRoot, file.fileName), "wx");
|
|
693
|
+
try {
|
|
694
|
+
await handle.writeFile(file.content, { encoding: "utf8", signal: options.signal });
|
|
695
|
+
await handle.sync();
|
|
696
|
+
}
|
|
697
|
+
finally {
|
|
698
|
+
await handle.close();
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
throwIfPublicationCancelled(options.signal);
|
|
702
|
+
await fs.mkdir(artifactRoot, { recursive: true });
|
|
703
|
+
await fs.mkdir(backupRoot, { recursive: true });
|
|
704
|
+
// Hide the previous commit marker before changing any supporting file.
|
|
705
|
+
for (const fileName of [commitFileName, ...orderedFiles.map((file) => file.fileName).filter((name) => name !== commitFileName)]) {
|
|
706
|
+
const livePath = path.join(artifactRoot, fileName);
|
|
707
|
+
try {
|
|
708
|
+
await fs.rename(livePath, path.join(backupRoot, fileName));
|
|
709
|
+
backedUp.add(fileName);
|
|
710
|
+
}
|
|
711
|
+
catch (error) {
|
|
712
|
+
if (!isNodeError(error) || error.code !== "ENOENT") {
|
|
713
|
+
throw error;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
for (const [index, file] of orderedFiles.entries()) {
|
|
718
|
+
throwIfPublicationCancelled(options.signal);
|
|
719
|
+
await options.beforePublishFile?.(file.fileName, index);
|
|
720
|
+
await fs.rename(path.join(stagingRoot, file.fileName), path.join(artifactRoot, file.fileName));
|
|
721
|
+
published.add(file.fileName);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
catch (error) {
|
|
725
|
+
const rollbackErrors = [];
|
|
726
|
+
for (const file of [...orderedFiles].reverse()) {
|
|
727
|
+
if (!published.has(file.fileName))
|
|
728
|
+
continue;
|
|
729
|
+
await fs.rm(path.join(artifactRoot, file.fileName), { force: true }).catch(() => {
|
|
730
|
+
rollbackErrors.push(file.fileName);
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
// Restore the previous marker last for the same observable commit boundary.
|
|
734
|
+
const restoreOrder = [
|
|
735
|
+
...orderedFiles.filter((file) => file.fileName !== commitFileName),
|
|
736
|
+
orderedFiles.find((file) => file.fileName === commitFileName)
|
|
737
|
+
];
|
|
738
|
+
for (const file of restoreOrder) {
|
|
739
|
+
if (!backedUp.has(file.fileName))
|
|
740
|
+
continue;
|
|
741
|
+
await fs.rename(path.join(backupRoot, file.fileName), path.join(artifactRoot, file.fileName)).catch(() => {
|
|
742
|
+
rollbackErrors.push(file.fileName);
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
if (rollbackErrors.length === 0
|
|
746
|
+
&& error instanceof Error
|
|
747
|
+
&& error.message.startsWith("artifact_atomic_publish_cancelled:")) {
|
|
748
|
+
throw error;
|
|
749
|
+
}
|
|
750
|
+
const suffix = rollbackErrors.length > 0 ? " Rollback did not restore every bundle file." : "";
|
|
751
|
+
throw new Error(`artifact_atomic_publish_failure: completion artifact bundle was not published.${suffix}`, { cause: error });
|
|
752
|
+
}
|
|
753
|
+
finally {
|
|
754
|
+
await fs.rm(stagingRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
function validateArtifactBundleFiles(files, commitFileName) {
|
|
758
|
+
if (files.length === 0 || !files.some((file) => file.fileName === commitFileName)) {
|
|
759
|
+
throw new Error("artifact_atomic_publish_failure: bundle commit file is missing.");
|
|
760
|
+
}
|
|
761
|
+
const names = new Set();
|
|
762
|
+
for (const file of files) {
|
|
763
|
+
if (!file.fileName || path.basename(file.fileName) !== file.fileName || names.has(file.fileName)) {
|
|
764
|
+
throw new Error("artifact_atomic_publish_failure: bundle file names must be unique safe base names.");
|
|
765
|
+
}
|
|
766
|
+
names.add(file.fileName);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
function throwIfPublicationCancelled(signal) {
|
|
770
|
+
if (signal?.aborted) {
|
|
771
|
+
throw new Error("artifact_atomic_publish_cancelled: completion artifact bundle publication was cancelled.");
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
function validatePostRedaction(validate) {
|
|
775
|
+
try {
|
|
776
|
+
validate();
|
|
777
|
+
}
|
|
778
|
+
catch (error) {
|
|
779
|
+
throw postRedactionValidationError(error);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function postRedactionValidationError(error) {
|
|
783
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
784
|
+
return new Error(`artifact_post_redaction_validation_failed: ${message}`, { cause: error });
|
|
785
|
+
}
|
|
786
|
+
function assertStructuralFieldsUnchanged(original, redacted) {
|
|
787
|
+
if (structuralFieldFingerprint(original) !== structuralFieldFingerprint(redacted)) {
|
|
788
|
+
// This stable code is intentionally safe for telemetry: it does not include
|
|
789
|
+
// the structural value or any candidate secret.
|
|
790
|
+
throw new Error("artifact_redaction_changed_structural_field: redaction changed a structural identifier; the bundle was not published.");
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
function structuralFieldFingerprint(value, key = "", result = []) {
|
|
794
|
+
if (Array.isArray(value)) {
|
|
795
|
+
if (isStructuralFieldKey(key) && value.every((item) => item === null || typeof item !== "object")) {
|
|
796
|
+
result.push(stableStringify(value));
|
|
797
|
+
}
|
|
798
|
+
else {
|
|
799
|
+
for (const item of value)
|
|
800
|
+
structuralFieldFingerprint(item, key, result);
|
|
801
|
+
}
|
|
802
|
+
return result.join("\n");
|
|
803
|
+
}
|
|
804
|
+
if (!value || typeof value !== "object") {
|
|
805
|
+
if (isStructuralFieldKey(key))
|
|
806
|
+
result.push(stableStringify(value));
|
|
807
|
+
return result.join("\n");
|
|
808
|
+
}
|
|
809
|
+
for (const [childKey, child] of Object.entries(value)) {
|
|
810
|
+
if (isStructuralFieldKey(childKey)) {
|
|
811
|
+
result.push(`${childKey}:${stableStringify(child)}`);
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
structuralFieldFingerprint(child, childKey, result);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return result.join("\n");
|
|
818
|
+
}
|
|
819
|
+
function isStructuralFieldKey(key) {
|
|
820
|
+
return key === "id"
|
|
821
|
+
|| key === "before"
|
|
822
|
+
|| key === "after"
|
|
823
|
+
|| key === "from"
|
|
824
|
+
|| key === "to"
|
|
825
|
+
|| key === "tasks"
|
|
826
|
+
|| key === "phases"
|
|
827
|
+
|| /(?:^|_)(?:id|ids)$/i.test(key)
|
|
828
|
+
|| /(?:Id|Ids)$/.test(key);
|
|
829
|
+
}
|
|
@@ -9,7 +9,8 @@ export type DebugCompletionPhase = "debug_repro" | "debug_root_cause" | "debug_f
|
|
|
9
9
|
export type ArchitectCompletionPhase = "architect";
|
|
10
10
|
export type DraftCompletionPhase = "draft";
|
|
11
11
|
export type DecomposeCompletionPhase = "decompose";
|
|
12
|
-
export type
|
|
12
|
+
export type CiRepairCompletionPhase = "ci_repair";
|
|
13
|
+
export type CompletionPhase = DevelopCompletionPhase | "qa" | DebugCompletionPhase | ArchitectCompletionPhase | DraftCompletionPhase | DecomposeCompletionPhase | CiRepairCompletionPhase;
|
|
13
14
|
export type QaCompletionGate = "qa_plan" | "qa_regression" | "qa_api" | "qa_manual_web" | "qa_bug_repro" | "qa_findings" | "qa_verdict";
|
|
14
15
|
export type DraftCompletionGate = "draft_intent" | "draft_repo_context" | "draft_graph_context" | "draft_product_scope" | "draft_work_order_plan" | "draft_critic" | "draft_proposal";
|
|
15
16
|
export type DecomposeCompletionGate = "decompose_subject_intake" | "decompose_context_review" | "decompose_breakdown_strategy" | "decompose_child_items" | "decompose_dependency_graph" | "decompose_critic" | "decompose_proposal";
|
|
@@ -25,6 +26,7 @@ export interface CompletionToolRuntimeOptions {
|
|
|
25
26
|
readonly dispatch: RunOnceDispatchPacket;
|
|
26
27
|
readonly worktree: WorktreeHandle;
|
|
27
28
|
readonly phase?: CompletionPhase;
|
|
29
|
+
readonly runtimeEnvironment?: Readonly<Record<string, string>>;
|
|
28
30
|
}
|
|
29
31
|
export interface CompletionToolServerConfig {
|
|
30
32
|
readonly worktreePath: string;
|
|
@@ -47,6 +49,13 @@ export interface CompletionToolCallResult {
|
|
|
47
49
|
readonly text: string;
|
|
48
50
|
}[];
|
|
49
51
|
readonly structuredContent: CompletionArtifactReference & {
|
|
52
|
+
readonly artifact_written: true;
|
|
53
|
+
readonly artifact_receipt: {
|
|
54
|
+
readonly schema_version: 1;
|
|
55
|
+
readonly path: string;
|
|
56
|
+
readonly report_path: string;
|
|
57
|
+
readonly content_hash: string;
|
|
58
|
+
};
|
|
50
59
|
readonly gate: {
|
|
51
60
|
readonly phase: CompletionPhase;
|
|
52
61
|
readonly qaGate?: QaCompletionGate;
|
|
@@ -56,7 +65,7 @@ export interface CompletionToolCallResult {
|
|
|
56
65
|
readonly allowed: boolean;
|
|
57
66
|
readonly reason: string;
|
|
58
67
|
};
|
|
59
|
-
readonly nextPhase: CompletionPhase | "dev_test_ready" | "qa_ready" | "sign_off_ready";
|
|
68
|
+
readonly nextPhase: CompletionPhase | "dev_test_ready" | "qa_ready" | "sign_off_ready" | "provider_checks_ready";
|
|
60
69
|
readonly nextInstruction: string;
|
|
61
70
|
};
|
|
62
71
|
}
|
|
@@ -98,6 +107,7 @@ export declare const qaFindingScopes: Set<string>;
|
|
|
98
107
|
export declare const qaCandidateBugActions: Set<string>;
|
|
99
108
|
export declare const qaScopeSurfaces: Set<string>;
|
|
100
109
|
export declare const qaScopeEvidence: Set<string>;
|
|
110
|
+
export declare const qaScopeCapabilities: Set<string>;
|
|
101
111
|
export declare const qaScopeSources: Set<string>;
|
|
102
112
|
export declare const qaScopeAuthorities: Set<string>;
|
|
103
113
|
export declare const qaScopeConfidence: Set<string>;
|
|
@@ -33,6 +33,11 @@ export declare function requireField(payload: Record<string, unknown>, key: stri
|
|
|
33
33
|
export declare function requireNonPassEvidence(payload: Record<string, unknown>): void;
|
|
34
34
|
export declare function requireImplementationEvidence(payload: Record<string, unknown>): void;
|
|
35
35
|
export declare function requirePassingCommands(payload: Record<string, unknown>): void;
|
|
36
|
+
export declare class CompletionPayloadValidationError extends Error {
|
|
37
|
+
readonly issues: readonly string[];
|
|
38
|
+
constructor(issues: readonly string[]);
|
|
39
|
+
}
|
|
40
|
+
export declare function throwCompletionPayloadIssues(issues: readonly string[]): void;
|
|
36
41
|
export declare function requireArray(payload: Record<string, unknown>, key: string): void;
|
|
37
42
|
export declare function requireNonEmptyArray(payload: Record<string, unknown>, key: string): void;
|
|
38
43
|
export declare function arrayLength(payload: Record<string, unknown>, key: string): number;
|
|
@@ -85,6 +85,9 @@ export function parseStrictCompletionPhase(value) {
|
|
|
85
85
|
if (normalized === "decompose" || normalized === "decomposition" || normalized === "work_order_decomposition" || normalized === "feature_decomposition") {
|
|
86
86
|
return "decompose";
|
|
87
87
|
}
|
|
88
|
+
if (normalized === "ci_repair" || normalized === "pr_checks" || normalized === "repair_pr_checks") {
|
|
89
|
+
return "ci_repair";
|
|
90
|
+
}
|
|
88
91
|
return null;
|
|
89
92
|
}
|
|
90
93
|
export function normalizeDebugCompletionPhase(value) {
|
|
@@ -156,6 +159,9 @@ export function phaseFromToolName(toolName) {
|
|
|
156
159
|
if (normalized === "forge.complete_architect_plan" || normalized === "complete_architect_plan") {
|
|
157
160
|
return "architect";
|
|
158
161
|
}
|
|
162
|
+
if (normalized === "forge.complete_ci_repair" || normalized === "complete_ci_repair") {
|
|
163
|
+
return "ci_repair";
|
|
164
|
+
}
|
|
159
165
|
if (draftGateFromToolName(toolName)) {
|
|
160
166
|
return "draft";
|
|
161
167
|
}
|
|
@@ -256,28 +262,45 @@ export function requirePassingCommands(payload) {
|
|
|
256
262
|
if (commands.length === 0) {
|
|
257
263
|
throw new Error("commands must include at least one deterministic validation command.");
|
|
258
264
|
}
|
|
265
|
+
const issues = [];
|
|
259
266
|
for (const [index, command] of commands.entries()) {
|
|
260
267
|
if (!command || typeof command !== "object" || Array.isArray(command)) {
|
|
261
|
-
|
|
268
|
+
issues.push(`commands[${index}] must be an object.`);
|
|
269
|
+
continue;
|
|
262
270
|
}
|
|
263
271
|
const record = command;
|
|
264
272
|
if (typeof record.command !== "string" || record.command.trim().length === 0) {
|
|
265
|
-
|
|
273
|
+
issues.push(`commands[${index}].command must be a non-empty string.`);
|
|
266
274
|
}
|
|
267
275
|
if (typeof record.summary !== "string" || record.summary.trim().length === 0) {
|
|
268
|
-
|
|
276
|
+
issues.push(`commands[${index}].summary must be a non-empty string.`);
|
|
269
277
|
}
|
|
270
278
|
if (typeof record.exit_code !== "number" || !Number.isFinite(record.exit_code)) {
|
|
271
|
-
|
|
279
|
+
issues.push(`commands[${index}].exit_code must be a finite number.`);
|
|
272
280
|
}
|
|
273
|
-
if (record.exit_code !== 0) {
|
|
274
|
-
|
|
281
|
+
else if (record.exit_code !== 0) {
|
|
282
|
+
issues.push(`commands[${index}].exit_code must be 0 when status is pass.`);
|
|
275
283
|
}
|
|
276
284
|
const status = typeof record.status === "string" ? record.status.trim().toLowerCase() : "";
|
|
277
285
|
if (status !== "pass") {
|
|
278
|
-
|
|
286
|
+
issues.push(`commands[${index}].status must be pass.`);
|
|
279
287
|
}
|
|
280
288
|
}
|
|
289
|
+
throwCompletionPayloadIssues(issues);
|
|
290
|
+
}
|
|
291
|
+
export class CompletionPayloadValidationError extends Error {
|
|
292
|
+
issues;
|
|
293
|
+
constructor(issues) {
|
|
294
|
+
super(`completion_payload_invalid: ${issues.length} correction${issues.length === 1 ? "" : "s"} required; no artifact was written.\n`
|
|
295
|
+
+ issues.map(issue => `- ${issue}`).join("\n")
|
|
296
|
+
+ "\nRepair every listed field and resubmit this gate once.");
|
|
297
|
+
this.issues = issues;
|
|
298
|
+
this.name = "CompletionPayloadValidationError";
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
export function throwCompletionPayloadIssues(issues) {
|
|
302
|
+
if (issues.length > 0)
|
|
303
|
+
throw new CompletionPayloadValidationError(issues);
|
|
281
304
|
}
|
|
282
305
|
export function requireArray(payload, key) {
|
|
283
306
|
if (!Array.isArray(payload[key])) {
|
|
@@ -600,9 +623,22 @@ export async function readOptionalFile(filePath) {
|
|
|
600
623
|
}
|
|
601
624
|
export async function writeFileAtomic(filePath, content) {
|
|
602
625
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
603
|
-
const tempPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${process.pid}.${
|
|
626
|
+
const tempPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
604
627
|
await fs.writeFile(tempPath, content, "utf8");
|
|
605
|
-
|
|
628
|
+
try {
|
|
629
|
+
await fs.rename(tempPath, filePath);
|
|
630
|
+
}
|
|
631
|
+
catch (error) {
|
|
632
|
+
if (process.platform !== "win32" || !isNodeError(error) || (error.code !== "EPERM" && error.code !== "EEXIST")) {
|
|
633
|
+
await fs.rm(tempPath, { force: true });
|
|
634
|
+
throw error;
|
|
635
|
+
}
|
|
636
|
+
// Windows rename does not reliably replace an existing destination. QA-cycle gates
|
|
637
|
+
// intentionally refresh canonical bundle files, so remove only the exact destination
|
|
638
|
+
// after the replacement temp file is durable, then complete the rename.
|
|
639
|
+
await fs.rm(filePath, { force: true });
|
|
640
|
+
await fs.rename(tempPath, filePath);
|
|
641
|
+
}
|
|
606
642
|
}
|
|
607
643
|
export function stableStringify(value) {
|
|
608
644
|
return JSON.stringify(sortJson(value));
|
|
@@ -626,7 +662,12 @@ export function forgeBridgeCliBinPath() {
|
|
|
626
662
|
return fileURLToPath(new URL("../cli/bin.js", import.meta.url));
|
|
627
663
|
}
|
|
628
664
|
export function tomlString(value) {
|
|
629
|
-
|
|
665
|
+
if (value.includes("'''")) {
|
|
666
|
+
throw new Error("Codex MCP configuration values cannot contain three consecutive apostrophes.");
|
|
667
|
+
}
|
|
668
|
+
// Codex config overrides pass through npm's PowerShell shim on Windows.
|
|
669
|
+
// Literal TOML strings survive that boundary; basic-string double quotes do not.
|
|
670
|
+
return `'''${value}'''`;
|
|
630
671
|
}
|
|
631
672
|
export function tomlStringArray(values) {
|
|
632
673
|
return `[${values.map(tomlString).join(", ")}]`;
|
|
@@ -133,6 +133,7 @@ export const qaScopeEvidence = new Set([
|
|
|
133
133
|
"migration_check",
|
|
134
134
|
"config_check"
|
|
135
135
|
]);
|
|
136
|
+
export const qaScopeCapabilities = new Set(["docker_engine"]);
|
|
136
137
|
export const qaScopeSources = new Set(["user", "draft", "decompose", "architect", "heuristic", "system"]);
|
|
137
138
|
export const qaScopeAuthorities = new Set(["user", "decompose", "architect", "system"]);
|
|
138
139
|
export const qaScopeConfidence = new Set(["explicit", "inferred"]);
|
|
@@ -140,6 +141,7 @@ export const qaScopeFields = new Set([
|
|
|
140
141
|
"schema_version",
|
|
141
142
|
"surface",
|
|
142
143
|
"required_evidence",
|
|
144
|
+
"required_capabilities",
|
|
143
145
|
"not_applicable",
|
|
144
146
|
"browser_required",
|
|
145
147
|
"api_required",
|
|
@@ -195,6 +197,10 @@ export const phaseFiles = {
|
|
|
195
197
|
decompose: {
|
|
196
198
|
jsonFileName: "decompose.proposal.json",
|
|
197
199
|
reportFileName: "decompose.report.md"
|
|
200
|
+
},
|
|
201
|
+
ci_repair: {
|
|
202
|
+
jsonFileName: "ci-repair.handoff.json",
|
|
203
|
+
reportFileName: "ci-repair.report.md"
|
|
198
204
|
}
|
|
199
205
|
};
|
|
200
206
|
export function completionPhaseFromDispatch(dispatch) {
|
|
@@ -224,6 +230,9 @@ export function normalizeCompletionPhase(value) {
|
|
|
224
230
|
if (normalized === "decompose" || normalized === "decomposition" || normalized === "work_order_decomposition" || normalized === "feature_decomposition") {
|
|
225
231
|
return "decompose";
|
|
226
232
|
}
|
|
233
|
+
if (normalized === "ci_repair" || normalized === "pr_checks" || normalized === "repair_pr_checks") {
|
|
234
|
+
return "ci_repair";
|
|
235
|
+
}
|
|
227
236
|
return "implementation";
|
|
228
237
|
}
|
|
229
238
|
export function usesDevelopCycleCompletion(dispatch) {
|
|
@@ -275,9 +284,11 @@ export function completionToolNamesForPhase(phase) {
|
|
|
275
284
|
? "complete_self_review"
|
|
276
285
|
: phase === "validation"
|
|
277
286
|
? "complete_validation"
|
|
278
|
-
: phase === "
|
|
279
|
-
? "
|
|
280
|
-
: "
|
|
287
|
+
: phase === "ci_repair"
|
|
288
|
+
? "complete_ci_repair"
|
|
289
|
+
: phase === "architect"
|
|
290
|
+
? "complete_architect_plan"
|
|
291
|
+
: "complete_qa";
|
|
281
292
|
return [`forge.${base}`, base];
|
|
282
293
|
}
|
|
283
294
|
export function completionToolNamesForQaGate(gate) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { type CompletionPhase, type DebugCompletionPhase, type DecomposeCompletionGate, type DevelopCompletionPhase, type DraftCompletionGate, type QaCompletionGate } from "./phase-completion.js";
|
|
2
|
-
export declare function completionPromptForPhase(phase: CompletionPhase): string;
|
|
2
|
+
export declare function completionPromptForPhase(phase: CompletionPhase, artifactRoot?: string): string;
|
|
3
3
|
export declare function completionPromptForDevelopCycle(phase: CompletionPhase): string;
|
|
4
|
-
export declare function completionPromptForQaCycle(currentGate?: QaCompletionGate): string;
|
|
4
|
+
export declare function completionPromptForQaCycle(currentGate?: QaCompletionGate, artifactRoot?: string): string;
|
|
5
5
|
export declare function completionPromptForDraftCycle(currentGate?: DraftCompletionGate): string;
|
|
6
6
|
export declare function completionPromptForDecomposeCycle(currentGate?: DecomposeCompletionGate): string;
|
|
7
7
|
export declare function completionPromptForDebugCycle(currentGate?: DebugCompletionPhase): string;
|
|
8
8
|
export declare function nextPhaseAfter(phase: CompletionPhase): DevelopCompletionPhase | null;
|
|
9
9
|
export declare function nextDebugPhaseAfter(phase: DebugCompletionPhase): DebugCompletionPhase | null;
|
|
10
|
-
export declare function completionToolNextInstruction(completedPhase: CompletionPhase, status: string, nextPhase: CompletionPhase | "dev_test_ready" | "qa_ready" | "sign_off_ready"): string;
|
|
10
|
+
export declare function completionToolNextInstruction(completedPhase: CompletionPhase, status: string, nextPhase: CompletionPhase | "dev_test_ready" | "qa_ready" | "sign_off_ready" | "provider_checks_ready"): string;
|
|
11
11
|
export declare function completionToolNextInstructionForDraftGate(completedGate: DraftCompletionGate, status: string, nextGate: DraftCompletionGate | null): string;
|
|
12
12
|
export declare function completionToolNextInstructionForQaGate(completedGate: QaCompletionGate, status: string, nextGate: QaCompletionGate | null): string;
|
|
13
13
|
export declare function completionToolNextInstructionForDecomposeGate(completedGate: DecomposeCompletionGate, status: string, nextGate: DecomposeCompletionGate | null): string;
|