@loucompanion/forge-bridge 0.1.3-dev.9bc5014a2270 → 0.1.3-dev.a58d7cd1092f
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 +66 -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.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 +646 -90
- package/dist/session/provider-cli/provider-cli.types.d.ts +5 -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 +530 -42
- 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 +36 -2
- package/dist/worktree/dependency-warmer.js +347 -11
- 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,13 +1,13 @@
|
|
|
1
1
|
import { completionPhaseOrder, completionToolNamesForPhase, debugCompletionPhaseOrder, expectedCompletionToolName, expectedDebugCompletionToolName, expectedDecomposeCompletionToolName, phaseFiles, toolServerName } from "./phase-completion.js";
|
|
2
2
|
import { isDebugCompletionPhase, isDevelopCompletionPhase } from "./phase-completion.validation.js";
|
|
3
|
-
export function completionPromptForPhase(phase) {
|
|
3
|
+
export function completionPromptForPhase(phase, artifactRoot = ".forge/work-items/{workItemId}/artifacts") {
|
|
4
4
|
const toolName = expectedCompletionToolName(phase);
|
|
5
5
|
const alias = completionToolNamesForPhase(phase).find((name) => name !== toolName);
|
|
6
6
|
const artifactName = phaseFiles[phase].jsonFileName;
|
|
7
7
|
if (phase === "qa") {
|
|
8
8
|
return `Forge completion tools are available through the MCP server named \`${toolServerName}\`.
|
|
9
9
|
Do not write QA artifacts by hand. When QA is complete, call \`${toolName}(...)\`${alias ? ` (or \`${alias}\` if your client shows unqualified MCP tool names)` : ""} with the required structured QA payload.
|
|
10
|
-
That tool writes the durable QA bundle under
|
|
10
|
+
That tool writes the durable QA bundle under \`${artifactRoot}/\`: \`qa.plan.json\`, \`qa.evidence.json\`, \`qa.findings.json\`, \`${artifactName}\`, and \`qa.report.md\`.
|
|
11
11
|
Use \`status: "pass"\` only when the validated changes satisfy acceptance criteria with concrete evidence. Use \`status: "needs_rework"\` with actionable in-scope findings, or \`status: "blocked"\` with a concrete blocker_reason.
|
|
12
12
|
Include \`policy_warnings\` for non-blocking Forge policy or scope mismatches that should remain visible after QA.
|
|
13
13
|
Forge will mirror only the tool-written QA artifacts; reporting QA completion in prose is not enough.`;
|
|
@@ -17,11 +17,20 @@ Forge will mirror only the tool-written QA artifacts; reporting QA completion in
|
|
|
17
17
|
Do not write Architect artifacts by hand. When the plan is complete, call \`${toolName}(...)\`${alias ? ` (or \`${alias}\` if your client shows unqualified MCP tool names)` : ""} with the full structured Architect planning payload.
|
|
18
18
|
That tool writes \`.forge/work-items/{workItemId}/artifacts/implementation.plan.json\`, \`qa.plan.json\`, \`${artifactName}\`, and \`architect.report.md\`.
|
|
19
19
|
The payload must put \`phases\`, \`tasks\`, \`dependencies\`, and \`phase_gates\` as top-level fields inside \`implementation_plan\`, for example:
|
|
20
|
-
\`{ "status": "pass", "summary": "...", "implementation_plan": { "schema_version": 1, "plan_id": "plan-...", "phases": [{ "id": "phase-1", "tasks": ["task-1"], "gate": "gate-1" }], "tasks": [{ "id": "task-1", "verification_scope": { "schema_version": 1, "surface": ["domain_contract"], "required_evidence": ["unit_tests"], "not_applicable": ["browser_manual", "api_http"], "browser_required": false, "api_required": false, "rationale": "Backend-only child task." }, "tests": [{ "command": "npm test" }] }], "dependencies": [], "phase_gates": [{ "id": "gate-1", "phase": "phase-1" }] }, "qa_plan": { "schema_version": 1, "final_qa": [{ "id": "qa-1", "coverage": "..." }] }, "report_markdown": "# Architect Report" }\`.
|
|
20
|
+
\`{ "status": "pass", "summary": "...", "implementation_plan": { "schema_version": 1, "plan_id": "plan-...", "phases": [{ "id": "phase-1", "tasks": ["task-1"], "gate": "gate-1" }], "tasks": [{ "id": "task-1", "verification_scope": { "schema_version": 1, "surface": ["domain_contract"], "required_evidence": ["unit_tests"], "required_capabilities": [], "not_applicable": ["browser_manual", "api_http"], "browser_required": false, "api_required": false, "rationale": "Backend-only child task." }, "tests": [{ "command": "npm test" }] }], "dependencies": [], "phase_gates": [{ "id": "gate-1", "phase": "phase-1" }] }, "qa_plan": { "schema_version": 1, "final_qa": [{ "id": "qa-1", "coverage": "..." }] }, "report_markdown": "# Architect Report" }\`.
|
|
21
21
|
\`implementation_plan.tasks\` are Develop child tasks only: each task should include \`verification_scope\` describing task-level QA evidence, and must require a focused repo/product implementation change. Put parent-story QA, sign-off, child-count/materialization inventory, parent plan artifact checks, and final integrated acceptance proof in \`qa_plan.final_qa\`, not as child Develop tasks.
|
|
22
|
+
When a task's implementation or validation starts containers or otherwise needs the host Docker daemon, include \`"required_capabilities": ["docker_engine"]\` in its verification scope. Do not encode a database, image, or Docker version as a runner capability; the repository owns those versions.
|
|
22
23
|
Dependency entries use canonical \`before\` and \`after\` task ids, for example \`{ "before": "task-1", "after": "task-2" }\`. Forge accepts \`from\`/\`to\` only as aliases and normalizes them before writing artifacts.
|
|
23
24
|
Use \`status: "pass"\` for a valid pending-approval plan, \`needs_revision\` when story/design input must change, or \`blocked\` with a concrete blocker.
|
|
24
25
|
Forge will mirror only the tool-written Architect artifacts; reporting the plan in prose is not enough.`;
|
|
26
|
+
}
|
|
27
|
+
if (phase === "ci_repair") {
|
|
28
|
+
return `Forge completion tools are available through the MCP server named \`${toolServerName}\`.
|
|
29
|
+
Read the generated \`.forge/work-items/{workItemId}/ci-repair.readiness.json\` before running validation.
|
|
30
|
+
Do not write the CI-repair handoff by hand. After the repair and locally equivalent validation are complete, call \`${toolName}(...)\`${alias ? ` (or \`${alias}\` if your client shows unqualified MCP tool names)` : ""}.
|
|
31
|
+
That tool validates failed-check inspection, root cause, scoped changes, narrow proof, every failed-job guard/test/drift step, provider-only classifications, broader validation, remaining risk, provider state, and push readiness.
|
|
32
|
+
It writes \`.forge/work-items/{workItemId}/artifacts/${artifactName}\`. Forge Bridge will not commit, push, or report CI repair success until this gate passes.
|
|
33
|
+
For a step classified \`provider_only\`, include the exact reason and provider check that will supply the missing proof. Do not weaken or delete the provider check.`;
|
|
25
34
|
}
|
|
26
35
|
return `Forge completion tools are available through the MCP server named \`${toolServerName}\`.
|
|
27
36
|
Do not write phase artifacts by hand. When the phase is complete, call \`${toolName}(...)\`${alias ? ` (or \`${alias}\` if your client shows unqualified MCP tool names)` : ""} with the required evidence payload.
|
|
@@ -38,10 +47,10 @@ Complete the dev cycle in order inside this same provider session:
|
|
|
38
47
|
Each tool writes the canonical artifact under \`.forge/work-items/{workItemId}/artifacts/\` and returns the next-phase instruction Forge expects you to follow.
|
|
39
48
|
If the current phase cannot pass, call the current phase tool with a non-pass status and explicit blockers instead of reporting completion in prose only.`;
|
|
40
49
|
}
|
|
41
|
-
export function completionPromptForQaCycle(currentGate = "qa_plan") {
|
|
50
|
+
export function completionPromptForQaCycle(currentGate = "qa_plan", artifactRoot = ".forge/work-items/{workItemId}/artifacts") {
|
|
42
51
|
return `Forge completion tools are available through the MCP server named \`${toolServerName}\`.
|
|
43
52
|
Do not write QA artifacts by hand. The tool server enforces QA gate order and starts this run at \`${currentGate}\`.
|
|
44
|
-
Complete the QA cycle in order inside this same provider session:
|
|
53
|
+
Complete the QA cycle in order inside this same provider session. Start long, independent regression verification as early as practical, continue API/source/manual review while it runs, and wait for its receipt only when those independent checks are complete:
|
|
45
54
|
1. Call \`forge.complete_qa_plan(...)\` after the QA test plan/checklist is ready.
|
|
46
55
|
2. Follow the returned regression instruction, then call \`forge.complete_qa_regression(...)\`.
|
|
47
56
|
3. Follow the returned API/backend instruction, then call \`forge.complete_qa_api(...)\`.
|
|
@@ -50,7 +59,9 @@ Complete the QA cycle in order inside this same provider session:
|
|
|
50
59
|
6. Follow the returned findings instruction, then call \`forge.complete_qa_findings(...)\`.
|
|
51
60
|
7. Follow the returned verdict instruction, then call \`forge.complete_qa_verdict(...)\`.
|
|
52
61
|
Use \`status: "skipped"\` with a reason for gates that are not applicable. If a gate is blocked, keep moving through later gates as skipped due to the blocker and finish with a blocked verdict.
|
|
53
|
-
|
|
62
|
+
On a managed host, use focused checks plus the relevant runner-authored \`forge.start_managed_verification\` profiles for normal Story QA; shard slow tiers when broader evidence is justified. Reserve the \`dotnet_solution\` profile for shared test/runtime infrastructure, Feature/PR integration, or an explicit QA-plan requirement. Use \`forge.start_managed_command\` only for other commands expected to exceed two minutes. Continue independent QA work, then call \`forge.wait_managed_command\` for the durable terminal receipt; if it returns \`command_continues=true\`, continue useful work or wait again after \`retry_after_ms\`. Do not poll \`forge.get_managed_command\`. Include the terminal \`managed_command_id\` in command evidence. Forge records live progress outside the provider transcript.
|
|
63
|
+
For a negative assertion, make the command itself exit 0 when the forbidden condition is absent. In particular, raw \`rg\` exit 1 means no matches but cannot be submitted as passing evidence; wrap it so no match exits 0, a match fails, and tool errors remain failures. Before every completion call, ensure passing command records have \`exit_code: 0\`, \`status: "pass"\`, and a summary. A rejected call writes no artifact; repair every issue returned by Forge and resubmit the same gate once without probing the filesystem.
|
|
64
|
+
The final verdict tool writes the canonical QA bundle under \`${artifactRoot}/\`: \`qa.plan.json\`, \`qa.evidence.json\`, \`qa.findings.json\`, \`qa.verdict.json\`, and \`qa.report.md\`.
|
|
54
65
|
Include \`policy_warnings\` in the final verdict for non-blocking Forge policy or scope mismatches that should remain visible after QA.
|
|
55
66
|
The legacy \`forge.complete_qa\` one-shot tool is not available in normal QA-cycle mode. Reporting QA completion in prose is not enough.`;
|
|
56
67
|
}
|
|
@@ -81,7 +92,7 @@ Required order:
|
|
|
81
92
|
5. Follow the returned dependency-graph instruction, validate keys/dependencies/order, then call \`forge.complete_decompose_dependency_graph(...)\`.
|
|
82
93
|
6. Follow the returned critic instruction, critique scope, DAG safety, manual reconciliation risk, and missing acceptance detail, then call \`forge.complete_decompose_critic(...)\`.
|
|
83
94
|
7. Follow the returned proposal instruction, then call \`forge.complete_decompose_proposal(...)\` with final JSON in \`proposal_json\`.
|
|
84
|
-
The final proposal tool writes \`decompose.proposal.json\` under \`.forge/work-items/{workItemId}/artifacts/\`. The proposal_json object must contain summary, optional blockedReason, and candidates. Work Order-to-Feature candidates may include \`estimated_qa_scope\`; Feature-to-Story candidates should include \`qa_scope\` with schema_version 1 plus scheduleIndex, waveIndex, expectedOutput, handoffSummary, and dependencyReason for dependency handoff.`;
|
|
95
|
+
The final proposal tool writes \`decompose.proposal.json\` under \`.forge/work-items/{workItemId}/artifacts/\`. The proposal_json object must contain summary, optional blockedReason, and candidates. Work Order-to-Feature candidates may include \`estimated_qa_scope\`; Feature-to-Story candidates should include \`qa_scope\` with schema_version 1 plus scheduleIndex, waveIndex, expectedOutput, handoffSummary, and dependencyReason for dependency handoff. Set \`qa_scope.required_capabilities\` to \`["docker_engine"]\` when implementation or validation starts containers or needs a Docker daemon; repository-specific service and image versions do not belong in the runner capability name.`;
|
|
85
96
|
}
|
|
86
97
|
export function completionPromptForDebugCycle(currentGate = "debug_repro") {
|
|
87
98
|
return `Forge completion tools are available through the MCP server named \`${toolServerName}\`.
|
|
@@ -115,6 +126,11 @@ export function completionToolNextInstruction(completedPhase, status, nextPhase)
|
|
|
115
126
|
}
|
|
116
127
|
return "QA accepted. Forge has the structured evidence needed to move this work item to Sign-Off.";
|
|
117
128
|
}
|
|
129
|
+
if (completedPhase === "ci_repair") {
|
|
130
|
+
return status === "pass"
|
|
131
|
+
? "CI repair handoff accepted. Forge Bridge may now commit, push, and return the PR to provider checks."
|
|
132
|
+
: "Stay in CI repair, address the exact missing evidence returned by this gate, then call forge.complete_ci_repair again.";
|
|
133
|
+
}
|
|
118
134
|
if (status !== "pass") {
|
|
119
135
|
return `Stay in ${completedPhase}, address the recorded blockers, then call ${expectedCompletionToolName(completedPhase)} again.`;
|
|
120
136
|
}
|
|
@@ -203,7 +219,7 @@ export function completionToolNextInstructionForDebugGate(completedGate, status,
|
|
|
203
219
|
function qaGateInstruction(gate) {
|
|
204
220
|
switch (gate) {
|
|
205
221
|
case "qa_regression":
|
|
206
|
-
return "Run or explicitly skip applicable regression/build/lint/typecheck/e2e checks, then call forge.complete_qa_regression.";
|
|
222
|
+
return "Run or explicitly skip applicable regression/build/lint/typecheck/e2e checks, then call forge.complete_qa_regression. For normal Story QA, prefer focused checks and runner-authored forge.start_managed_verification tier profiles; reserve dotnet_solution for shared infrastructure, Feature/PR integration, or an explicit plan requirement. Use forge.start_managed_command only for other commands expected to exceed two minutes, continue independent QA, then call forge.wait_managed_command and include its terminal managed_command_id in command evidence. If the wait returns command_continues=true, continue useful work or wait again after retry_after_ms; do not poll get_managed_command. Normalize negative assertions so an expected no-match exits 0. A reproduced failure outside the current diff and acceptance criteria may remain non-blocking only when the current item has independent passing evidence: preserve it as an out_of_scope_failure with candidate_bug_id, scope_evidence, acceptance_criteria_independent: true, and a failing focused repro command/exit code. Never classify an unexplained, in-scope, or infrastructure failure this way.";
|
|
207
223
|
case "qa_api":
|
|
208
224
|
return "Run or explicitly skip applicable API/backend contract checks, then call forge.complete_qa_api.";
|
|
209
225
|
case "qa_manual_web":
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type CompletionToolCallResult, type CompletionToolRuntime, type CompletionToolRuntimeOptions, type CompletionToolServerConfig } from "./phase-completion.js";
|
|
2
|
+
import { type ManagedAttemptToolResult } from "./managed-attempt-tools.js";
|
|
2
3
|
export declare function prepareCompletionToolRuntime(options: CompletionToolRuntimeOptions): Promise<CompletionToolRuntime>;
|
|
3
4
|
export declare function buildCompletionToolServerArgs(config: CompletionToolServerConfig): readonly string[];
|
|
4
5
|
export declare function codexMcpConfigArgs(runtime: CompletionToolRuntime): readonly string[];
|
|
@@ -10,11 +11,12 @@ export declare class CompletionToolServerSession {
|
|
|
10
11
|
private currentQaGate;
|
|
11
12
|
private currentDraftGate;
|
|
12
13
|
private currentDecomposeGate;
|
|
14
|
+
private readonly managedCommandReceipts;
|
|
13
15
|
private completed;
|
|
14
16
|
constructor(config: CompletionToolServerConfig);
|
|
15
17
|
get instructions(): string;
|
|
16
18
|
toolDefinitions(): readonly unknown[];
|
|
17
|
-
callTool(toolName: string, input: unknown): Promise<CompletionToolCallResult>;
|
|
19
|
+
callTool(toolName: string, input: unknown): Promise<CompletionToolCallResult | ManagedAttemptToolResult>;
|
|
18
20
|
private callDecomposeGateTool;
|
|
19
21
|
private callDraftGateTool;
|
|
20
22
|
private callDebugGateTool;
|
|
@@ -25,6 +27,15 @@ export declare class CompletionToolServerSession {
|
|
|
25
27
|
private advanceAfterQaGate;
|
|
26
28
|
private advanceAfterDebugGate;
|
|
27
29
|
}
|
|
30
|
+
export declare function completionToolRuntimeEnvironment(runtimeEnvironment?: Readonly<Record<string, string>>): Readonly<Record<string, string>>;
|
|
31
|
+
/**
|
|
32
|
+
* A solution-wide Forge test is long enough that a generic provider shell can
|
|
33
|
+
* expire while VSTest is still healthy. On managed host attempts, only the
|
|
34
|
+
* attempt-owned command runner can prove the exact process outcome and clean
|
|
35
|
+
* up its process tree. Keep this check at the completion gate so prompt drift
|
|
36
|
+
* cannot turn an arbitrary shell timeout into a QA blocker.
|
|
37
|
+
*/
|
|
38
|
+
export declare function assertManagedQaRegressionEvidence(input: unknown, receipts: readonly Readonly<Record<string, unknown>>[]): void;
|
|
28
39
|
export declare function runCompletionMcpServer(config: CompletionToolServerConfig): Promise<void>;
|
|
29
40
|
export declare function parseCompletionMcpArgs(argv: readonly string[]): {
|
|
30
41
|
readonly ok: true;
|
|
@@ -34,3 +45,4 @@ export declare function parseCompletionMcpArgs(argv: readonly string[]): {
|
|
|
34
45
|
readonly error: string;
|
|
35
46
|
};
|
|
36
47
|
export declare function completionRedactionEnv(secrets: readonly string[]): Readonly<Record<string, string>>;
|
|
48
|
+
export declare function completionToolRejectedResult(toolName: string, error: unknown): Readonly<Record<string, unknown>>;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { completionPhaseFromDispatch, completionPhaseOrder, completionRedactionSecretsEnv, completionToolNamesForDebugGate, completionToolNamesForDecomposeGate, completionToolNamesForDraftGate, completionToolNamesForPhase, completionToolNamesForQaGate, debugCompletionPhaseOrder, decomposeCompletionGateOrder, draftCompletionGateOrder, qaCompletionGateOrder, toolServerName, usesArchitectCycleCompletion, usesDebugCycleCompletion, usesDevelopCycleCompletion, usesQaCycleCompletion } from "./phase-completion.js";
|
|
4
|
-
import { asDebugCompletionPhase, asJsonRpcMessage, asMutableRecord, asRecord, decomposeGateFromToolName, draftGateFromToolName, errorMessage, forgeBridgeCliBinPath, isDebugCompletionPhase, isDecomposeDispatch, isDevelopCompletionPhase, normalizedOwnerType, parseCompletionRedactionSecrets, parseOptionalJsonObject, parseStrictCompletionPhase, phaseFromToolName, qaGateFromToolName, requiredString, rpcError, tomlString, tomlStringArray, writeFileAtomic, writeJsonRpc } from "./phase-completion.validation.js";
|
|
4
|
+
import { CompletionPayloadValidationError, asDebugCompletionPhase, asJsonRpcMessage, asMutableRecord, asRecord, artifactOwnerRelativeRoot, decomposeGateFromToolName, draftGateFromToolName, errorMessage, forgeBridgeCliBinPath, isDebugCompletionPhase, isDecomposeDispatch, isDevelopCompletionPhase, normalizedOwnerType, parseCompletionRedactionSecrets, parseOptionalJsonObject, parseStrictCompletionPhase, phaseFromToolName, qaGateFromToolName, requiredString, rpcError, tomlString, tomlStringArray, writeFileAtomic, writeJsonRpc } from "./phase-completion.validation.js";
|
|
5
5
|
import { completionPromptForDebugCycle, completionPromptForDecomposeCycle, completionPromptForDevelopCycle, completionPromptForDraftCycle, completionPromptForPhase, completionPromptForQaCycle, completionToolNextInstruction, completionToolNextInstructionForDebugGate, completionToolNextInstructionForDecomposeGate, completionToolNextInstructionForDraftGate, completionToolNextInstructionForQaGate, nextDebugPhaseAfter, nextPhaseAfter } from "./phase-completion.prompts.js";
|
|
6
6
|
import { toolDescription, toolInputSchema } from "./phase-completion.schemas.js";
|
|
7
7
|
import { writeCompletionArtifact, writeDecomposeGateCompletionArtifact, writeDraftGateCompletionArtifact } from "./phase-completion.artifacts.js";
|
|
8
|
+
import { callManagedAttemptTool, isManagedAttemptTool, managedAttemptToolDefinition, managedAttemptToolNames, } from "./managed-attempt-tools.js";
|
|
8
9
|
export async function prepareCompletionToolRuntime(options) {
|
|
9
10
|
const phase = options.phase ?? completionPhaseFromDispatch(options.dispatch);
|
|
10
11
|
const worktreePath = path.resolve(options.worktree.worktreePath);
|
|
@@ -32,7 +33,7 @@ export async function prepareCompletionToolRuntime(options) {
|
|
|
32
33
|
serverName: toolServerName,
|
|
33
34
|
command: process.execPath,
|
|
34
35
|
args: [forgeBridgeCliBinPath(), ...mcpArgs],
|
|
35
|
-
env:
|
|
36
|
+
env: completionToolRuntimeEnvironment(options.runtimeEnvironment),
|
|
36
37
|
cleanup: async () => undefined
|
|
37
38
|
};
|
|
38
39
|
if (options.dispatch.execution.provider.trim().toLowerCase() !== "cursor") {
|
|
@@ -82,7 +83,11 @@ export function codexMcpConfigArgs(runtime) {
|
|
|
82
83
|
"-c",
|
|
83
84
|
`${prefix}.command=${tomlString(runtime.command)}`,
|
|
84
85
|
"-c",
|
|
85
|
-
`${prefix}.args=${tomlStringArray(runtime.args)}
|
|
86
|
+
`${prefix}.args=${tomlStringArray(runtime.args)}`,
|
|
87
|
+
"-c",
|
|
88
|
+
`${prefix}.default_tools_approval_mode=${tomlString("approve")}`,
|
|
89
|
+
"-c",
|
|
90
|
+
`${prefix}.tool_timeout_sec=7200`
|
|
86
91
|
];
|
|
87
92
|
for (const [key, value] of Object.entries(runtime.env)) {
|
|
88
93
|
args.push("-c", `${prefix}.env.${key}=${tomlString(value)}`);
|
|
@@ -117,6 +122,7 @@ export class CompletionToolServerSession {
|
|
|
117
122
|
currentQaGate = "qa_plan";
|
|
118
123
|
currentDraftGate = "draft_intent";
|
|
119
124
|
currentDecomposeGate = "decompose_subject_intake";
|
|
125
|
+
managedCommandReceipts = [];
|
|
120
126
|
completed = false;
|
|
121
127
|
constructor(config) {
|
|
122
128
|
this.config = config;
|
|
@@ -130,15 +136,28 @@ export class CompletionToolServerSession {
|
|
|
130
136
|
: this.config.debugCycle
|
|
131
137
|
? completionPromptForDebugCycle(asDebugCompletionPhase(this.currentPhase))
|
|
132
138
|
: this.config.qaCycle
|
|
133
|
-
? completionPromptForQaCycle(this.currentQaGate)
|
|
139
|
+
? completionPromptForQaCycle(this.currentQaGate, artifactOwnerRelativeRoot(this.config.ownerType, this.config.ownerId, this.config.workItemId))
|
|
134
140
|
: this.config.developCycle
|
|
135
141
|
? completionPromptForDevelopCycle(this.currentPhase)
|
|
136
|
-
: completionPromptForPhase(this.currentPhase);
|
|
142
|
+
: completionPromptForPhase(this.currentPhase, artifactOwnerRelativeRoot(this.config.ownerType, this.config.ownerId, this.config.workItemId));
|
|
137
143
|
}
|
|
138
144
|
toolDefinitions() {
|
|
139
|
-
|
|
145
|
+
const completionTools = completionToolNamesForServer(this.config).map((name) => toolDefinition(name, phaseFromToolName(name), qaGateFromToolName(name), decomposeGateFromToolName(name)));
|
|
146
|
+
return managedAttemptToolsAvailable()
|
|
147
|
+
? [...completionTools, ...managedAttemptToolNames.map(managedAttemptToolDefinition)]
|
|
148
|
+
: completionTools;
|
|
140
149
|
}
|
|
141
150
|
async callTool(toolName, input) {
|
|
151
|
+
if (isManagedAttemptTool(toolName)) {
|
|
152
|
+
if (!managedAttemptToolsAvailable()) {
|
|
153
|
+
throw new Error("Forge managed-attempt tools require an active managed host runtime.");
|
|
154
|
+
}
|
|
155
|
+
const result = await callManagedAttemptTool(toolName, input, this.config.worktreePath);
|
|
156
|
+
if (Array.isArray(result.structuredContent.command)) {
|
|
157
|
+
this.managedCommandReceipts.push(result.structuredContent);
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
142
161
|
const requestedPhase = phaseFromToolName(toolName);
|
|
143
162
|
if (this.completed) {
|
|
144
163
|
throw new Error("Forge completion cycle is already complete; no more completion calls are accepted.");
|
|
@@ -178,6 +197,7 @@ export class CompletionToolServerSession {
|
|
|
178
197
|
],
|
|
179
198
|
structuredContent: {
|
|
180
199
|
...reference,
|
|
200
|
+
...acceptedArtifactReceipt(reference),
|
|
181
201
|
gate: {
|
|
182
202
|
phase: completedPhase,
|
|
183
203
|
status,
|
|
@@ -215,6 +235,7 @@ export class CompletionToolServerSession {
|
|
|
215
235
|
content: [{ type: "text", text: `Forge recorded ${completedGate} artifact ${path.relative(reference.repoPath, reference.path)} (${reference.status}, ${reference.contentHash}). ${nextInstruction}` }],
|
|
216
236
|
structuredContent: {
|
|
217
237
|
...reference,
|
|
238
|
+
...acceptedArtifactReceipt(reference),
|
|
218
239
|
gate: { phase: "decompose", decomposeGate: completedGate, status, allowed: pass, reason },
|
|
219
240
|
nextPhase,
|
|
220
241
|
nextInstruction
|
|
@@ -239,6 +260,7 @@ export class CompletionToolServerSession {
|
|
|
239
260
|
content: [{ type: "text", text: `Forge recorded ${completedGate} artifact ${path.relative(reference.repoPath, reference.path)} (${reference.status}, ${reference.contentHash}). ${nextInstruction}` }],
|
|
240
261
|
structuredContent: {
|
|
241
262
|
...reference,
|
|
263
|
+
...acceptedArtifactReceipt(reference),
|
|
242
264
|
gate: { phase: "draft", draftGate: completedGate, status, allowed: status !== "blocked", reason: nextGate ? `${completedGate} artifact accepted; continue to ${nextGate}.` : `Draft proposal artifact accepted with status ${status}.` },
|
|
243
265
|
nextPhase,
|
|
244
266
|
nextInstruction
|
|
@@ -272,6 +294,7 @@ export class CompletionToolServerSession {
|
|
|
272
294
|
],
|
|
273
295
|
structuredContent: {
|
|
274
296
|
...reference,
|
|
297
|
+
...acceptedArtifactReceipt(reference),
|
|
275
298
|
gate: {
|
|
276
299
|
phase: completedGate,
|
|
277
300
|
status,
|
|
@@ -292,6 +315,9 @@ export class CompletionToolServerSession {
|
|
|
292
315
|
throw new Error(`Tool ${toolName} is out of order. Current Forge QA gate is ${this.currentQaGate}.`);
|
|
293
316
|
}
|
|
294
317
|
const completedGate = this.currentQaGate;
|
|
318
|
+
if (completedGate === "qa_regression" && managedAttemptToolsAvailable()) {
|
|
319
|
+
assertManagedQaRegressionEvidence(input, this.managedCommandReceipts);
|
|
320
|
+
}
|
|
295
321
|
const reference = await writeCompletionArtifact({ ...this.config, phase: "qa" }, toolName, input);
|
|
296
322
|
const status = reference.status.trim().toLowerCase();
|
|
297
323
|
const nextGate = this.advanceAfterQaGate(completedGate);
|
|
@@ -310,6 +336,7 @@ export class CompletionToolServerSession {
|
|
|
310
336
|
],
|
|
311
337
|
structuredContent: {
|
|
312
338
|
...reference,
|
|
339
|
+
...acceptedArtifactReceipt(reference),
|
|
313
340
|
gate: {
|
|
314
341
|
phase: "qa",
|
|
315
342
|
qaGate: completedGate,
|
|
@@ -327,6 +354,10 @@ export class CompletionToolServerSession {
|
|
|
327
354
|
this.completed = true;
|
|
328
355
|
return "sign_off_ready";
|
|
329
356
|
}
|
|
357
|
+
if (phase === "ci_repair") {
|
|
358
|
+
this.completed = true;
|
|
359
|
+
return "provider_checks_ready";
|
|
360
|
+
}
|
|
330
361
|
if (isDebugCompletionPhase(phase)) {
|
|
331
362
|
return nextDebugPhaseAfter(phase) ?? "qa_ready";
|
|
332
363
|
}
|
|
@@ -382,6 +413,118 @@ export class CompletionToolServerSession {
|
|
|
382
413
|
return null;
|
|
383
414
|
}
|
|
384
415
|
}
|
|
416
|
+
const completionToolRuntimeEnvironmentNames = [
|
|
417
|
+
"FORGE_RUNTIME_MODE",
|
|
418
|
+
"FORGE_RESOURCE_LEASE_ID",
|
|
419
|
+
"FORGE_DISPATCH_ATTEMPT_ID",
|
|
420
|
+
"FORGE_E2E_OWNERSHIP_MODE",
|
|
421
|
+
"FORGE_E2E_OWNERSHIP_TOKEN",
|
|
422
|
+
"FORGE_WORKTREE_PATH",
|
|
423
|
+
"FORGE_LOG_DIR",
|
|
424
|
+
"FORGE_CACHE_DIR",
|
|
425
|
+
"FORGE_TEST_OUTPUT_DIR",
|
|
426
|
+
"FORGE_ATTEMPT_DATABASE_SCHEMA",
|
|
427
|
+
"FORGE_ATTEMPT_DATABASE_READINESS_PATH",
|
|
428
|
+
"FORGE_CI_REPAIR_READINESS_PATH",
|
|
429
|
+
"FORGE_CI_REPAIR_HANDOFF_PATH",
|
|
430
|
+
"FORGE_CI_REPAIR_PROVIDER_ONLY_SPECS",
|
|
431
|
+
"FORGE_PLAYWRIGHT_CONFIG",
|
|
432
|
+
"FORGE_E2E_API_URL",
|
|
433
|
+
"FORGE_E2E_WEB_URL",
|
|
434
|
+
"PLAYWRIGHT_BROWSERS_PATH",
|
|
435
|
+
"FORGE_TMPDIR",
|
|
436
|
+
"NPM_CONFIG_CACHE",
|
|
437
|
+
"TMPDIR",
|
|
438
|
+
"TMP",
|
|
439
|
+
"TEMP",
|
|
440
|
+
];
|
|
441
|
+
export function completionToolRuntimeEnvironment(runtimeEnvironment) {
|
|
442
|
+
if (!runtimeEnvironment)
|
|
443
|
+
return {};
|
|
444
|
+
const env = {};
|
|
445
|
+
for (const name of completionToolRuntimeEnvironmentNames) {
|
|
446
|
+
const value = runtimeEnvironment[name]?.trim();
|
|
447
|
+
if (value)
|
|
448
|
+
env[name] = value;
|
|
449
|
+
}
|
|
450
|
+
return env;
|
|
451
|
+
}
|
|
452
|
+
const minimumManagedSolutionTestTimeoutMs = 45 * 60 * 1000;
|
|
453
|
+
/**
|
|
454
|
+
* A solution-wide Forge test is long enough that a generic provider shell can
|
|
455
|
+
* expire while VSTest is still healthy. On managed host attempts, only the
|
|
456
|
+
* attempt-owned command runner can prove the exact process outcome and clean
|
|
457
|
+
* up its process tree. Keep this check at the completion gate so prompt drift
|
|
458
|
+
* cannot turn an arbitrary shell timeout into a QA blocker.
|
|
459
|
+
*/
|
|
460
|
+
export function assertManagedQaRegressionEvidence(input, receipts) {
|
|
461
|
+
const payload = asRecord(input, "QA regression completion payload");
|
|
462
|
+
const nestedEvidence = asMutableRecord(payload.evidence);
|
|
463
|
+
const commands = payload.commands ?? nestedEvidence.commands;
|
|
464
|
+
if (!Array.isArray(commands))
|
|
465
|
+
return;
|
|
466
|
+
for (const [index, entry] of commands.entries()) {
|
|
467
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
468
|
+
continue;
|
|
469
|
+
const command = entry.command;
|
|
470
|
+
if (typeof command !== "string" || !isForgeSolutionTestCommand(command))
|
|
471
|
+
continue;
|
|
472
|
+
const managedCommandId = entry.managed_command_id;
|
|
473
|
+
if (typeof managedCommandId !== "string" || !managedCommandId.trim()) {
|
|
474
|
+
throw new Error(`qa_regression_managed_command_id_required: commands[${index}] runs the solution-wide Forge test. `
|
|
475
|
+
+ "Run it with forge.start_managed_command, continue independent work, then call forge.wait_managed_command for a transport-safe receipt. "
|
|
476
|
+
+ "If command_continues=true, continue useful work or wait again after retry_after_ms. Include the terminal managed_command_id in this command evidence.");
|
|
477
|
+
}
|
|
478
|
+
const matchingReceipts = receipts.filter((receipt) => {
|
|
479
|
+
const tokens = receipt.command;
|
|
480
|
+
return receipt.command_id === managedCommandId
|
|
481
|
+
&& Array.isArray(tokens)
|
|
482
|
+
&& tokens.every((token) => typeof token === "string")
|
|
483
|
+
&& isForgeSolutionTestCommand(tokens.join(" "));
|
|
484
|
+
});
|
|
485
|
+
if (matchingReceipts.length === 0) {
|
|
486
|
+
throw new Error(`qa_regression_managed_command_required: commands[${index}] runs the solution-wide Forge test. `
|
|
487
|
+
+ "Run it with forge.start_managed_command, continue independent work, then call forge.wait_managed_command for a transport-safe receipt. "
|
|
488
|
+
+ "If command_continues=true, continue useful work or wait again after retry_after_ms. Include its matching terminal managed_command_id; generic-shell timeout evidence is not accepted on a managed host.");
|
|
489
|
+
}
|
|
490
|
+
const terminalReceipts = matchingReceipts.filter((receipt) => typeof receipt.status === "string" && [
|
|
491
|
+
"pass",
|
|
492
|
+
"fail",
|
|
493
|
+
"timed_out",
|
|
494
|
+
"timeout_termination_failed",
|
|
495
|
+
"runner_error",
|
|
496
|
+
].includes(receipt.status));
|
|
497
|
+
if (terminalReceipts.length === 0) {
|
|
498
|
+
throw new Error(`qa_regression_managed_command_in_progress: commands[${index}] has not produced a terminal managed receipt. `
|
|
499
|
+
+ "Call forge.wait_managed_command. If command_continues=true, continue useful work or wait again after retry_after_ms; resubmit this gate only after the receipt is terminal.");
|
|
500
|
+
}
|
|
501
|
+
const adequateReceipt = terminalReceipts.some((receipt) => {
|
|
502
|
+
if (receipt.timed_out !== true)
|
|
503
|
+
return true;
|
|
504
|
+
return typeof receipt.timeout_ms === "number"
|
|
505
|
+
&& receipt.timeout_ms >= minimumManagedSolutionTestTimeoutMs
|
|
506
|
+
&& receipt.process_tree_terminated === true;
|
|
507
|
+
});
|
|
508
|
+
if (!adequateReceipt) {
|
|
509
|
+
throw new Error(`qa_regression_managed_timeout_too_short: commands[${index}] timed out before the minimum `
|
|
510
|
+
+ `${minimumManagedSolutionTestTimeoutMs}ms solution-test budget. Rerun it with forge.start_managed_command `
|
|
511
|
+
+ "using at least a 45-minute timeout and resubmit this gate.");
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function isForgeSolutionTestCommand(command) {
|
|
516
|
+
const normalized = command.trim().toLowerCase().replaceAll("\\", "/");
|
|
517
|
+
return normalized.includes("dotnet")
|
|
518
|
+
&& /(?:^|\s)test(?:\s|$)/u.test(normalized)
|
|
519
|
+
&& /(?:^|[\s/"'])forge\.sln(?:[\s"']|$)/u.test(normalized);
|
|
520
|
+
}
|
|
521
|
+
function managedAttemptToolsAvailable() {
|
|
522
|
+
return process.env.FORGE_RUNTIME_MODE === "host"
|
|
523
|
+
&& Boolean(process.env.FORGE_RESOURCE_LEASE_ID?.trim())
|
|
524
|
+
&& Boolean(process.env.FORGE_DISPATCH_ATTEMPT_ID?.trim())
|
|
525
|
+
&& Boolean(process.env.FORGE_LOG_DIR?.trim())
|
|
526
|
+
&& Boolean(process.env.FORGE_TEST_OUTPUT_DIR?.trim());
|
|
527
|
+
}
|
|
385
528
|
export async function runCompletionMcpServer(config) {
|
|
386
529
|
const session = new CompletionToolServerSession(config);
|
|
387
530
|
process.stdin.setEncoding("utf8");
|
|
@@ -437,7 +580,7 @@ export function parseCompletionMcpArgs(argv) {
|
|
|
437
580
|
const rawPhase = argv[++index];
|
|
438
581
|
const parsedPhase = parseStrictCompletionPhase(rawPhase);
|
|
439
582
|
if (!parsedPhase) {
|
|
440
|
-
return { ok: false, error: "mcp --phase must be one of: implementation, self-review, validation, qa, architect, draft, debug-repro, debug-root-cause, debug-fix, debug-regression." };
|
|
583
|
+
return { ok: false, error: "mcp --phase must be one of: implementation, self-review, validation, qa, architect, draft, decompose, ci-repair, debug-repro, debug-root-cause, debug-fix, debug-regression." };
|
|
441
584
|
}
|
|
442
585
|
phase = parsedPhase;
|
|
443
586
|
}
|
|
@@ -596,10 +739,48 @@ async function handleJsonRpcRequest(session, method, params) {
|
|
|
596
739
|
const call = asRecord(params, "tools/call params");
|
|
597
740
|
const name = requiredString(call, "name");
|
|
598
741
|
const args = call.arguments ?? {};
|
|
599
|
-
|
|
742
|
+
try {
|
|
743
|
+
return await session.callTool(name, args);
|
|
744
|
+
}
|
|
745
|
+
catch (error) {
|
|
746
|
+
if (isManagedAttemptTool(name))
|
|
747
|
+
throw error;
|
|
748
|
+
return completionToolRejectedResult(name, error);
|
|
749
|
+
}
|
|
600
750
|
}
|
|
601
751
|
throw new Error(`Unsupported MCP method: ${method}`);
|
|
602
752
|
}
|
|
753
|
+
export function completionToolRejectedResult(toolName, error) {
|
|
754
|
+
const issues = error instanceof CompletionPayloadValidationError
|
|
755
|
+
? [...error.issues]
|
|
756
|
+
: [errorMessage(error)];
|
|
757
|
+
const repairInstruction = "Repair every listed issue and resubmit this same gate once. Do not inspect the artifact path: rejected calls never write or replace completion artifacts.";
|
|
758
|
+
return {
|
|
759
|
+
content: [{
|
|
760
|
+
type: "text",
|
|
761
|
+
text: `Forge rejected ${toolName}. No artifact was written.\n${issues.map(issue => `- ${issue}`).join("\n")}\n${repairInstruction}`,
|
|
762
|
+
}],
|
|
763
|
+
structuredContent: {
|
|
764
|
+
status: "rejected",
|
|
765
|
+
tool_name: toolName,
|
|
766
|
+
artifact_written: false,
|
|
767
|
+
issues,
|
|
768
|
+
repair_instruction: repairInstruction,
|
|
769
|
+
},
|
|
770
|
+
isError: true,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function acceptedArtifactReceipt(reference) {
|
|
774
|
+
return {
|
|
775
|
+
artifact_written: true,
|
|
776
|
+
artifact_receipt: {
|
|
777
|
+
schema_version: 1,
|
|
778
|
+
path: reference.path,
|
|
779
|
+
report_path: reference.reportPath,
|
|
780
|
+
content_hash: reference.contentHash,
|
|
781
|
+
},
|
|
782
|
+
};
|
|
783
|
+
}
|
|
603
784
|
function toolDefinition(name, phase, qaGate = null, decomposeGate = null) {
|
|
604
785
|
return {
|
|
605
786
|
name,
|
|
@@ -16,6 +16,9 @@ export function toolDescription(phase, qaGate = null, decomposeGate = null) {
|
|
|
16
16
|
if (phase === "implementation") {
|
|
17
17
|
return "Complete Forge implementation by writing implementation.completeness.json with scoped changed-file, verification, acceptance-criteria, gap, and next-phase evidence.";
|
|
18
18
|
}
|
|
19
|
+
if (phase === "ci_repair") {
|
|
20
|
+
return "Complete Forge CI repair by writing ci-repair.handoff.json with failed-check, root-cause, scoped-change, workflow-equivalence, validation, provider-state, and push-readiness evidence.";
|
|
21
|
+
}
|
|
19
22
|
if (phase === "self_review") {
|
|
20
23
|
return "Complete Forge self-review by writing self-review.report.json with diff review, findings, security, edge-case, design, deterministic-comparison, fixes, and remaining-risk evidence.";
|
|
21
24
|
}
|
|
@@ -55,6 +58,94 @@ export function toolInputSchema(phase, qaGate = null, decomposeGate = null) {
|
|
|
55
58
|
if (isDebugCompletionPhase(phase)) {
|
|
56
59
|
return debugToolInputSchema(phase, baseProperties);
|
|
57
60
|
}
|
|
61
|
+
if (phase === "ci_repair") {
|
|
62
|
+
return objectToolSchema({
|
|
63
|
+
...baseProperties,
|
|
64
|
+
failed_checks: {
|
|
65
|
+
type: "array",
|
|
66
|
+
description: "Required and non-empty for pass. Provider check/job/step evidence inspected before repair.",
|
|
67
|
+
items: evidenceObjectSchema(["name", "provider_state"], {
|
|
68
|
+
name: { type: "string" },
|
|
69
|
+
provider_state: { type: "string" },
|
|
70
|
+
workflow: { type: "string" },
|
|
71
|
+
job: { type: "string" },
|
|
72
|
+
step: { type: "string" },
|
|
73
|
+
details_url: { type: "string" },
|
|
74
|
+
evidence: { type: "string" }
|
|
75
|
+
})
|
|
76
|
+
},
|
|
77
|
+
root_cause: {
|
|
78
|
+
type: "object",
|
|
79
|
+
description: "Required for pass. Include classification, summary, and concrete evidence.",
|
|
80
|
+
additionalProperties: true,
|
|
81
|
+
properties: {
|
|
82
|
+
classification: { type: "string" },
|
|
83
|
+
summary: { type: "string" },
|
|
84
|
+
evidence: { type: "array", items: { type: "string" } }
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
changed_files: {
|
|
88
|
+
type: "array",
|
|
89
|
+
description: "Files changed by the repair. May be empty only when no_code_action explains the provider-only action or blocker.",
|
|
90
|
+
items: evidenceObjectSchema(["path", "summary"], {
|
|
91
|
+
path: { type: "string" },
|
|
92
|
+
summary: { type: "string" }
|
|
93
|
+
})
|
|
94
|
+
},
|
|
95
|
+
no_code_action: {
|
|
96
|
+
type: "object",
|
|
97
|
+
description: "Required when changed_files is empty. Describe the evidence-backed provider action or blocker.",
|
|
98
|
+
additionalProperties: true,
|
|
99
|
+
properties: {
|
|
100
|
+
action: { type: "string" },
|
|
101
|
+
reason: { type: "string" },
|
|
102
|
+
evidence: { type: "string" }
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
narrow_validation: {
|
|
106
|
+
type: "array",
|
|
107
|
+
description: "Required and non-empty for pass. The focused failing command or evidence-backed substitute.",
|
|
108
|
+
items: commandEvidenceSchema()
|
|
109
|
+
},
|
|
110
|
+
workflow_validation: {
|
|
111
|
+
type: "array",
|
|
112
|
+
description: "Required and non-empty for pass. Each failed-job guard/test/drift step with local equivalence supported, substitute, or provider_only.",
|
|
113
|
+
items: evidenceObjectSchema(["step", "equivalence", "status"], {
|
|
114
|
+
step: { type: "string" },
|
|
115
|
+
equivalence: { type: "string", enum: ["supported", "substitute", "provider_only"] },
|
|
116
|
+
command: { type: "string" },
|
|
117
|
+
exit_code: { type: "number" },
|
|
118
|
+
status: { type: "string" },
|
|
119
|
+
summary: { type: "string" },
|
|
120
|
+
reason: { type: "string" },
|
|
121
|
+
substitute: { type: "string" }
|
|
122
|
+
})
|
|
123
|
+
},
|
|
124
|
+
broader_validation: {
|
|
125
|
+
type: "array",
|
|
126
|
+
description: "Required and non-empty for pass. Normal validation covering every touched compiled/tested surface.",
|
|
127
|
+
items: commandEvidenceSchema()
|
|
128
|
+
},
|
|
129
|
+
remaining_risks: {
|
|
130
|
+
type: "array",
|
|
131
|
+
description: "Required for pass, even when empty.",
|
|
132
|
+
items: evidenceObjectSchema(["summary"], {
|
|
133
|
+
summary: { type: "string" },
|
|
134
|
+
mitigation: { type: "string" },
|
|
135
|
+
owner: { type: "string" }
|
|
136
|
+
})
|
|
137
|
+
},
|
|
138
|
+
provider_state: {
|
|
139
|
+
type: "string",
|
|
140
|
+
enum: ["ready_for_rerun", "rerun_requested", "checks_pending", "still_failing", "blocked_by_provider"],
|
|
141
|
+
description: "Provider state after local validation and before Forge-owned publication."
|
|
142
|
+
},
|
|
143
|
+
ready_for_push: {
|
|
144
|
+
type: "boolean",
|
|
145
|
+
description: "Required for pass and must be true. Forge Bridge performs the commit/push only after this gate passes."
|
|
146
|
+
}
|
|
147
|
+
}, ["status", "summary"], "For status pass, include failed_checks, root_cause, changed_files or no_code_action, narrow_validation, workflow_validation, broader_validation, remaining_risks, provider_state, and ready_for_push: true.");
|
|
148
|
+
}
|
|
58
149
|
if (phase === "implementation") {
|
|
59
150
|
return objectToolSchema({
|
|
60
151
|
...baseProperties,
|
|
@@ -130,7 +221,10 @@ export function toolInputSchema(phase, qaGate = null, decomposeGate = null) {
|
|
|
130
221
|
file: { type: "string" }
|
|
131
222
|
})
|
|
132
223
|
},
|
|
133
|
-
security_review:
|
|
224
|
+
security_review: {
|
|
225
|
+
...reviewSectionSchema(),
|
|
226
|
+
description: "Required for pass. Use pass after review, or not_applicable only with notes explaining why the change has no security-relevant surface."
|
|
227
|
+
},
|
|
134
228
|
edge_case_review: reviewSectionSchema(),
|
|
135
229
|
design_review: {
|
|
136
230
|
...reviewSectionSchema(),
|
|
@@ -198,7 +292,7 @@ export function toolInputSchema(phase, qaGate = null, decomposeGate = null) {
|
|
|
198
292
|
},
|
|
199
293
|
sidecars: {
|
|
200
294
|
type: "array",
|
|
201
|
-
description: "Optional QA sidecar files already written under
|
|
295
|
+
description: "Optional QA sidecar files already written under the dispatch-provided QA artifacts path with path/content_hash/bytes metadata.",
|
|
202
296
|
items: evidenceObjectSchema(["path", "content_hash", "bytes"], {
|
|
203
297
|
path: { type: "string" },
|
|
204
298
|
content_hash: { type: "string" },
|
|
@@ -628,6 +722,11 @@ function qaScopeSchema(description) {
|
|
|
628
722
|
schema_version: { type: "number", enum: [1] },
|
|
629
723
|
surface: { type: "array", items: { type: "string", enum: [...qaScopeSurfaces] } },
|
|
630
724
|
required_evidence: { type: "array", items: { type: "string", enum: [...qaScopeEvidence] } },
|
|
725
|
+
required_capabilities: {
|
|
726
|
+
type: "array",
|
|
727
|
+
description: "Runner facilities required by implementation and validation. Use docker_engine when tests or project services require access to a Docker daemon, even when the agent itself runs in a managed host worktree.",
|
|
728
|
+
items: { type: "string", enum: ["docker_engine"] }
|
|
729
|
+
},
|
|
631
730
|
not_applicable: { type: "array", items: { type: "string", enum: [...qaScopeEvidence] } },
|
|
632
731
|
browser_required: { type: "boolean" },
|
|
633
732
|
api_required: { type: "boolean" },
|
|
@@ -652,9 +751,23 @@ function qaScopeSchema(description) {
|
|
|
652
751
|
function commandEvidenceSchema() {
|
|
653
752
|
return evidenceObjectSchema(["command", "exit_code", "status", "summary"], {
|
|
654
753
|
command: { type: "string" },
|
|
655
|
-
|
|
754
|
+
managed_command_id: { type: "string", description: "Receipt ID returned by forge.start_managed_command after forge.wait_managed_command reports a terminal receipt. A transport-safe wait can return command_continues=true; wait again after retry_after_ms before submitting the gate." },
|
|
755
|
+
exit_code: { type: ["number", "null"], description: "Process exit code, or null when the managed runner timed out or could not launch the process." },
|
|
656
756
|
status: { type: "string" },
|
|
657
|
-
summary: { type: "string" }
|
|
757
|
+
summary: { type: "string" },
|
|
758
|
+
classification: {
|
|
759
|
+
type: "string",
|
|
760
|
+
enum: ["out_of_scope_failure", "baseline_failure"],
|
|
761
|
+
description: "Only for a verified non-blocking failure in a passing QA regression gate. Never use for an in-scope or unexplained failure."
|
|
762
|
+
},
|
|
763
|
+
candidate_bug_id: { type: "string", description: "Required for a classified non-blocking failure; links the preserved failure to candidate_bugs." },
|
|
764
|
+
scope_evidence: { type: "string", description: "Required proof that the failing surface is outside the current acceptance criteria and changed-file scope." },
|
|
765
|
+
acceptance_criteria_independent: { type: "boolean", description: "Required and true only when the current item can be judged independently of this failure." },
|
|
766
|
+
repro_command: { type: "string", description: "Required for out_of_scope_failure; focused command that reproduced the unrelated failure." },
|
|
767
|
+
repro_exit_code: { type: "number", description: "Required non-zero focused repro exit code for out_of_scope_failure." },
|
|
768
|
+
baseline_ref: { type: "string", description: "Required for baseline_failure; immutable ref where the same failure was observed." },
|
|
769
|
+
baseline_exit_code: { type: "number", description: "Required non-zero baseline command exit code for baseline_failure." },
|
|
770
|
+
failure_signature_match: { type: "boolean", description: "Required and true for baseline_failure after matching failing test/error signatures." }
|
|
658
771
|
});
|
|
659
772
|
}
|
|
660
773
|
function resolvedFindingSchema() {
|
|
@@ -2,9 +2,12 @@ import { type CompletionPhase, type DebugCompletionPhase, type QaCompletionGate
|
|
|
2
2
|
export declare function normalizeCompletionStatus(value: string, phase?: CompletionPhase): string;
|
|
3
3
|
export declare function normalizeQaGateStatus(value: string): string;
|
|
4
4
|
export declare function validatePayloadShape(phase: CompletionPhase, payload: Record<string, unknown>): void;
|
|
5
|
+
export declare function validateCiRepairPayloadShape(payload: Record<string, unknown>): void;
|
|
5
6
|
export declare function validateDebugPayloadShape(phase: DebugCompletionPhase, payload: Record<string, unknown>): void;
|
|
6
7
|
export declare function validateQaGatePayloadShape(gate: QaCompletionGate, payload: Record<string, unknown>, status: string): void;
|
|
7
|
-
export declare function validateQaGatePassingCommands(commands: readonly unknown[]
|
|
8
|
+
export declare function validateQaGatePassingCommands(commands: readonly unknown[], options?: {
|
|
9
|
+
readonly allowClassifiedNonBlockingFailures?: boolean;
|
|
10
|
+
}): void;
|
|
8
11
|
export declare function validateArchitectPayloadShape(payload: Record<string, unknown>, status: string): void;
|
|
9
12
|
export declare function validateArchitectTaskDevelopScope(task: Record<string, unknown>, taskId: string): void;
|
|
10
13
|
export declare function validateArchitectVerificationScope(task: Record<string, unknown>, taskId: string): void;
|