@loucompanion/forge-bridge 0.1.3-dev.f66395c3ac21 → 0.1.3-dev.fbe15023cdfe

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.
Files changed (150) hide show
  1. package/README.md +78 -0
  2. package/dist/bridge/bridge.base.d.ts +6 -1
  3. package/dist/bridge/bridge.service.d.ts +60 -3
  4. package/dist/bridge/bridge.service.js +970 -24
  5. package/dist/bridge/bridge.types.d.ts +172 -1
  6. package/dist/cleanup/cleanup.service.d.ts +13 -2
  7. package/dist/cleanup/cleanup.service.js +435 -3
  8. package/dist/cleanup/cleanup.types.d.ts +10 -0
  9. package/dist/cleanup/index.d.ts +1 -1
  10. package/dist/cleanup/index.js +1 -1
  11. package/dist/cleanup/internals.d.ts +2 -2
  12. package/dist/cleanup/internals.js +1 -1
  13. package/dist/cli/cli.help.d.ts +2 -0
  14. package/dist/cli/cli.help.js +217 -0
  15. package/dist/cli/cli.service.d.ts +42 -2
  16. package/dist/cli/cli.service.js +356 -205
  17. package/dist/cli/local-config.d.ts +6 -0
  18. package/dist/cli/local-config.js +47 -3
  19. package/dist/runtime/attempt-node-runtime-launcher.d.ts +17 -0
  20. package/dist/runtime/attempt-node-runtime-launcher.js +101 -0
  21. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  22. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  23. package/dist/runtime/attempt-process-supervisor.d.ts +86 -0
  24. package/dist/runtime/attempt-process-supervisor.js +350 -0
  25. package/dist/runtime/generated-output-reset.d.ts +10 -0
  26. package/dist/runtime/generated-output-reset.js +103 -0
  27. package/dist/runtime/host-listener-inspector.d.ts +44 -0
  28. package/dist/runtime/host-listener-inspector.js +324 -0
  29. package/dist/runtime/host-port-broker.d.ts +76 -0
  30. package/dist/runtime/host-port-broker.js +333 -0
  31. package/dist/runtime/host-runtime-contract.d.ts +47 -0
  32. package/dist/runtime/host-runtime-contract.js +700 -0
  33. package/dist/runtime/host-runtime-lease.d.ts +133 -0
  34. package/dist/runtime/host-runtime-lease.js +1460 -0
  35. package/dist/runtime/host-runtime-promotion.d.ts +23 -0
  36. package/dist/runtime/host-runtime-promotion.js +159 -0
  37. package/dist/runtime/node-host-runtime-adapter.d.ts +51 -0
  38. package/dist/runtime/node-host-runtime-adapter.js +268 -0
  39. package/dist/runtime/playwright-host-runtime-adapter.d.ts +20 -0
  40. package/dist/runtime/playwright-host-runtime-adapter.js +220 -0
  41. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  42. package/dist/runtime/runtime-isolation-lease.js +158 -0
  43. package/dist/sandbox/command-runner.d.ts +4 -0
  44. package/dist/sandbox/command-runner.js +47 -0
  45. package/dist/sandbox/contract.d.ts +5 -0
  46. package/dist/sandbox/contract.js +276 -0
  47. package/dist/sandbox/event-budget.d.ts +11 -0
  48. package/dist/sandbox/event-budget.js +27 -0
  49. package/dist/sandbox/image-policy.d.ts +35 -0
  50. package/dist/sandbox/image-policy.js +247 -0
  51. package/dist/sandbox/index.d.ts +5 -0
  52. package/dist/sandbox/index.js +5 -0
  53. package/dist/sandbox/lease-watchdog.d.ts +18 -0
  54. package/dist/sandbox/lease-watchdog.js +67 -0
  55. package/dist/sandbox/preflight.d.ts +54 -0
  56. package/dist/sandbox/preflight.js +335 -0
  57. package/dist/sandbox/process.d.ts +20 -0
  58. package/dist/sandbox/process.js +209 -0
  59. package/dist/sandbox/provider-auth/codex.d.ts +26 -0
  60. package/dist/sandbox/provider-auth/codex.js +212 -0
  61. package/dist/sandbox/repository-lock.d.ts +5 -0
  62. package/dist/sandbox/repository-lock.js +119 -0
  63. package/dist/sandbox/runtime.d.ts +297 -0
  64. package/dist/sandbox/runtime.js +2338 -0
  65. package/dist/sandbox/sandbox.types.d.ts +86 -0
  66. package/dist/sandbox/sandbox.types.js +1 -0
  67. package/dist/sandbox/smoke.d.ts +76 -0
  68. package/dist/sandbox/smoke.js +380 -0
  69. package/dist/sandbox/worker-supervisor.d.ts +13 -0
  70. package/dist/sandbox/worker-supervisor.js +36 -0
  71. package/dist/sandbox/worker.d.ts +1 -0
  72. package/dist/sandbox/worker.js +225 -0
  73. package/dist/sandbox/workspace.service.d.ts +13 -0
  74. package/dist/sandbox/workspace.service.js +72 -0
  75. package/dist/session/managed-attempt-tools.d.ts +12 -0
  76. package/dist/session/managed-attempt-tools.js +561 -0
  77. package/dist/session/phase-completion.artifacts.d.ts +10 -0
  78. package/dist/session/phase-completion.artifacts.js +627 -0
  79. package/dist/session/phase-completion.d.ts +43 -42
  80. package/dist/session/phase-completion.helpers.d.ts +112 -0
  81. package/dist/session/phase-completion.helpers.js +684 -0
  82. package/dist/session/phase-completion.js +114 -3007
  83. package/dist/session/phase-completion.prompts.d.ts +14 -0
  84. package/dist/session/phase-completion.prompts.js +234 -0
  85. package/dist/session/phase-completion.runtime.d.ts +47 -0
  86. package/dist/session/phase-completion.runtime.js +731 -0
  87. package/dist/session/phase-completion.schemas.d.ts +3 -0
  88. package/dist/session/phase-completion.schemas.js +812 -0
  89. package/dist/session/phase-completion.validation.d.ts +37 -0
  90. package/dist/session/phase-completion.validation.js +828 -0
  91. package/dist/session/provider-cli/ci-repair-readiness.d.ts +8 -0
  92. package/dist/session/provider-cli/ci-repair-readiness.js +90 -0
  93. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +93 -0
  94. package/dist/session/provider-cli/provider-cli.artifacts.js +778 -0
  95. package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
  96. package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
  97. package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
  98. package/dist/session/provider-cli/provider-cli.deterministic.d.ts +14 -0
  99. package/dist/session/provider-cli/provider-cli.deterministic.js +375 -0
  100. package/dist/session/provider-cli/provider-cli.process.d.ts +48 -0
  101. package/dist/session/provider-cli/provider-cli.process.js +474 -0
  102. package/dist/session/provider-cli/provider-cli.prompts.d.ts +47 -0
  103. package/dist/session/provider-cli/provider-cli.prompts.js +595 -0
  104. package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
  105. package/dist/session/provider-cli/provider-cli.qa-sidecars.js +66 -0
  106. package/dist/session/provider-cli/provider-cli.runtime.d.ts +9 -0
  107. package/dist/session/provider-cli/provider-cli.runtime.js +41 -0
  108. package/dist/session/provider-cli/provider-cli.service.d.ts +22 -53
  109. package/dist/session/provider-cli/provider-cli.service.js +587 -2530
  110. package/dist/session/provider-cli/provider-cli.types.d.ts +6 -0
  111. package/dist/session/provider-cli/provider-output-state.d.ts +22 -0
  112. package/dist/session/provider-cli/provider-output-state.js +473 -0
  113. package/dist/session/session-redaction.d.ts +3 -0
  114. package/dist/session/session-redaction.js +23 -0
  115. package/dist/session/session.base.d.ts +6 -1
  116. package/dist/session/session.service.d.ts +8 -2
  117. package/dist/session/session.service.js +463 -58
  118. package/dist/session/session.types.d.ts +177 -1
  119. package/dist/session/session.types.js +352 -3
  120. package/dist/shared/path/path.js +3 -1
  121. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  122. package/dist/shared/process/host-process-recovery.js +78 -0
  123. package/dist/shared/process/host-process-supervisor.d.ts +57 -0
  124. package/dist/shared/process/host-process-supervisor.js +282 -0
  125. package/dist/shared/process/process.d.ts +1 -0
  126. package/dist/shared/process/process.js +1 -0
  127. package/dist/shared/process/windows-launch.d.ts +8 -0
  128. package/dist/shared/process/windows-launch.js +41 -0
  129. package/dist/shared/provider-capabilities.d.ts +1 -0
  130. package/dist/shared/provider-capabilities.js +3 -0
  131. package/dist/shared/redaction/redaction.js +10 -3
  132. package/dist/version.d.ts +1 -1
  133. package/dist/version.js +1 -1
  134. package/dist/worktree/artifact-injector.d.ts +3 -2
  135. package/dist/worktree/artifact-injector.js +82 -10
  136. package/dist/worktree/dependency-warmer.d.ts +21 -1
  137. package/dist/worktree/dependency-warmer.js +138 -5
  138. package/dist/worktree/index.d.ts +1 -0
  139. package/dist/worktree/index.js +1 -0
  140. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  141. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  142. package/dist/worktree/worktree-paths.d.ts +4 -0
  143. package/dist/worktree/worktree-paths.js +30 -0
  144. package/dist/worktree/worktree.base.d.ts +3 -0
  145. package/dist/worktree/worktree.service.d.ts +9 -3
  146. package/dist/worktree/worktree.service.js +100 -52
  147. package/dist/worktree/worktree.types.d.ts +3 -1
  148. package/package.json +6 -3
  149. package/runtime/windows-job-supervisor.ps1 +356 -0
  150. package/runtime/windows-listener-owner.ps1 +33 -0
@@ -0,0 +1,812 @@
1
+ import { decomposeGateFileNames, isDebugCompletionPhase, phaseFiles, qaBlockerReasons, qaGateFileNames, qaScopeAuthorities, qaScopeConfidence, qaScopeEvidence, qaScopeSources, qaScopeSurfaces, resolvedFindingStatusValues } from "./phase-completion.js";
2
+ export function toolDescription(phase, qaGate = null, decomposeGate = null) {
3
+ if (qaGate) {
4
+ return qaGate === "qa_verdict"
5
+ ? "Complete the final Forge QA verdict by writing the canonical QA bundle with pass, needs_rework, or blocked evidence."
6
+ : `Complete the ${qaGate.replace(/_/g, " ")} QA gate by writing ${qaGateFileNames[qaGate]} before continuing to the next QA gate.`;
7
+ }
8
+ if (decomposeGate) {
9
+ return decomposeGate === "decompose_proposal"
10
+ ? "Complete the Forge Decompose proposal by writing decompose.proposal.json with candidate child items and optional structured QA scopes."
11
+ : `Complete the ${decomposeGate.replace(/_/g, " ")} Decompose gate by writing ${decomposeGateFileNames[decomposeGate]} before continuing.`;
12
+ }
13
+ if (isDebugCompletionPhase(phase)) {
14
+ return `Complete the ${phase.replace(/_/g, " ")} Debug gate by writing ${phaseFiles[phase].jsonFileName} with structured bug-fix evidence before continuing.`;
15
+ }
16
+ if (phase === "implementation") {
17
+ return "Complete Forge implementation by writing implementation.completeness.json with scoped changed-file, verification, acceptance-criteria, gap, and next-phase evidence.";
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
+ }
22
+ if (phase === "self_review") {
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.";
24
+ }
25
+ if (phase === "qa") {
26
+ return "Complete Forge QA by writing qa.plan.json, qa.evidence.json, qa.findings.json, qa.verdict.json, and qa.report.md with structured pass, needs-rework, or blocked evidence.";
27
+ }
28
+ if (phase === "architect") {
29
+ return "Complete Forge Architect planning by writing implementation.plan.json, qa.plan.json, architect.verdict.json, and architect.report.md with a pending-approval plan or explicit revision/blocker reasons.";
30
+ }
31
+ return "Complete Forge validation by writing validation.verdict.json with command, build/test/lint/design, drift, acceptance-criteria, and ready-for-dev-test evidence.";
32
+ }
33
+ export function toolInputSchema(phase, qaGate = null, decomposeGate = null) {
34
+ const baseProperties = {
35
+ status: {
36
+ type: "string",
37
+ enum: ["pass", "needs_rework", "fail", "failed"],
38
+ description: "Use pass only when the phase evidence satisfies Forge's gate. Use needs_rework/fail/failed with explicit blockers or findings otherwise."
39
+ },
40
+ summary: { type: "string", description: "Concise phase summary." },
41
+ report_markdown: { type: "string", description: "Optional human-readable report. Forge will generate a fallback report if omitted." },
42
+ blockers: {
43
+ type: "array",
44
+ description: "Required for non-pass status unless findings, failures, known_gaps, remaining_risks, or commands provide explicit evidence.",
45
+ items: evidenceObjectSchema(["reason"], {
46
+ reason: { type: "string" },
47
+ owner: { type: "string" },
48
+ next_step: { type: "string" }
49
+ })
50
+ }
51
+ };
52
+ if (qaGate && qaGate !== "qa_verdict") {
53
+ return qaGateToolInputSchema(qaGate);
54
+ }
55
+ if (decomposeGate) {
56
+ return decomposeGateToolInputSchema(decomposeGate, baseProperties);
57
+ }
58
+ if (isDebugCompletionPhase(phase)) {
59
+ return debugToolInputSchema(phase, baseProperties);
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
+ }
149
+ if (phase === "implementation") {
150
+ return objectToolSchema({
151
+ ...baseProperties,
152
+ plan: {
153
+ type: "array",
154
+ description: "Required for pass. Ordered implementation steps with status.",
155
+ items: evidenceObjectSchema(["step", "status"], {
156
+ step: { type: "string" },
157
+ status: { type: "string" },
158
+ evidence: { type: "string" }
159
+ })
160
+ },
161
+ changed_files: {
162
+ type: "array",
163
+ description: "Required for pass. Must be non-empty unless known_gaps explains why.",
164
+ items: evidenceObjectSchema(["path", "summary"], {
165
+ path: { type: "string" },
166
+ summary: { type: "string" }
167
+ })
168
+ },
169
+ tests_added_or_updated: {
170
+ type: "array",
171
+ description: "Required for pass. Use an object explaining why no tests were needed for documentation/config-only work if empty would be ambiguous.",
172
+ items: evidenceObjectSchema(["path", "summary"], {
173
+ path: { type: "string" },
174
+ summary: { type: "string" }
175
+ })
176
+ },
177
+ docs_added_or_updated: {
178
+ type: "array",
179
+ description: "Required for pass. Use an object explaining why no docs changed when appropriate.",
180
+ items: evidenceObjectSchema(["path", "summary"], {
181
+ path: { type: "string" },
182
+ summary: { type: "string" }
183
+ })
184
+ },
185
+ verification: {
186
+ type: "array",
187
+ description: "Implementation verification evidence. Objects should include command, exit_code, status, and summary when command-based.",
188
+ items: commandEvidenceSchema()
189
+ },
190
+ acceptance_criteria: acceptanceCriteriaSchema(),
191
+ known_gaps: {
192
+ type: "array",
193
+ description: "Required for pass, even when empty. If changed_files/tests/docs arrays are empty, explain why here.",
194
+ items: evidenceObjectSchema(["summary"], {
195
+ summary: { type: "string" },
196
+ impact: { type: "string" },
197
+ follow_up: { type: "string" }
198
+ })
199
+ },
200
+ next_phase_context: {
201
+ description: "Required for pass. Context the self-review phase should carry forward."
202
+ },
203
+ resolved_findings: {
204
+ type: "array",
205
+ description: "Required for QA rework. Each entry must use status resolved, disputed, or not_reproduced.",
206
+ items: resolvedFindingSchema()
207
+ }
208
+ }, ["status", "summary"], "For status pass, include plan, changed_files, tests_added_or_updated, docs_added_or_updated, acceptance_criteria, known_gaps, and next_phase_context. Acceptance criteria entries must use status: satisfied plus evidence.");
209
+ }
210
+ if (phase === "self_review") {
211
+ return objectToolSchema({
212
+ ...baseProperties,
213
+ reviewed_diff: { type: "boolean", description: "Required for pass. True after reviewing the implementation diff." },
214
+ findings: {
215
+ type: "array",
216
+ description: "Required for pass, even when empty. Include finding objects for issues discovered during review.",
217
+ items: evidenceObjectSchema(["severity", "summary", "status"], {
218
+ severity: { type: "string" },
219
+ summary: { type: "string" },
220
+ status: { type: "string" },
221
+ file: { type: "string" }
222
+ })
223
+ },
224
+ security_review: reviewSectionSchema(),
225
+ edge_case_review: reviewSectionSchema(),
226
+ design_review: {
227
+ ...reviewSectionSchema(),
228
+ properties: {
229
+ ...reviewSectionProperties(),
230
+ screenshots: { type: "array", items: evidenceObjectSchema(["path"], { path: { type: "string" }, summary: { type: "string" } }) }
231
+ }
232
+ },
233
+ deterministic_comparison: {
234
+ type: "object",
235
+ description: "Required for pass. Evidence that the reviewed diff matches the intended deterministic change.",
236
+ additionalProperties: true,
237
+ properties: {
238
+ status: { type: "string" },
239
+ command_or_tool: { type: "string" },
240
+ notes: { type: "string" }
241
+ }
242
+ },
243
+ fixes_applied: {
244
+ type: "array",
245
+ description: "Required for pass, even when empty.",
246
+ items: evidenceObjectSchema(["summary"], { summary: { type: "string" }, path: { type: "string" } })
247
+ },
248
+ remaining_risks: {
249
+ type: "array",
250
+ description: "Required for pass, even when empty.",
251
+ items: evidenceObjectSchema(["summary"], { summary: { type: "string" }, mitigation: { type: "string" } })
252
+ }
253
+ }, ["status", "summary"], "For status pass, include reviewed_diff, findings, security_review, edge_case_review, design_review, deterministic_comparison, fixes_applied, and remaining_risks.");
254
+ }
255
+ if (phase === "qa") {
256
+ return objectToolSchema({
257
+ ...baseProperties,
258
+ status: {
259
+ type: "string",
260
+ enum: ["pass", "needs_rework", "blocked"],
261
+ description: "Use pass only when QA evidence satisfies acceptance criteria, needs_rework for actionable in-scope defects, or blocked for an inconclusive QA blocker."
262
+ },
263
+ checklist: {
264
+ type: "array",
265
+ description: "QA plan/checklist. Forge writes this to qa.plan.json; a default checklist is generated if omitted.",
266
+ items: { type: "string" }
267
+ },
268
+ tested_acceptance_criteria: {
269
+ type: "array",
270
+ description: "Required and non-empty for pass/needs_rework. Criteria actually tested by QA.",
271
+ items: { type: "string" }
272
+ },
273
+ commands: {
274
+ type: "array",
275
+ description: "Command evidence written to qa.evidence.json.",
276
+ items: commandEvidenceSchema()
277
+ },
278
+ browser_checks: {
279
+ type: "array",
280
+ description: "Browser/manual UI checks with URL or route, action, observed result, screenshot/trace references, console/network notes, and status.",
281
+ items: evidenceObjectSchema(["summary", "status"], {
282
+ summary: { type: "string" },
283
+ url: { type: "string" },
284
+ route: { type: "string" },
285
+ status: { type: "string" },
286
+ screenshot: { type: "string" },
287
+ trace: { type: "string" }
288
+ })
289
+ },
290
+ sidecars: {
291
+ type: "array",
292
+ description: "Optional QA sidecar files already written under the dispatch-provided QA artifacts path with path/content_hash/bytes metadata.",
293
+ items: evidenceObjectSchema(["path", "content_hash", "bytes"], {
294
+ path: { type: "string" },
295
+ content_hash: { type: "string" },
296
+ bytes: { type: "number" }
297
+ })
298
+ },
299
+ notes: { type: "string", description: "Additional QA evidence notes." },
300
+ evidence: {
301
+ type: "object",
302
+ description: "Optional wrapper containing commands, browser_checks, and notes when a client prefers nested evidence."
303
+ },
304
+ findings: {
305
+ type: "array",
306
+ description: "QA findings. needs_rework requires at least one actionable in_scope finding.",
307
+ items: evidenceObjectSchema(["id", "severity", "scope", "title"], {
308
+ id: { type: "string" },
309
+ severity: { type: "string", enum: ["critical", "major", "minor"] },
310
+ scope: { type: "string", enum: ["in_scope", "out_of_scope", "unclear"] },
311
+ title: { type: "string" },
312
+ acceptance_criteria: { type: "array", items: { type: "string" } },
313
+ repro_steps: { type: "array", items: { type: "string" } },
314
+ expected: { type: "string" },
315
+ actual: { type: "string" },
316
+ evidence: { type: "object" },
317
+ likely_area: { type: "array", items: { type: "string" } },
318
+ required_fix: { type: "string" },
319
+ required_regression: { type: "string" }
320
+ })
321
+ },
322
+ candidate_bugs: {
323
+ type: "array",
324
+ description: "Out-of-scope/follow-up bug candidates. They do not block the current item unless attached as in-scope findings.",
325
+ items: evidenceObjectSchema(["id", "title", "suggested_action"], {
326
+ id: { type: "string" },
327
+ title: { type: "string" },
328
+ severity: { type: "string" },
329
+ scope_reason: { type: "string" },
330
+ repro_steps: { type: "array", items: { type: "string" } },
331
+ evidence: { type: "object" },
332
+ suggested_action: { type: "string", enum: ["create_bug", "create_follow_up", "accept_risk", "attach_to_current_item"] }
333
+ })
334
+ },
335
+ policy_warnings: {
336
+ type: "array",
337
+ description: "Non-blocking Forge policy or scope warnings surfaced with the QA verdict.",
338
+ items: evidenceObjectSchema(["code", "message"], {
339
+ code: { type: "string" },
340
+ message: { type: "string" },
341
+ severity: { type: "string" },
342
+ evidence: { type: "object" }
343
+ })
344
+ },
345
+ blocker_reason: {
346
+ type: "string",
347
+ enum: [...qaBlockerReasons],
348
+ description: "Required when status is blocked."
349
+ }
350
+ }, ["status", "summary"], "For pass, include tested_acceptance_criteria plus concrete command/browser/note evidence. For needs_rework, include at least one actionable in_scope finding. For blocked, include blocker_reason.");
351
+ }
352
+ if (phase === "architect") {
353
+ return objectToolSchema({
354
+ status: {
355
+ type: "string",
356
+ enum: ["pass", "needs_revision", "blocked"],
357
+ description: "Use pass only for a complete pending-approval Architect plan. Use needs_revision for fixable story/design/repo gaps, or blocked for missing required context or decisions."
358
+ },
359
+ summary: { type: "string", description: "Concise Architect plan verdict summary." },
360
+ implementation_plan: {
361
+ type: "object",
362
+ description: "Required for pass. Top-level fields inside this object must include schema_version, plan_id, phases, tasks, dependencies, and phase_gates.",
363
+ additionalProperties: true,
364
+ properties: {
365
+ schema_version: { type: "number", enum: [1] },
366
+ plan_id: { type: "string" },
367
+ phases: {
368
+ type: "array",
369
+ description: "Required top-level implementation_plan.phases array.",
370
+ items: { type: "object", additionalProperties: true }
371
+ },
372
+ tasks: {
373
+ type: "array",
374
+ description: "Required Develop child task list. Each task must include id, a non-empty tests array, and should include verification_scope for task-level QA evidence. Do not put parent-story QA/sign-off, child-count/materialization inventory, parent plan artifact checks, or final acceptance proof here; those belong in qa_plan.final_qa.",
375
+ items: {
376
+ type: "object",
377
+ additionalProperties: true,
378
+ properties: {
379
+ id: { type: "string" },
380
+ title: { type: "string" },
381
+ verification_scope: qaScopeSchema("Task-level QA/verification scope for the materialized child task."),
382
+ tests: {
383
+ type: "array",
384
+ items: { type: "object", additionalProperties: true }
385
+ }
386
+ }
387
+ }
388
+ },
389
+ dependencies: {
390
+ type: "array",
391
+ description: "Required dependency edge array. Use [] when no task dependencies exist. Each edge should use before and after task ids; from/to are accepted only as aliases and normalized before writing artifacts.",
392
+ items: {
393
+ type: "object",
394
+ additionalProperties: true,
395
+ properties: {
396
+ id: { type: "string" },
397
+ before: { type: "string", description: "Task id that must complete first." },
398
+ after: { type: "string", description: "Task id that depends on before." },
399
+ from: { type: "string", description: "Alias for before." },
400
+ to: { type: "string", description: "Alias for after." },
401
+ type: { type: "string" },
402
+ rationale: { type: "string" }
403
+ }
404
+ }
405
+ },
406
+ phase_gates: {
407
+ type: "array",
408
+ description: "Required non-empty phase gate array.",
409
+ items: { type: "object", additionalProperties: true }
410
+ }
411
+ }
412
+ },
413
+ qa_plan: {
414
+ type: "object",
415
+ description: "Required for pass. Must include schema_version: 1 and non-empty final_qa coverage.",
416
+ additionalProperties: true,
417
+ properties: {
418
+ schema_version: { type: "number", enum: [1] },
419
+ final_qa: {
420
+ type: "array",
421
+ description: "Required non-empty final QA coverage array.",
422
+ items: { type: "object", additionalProperties: true }
423
+ }
424
+ }
425
+ },
426
+ report_markdown: { type: "string", description: "Human-readable Architect report. Use report as an alias only when the client cannot send report_markdown." },
427
+ report: { type: "string", description: "Alias for report_markdown." },
428
+ blockers: baseProperties.blockers,
429
+ revision_questions: {
430
+ type: "array",
431
+ description: "Required for needs_revision when blockers are not used.",
432
+ items: { type: "string" }
433
+ },
434
+ revision_reasons: {
435
+ type: "array",
436
+ description: "Concrete reasons that prevent pass status.",
437
+ items: { type: "string" }
438
+ }
439
+ }, ["status", "summary"], "For status pass, include implementation_plan and qa_plan at the top level of the MCP tool arguments. implementation_plan.phases must be a top-level array inside implementation_plan, not nested under another object.");
440
+ }
441
+ return objectToolSchema({
442
+ ...baseProperties,
443
+ commands: {
444
+ type: "array",
445
+ description: "Required for pass. Include at least one deterministic validation command with exit_code: 0 and status: pass.",
446
+ items: commandEvidenceSchema()
447
+ },
448
+ acceptance_criteria: acceptanceCriteriaSchema(),
449
+ build_status: { type: "string", description: "Required for pass and must be pass." },
450
+ test_status: { type: "string", description: "Required for pass and must be pass." },
451
+ lint_status: { type: "string", description: "Required for pass. pass/fail/needs_rework/not_applicable." },
452
+ design_status: { type: "string", description: "Required for pass. pass/fail/needs_rework/not_applicable." },
453
+ drift_checks: {
454
+ type: "array",
455
+ description: "Required for pass. Deterministic checks for unintended drift.",
456
+ items: evidenceObjectSchema(["command_or_tool", "status", "summary"], {
457
+ command_or_tool: { type: "string" },
458
+ status: { type: "string" },
459
+ summary: { type: "string" }
460
+ })
461
+ },
462
+ ready_for_dev_test: { type: "boolean", description: "Required for pass and must be true." }
463
+ }, ["status", "summary"], "For status pass, commands must include at least one passing deterministic command, acceptance criteria entries must use status: satisfied plus evidence, and ready_for_dev_test must be true.");
464
+ }
465
+ function decomposeGateToolInputSchema(gate, baseProperties) {
466
+ if (gate !== "decompose_proposal") {
467
+ return objectToolSchema({
468
+ ...baseProperties,
469
+ findings: {
470
+ type: "array",
471
+ description: "Optional issues, constraints, or risks discovered in this Decompose gate.",
472
+ items: { type: "object", additionalProperties: true }
473
+ }
474
+ }, ["status", "summary"], `Record the ${gate.replace(/_/g, " ")} Decompose gate before continuing.`);
475
+ }
476
+ const candidateSchema = {
477
+ type: "object",
478
+ additionalProperties: true,
479
+ properties: {
480
+ origin: { type: "string" },
481
+ key: { type: "string" },
482
+ title: { type: "string" },
483
+ summary: { type: "string" },
484
+ description: { type: "string" },
485
+ acceptanceCriteria: { type: "string" },
486
+ planningPacket: { type: "string" },
487
+ sortOrder: { type: "number" },
488
+ scheduleIndex: { type: "number", description: "Feature-to-Story total schedule order for dependency-safe execution." },
489
+ schedule_index: { type: "number", description: "Snake_case alias for scheduleIndex." },
490
+ waveIndex: { type: "number", description: "Feature-to-Story parallel wave number." },
491
+ wave_index: { type: "number", description: "Snake_case alias for waveIndex." },
492
+ featureScheduleIndex: { type: "number", description: "Optional ordering within the Feature." },
493
+ feature_schedule_index: { type: "number", description: "Snake_case alias for featureScheduleIndex." },
494
+ expectedOutput: { type: "string", description: "Durable output this Story should produce for dependents." },
495
+ expected_output: { type: "string", description: "Snake_case alias for expectedOutput." },
496
+ handoffSummary: { type: "string", description: "How dependent Stories should consume this Story output." },
497
+ handoff_summary: { type: "string", description: "Snake_case alias for handoffSummary." },
498
+ dependencyReason: { type: "string", description: "Why this Story depends on its dependsOn predecessors." },
499
+ dependency_reason: { type: "string", description: "Snake_case alias for dependencyReason." },
500
+ points: { type: "number" },
501
+ dependsOn: { type: "array", items: { type: "string" } },
502
+ estimatedQaScope: qaScopeSchema("Work Order-to-Feature planning QA estimate. This is not dispatch-authoritative QA scope."),
503
+ estimated_qa_scope: qaScopeSchema("Work Order-to-Feature planning QA estimate. Snake_case alias for estimatedQaScope."),
504
+ qaScope: qaScopeSchema("Feature-to-Story QA scope that becomes Story-level QA scope after proposal approval."),
505
+ qa_scope: qaScopeSchema("Feature-to-Story QA scope. Snake_case alias for qaScope."),
506
+ designerRefCandidates: designerRefCandidatesSchema("Designer packet refs that justify this candidate. Omit to inherit all parent refs unchanged."),
507
+ designer_ref_candidates: designerRefCandidatesSchema("Snake_case alias for designerRefCandidates.")
508
+ }
509
+ };
510
+ const proposalSchema = {
511
+ type: "object",
512
+ additionalProperties: true,
513
+ description: "Final Decompose proposal JSON. Must include candidates, and may include blockedReason.",
514
+ properties: {
515
+ summary: { type: "string" },
516
+ blockedReason: { type: "string" },
517
+ blocked_reason: { type: "string" },
518
+ candidates: {
519
+ type: "array",
520
+ items: candidateSchema
521
+ }
522
+ }
523
+ };
524
+ return objectToolSchema({
525
+ ...baseProperties,
526
+ status: {
527
+ type: "string",
528
+ enum: ["pass", "needs_rework", "blocked"],
529
+ description: "Use pass only when the Decompose proposal is ready for user sign-off."
530
+ },
531
+ proposal_json: proposalSchema,
532
+ decomposition_json: {
533
+ ...proposalSchema,
534
+ description: "Alias for proposal_json when a client cannot send proposal_json."
535
+ }
536
+ }, ["status", "summary"], "For the final Decompose proposal, include proposal_json with a candidates array. Work Order-to-Feature candidates may include estimatedQaScope/estimated_qa_scope; Feature-to-Story candidates may include qaScope/qa_scope. Both levels may include designerRefCandidates/designer_ref_candidates.");
537
+ }
538
+ function designerRefCandidatesSchema(description) {
539
+ return {
540
+ type: "array",
541
+ description,
542
+ items: {
543
+ type: "object",
544
+ additionalProperties: false,
545
+ properties: {
546
+ designerRefId: { type: "string" },
547
+ designer_ref_id: { type: "string" },
548
+ refKey: { type: "string" },
549
+ ref_key: { type: "string" },
550
+ path: { type: "string" },
551
+ anchor: { type: "string" },
552
+ reason: { type: "string" },
553
+ required: { type: "boolean" }
554
+ }
555
+ }
556
+ };
557
+ }
558
+ function debugToolInputSchema(phase, baseProperties) {
559
+ if (phase === "debug_repro") {
560
+ return objectToolSchema({
561
+ ...baseProperties,
562
+ repro_steps: {
563
+ type: "array",
564
+ description: "Required for pass. Minimal reproduction or characterization steps.",
565
+ items: { type: "string" }
566
+ },
567
+ observed: { type: "string", description: "Observed failure behavior." },
568
+ expected: { type: "string", description: "Expected behavior." },
569
+ evidence: {
570
+ type: "array",
571
+ description: "Required for pass. Concrete logs, commands, screenshots, file refs, or characterization notes.",
572
+ items: { type: "object", additionalProperties: true }
573
+ }
574
+ }, ["status", "summary"], "For status pass, include repro_steps, observed, expected, and evidence.");
575
+ }
576
+ if (phase === "debug_root_cause") {
577
+ return objectToolSchema({
578
+ ...baseProperties,
579
+ root_cause: { type: "string", description: "Likely root cause tied to code or config evidence." },
580
+ evidence: {
581
+ type: "array",
582
+ description: "Required for pass. Concrete code/log/test evidence supporting the root cause.",
583
+ items: { type: "object", additionalProperties: true }
584
+ },
585
+ eliminated_alternatives: {
586
+ type: "array",
587
+ description: "Required for pass. Plausible alternatives checked or ruled out.",
588
+ items: { type: "object", additionalProperties: true }
589
+ },
590
+ risk_area: { type: "string", description: "Subsystem or behavior most at risk from the fix." }
591
+ }, ["status", "summary"], "For status pass, include root_cause, evidence, eliminated_alternatives, and risk_area.");
592
+ }
593
+ if (phase === "debug_fix") {
594
+ return objectToolSchema({
595
+ ...baseProperties,
596
+ changed_files: {
597
+ type: "array",
598
+ description: "Required for pass. Files changed by the bug fix.",
599
+ items: evidenceObjectSchema(["path", "summary"], { path: { type: "string" }, summary: { type: "string" } })
600
+ },
601
+ tests_added_or_updated: {
602
+ type: "array",
603
+ description: "Required for pass. Regression tests or checks added/updated, or justify omission in remaining_risks.",
604
+ items: evidenceObjectSchema(["path", "summary"], { path: { type: "string" }, summary: { type: "string" } })
605
+ },
606
+ remaining_risks: {
607
+ type: "array",
608
+ description: "Required for pass, even when empty. Include why no regression coverage changed when tests_added_or_updated is empty.",
609
+ items: evidenceObjectSchema(["summary"], { summary: { type: "string" }, mitigation: { type: "string" } })
610
+ },
611
+ resolved_findings: {
612
+ type: "array",
613
+ description: "Required for QA rework. Each entry must use status resolved, disputed, or not_reproduced.",
614
+ items: resolvedFindingSchema()
615
+ }
616
+ }, ["status", "summary"], "For status pass, include changed_files, tests_added_or_updated, and remaining_risks.");
617
+ }
618
+ return objectToolSchema({
619
+ ...baseProperties,
620
+ commands: {
621
+ type: "array",
622
+ description: "Required for pass. Targeted regression/repro-negative commands with exit_code: 0 and status: pass.",
623
+ items: commandEvidenceSchema()
624
+ },
625
+ required_regressions: {
626
+ type: "array",
627
+ description: "Required for pass. Repro-negative and regression coverage checklist.",
628
+ items: { type: "string" }
629
+ },
630
+ build_status: { type: "string", description: "Required for pass. pass or not_applicable." },
631
+ test_status: { type: "string", description: "Required for pass. pass or not_applicable." },
632
+ repro_negative: { type: "boolean", description: "Required for pass and must be true after the original repro no longer fails." },
633
+ qa_ready: { type: "boolean", description: "Required for pass and must be true." }
634
+ }, ["status", "summary"], "For status pass, commands must include at least one passing deterministic debug regression check, repro_negative must be true, and qa_ready must be true.");
635
+ }
636
+ function qaGateToolInputSchema(gate) {
637
+ const baseProperties = {
638
+ status: {
639
+ type: "string",
640
+ enum: ["pass", "skipped", "blocked"],
641
+ description: "Use pass when this QA gate produced evidence, skipped when not applicable, or blocked when a prerequisite prevents this gate."
642
+ },
643
+ summary: { type: "string", description: "Concise QA gate summary." },
644
+ reason: { type: "string", description: "Required for skipped or blocked gates." },
645
+ blocker_reason: {
646
+ type: "string",
647
+ enum: [...qaBlockerReasons],
648
+ description: "Required when status is blocked."
649
+ }
650
+ };
651
+ if (gate === "qa_plan") {
652
+ return objectToolSchema({
653
+ ...baseProperties,
654
+ checklist: {
655
+ type: "array",
656
+ description: "Required for pass. QA checklist entries planned for this work item.",
657
+ items: { type: "string" }
658
+ },
659
+ acceptance_criteria: {
660
+ type: "array",
661
+ description: "Acceptance criteria selected for QA.",
662
+ items: { type: "string" }
663
+ }
664
+ }, ["status", "summary"], "Record the QA plan gate. Pass requires a non-empty checklist.");
665
+ }
666
+ if (gate === "qa_findings") {
667
+ return objectToolSchema({
668
+ ...baseProperties,
669
+ findings: {
670
+ type: "array",
671
+ description: "Normalized in-scope and out-of-scope QA findings.",
672
+ items: { type: "object", additionalProperties: true }
673
+ },
674
+ candidate_bugs: {
675
+ type: "array",
676
+ description: "Out-of-scope or follow-up candidates.",
677
+ items: { type: "object", additionalProperties: true }
678
+ }
679
+ }, ["status", "summary"], "Record normalized QA findings. Pass or skipped requires findings and candidate_bugs arrays, even when empty.");
680
+ }
681
+ return objectToolSchema({
682
+ ...baseProperties,
683
+ commands: {
684
+ type: "array",
685
+ description: "Command evidence for this gate when applicable.",
686
+ items: commandEvidenceSchema()
687
+ },
688
+ browser_checks: {
689
+ type: "array",
690
+ description: "Browser/manual evidence for this gate when applicable.",
691
+ items: { type: "object", additionalProperties: true }
692
+ },
693
+ notes: { type: "string", description: "Evidence notes for this gate." }
694
+ }, ["status", "summary"], "Record this QA evidence gate. Use skipped with a reason when the gate is not applicable.");
695
+ }
696
+ function objectToolSchema(properties, required, description) {
697
+ return {
698
+ type: "object",
699
+ additionalProperties: true,
700
+ description,
701
+ properties,
702
+ required: [...required]
703
+ };
704
+ }
705
+ function evidenceObjectSchema(required, properties) {
706
+ return {
707
+ type: "object",
708
+ additionalProperties: true,
709
+ properties,
710
+ required: [...required]
711
+ };
712
+ }
713
+ function qaScopeSchema(description) {
714
+ return {
715
+ type: "object",
716
+ additionalProperties: false,
717
+ description,
718
+ properties: {
719
+ schema_version: { type: "number", enum: [1] },
720
+ surface: { type: "array", items: { type: "string", enum: [...qaScopeSurfaces] } },
721
+ required_evidence: { type: "array", items: { type: "string", enum: [...qaScopeEvidence] } },
722
+ not_applicable: { type: "array", items: { type: "string", enum: [...qaScopeEvidence] } },
723
+ browser_required: { type: "boolean" },
724
+ api_required: { type: "boolean" },
725
+ source: { type: "string", enum: [...qaScopeSources] },
726
+ authority: { type: "string", enum: [...qaScopeAuthorities] },
727
+ confidence: { type: "string", enum: [...qaScopeConfidence] },
728
+ confirmed_by_user: { type: "boolean" },
729
+ locked: { type: "boolean" },
730
+ derived_from: {
731
+ type: "object",
732
+ additionalProperties: false,
733
+ properties: {
734
+ work_item_id: { type: "string" },
735
+ mode: { type: "string" }
736
+ }
737
+ },
738
+ rationale: { type: "string" }
739
+ },
740
+ required: ["schema_version", "required_evidence", "not_applicable"]
741
+ };
742
+ }
743
+ function commandEvidenceSchema() {
744
+ return evidenceObjectSchema(["command", "exit_code", "status", "summary"], {
745
+ command: { type: "string" },
746
+ managed_command_id: { type: "string", description: "Receipt ID returned by forge.start_managed_command and completed through one forge.wait_managed_command call for managed-host evidence." },
747
+ exit_code: { type: ["number", "null"], description: "Process exit code, or null when the managed runner timed out or could not launch the process." },
748
+ status: { type: "string" },
749
+ summary: { type: "string" },
750
+ classification: {
751
+ type: "string",
752
+ enum: ["out_of_scope_failure", "baseline_failure"],
753
+ description: "Only for a verified non-blocking failure in a passing QA regression gate. Never use for an in-scope or unexplained failure."
754
+ },
755
+ candidate_bug_id: { type: "string", description: "Required for a classified non-blocking failure; links the preserved failure to candidate_bugs." },
756
+ scope_evidence: { type: "string", description: "Required proof that the failing surface is outside the current acceptance criteria and changed-file scope." },
757
+ acceptance_criteria_independent: { type: "boolean", description: "Required and true only when the current item can be judged independently of this failure." },
758
+ repro_command: { type: "string", description: "Required for out_of_scope_failure; focused command that reproduced the unrelated failure." },
759
+ repro_exit_code: { type: "number", description: "Required non-zero focused repro exit code for out_of_scope_failure." },
760
+ baseline_ref: { type: "string", description: "Required for baseline_failure; immutable ref where the same failure was observed." },
761
+ baseline_exit_code: { type: "number", description: "Required non-zero baseline command exit code for baseline_failure." },
762
+ failure_signature_match: { type: "boolean", description: "Required and true for baseline_failure after matching failing test/error signatures." }
763
+ });
764
+ }
765
+ function resolvedFindingSchema() {
766
+ return evidenceObjectSchema([
767
+ "qa_id",
768
+ "status",
769
+ "summary",
770
+ "evidence",
771
+ "changed_files",
772
+ "tests_added_or_updated",
773
+ "regression_evidence"
774
+ ], {
775
+ qa_id: { type: "string" },
776
+ status: {
777
+ type: "string",
778
+ enum: [...resolvedFindingStatusValues],
779
+ description: "Use resolved when fixed, disputed when the QA finding is incorrect/out-of-scope, or not_reproduced when retesting cannot reproduce it."
780
+ },
781
+ summary: { type: "string" },
782
+ evidence: { description: "Concrete evidence for the resolution." },
783
+ changed_files: { type: "array", description: "Required and non-empty when status is resolved." },
784
+ tests_added_or_updated: { type: "array", description: "Required and non-empty when status is resolved." },
785
+ regression_evidence: { description: "Concrete regression evidence for this QA finding." }
786
+ });
787
+ }
788
+ function acceptanceCriteriaSchema() {
789
+ return {
790
+ type: "array",
791
+ description: "Required for pass. Every entry must be an object with status: satisfied and non-empty evidence.",
792
+ items: evidenceObjectSchema(["criterion", "evidence", "status"], {
793
+ criterion: { type: "string" },
794
+ evidence: { type: "string" },
795
+ status: { type: "string", enum: ["satisfied"] }
796
+ })
797
+ };
798
+ }
799
+ function reviewSectionSchema() {
800
+ return {
801
+ type: "object",
802
+ description: "Required for pass. Use status pass/fail/needs_rework/not_applicable plus notes.",
803
+ additionalProperties: true,
804
+ properties: reviewSectionProperties()
805
+ };
806
+ }
807
+ function reviewSectionProperties() {
808
+ return {
809
+ status: { type: "string" },
810
+ notes: { type: "string" }
811
+ };
812
+ }