@tea-agent/loop-agent 0.1.0-fe-test.0

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 (1050) hide show
  1. package/AGENTS.md +114 -0
  2. package/CHANGELOG.md +2283 -0
  3. package/README.md +177 -0
  4. package/bin/agent-worker.js +22 -0
  5. package/bin/loop-agent.js +57 -0
  6. package/dist/adapters/aimax.js +91 -0
  7. package/dist/adapters/context.js +32 -0
  8. package/dist/adapters/index.js +29 -0
  9. package/dist/adapters/loop-agent.js +192 -0
  10. package/dist/adapters/types.js +1 -0
  11. package/dist/application/context-usage/skill-resolution-stats.js +263 -0
  12. package/dist/application/dag/args.js +486 -0
  13. package/dist/application/dag/generate-task-dag.js +408 -0
  14. package/dist/application/dag/report-dag.js +14 -0
  15. package/dist/application/dag/run-dag.js +109 -0
  16. package/dist/application/dag/validate-dag.js +166 -0
  17. package/dist/application/evaluation/alias.js +184 -0
  18. package/dist/application/evaluation/budget.js +192 -0
  19. package/dist/application/evaluation/campaign-hash.js +47 -0
  20. package/dist/application/evaluation/campaign-matrix.js +372 -0
  21. package/dist/application/evaluation/campaign-scorecard.js +135 -0
  22. package/dist/application/evaluation/campaign.js +370 -0
  23. package/dist/application/evaluation/candidate-hash.js +75 -0
  24. package/dist/application/evaluation/candidate.js +69 -0
  25. package/dist/application/evaluation/corpus-hash.js +38 -0
  26. package/dist/application/evaluation/corpus.js +56 -0
  27. package/dist/application/evaluation/experiment.js +294 -0
  28. package/dist/application/evaluation/ignition.js +198 -0
  29. package/dist/application/evaluation/integrity-audit.js +162 -0
  30. package/dist/application/evaluation/outer-loop.js +132 -0
  31. package/dist/application/evaluation/pi-cell-executor.js +39 -0
  32. package/dist/application/evaluation/private-verifier.js +46 -0
  33. package/dist/application/evaluation/promotion-policy.js +151 -0
  34. package/dist/application/evaluation/proposer.js +98 -0
  35. package/dist/application/evaluation/replay.js +289 -0
  36. package/dist/application/evaluation/types.js +652 -0
  37. package/dist/application/loop/run-action.js +19 -0
  38. package/dist/application/task-lifecycle/advance.js +1204 -0
  39. package/dist/application/task-lifecycle/gates.js +130 -0
  40. package/dist/application/task-lifecycle/index.js +7 -0
  41. package/dist/application/task-lifecycle/observe.js +424 -0
  42. package/dist/application/task-lifecycle/plan-transitions.js +251 -0
  43. package/dist/application/task-lifecycle/recommendations.js +180 -0
  44. package/dist/application/task-lifecycle/record.js +73 -0
  45. package/dist/application/task-lifecycle/types.js +1 -0
  46. package/dist/build-stamp.json +6 -0
  47. package/dist/cli/catalog.js +24 -0
  48. package/dist/cli/command-definitions.js +659 -0
  49. package/dist/cli/help.js +56 -0
  50. package/dist/cli/index.js +5 -0
  51. package/dist/cli/program.js +647 -0
  52. package/dist/cli/router.js +13 -0
  53. package/dist/cli/update/init-surface-notifier.js +167 -0
  54. package/dist/cli/update/notifier.js +117 -0
  55. package/dist/cli/update/npm-client.js +151 -0
  56. package/dist/cli/update/policy.js +92 -0
  57. package/dist/cli/update/runtime-activity.js +29 -0
  58. package/dist/cli/update/state.js +68 -0
  59. package/dist/cli-governance/active-residue-check.js +38 -0
  60. package/dist/cli.js +57 -0
  61. package/dist/commands/client-recovery.js +1373 -0
  62. package/dist/commands/closeout.js +13 -0
  63. package/dist/commands/coverage-audit.js +14 -0
  64. package/dist/commands/coverage-report.js +50 -0
  65. package/dist/commands/cursor-prompt.js +182 -0
  66. package/dist/commands/dag-approve.js +142 -0
  67. package/dist/commands/dag-final-verification.js +76 -0
  68. package/dist/commands/dag-init-hybrid.js +57 -0
  69. package/dist/commands/dag-reconcile-run.js +5 -0
  70. package/dist/commands/dag-reconcile-tasks.js +51 -0
  71. package/dist/commands/dag-reject.js +91 -0
  72. package/dist/commands/dag-report.js +76 -0
  73. package/dist/commands/dag-request-interrupt.js +20 -0
  74. package/dist/commands/dag-rerun-task.js +19 -0
  75. package/dist/commands/dag-rerun.js +111 -0
  76. package/dist/commands/dag-resume.js +35 -0
  77. package/dist/commands/dag-run-task.js +12 -0
  78. package/dist/commands/dag-validate.js +20 -0
  79. package/dist/commands/dag-workflow-compile.js +91 -0
  80. package/dist/commands/dag-workflow-plan.js +130 -0
  81. package/dist/commands/dag-workflow-validate.js +66 -0
  82. package/dist/commands/delegate.js +130 -0
  83. package/dist/commands/docs-archive.js +5 -0
  84. package/dist/commands/docs-audit.js +6 -0
  85. package/dist/commands/doctor.js +283 -0
  86. package/dist/commands/eval.js +1398 -0
  87. package/dist/commands/examples.js +90 -0
  88. package/dist/commands/goal.js +92 -0
  89. package/dist/commands/handoff-check.js +5 -0
  90. package/dist/commands/harvest.js +44 -0
  91. package/dist/commands/import-prd.js +81 -0
  92. package/dist/commands/init-upgrade.js +3085 -0
  93. package/dist/commands/init.js +3630 -0
  94. package/dist/commands/inspect.js +11 -0
  95. package/dist/commands/instructions.js +223 -0
  96. package/dist/commands/knowledge.js +162 -0
  97. package/dist/commands/loop-benchmark.js +72 -0
  98. package/dist/commands/loop.js +251 -0
  99. package/dist/commands/new-task.js +5 -0
  100. package/dist/commands/operator.js +44 -0
  101. package/dist/commands/pi-prompt.js +174 -0
  102. package/dist/commands/pi-reuse-benchmark.js +153 -0
  103. package/dist/commands/plan-list.js +5 -0
  104. package/dist/commands/plan.js +50 -0
  105. package/dist/commands/promote-run.js +29 -0
  106. package/dist/commands/reference-index.js +16 -0
  107. package/dist/commands/run-dag-progress.js +123 -0
  108. package/dist/commands/run-dag.js +22 -0
  109. package/dist/commands/spine.js +38 -0
  110. package/dist/commands/stats.js +113 -0
  111. package/dist/commands/status.js +57 -0
  112. package/dist/commands/study-init.js +192 -0
  113. package/dist/commands/task-advance.js +366 -0
  114. package/dist/commands/task-contract.js +269 -0
  115. package/dist/commands/task-source-prepare.js +479 -0
  116. package/dist/commands/task-status.js +133 -0
  117. package/dist/commands/workflow.js +259 -0
  118. package/dist/commands/worktree-create.js +31 -0
  119. package/dist/commands/worktree-list.js +5 -0
  120. package/dist/commands/worktree-remove.js +26 -0
  121. package/dist/executors/config-core.js +5 -0
  122. package/dist/executors/config.js +2 -0
  123. package/dist/executors/dag-pi-executor.js +1277 -0
  124. package/dist/executors/dag-static-executor.js +42 -0
  125. package/dist/executors/dag.js +3 -0
  126. package/dist/executors/index.js +6 -0
  127. package/dist/executors/model-routing.js +71 -0
  128. package/dist/executors/pi-defaults.js +9 -0
  129. package/dist/executors/pi-event-serializer.js +65 -0
  130. package/dist/executors/pi-executor.js +981 -0
  131. package/dist/executors/pi-playwright-cli-tool.js +1006 -0
  132. package/dist/executors/pi-prompt-transport.js +198 -0
  133. package/dist/executors/pi-reuse-benchmark.js +316 -0
  134. package/dist/executors/pi-runtime-reuse.js +29 -0
  135. package/dist/executors/pi-sdk-executor.js +722 -0
  136. package/dist/executors/pi-sdk.js +1 -0
  137. package/dist/executors/pi-writer-tool-policy.js +266 -0
  138. package/dist/executors/pi.js +3 -0
  139. package/dist/executors/playwright-cli-launcher.js +63 -0
  140. package/dist/executors/process-tree.js +33 -0
  141. package/dist/executors/shell-executor.js +3055 -0
  142. package/dist/executors/shell-presets.js +99 -0
  143. package/dist/executors/shell-verification.js +262 -0
  144. package/dist/executors/shell-write-guard.js +543 -0
  145. package/dist/executors/shell.js +3 -0
  146. package/dist/executors/static.js +1 -0
  147. package/dist/governance/checks.js +437 -0
  148. package/dist/governance/document-index-closure.js +164 -0
  149. package/dist/governance/exec-plans.js +549 -0
  150. package/dist/governance/harness.js +9 -0
  151. package/dist/governance/index.js +3 -0
  152. package/dist/governance/manifest-types.js +254 -0
  153. package/dist/governance/manifest.js +2 -0
  154. package/dist/governance/path-guard.js +69 -0
  155. package/dist/governance/path-guards.js +2 -0
  156. package/dist/governance/profiles.js +3 -0
  157. package/dist/governance/requirement-coverage.js +425 -0
  158. package/dist/governance/skill-safety.js +135 -0
  159. package/dist/governance/spine-audit.js +155 -0
  160. package/dist/infrastructure/evaluation/alias-store.js +199 -0
  161. package/dist/infrastructure/evaluation/campaign-store.js +154 -0
  162. package/dist/infrastructure/evaluation/candidate-store.js +439 -0
  163. package/dist/infrastructure/evaluation/corpus-store.js +181 -0
  164. package/dist/infrastructure/evaluation/experiment-store.js +124 -0
  165. package/dist/infrastructure/evaluation/ignition-store.js +82 -0
  166. package/dist/infrastructure/evaluation/private-verifier-store.js +145 -0
  167. package/dist/infrastructure/evaluation/proposer-store.js +78 -0
  168. package/dist/infrastructure/evaluation/store.js +40 -0
  169. package/dist/infrastructure/harness/active-residue-policy.js +73 -0
  170. package/dist/infrastructure/harness/artifact-store.js +72 -0
  171. package/dist/infrastructure/harness/atomic-write.js +50 -0
  172. package/dist/infrastructure/harness/completed-facts-guard.js +40 -0
  173. package/dist/infrastructure/harness/loop-action-store.js +20 -0
  174. package/dist/infrastructure/harness/loop-store.js +41 -0
  175. package/dist/infrastructure/harness/one-shot-run-store.js +94 -0
  176. package/dist/infrastructure/harness/task-store.js +108 -0
  177. package/dist/records/closeout.js +2 -0
  178. package/dist/records/harvest.js +215 -0
  179. package/dist/records/index.js +3 -0
  180. package/dist/records/one-shot-runs.js +386 -0
  181. package/dist/records/promotion.js +200 -0
  182. package/dist/shared/artifacts-core.js +107 -0
  183. package/dist/shared/artifacts.js +2 -0
  184. package/dist/shared/context-files.js +32 -0
  185. package/dist/shared/context.js +2 -0
  186. package/dist/shared/copy-dir.js +17 -0
  187. package/dist/shared/git-progress.js +172 -0
  188. package/dist/shared/index.js +5 -0
  189. package/dist/shared/logger.js +17 -0
  190. package/dist/shared/one-shot-prompt-args.js +98 -0
  191. package/dist/shared/openspec-spec.js +125 -0
  192. package/dist/shared/operator/capabilities.js +2659 -0
  193. package/dist/shared/operator/command-lifecycle.js +94 -0
  194. package/dist/shared/operator/envelope.js +59 -0
  195. package/dist/shared/operator/index.js +5 -0
  196. package/dist/shared/operator/registry.js +38 -0
  197. package/dist/shared/operator/types.js +5 -0
  198. package/dist/shared/output-truncation.js +37 -0
  199. package/dist/shared/package-metadata.js +530 -0
  200. package/dist/shared/path-refs.js +31 -0
  201. package/dist/shared/pi-retry-settings.js +23 -0
  202. package/dist/shared/playwright-cli-command-policy.js +41 -0
  203. package/dist/shared/preview.js +39 -0
  204. package/dist/shared/prompts.js +26 -0
  205. package/dist/shared/reference-context.js +264 -0
  206. package/dist/shared/resilient-git.js +133 -0
  207. package/dist/shared/runtime-activity.js +6 -0
  208. package/dist/shared/timeout-policy.js +24 -0
  209. package/dist/shared/timeout.js +1 -0
  210. package/dist/shared/types.js +5 -0
  211. package/dist/sidecars/cursor-prompt/executor.js +428 -0
  212. package/dist/sidecars/cursor-prompt/index.js +3 -0
  213. package/dist/sidecars/cursor-prompt/stream.js +121 -0
  214. package/dist/task/config-types.js +235 -0
  215. package/dist/task/config.js +2 -0
  216. package/dist/task/contract/adopt.js +166 -0
  217. package/dist/task/contract/apply.js +326 -0
  218. package/dist/task/contract/canonicalize.js +60 -0
  219. package/dist/task/contract/constants.js +30 -0
  220. package/dist/task/contract/diff.js +177 -0
  221. package/dist/task/contract/hash.js +42 -0
  222. package/dist/task/contract/import-revision.js +96 -0
  223. package/dist/task/contract/index.js +17 -0
  224. package/dist/task/contract/journal.js +155 -0
  225. package/dist/task/contract/lock.js +153 -0
  226. package/dist/task/contract/observe.js +296 -0
  227. package/dist/task/contract/paths.js +19 -0
  228. package/dist/task/contract/project.js +185 -0
  229. package/dist/task/contract/recover.js +312 -0
  230. package/dist/task/contract/request-ledger.js +37 -0
  231. package/dist/task/contract/schema.js +153 -0
  232. package/dist/task/contract/transaction.js +160 -0
  233. package/dist/task/contract/types.js +1 -0
  234. package/dist/task/contract/validate-draft.js +106 -0
  235. package/dist/task/dag-source-paths.js +50 -0
  236. package/dist/task/delegate.js +208 -0
  237. package/dist/task/frontend-preflight.js +131 -0
  238. package/dist/task/frontend-project-capability.js +570 -0
  239. package/dist/task/goal-audit.js +51 -0
  240. package/dist/task/goal-policy.js +8 -0
  241. package/dist/task/goal.js +3 -0
  242. package/dist/task/ids.js +1 -0
  243. package/dist/task/index.js +12 -0
  244. package/dist/task/lifecycle.js +1 -0
  245. package/dist/task/operator/capabilities.js +6 -0
  246. package/dist/task/operator/envelope.js +2 -0
  247. package/dist/task/operator/index.js +5 -0
  248. package/dist/task/operator/registry.js +2 -0
  249. package/dist/task/operator/types.js +1 -0
  250. package/dist/task/paths.js +1 -0
  251. package/dist/task/read-model.js +140 -0
  252. package/dist/task/runtime.js +708 -0
  253. package/dist/task/source-prepare/artifact-meta.js +137 -0
  254. package/dist/task/source-prepare/build-draft.js +232 -0
  255. package/dist/task/source-prepare/completeness.js +248 -0
  256. package/dist/task/source-prepare/index.js +10 -0
  257. package/dist/task/source-prepare/parse-intent.js +496 -0
  258. package/dist/task/source-prepare/path-policy.js +197 -0
  259. package/dist/task/source-prepare/placeholder-paths.js +77 -0
  260. package/dist/task/source-prepare/prepare.js +920 -0
  261. package/dist/task/source-prepare/reference-integrity.js +290 -0
  262. package/dist/task/source-prepare/semantic-intake.js +717 -0
  263. package/dist/task/source-prepare/types.js +7 -0
  264. package/dist/task/source-references.js +228 -0
  265. package/dist/task/source-state.js +1 -0
  266. package/dist/task/state.js +41 -0
  267. package/dist/task/subagent-guidance.js +1 -0
  268. package/dist/task/task-demand-routing.js +443 -0
  269. package/dist/task/workflow-state-types.js +92 -0
  270. package/dist/task/worktree-cleanup.js +140 -0
  271. package/dist/task/worktree.js +388 -0
  272. package/dist/verification/maven/cache.js +142 -0
  273. package/dist/verification/maven/index.js +120 -0
  274. package/dist/verification/maven/plan-commands.js +421 -0
  275. package/dist/verification/maven/pom-static.js +136 -0
  276. package/dist/verification/maven/scope-resolve.js +153 -0
  277. package/dist/verification/maven/stale.js +130 -0
  278. package/dist/verification/maven/types.js +23 -0
  279. package/dist/verification/maven/workspace-graph.js +322 -0
  280. package/dist/worker/cli.js +957 -0
  281. package/dist/worker/closeout/apply.js +73 -0
  282. package/dist/worker/closeout/preview.js +30 -0
  283. package/dist/worker/console/app-data.js +316 -0
  284. package/dist/worker/console/chat/artifact-card.js +30 -0
  285. package/dist/worker/console/chat/assistant-content.js +121 -0
  286. package/dist/worker/console/chat/chat-event-store.js +981 -0
  287. package/dist/worker/console/chat/chat-ui-policy.js +25 -0
  288. package/dist/worker/console/chat/compaction-errors.js +64 -0
  289. package/dist/worker/console/chat/composer-draft-store.js +45 -0
  290. package/dist/worker/console/chat/context-panel.js +54 -0
  291. package/dist/worker/console/chat/contract-apply-receipt-store.js +174 -0
  292. package/dist/worker/console/chat/explore-tools.js +299 -0
  293. package/dist/worker/console/chat/human-gate-card.js +45 -0
  294. package/dist/worker/console/chat/interview-adapter.js +149 -0
  295. package/dist/worker/console/chat/mcp-inventory.js +221 -0
  296. package/dist/worker/console/chat/model-resolver.js +215 -0
  297. package/dist/worker/console/chat/operation-card.js +92 -0
  298. package/dist/worker/console/chat/pi-console-config.js +376 -0
  299. package/dist/worker/console/chat/pi-runtime.js +2446 -0
  300. package/dist/worker/console/chat/repo-browser.js +140 -0
  301. package/dist/worker/console/chat/repo-walk.js +125 -0
  302. package/dist/worker/console/chat/resource-loader.js +59 -0
  303. package/dist/worker/console/chat/resource-preferences-store.js +152 -0
  304. package/dist/worker/console/chat/routes.js +3137 -0
  305. package/dist/worker/console/chat/runtime-context.js +102 -0
  306. package/dist/worker/console/chat/runtime-selection.js +96 -0
  307. package/dist/worker/console/chat/semantic-activity.js +489 -0
  308. package/dist/worker/console/chat/session-store.js +484 -0
  309. package/dist/worker/console/chat/shortcuts.js +217 -0
  310. package/dist/worker/console/chat/tool-adapter.js +131 -0
  311. package/dist/worker/console/chat/tool-preview.js +247 -0
  312. package/dist/worker/console/chat/tools.js +212 -0
  313. package/dist/worker/console/chat/turn-execution-registry.js +82 -0
  314. package/dist/worker/console/chat/turn-process.js +346 -0
  315. package/dist/worker/console/chat/usage.js +165 -0
  316. package/dist/worker/console/chat/workspace-landing.js +103 -0
  317. package/dist/worker/console/dag-confirmation.js +347 -0
  318. package/dist/worker/console/dag-execution-receipt.js +393 -0
  319. package/dist/worker/console/doctor.js +230 -0
  320. package/dist/worker/console/draft-store.js +158 -0
  321. package/dist/worker/console/harness-pi-model-matrix.js +137 -0
  322. package/dist/worker/console/human-gate-token.js +130 -0
  323. package/dist/worker/console/index.js +17 -0
  324. package/dist/worker/console/inspect-split.js +82 -0
  325. package/dist/worker/console/interview/assessment.js +67 -0
  326. package/dist/worker/console/interview/grill-me.js +277 -0
  327. package/dist/worker/console/interview/session.js +100 -0
  328. package/dist/worker/console/interview/tools.js +109 -0
  329. package/dist/worker/console/loopback.js +16 -0
  330. package/dist/worker/console/mutation-gate-receipt-store.js +184 -0
  331. package/dist/worker/console/night-aux-ticker.js +146 -0
  332. package/dist/worker/console/observe-health-match.js +179 -0
  333. package/dist/worker/console/observe-link.js +42 -0
  334. package/dist/worker/console/open-browser.js +134 -0
  335. package/dist/worker/console/operation-run-facts.js +190 -0
  336. package/dist/worker/console/operation-runner.js +353 -0
  337. package/dist/worker/console/operation-sse.js +184 -0
  338. package/dist/worker/console/operation-store.js +169 -0
  339. package/dist/worker/console/operation-wait.js +355 -0
  340. package/dist/worker/console/operator-actions.js +3768 -0
  341. package/dist/worker/console/operator-selection.js +105 -0
  342. package/dist/worker/console/operator-surface-health.js +24 -0
  343. package/dist/worker/console/operator-user-error.js +45 -0
  344. package/dist/worker/console/pi-readiness.js +288 -0
  345. package/dist/worker/console/prd-identity.js +102 -0
  346. package/dist/worker/console/prd-intake-bridge.js +441 -0
  347. package/dist/worker/console/prd-reference-discovery.js +124 -0
  348. package/dist/worker/console/recovery-cta.js +320 -0
  349. package/dist/worker/console/recovery-error-copy.js +198 -0
  350. package/dist/worker/console/recovery-selection.js +107 -0
  351. package/dist/worker/console/repo-fingerprint.js +35 -0
  352. package/dist/worker/console/resolve-dag-run-for-task.js +115 -0
  353. package/dist/worker/console/resource-loader.js +95 -0
  354. package/dist/worker/console/routes.js +475 -0
  355. package/dist/worker/console/security.js +170 -0
  356. package/dist/worker/console/server.js +357 -0
  357. package/dist/worker/console/sibling-controller.js +33 -0
  358. package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-BmOEx0gA.js +1 -0
  359. package/dist/worker/console/static/assets/arc-BGiVVe8b.js +1 -0
  360. package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-nDdu4rgJ.js +36 -0
  361. package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-Bb5u0BWf.js +132 -0
  362. package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-BlhC0j94.js +10 -0
  363. package/dist/worker/console/static/assets/channel-Tl-FkUqr.js +1 -0
  364. package/dist/worker/console/static/assets/chunk-2GRJ4B5K-Ai8_qQXI.js +1 -0
  365. package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B9gyCjqX.js +1 -0
  366. package/dist/worker/console/static/assets/chunk-5RXB4S5H-Bzbj3zGH.js +231 -0
  367. package/dist/worker/console/static/assets/chunk-5VM5RSS4-BT415PvT.js +15 -0
  368. package/dist/worker/console/static/assets/chunk-6Q2QTUOP-D_A5_5QL.js +88 -0
  369. package/dist/worker/console/static/assets/chunk-GF5L2VYU-srJkaP3f.js +206 -0
  370. package/dist/worker/console/static/assets/chunk-JWPE2WC7-UYGvEP4l.js +1 -0
  371. package/dist/worker/console/static/assets/chunk-KBJHAD2P-B8luBrrz.js +1 -0
  372. package/dist/worker/console/static/assets/chunk-RYQCIY6F-DN7wXZSq.js +1 -0
  373. package/dist/worker/console/static/assets/chunk-XXDRQBXY-DXKyMuKS.js +1 -0
  374. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BaehtRk4.js +1 -0
  375. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BaehtRk4.js +1 -0
  376. package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-dqvoynXj.js +1 -0
  377. package/dist/worker/console/static/assets/cynefin-VYW2F7L2--PcyL34A.js +166 -0
  378. package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DvWs_hTY.js +62 -0
  379. package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +321 -0
  380. package/dist/worker/console/static/assets/dagre-VZM6K2ZE-DxeIxbyO.js +4 -0
  381. package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +1 -0
  382. package/dist/worker/console/static/assets/diagram-7IWD3JNH-DPCG61Of.js +30 -0
  383. package/dist/worker/console/static/assets/diagram-B4RE2ZJO-CmuU50-U.js +3 -0
  384. package/dist/worker/console/static/assets/diagram-LBJQPF4R-B61XLD_v.js +24 -0
  385. package/dist/worker/console/static/assets/diagram-Q27KOJAE-BB7HTRRY.js +24 -0
  386. package/dist/worker/console/static/assets/diagram-UB23O5K3-DvkboAIp.js +41 -0
  387. package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-BdJ_y1fL.js +1 -0
  388. package/dist/worker/console/static/assets/erDiagram-JOGREHBK-9bUBZTRi.js +85 -0
  389. package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-Dw4L4RBa.js +156 -0
  390. package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-Jr82N_QP.js +292 -0
  391. package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-DAz9wFLr.js +106 -0
  392. package/dist/worker/console/static/assets/graph-DOmOIIwC.js +1 -0
  393. package/dist/worker/console/static/assets/index-B7Y-0g3J.css +1 -0
  394. package/dist/worker/console/static/assets/index-DkCrqaYg.js +325 -0
  395. package/dist/worker/console/static/assets/infoDiagram-6WML65LV-CBB5Np7y.js +2 -0
  396. package/dist/worker/console/static/assets/init-Gi6I4Gst.js +1 -0
  397. package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-D8IOp94y.js +70 -0
  398. package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-Dgd3AHGG.js +139 -0
  399. package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BmYOxLE9.js +89 -0
  400. package/dist/worker/console/static/assets/layout-D-LzfAck.js +1 -0
  401. package/dist/worker/console/static/assets/linear-DfYTEF6J.js +1 -0
  402. package/dist/worker/console/static/assets/map-DxJ2ADlA.js +1 -0
  403. package/dist/worker/console/static/assets/mermaid.core-BjuFPRqa.js +308 -0
  404. package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS-CLyUViGd.js +96 -0
  405. package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +1 -0
  406. package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-BvX2Di4-.js +1 -0
  407. package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-BFj0_OLv.js +39 -0
  408. package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-BIUrD_hx.js +7 -0
  409. package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-CHBB48fj.js +1 -0
  410. package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-sJrYw2Sf.js +84 -0
  411. package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-BzHH6fPR.js +40 -0
  412. package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-7QCnelCt.js +162 -0
  413. package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-sDYSxn72.js +1 -0
  414. package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-XpiUdwzm.js +1 -0
  415. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-eEhq2A8Y.js +1 -0
  416. package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-DvqgNPzD.js +2 -0
  417. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-la8AC7fo.js +8 -0
  418. package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-2TyOYNx0.js +120 -0
  419. package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-B-nM6wM2.js +34 -0
  420. package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-B86wPdbr.js +78 -0
  421. package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-Dr1C4AQz.js +7 -0
  422. package/dist/worker/console/static/favicon.svg +37 -0
  423. package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
  424. package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
  425. package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
  426. package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
  427. package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
  428. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
  429. package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
  430. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
  431. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
  432. package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
  433. package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
  434. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
  435. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
  436. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
  437. package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
  438. package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
  439. package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
  440. package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
  441. package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
  442. package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
  443. package/dist/worker/console/static/index.html +15 -0
  444. package/dist/worker/console/static-src/app/console-types.js +184 -0
  445. package/dist/worker/console/static-src/app/useConsoleShell.js +101 -0
  446. package/dist/worker/console/static-src/app/useOperatorActions.js +324 -0
  447. package/dist/worker/console/static-src/app/usePrdImport.js +172 -0
  448. package/dist/worker/console/static-src/app/useRecoveryActions.js +289 -0
  449. package/dist/worker/console/static-src/app/useRecoveryConsole.js +425 -0
  450. package/dist/worker/console/static-src/app/useTaskWizard.js +296 -0
  451. package/dist/worker/console/static-src/chat-markdown-security.js +38 -0
  452. package/dist/worker/console/static-src/chat-view-types.js +2 -0
  453. package/dist/worker/console/static-src/night/night-types.js +24 -0
  454. package/dist/worker/console/static-src/night/useNightBoard.js +94 -0
  455. package/dist/worker/console/static-src/night/useNightWizard.js +171 -0
  456. package/dist/worker/console/static-src/night-prepare-result.js +118 -0
  457. package/dist/worker/console/static-src/operator-chat/activity-journey.js +125 -0
  458. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +73 -0
  459. package/dist/worker/console/static-src/operator-chat/activity-references.js +20 -0
  460. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +420 -0
  461. package/dist/worker/console/static-src/operator-chat/format.js +71 -0
  462. package/dist/worker/console/static-src/operator-chat/landing-density.js +23 -0
  463. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +45 -0
  464. package/dist/worker/console/static-src/operator-chat/refs.js +86 -0
  465. package/dist/worker/console/static-src/operator-chat/resource-auto-invocation.js +91 -0
  466. package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
  467. package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +257 -0
  468. package/dist/worker/console/static-src/operator-chat/session-title-watcher.js +128 -0
  469. package/dist/worker/console/static-src/operator-chat/sidebar-split.js +90 -0
  470. package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +24 -0
  471. package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +141 -0
  472. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +38 -0
  473. package/dist/worker/console/static-src/operator-chat/tools-catalog.js +77 -0
  474. package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +690 -0
  475. package/dist/worker/console/static-src/operator-chat/turn-submission.js +158 -0
  476. package/dist/worker/console/static-src/operator-chat/types.js +1 -0
  477. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +59 -0
  478. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +495 -0
  479. package/dist/worker/console/static-src/operator-chat/useChatStream.js +780 -0
  480. package/dist/worker/console/static-src/operator-chat/useChatThread.js +280 -0
  481. package/dist/worker/console/static-src/operator-chat/useComposer.js +257 -0
  482. package/dist/worker/console/static-src/operator-chat/useInterview.js +108 -0
  483. package/dist/worker/console/static-src/operator-chat/useOverlayFocus.js +84 -0
  484. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +148 -0
  485. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +358 -0
  486. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +196 -0
  487. package/dist/worker/console/static-src/operator-chat/useWorkspaceLayout.js +58 -0
  488. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +35 -0
  489. package/dist/worker/console/static-src/prd-file-import.js +218 -0
  490. package/dist/worker/console/vite.config.js +27 -0
  491. package/dist/worker/console/workflow-kinds.js +46 -0
  492. package/dist/worker/delivery/final-verification.js +302 -0
  493. package/dist/worker/delivery/git-transaction.js +497 -0
  494. package/dist/worker/delivery/package.js +537 -0
  495. package/dist/worker/delivery/verification-bundle.js +544 -0
  496. package/dist/worker/feature/acceptance-policy.js +227 -0
  497. package/dist/worker/feature/advance.js +301 -0
  498. package/dist/worker/feature/decision-loader.js +99 -0
  499. package/dist/worker/feature/discover.js +14 -0
  500. package/dist/worker/feature/doctor.js +223 -0
  501. package/dist/worker/feature/fullstack-validate.js +337 -0
  502. package/dist/worker/feature/next-action.js +140 -0
  503. package/dist/worker/feature/profile-schema.js +47 -0
  504. package/dist/worker/feature/ready-plan-projection.js +82 -0
  505. package/dist/worker/feature/reducer.js +137 -0
  506. package/dist/worker/feature/review.js +678 -0
  507. package/dist/worker/feature/run.js +390 -0
  508. package/dist/worker/feature/scaffold.js +812 -0
  509. package/dist/worker/feature/types.js +1 -0
  510. package/dist/worker/follow-up/approve.js +273 -0
  511. package/dist/worker/follow-up/factory.js +234 -0
  512. package/dist/worker/follow-up/paths.js +25 -0
  513. package/dist/worker/follow-up/policy.js +26 -0
  514. package/dist/worker/follow-up/schema.js +93 -0
  515. package/dist/worker/follow-up/store.js +96 -0
  516. package/dist/worker/loop-agent/command-result.js +1 -0
  517. package/dist/worker/loop-agent/controller-protocol.js +143 -0
  518. package/dist/worker/loop-agent/loop-agent-client.js +489 -0
  519. package/dist/worker/loop-agent/parse-json.js +14 -0
  520. package/dist/worker/materialize/harness-task-lifecycle-probe.js +126 -0
  521. package/dist/worker/materialize/harness-task-lineage.js +220 -0
  522. package/dist/worker/materialize/harness-task-materializer.js +648 -0
  523. package/dist/worker/metrics/projector.js +139 -0
  524. package/dist/worker/observability/dag-execution-trajectory.js +591 -0
  525. package/dist/worker/observability/event-history.js +216 -0
  526. package/dist/worker/observability/event-store.js +83 -0
  527. package/dist/worker/observability/events.js +79 -0
  528. package/dist/worker/observability/interrupt-eligibility.js +264 -0
  529. package/dist/worker/observability/progress-composite.js +34 -0
  530. package/dist/worker/observability/read-model.js +2499 -0
  531. package/dist/worker/observability/snapshot-store.js +43 -0
  532. package/dist/worker/observability/types.js +1 -0
  533. package/dist/worker/observe/dag-node-execution-output.js +180 -0
  534. package/dist/worker/observe/dag-run-artifacts.js +90 -0
  535. package/dist/worker/observe/health.js +58 -0
  536. package/dist/worker/observe/night-jobs.js +104 -0
  537. package/dist/worker/observe/node-input.js +441 -0
  538. package/dist/worker/observe/paths.js +174 -0
  539. package/dist/worker/observe/routes.js +1077 -0
  540. package/dist/worker/observe/server.js +117 -0
  541. package/dist/worker/observe/spec-evidence.js +398 -0
  542. package/dist/worker/observe/static/api.js +117 -0
  543. package/dist/worker/observe/static/app.js +169 -0
  544. package/dist/worker/observe/static/constants.js +182 -0
  545. package/dist/worker/observe/static/copy.js +67 -0
  546. package/dist/worker/observe/static/custom-select.js +567 -0
  547. package/dist/worker/observe/static/dag-edge-routing.js +368 -0
  548. package/dist/worker/observe/static/dag-helpers.js +161 -0
  549. package/dist/worker/observe/static/dag-history-labels.js +95 -0
  550. package/dist/worker/observe/static/dag-layout.d.ts +36 -0
  551. package/dist/worker/observe/static/dag-layout.js +163 -0
  552. package/dist/worker/observe/static/dag-model.js +145 -0
  553. package/dist/worker/observe/static/dom.js +220 -0
  554. package/dist/worker/observe/static/favicon.svg +37 -0
  555. package/dist/worker/observe/static/format-pool.d.ts +71 -0
  556. package/dist/worker/observe/static/format-pool.js +153 -0
  557. package/dist/worker/observe/static/format.js +347 -0
  558. package/dist/worker/observe/static/index.html +409 -0
  559. package/dist/worker/observe/static/kpi.js +78 -0
  560. package/dist/worker/observe/static/markdown-render.js +124 -0
  561. package/dist/worker/observe/static/operator-chrome.css +517 -0
  562. package/dist/worker/observe/static/operator-chrome.d.ts +83 -0
  563. package/dist/worker/observe/static/operator-chrome.js +623 -0
  564. package/dist/worker/observe/static/relations.js +133 -0
  565. package/dist/worker/observe/static/router.js +156 -0
  566. package/dist/worker/observe/static/run-processing.js +148 -0
  567. package/dist/worker/observe/static/shell-chrome.js +65 -0
  568. package/dist/worker/observe/static/state.js +496 -0
  569. package/dist/worker/observe/static/styles.css +3567 -0
  570. package/dist/worker/observe/static/views/batch.js +227 -0
  571. package/dist/worker/observe/static/views/dag-graph.js +508 -0
  572. package/dist/worker/observe/static/views/dag-inspector.js +1722 -0
  573. package/dist/worker/observe/static/views/dag-trajectory.js +313 -0
  574. package/dist/worker/observe/static/views/dag.js +483 -0
  575. package/dist/worker/observe/static/views/dags.js +877 -0
  576. package/dist/worker/observe/static/views/dashboard.js +747 -0
  577. package/dist/worker/observe/static/views/failures.js +143 -0
  578. package/dist/worker/observe/static/views/feature.js +492 -0
  579. package/dist/worker/observe/static/views/night.js +201 -0
  580. package/dist/worker/observe/static/views/pool.js +708 -0
  581. package/dist/worker/observe/static/views/run.js +453 -0
  582. package/dist/worker/observe/static/views/session-timeline.js +771 -0
  583. package/dist/worker/observe/static/views/shell.js +7 -0
  584. package/dist/worker/observe/static/views/task.js +315 -0
  585. package/dist/worker/observe/static/views/timeline.js +163 -0
  586. package/dist/worker/outcomes/adapters.js +153 -0
  587. package/dist/worker/outcomes/declared-artifacts.js +103 -0
  588. package/dist/worker/outcomes/evidence-tokens.js +29 -0
  589. package/dist/worker/outcomes/gate.js +40 -0
  590. package/dist/worker/outcomes/projector.js +227 -0
  591. package/dist/worker/outcomes/registry.js +1 -0
  592. package/dist/worker/outcomes/store.js +131 -0
  593. package/dist/worker/outcomes/types.js +79 -0
  594. package/dist/worker/pool/attempt-identity.js +41 -0
  595. package/dist/worker/pool/attempt-lease.js +184 -0
  596. package/dist/worker/pool/attempt-transition.js +210 -0
  597. package/dist/worker/pool/begin-attempt-with-lease.js +26 -0
  598. package/dist/worker/pool/begin-attempt.js +35 -0
  599. package/dist/worker/pool/doctor.js +165 -0
  600. package/dist/worker/pool/failure-routing.js +182 -0
  601. package/dist/worker/pool/migrate-state.js +303 -0
  602. package/dist/worker/pool/reconcile.js +285 -0
  603. package/dist/worker/pool/recovery-decision.js +163 -0
  604. package/dist/worker/pool/run-owner-store.js +126 -0
  605. package/dist/worker/pool/run-store.js +360 -0
  606. package/dist/worker/pool/runtime-reconcile-inventory.js +127 -0
  607. package/dist/worker/pool/state-projection.js +57 -0
  608. package/dist/worker/pool/types.js +17 -0
  609. package/dist/worker/pool/validation.js +144 -0
  610. package/dist/worker/preflight.js +157 -0
  611. package/dist/worker/profile-mapping.js +76 -0
  612. package/dist/worker/progress-reporter.js +63 -0
  613. package/dist/worker/report/morning-report.js +155 -0
  614. package/dist/worker/repos/repo-resolver.js +23 -0
  615. package/dist/worker/run-task/execute-prepared-task.js +178 -0
  616. package/dist/worker/run-task/run-task.js +912 -0
  617. package/dist/worker/runner/run-ready.js +561 -0
  618. package/dist/worker/runner/single-task-attempt.js +176 -0
  619. package/dist/worker/scheduler/admission.js +547 -0
  620. package/dist/worker/scheduler/auto-followup.js +99 -0
  621. package/dist/worker/scheduler/cli.js +644 -0
  622. package/dist/worker/scheduler/clock-install/darwin-launchd.js +238 -0
  623. package/dist/worker/scheduler/clock-install/linux-systemd-user.js +224 -0
  624. package/dist/worker/scheduler/clock-install/types.js +1 -0
  625. package/dist/worker/scheduler/clock-install/win32-schtasks.js +227 -0
  626. package/dist/worker/scheduler/clock-install.js +284 -0
  627. package/dist/worker/scheduler/clock.js +420 -0
  628. package/dist/worker/scheduler/dispatcher.js +503 -0
  629. package/dist/worker/scheduler/doctor.js +431 -0
  630. package/dist/worker/scheduler/evidence.js +170 -0
  631. package/dist/worker/scheduler/git-base.js +90 -0
  632. package/dist/worker/scheduler/index.js +25 -0
  633. package/dist/worker/scheduler/lease.js +114 -0
  634. package/dist/worker/scheduler/lifecycle.js +348 -0
  635. package/dist/worker/scheduler/lock.js +80 -0
  636. package/dist/worker/scheduler/morning-window.js +191 -0
  637. package/dist/worker/scheduler/night-git-finalizer.js +114 -0
  638. package/dist/worker/scheduler/night-harvest.js +412 -0
  639. package/dist/worker/scheduler/paths.js +92 -0
  640. package/dist/worker/scheduler/prepared-attempt-recovery.js +471 -0
  641. package/dist/worker/scheduler/recovery.js +277 -0
  642. package/dist/worker/scheduler/reservation.js +146 -0
  643. package/dist/worker/scheduler/retry.js +199 -0
  644. package/dist/worker/scheduler/scheduler-loop.js +272 -0
  645. package/dist/worker/scheduler/store.js +275 -0
  646. package/dist/worker/scheduler/traceability.js +54 -0
  647. package/dist/worker/scheduler/trigger.js +258 -0
  648. package/dist/worker/scheduler/types.js +369 -0
  649. package/dist/worker/scheduler/workspace-adapter.js +128 -0
  650. package/dist/worker/task-graph/acceptance-schema.js +40 -0
  651. package/dist/worker/task-graph/ready-planner.js +267 -0
  652. package/dist/worker/task-graph/ready-queue.js +23 -0
  653. package/dist/worker/task-graph/task-graph-schema.js +59 -0
  654. package/dist/worker/task-graph/types.js +1 -0
  655. package/dist/worker/task-graph/validate.js +293 -0
  656. package/dist/worker/task-spec/complexity-mapping.js +8 -0
  657. package/dist/worker/task-spec/schema.js +125 -0
  658. package/dist/worker/task-spec/types.js +1 -0
  659. package/dist/worker/task-spec/validate.js +391 -0
  660. package/dist/worker/task-spec/workflow-routing.js +149 -0
  661. package/dist/workflows/dag/authoring.js +8 -0
  662. package/dist/workflows/dag/authority-surface.js +138 -0
  663. package/dist/workflows/dag/backend-test-analysis-contract.js +507 -0
  664. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +1985 -0
  665. package/dist/workflows/dag/backend-test-case-manifest.js +638 -0
  666. package/dist/workflows/dag/backend-test-classification-contract.js +38 -0
  667. package/dist/workflows/dag/backend-test-contract-envelope.js +205 -0
  668. package/dist/workflows/dag/backend-test-coverage-contract.js +202 -0
  669. package/dist/workflows/dag/backend-test-execution-contract.js +715 -0
  670. package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
  671. package/dist/workflows/dag/backend-test-intake-context.js +178 -0
  672. package/dist/workflows/dag/backend-test-layout.js +133 -0
  673. package/dist/workflows/dag/backend-test-markdown-workflow.js +1984 -0
  674. package/dist/workflows/dag/backend-test-module-stem.js +26 -0
  675. package/dist/workflows/dag/backend-test-pytest-collection.js +859 -0
  676. package/dist/workflows/dag/backend-test-result-contract.js +1030 -0
  677. package/dist/workflows/dag/backend-test-scenario-param.js +1670 -0
  678. package/dist/workflows/dag/backend-test-scenario-partitions.js +249 -0
  679. package/dist/workflows/dag/backend-test-semantic-review-contract.js +36 -0
  680. package/dist/workflows/dag/backend-test-stability-contract.js +57 -0
  681. package/dist/workflows/dag/backend-test-writer-completeness.js +1058 -0
  682. package/dist/workflows/dag/budget-enforcement.js +67 -0
  683. package/dist/workflows/dag/canvas-observer.js +474 -0
  684. package/dist/workflows/dag/context-policy.js +137 -0
  685. package/dist/workflows/dag/contract-output-registry.js +14 -0
  686. package/dist/workflows/dag/contract-validator-registrations.js +10 -0
  687. package/dist/workflows/dag/controller-identity.js +104 -0
  688. package/dist/workflows/dag/convergence/controller.js +596 -0
  689. package/dist/workflows/dag/decision-envelope.js +557 -0
  690. package/dist/workflows/dag/decision-evidence.js +153 -0
  691. package/dist/workflows/dag/decision-gates.js +1 -0
  692. package/dist/workflows/dag/dynamic-runtime/condition.js +48 -0
  693. package/dist/workflows/dag/dynamic-runtime/loop-until.js +161 -0
  694. package/dist/workflows/dag/dynamic-runtime/map.js +435 -0
  695. package/dist/workflows/dag/dynamic-runtime/reduction.js +72 -0
  696. package/dist/workflows/dag/dynamic-runtime/shared.js +203 -0
  697. package/dist/workflows/dag/event-observer.js +132 -0
  698. package/dist/workflows/dag/executor-registry.js +23 -0
  699. package/dist/workflows/dag/facts.js +4 -0
  700. package/dist/workflows/dag/failure-category.js +118 -0
  701. package/dist/workflows/dag/failure-routing.js +105 -0
  702. package/dist/workflows/dag/final-verification.js +180 -0
  703. package/dist/workflows/dag/frontend-implementation-contract.js +1911 -0
  704. package/dist/workflows/dag/frontend-lint-baseline.js +474 -0
  705. package/dist/workflows/dag/frontend-plan-render.js +90 -0
  706. package/dist/workflows/dag/frontend-prewrite-gate.js +1011 -0
  707. package/dist/workflows/dag/frontend-project-capability.js +1 -0
  708. package/dist/workflows/dag/frontend-recovery-plan.js +73 -0
  709. package/dist/workflows/dag/frontend-recovery-root-manifest.js +123 -0
  710. package/dist/workflows/dag/frontend-recovery-run.js +539 -0
  711. package/dist/workflows/dag/frontend-repair.js +541 -0
  712. package/dist/workflows/dag/frontend-review-context.js +116 -0
  713. package/dist/workflows/dag/frontend-risk.js +161 -0
  714. package/dist/workflows/dag/frontend-test-case-checklist.js +343 -0
  715. package/dist/workflows/dag/frontend-test-case-manifest.js +104 -0
  716. package/dist/workflows/dag/frontend-test-case-quality.js +105 -0
  717. package/dist/workflows/dag/frontend-test-html-report.js +174 -0
  718. package/dist/workflows/dag/frontend-test-l5-report.js +177 -0
  719. package/dist/workflows/dag/frontend-test-result-contract.js +487 -0
  720. package/dist/workflows/dag/frontend-verification-trace.js +275 -0
  721. package/dist/workflows/dag/frontend-worktree-diff.js +201 -0
  722. package/dist/workflows/dag/frontend-writer-recovery.js +106 -0
  723. package/dist/workflows/dag/frontend-writer-rollback.js +821 -0
  724. package/dist/workflows/dag/governance-constants.js +5 -0
  725. package/dist/workflows/dag/governance-profile.js +413 -0
  726. package/dist/workflows/dag/index.js +6 -0
  727. package/dist/workflows/dag/init-hybrid.js +7345 -0
  728. package/dist/workflows/dag/interrupt-request.js +559 -0
  729. package/dist/workflows/dag/knowledge-curator.js +165 -0
  730. package/dist/workflows/dag/l5-report-metrics.js +33 -0
  731. package/dist/workflows/dag/lifecycle.js +855 -0
  732. package/dist/workflows/dag/liveness-policy.js +251 -0
  733. package/dist/workflows/dag/node-execution.js +1081 -0
  734. package/dist/workflows/dag/observer-compose.js +52 -0
  735. package/dist/workflows/dag/output-protocol.js +356 -0
  736. package/dist/workflows/dag/project-governance-context.js +514 -0
  737. package/dist/workflows/dag/prompt-source.js +88 -0
  738. package/dist/workflows/dag/prompt.js +348 -0
  739. package/dist/workflows/dag/reconcile-run.js +145 -0
  740. package/dist/workflows/dag/reconcile-tasks.js +404 -0
  741. package/dist/workflows/dag/recovery-recommendation.js +329 -0
  742. package/dist/workflows/dag/repair-artifact.js +363 -0
  743. package/dist/workflows/dag/report.js +1229 -0
  744. package/dist/workflows/dag/rerun-plan.js +614 -0
  745. package/dist/workflows/dag/rerun-run.js +556 -0
  746. package/dist/workflows/dag/rerun-task.js +370 -0
  747. package/dist/workflows/dag/retry-policy.js +263 -0
  748. package/dist/workflows/dag/run-store.js +41 -0
  749. package/dist/workflows/dag/runner.js +1069 -0
  750. package/dist/workflows/dag/runtime-contract.js +87 -0
  751. package/dist/workflows/dag/runtime.js +5 -0
  752. package/dist/workflows/dag/scheduler.js +497 -0
  753. package/dist/workflows/dag/sdd-embedded.js +128 -0
  754. package/dist/workflows/dag/skill-instructions.js +478 -0
  755. package/dist/workflows/dag/skill-snapshot.js +553 -0
  756. package/dist/workflows/dag/skills.js +41 -0
  757. package/dist/workflows/dag/spec.js +3 -0
  758. package/dist/workflows/dag/task-contract-binding.js +138 -0
  759. package/dist/workflows/dag/task-demand-routing.js +1 -0
  760. package/dist/workflows/dag/topo.js +30 -0
  761. package/dist/workflows/dag/types.js +1145 -0
  762. package/dist/workflows/dag/upstream-artifacts.js +98 -0
  763. package/dist/workflows/dag/validate.js +998 -0
  764. package/dist/workflows/dag/workspace-checkpoint.js +186 -0
  765. package/dist/workflows/dynamic/artifacts.js +65 -0
  766. package/dist/workflows/dynamic/compile.js +371 -0
  767. package/dist/workflows/dynamic/compileTypes.js +1 -0
  768. package/dist/workflows/dynamic/errors.js +5 -0
  769. package/dist/workflows/dynamic/index.js +7 -0
  770. package/dist/workflows/dynamic/profiles.js +156 -0
  771. package/dist/workflows/dynamic/spec.js +115 -0
  772. package/dist/workflows/dynamic/validate.js +275 -0
  773. package/dist/workflows/loop/actions/dag-action.js +129 -0
  774. package/dist/workflows/loop/actions/pi-review.js +267 -0
  775. package/dist/workflows/loop/actions/shared.js +157 -0
  776. package/dist/workflows/loop/actions/shell-verify.js +82 -0
  777. package/dist/workflows/loop/actions/types.js +1 -0
  778. package/dist/workflows/loop/actions/workflow-action.js +255 -0
  779. package/dist/workflows/loop/actions.js +160 -0
  780. package/dist/workflows/loop/benchmark.js +510 -0
  781. package/dist/workflows/loop/closeout.js +135 -0
  782. package/dist/workflows/loop/context.js +47 -0
  783. package/dist/workflows/loop/events.js +26 -0
  784. package/dist/workflows/loop/hash.js +32 -0
  785. package/dist/workflows/loop/index.js +8 -0
  786. package/dist/workflows/loop/paths.js +17 -0
  787. package/dist/workflows/loop/policy/auto-policy.js +112 -0
  788. package/dist/workflows/loop/policy/path-patterns.js +13 -0
  789. package/dist/workflows/loop/rounds.js +81 -0
  790. package/dist/workflows/loop/signals.js +52 -0
  791. package/dist/workflows/loop/state.js +116 -0
  792. package/dist/workflows/loop/templates.js +54 -0
  793. package/dist/workflows/loop/types.js +28 -0
  794. package/docs/README.md +82 -0
  795. package/docs/architecture/README.md +35 -0
  796. package/docs/architecture/dag-execution.md +165 -0
  797. package/docs/architecture/evolution.md +108 -0
  798. package/docs/architecture/facts-and-state.md +74 -0
  799. package/docs/architecture/runtime-boundaries.md +226 -0
  800. package/docs/architecture/system-overview.md +100 -0
  801. package/docs/architecture/worker-and-feature.md +122 -0
  802. package/docs/governance/README.md +18 -0
  803. package/docs/governance/harness-methodology-debugging.md +177 -0
  804. package/docs/governance/harness-methodology-tdd.md +130 -0
  805. package/docs/governance/harness-methodology-verification.md +27 -0
  806. package/docs/init-surface.manifest.json +358 -0
  807. package/docs/operations/README.md +15 -0
  808. package/docs/operations/local-development-environment.md +77 -0
  809. package/docs/skills/README.md +7 -0
  810. package/docs/skills/vetted-skill-registry.md +49 -0
  811. package/docs/templates/README.md +67 -0
  812. package/docs/templates/adr.md +60 -0
  813. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -0
  814. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -0
  815. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -0
  816. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -0
  817. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -0
  818. package/docs/templates/agent-dag-report.schema.json +473 -0
  819. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -0
  820. package/docs/templates/agent-dag.base.json +190 -0
  821. package/docs/templates/agent-dag.final-verification.json +185 -0
  822. package/docs/templates/agent-dag.schema.json +590 -0
  823. package/docs/templates/agent-dag.supervised-implementation.json +640 -0
  824. package/docs/templates/agent-worker-production-readiness-checklist.md +47 -0
  825. package/docs/templates/backend-test-analysis.schema.json +37 -0
  826. package/docs/templates/backend-test-case-manifest.schema.json +223 -0
  827. package/docs/templates/backend-test-dag.classify.prompt.md +75 -0
  828. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +41 -0
  829. package/docs/templates/backend-test-dag.json +671 -0
  830. package/docs/templates/backend-test-dag.retrospect.prompt.md +165 -0
  831. package/docs/templates/backend-test-dag.review-cases.prompt.md +36 -0
  832. package/docs/templates/backend-test-execution.schema.json +138 -0
  833. package/docs/templates/backend-test-result.schema.json +99 -0
  834. package/docs/templates/branch-merge-report.md +115 -0
  835. package/docs/templates/evaluation/agents-map-slim-v1.candidate.json +9 -0
  836. package/docs/templates/evaluation/agents-map-slim-v1.md +87 -0
  837. package/docs/templates/evaluation/agents-map-verbose-v0.candidate.json +9 -0
  838. package/docs/templates/evaluation/agents-map-verbose-v0.md +153 -0
  839. package/docs/templates/evaluation/campaign-budget-v1.json +12 -0
  840. package/docs/templates/evaluation/campaign-dogfood-v0.json +24 -0
  841. package/docs/templates/evaluation/campaign-evidence-v1.json +44 -0
  842. package/docs/templates/evaluation/context-policy-baseline-v1.json +17 -0
  843. package/docs/templates/evaluation/context-policy-role-specialized-v1.json +28 -0
  844. package/docs/templates/evaluation/corpus-dogfood-v0.manifest.json +118 -0
  845. package/docs/templates/evaluation/matrix-dag-dry-run-v1.json +21 -0
  846. package/docs/templates/evaluation/matrix-fixture-v1.json +10 -0
  847. package/docs/templates/evaluation/private-verifier-dogfood-v0.json +16 -0
  848. package/docs/templates/exec-plan.md +66 -0
  849. package/docs/templates/feature-spec.md +53 -0
  850. package/docs/templates/frontend-design-contract.md +68 -0
  851. package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -0
  852. package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -0
  853. package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -0
  854. package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -0
  855. package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -0
  856. package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -0
  857. package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -0
  858. package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -0
  859. package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -0
  860. package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -0
  861. package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -0
  862. package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -0
  863. package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -0
  864. package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -0
  865. package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -0
  866. package/docs/templates/frontend-eval/metrics.md +138 -0
  867. package/docs/templates/frontend-eval/smoke-targets.md +53 -0
  868. package/docs/templates/frontend-implementation-contract.schema.json +35 -0
  869. package/docs/templates/frontend-task-constraints.md +62 -0
  870. package/docs/templates/frontend-task-requirement.md +104 -0
  871. package/docs/templates/frontend-test-case-checklist.md +44 -0
  872. package/docs/templates/frontend-test-case.example.md +143 -0
  873. package/docs/templates/frontend-test-dag.generate-cases.prompt.md +63 -0
  874. package/docs/templates/frontend-test-dag.json +409 -0
  875. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +21 -0
  876. package/docs/templates/frontend-test-dag.retrospect.prompt.md +5 -0
  877. package/docs/templates/frontend-test-dag.review-cases.prompt.md +5 -0
  878. package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -0
  879. package/docs/templates/frontend-test-standard-scenarios.v1.json +159 -0
  880. package/docs/templates/harness.schema.json +351 -0
  881. package/docs/templates/hybrid-dag.json +188 -0
  882. package/docs/templates/init-evolution-review.md +35 -0
  883. package/docs/templates/init-managed-agents.md +157 -0
  884. package/docs/templates/interactive-ui-round2-experiment.md +66 -0
  885. package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -0
  886. package/docs/templates/knowledge-sync-dag.json +178 -0
  887. package/docs/templates/knowledge-sync-draft.schema.json +71 -0
  888. package/docs/templates/product-line/AGENTS.md +9 -0
  889. package/docs/templates/product-line/README.md +42 -0
  890. package/docs/templates/product-line/acceptance.yaml +23 -0
  891. package/docs/templates/product-line/closeout.yaml +9 -0
  892. package/docs/templates/product-line/design.md +13 -0
  893. package/docs/templates/product-line/feature-scaffold-batch.example.yaml +31 -0
  894. package/docs/templates/product-line/feature.yaml +11 -0
  895. package/docs/templates/product-line/links.md +10 -0
  896. package/docs/templates/product-line/requirement.md +17 -0
  897. package/docs/templates/product-line/scaffold-samples/README.md +36 -0
  898. package/docs/templates/product-line/scaffold-samples/backend-only/acceptance.yaml +16 -0
  899. package/docs/templates/product-line/scaffold-samples/backend-only/design.md +14 -0
  900. package/docs/templates/product-line/scaffold-samples/backend-only/feature.yaml +14 -0
  901. package/docs/templates/product-line/scaffold-samples/backend-only/requirement.md +6 -0
  902. package/docs/templates/product-line/scaffold-samples/backend-only/tasks/BE-IMPL-001.yaml +83 -0
  903. package/docs/templates/product-line/scaffold-samples/backend-only/tasks/task-graph.yaml +14 -0
  904. package/docs/templates/product-line/scaffold-samples/fe-with-api/acceptance.yaml +18 -0
  905. package/docs/templates/product-line/scaffold-samples/fe-with-api/design.md +18 -0
  906. package/docs/templates/product-line/scaffold-samples/fe-with-api/feature.yaml +14 -0
  907. package/docs/templates/product-line/scaffold-samples/fe-with-api/requirement.md +6 -0
  908. package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/BE-IMPL-001.yaml +84 -0
  909. package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/CONTRACT-001.yaml +84 -0
  910. package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/FE-IMPL-001.yaml +86 -0
  911. package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/task-graph.yaml +40 -0
  912. package/docs/templates/product-line/scaffold-samples/frontend-only/acceptance.yaml +16 -0
  913. package/docs/templates/product-line/scaffold-samples/frontend-only/design.md +14 -0
  914. package/docs/templates/product-line/scaffold-samples/frontend-only/feature.yaml +14 -0
  915. package/docs/templates/product-line/scaffold-samples/frontend-only/requirement.md +6 -0
  916. package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/FE-IMPL-001.yaml +85 -0
  917. package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/task-graph.yaml +14 -0
  918. package/docs/templates/product-line/task-graph.yaml +23 -0
  919. package/docs/templates/product-line/task.yaml +68 -0
  920. package/docs/templates/product-line/test-plan.md +7 -0
  921. package/docs/templates/production-readiness-checklist.md +57 -0
  922. package/docs/templates/progress-log.md +27 -0
  923. package/docs/templates/project-start-checklist.md +9 -0
  924. package/docs/templates/qa-report.md +48 -0
  925. package/docs/templates/sprint-contract.md +29 -0
  926. package/docs/templates/worker-dogfood-evidence.md +80 -0
  927. package/docs/templates/worker-dogfood-setup.md +68 -0
  928. package/examples/decision-gate-agent-dag.json +177 -0
  929. package/examples/example-dag.json +46 -0
  930. package/examples/hybrid-loop-agent-dag.json +189 -0
  931. package/examples/l5-report-coms-process-definition.html +322 -0
  932. package/examples/l5-report-demo.html +79 -0
  933. package/harness.json +90 -0
  934. package/package.json +98 -0
  935. package/scripts/kb-bootstrap-init-skeleton.sh +241 -0
  936. package/scripts/kb-graph-incremental-prepare.mjs +386 -0
  937. package/scripts/kb-graph-materialize.mjs +105 -0
  938. package/scripts/kb-graph-promote.mjs +164 -0
  939. package/scripts/kb-query.mjs +554 -0
  940. package/skills/agent-worker/SKILL.md +48 -0
  941. package/skills/agent-worker/references/agent-worker-operator.md +159 -0
  942. package/skills/ai-engineering-context/SKILL.md +48 -0
  943. package/skills/analyze-product-dependencies/SKILL.md +108 -0
  944. package/skills/analyze-product-dependencies/references/api-documentation-schema.md +35 -0
  945. package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +38 -0
  946. package/skills/analyze-product-dependencies/references/example.md +76 -0
  947. package/skills/analyze-product-dependencies/references/forward-test-cases.md +110 -0
  948. package/skills/analyze-product-dependencies/references/input-contract.md +34 -0
  949. package/skills/analyze-product-dependencies/references/kb-integration.md +64 -0
  950. package/skills/analyze-product-dependencies/references/scouting-rules.md +76 -0
  951. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +460 -0
  952. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +194 -0
  953. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +200 -0
  954. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +90 -0
  955. package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +138 -0
  956. package/skills/analyze-product-requirements/SKILL.md +145 -0
  957. package/skills/analyze-product-requirements/references/acceptance-criteria.md +87 -0
  958. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +70 -0
  959. package/skills/analyze-product-requirements/references/example.md +104 -0
  960. package/skills/analyze-product-requirements/references/forward-test-cases.md +144 -0
  961. package/skills/analyze-product-requirements/references/kb-integration.md +56 -0
  962. package/skills/analyze-product-requirements/references/product-analysis-schema.md +41 -0
  963. package/skills/analyze-product-requirements/references/product-requirement-schema.md +42 -0
  964. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +66 -0
  965. package/skills/analyze-product-requirements/scripts/compute-source-identity.mjs +35 -0
  966. package/skills/analyze-product-requirements/scripts/test-validators.mjs +501 -0
  967. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +100 -0
  968. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +109 -0
  969. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +109 -0
  970. package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +175 -0
  971. package/skills/browser-tools/SKILL.md +196 -0
  972. package/skills/browser-tools/browser-content.js +103 -0
  973. package/skills/browser-tools/browser-cookies.js +35 -0
  974. package/skills/browser-tools/browser-eval.js +53 -0
  975. package/skills/browser-tools/browser-hn-scraper.js +108 -0
  976. package/skills/browser-tools/browser-nav.js +44 -0
  977. package/skills/browser-tools/browser-pick.js +162 -0
  978. package/skills/browser-tools/browser-screenshot.js +34 -0
  979. package/skills/browser-tools/browser-start.js +86 -0
  980. package/skills/browser-tools/package-lock.json +2556 -0
  981. package/skills/browser-tools/package.json +19 -0
  982. package/skills/code-review-core/SKILL.md +20 -0
  983. package/skills/codebase-scout/SKILL.md +19 -0
  984. package/skills/frontend-bounded-implement/SKILL.md +53 -0
  985. package/skills/frontend-design-review/SKILL.md +79 -0
  986. package/skills/frontend-design-review/references/review-checklist.md +43 -0
  987. package/skills/frontend-implementation/SKILL.md +52 -0
  988. package/skills/frontend-implementation/references/code-standards.md +33 -0
  989. package/skills/frontend-implementation/references/design-spec.md +56 -0
  990. package/skills/frontend-implementation/references/node-contracts.md +31 -0
  991. package/skills/frontend-review/SKILL.md +55 -0
  992. package/skills/frontend-review/references/review-findings.md +50 -0
  993. package/skills/frontend-verification/SKILL.md +61 -0
  994. package/skills/frontend-verification/references/verification-checklist.md +48 -0
  995. package/skills/grill-me/SKILL.md +10 -0
  996. package/skills/grill-with-docs/SKILL.md +80 -0
  997. package/skills/grill-with-docs/adr-format.md +58 -0
  998. package/skills/grill-with-docs/context-format.md +52 -0
  999. package/skills/init-capability-evolution/SKILL.md +70 -0
  1000. package/skills/local-jacoco-coverage/SKILL.md +281 -0
  1001. package/skills/local-jacoco-coverage/references/requirement-to-source-mapping.md +85 -0
  1002. package/skills/local-jacoco-coverage/references/runtime-alignment.md +106 -0
  1003. package/skills/local-jacoco-coverage/scripts/run-coverage-analysis.sh +148 -0
  1004. package/skills/local-jacoco-coverage/scripts/start-jacoco-agent.sh +110 -0
  1005. package/skills/loop-agent/SKILL.md +68 -0
  1006. package/skills/loop-agent/references/README.md +67 -0
  1007. package/skills/loop-agent/references/command-reference.md +645 -0
  1008. package/skills/loop-agent/references/docs-converge.md +126 -0
  1009. package/skills/loop-agent/references/harness-policy.md +289 -0
  1010. package/skills/loop-agent/references/hybrid-dag.md +245 -0
  1011. package/skills/loop-agent/references/learned/README.md +21 -0
  1012. package/skills/loop-agent/references/long-running-loop.md +57 -0
  1013. package/skills/loop-agent/references/model-routing.md +45 -0
  1014. package/skills/loop-agent/references/multi-worktree.md +54 -0
  1015. package/skills/loop-agent/references/one-shot-runs.md +84 -0
  1016. package/skills/loop-agent/references/orchestrator-and-interventions.md +175 -0
  1017. package/skills/loop-agent/references/pi-prompt.md +23 -0
  1018. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -0
  1019. package/skills/loop-agent/references/post-implementation-and-patterns.md +54 -0
  1020. package/skills/loop-agent/references/source-and-plan-practice.md +169 -0
  1021. package/skills/loop-agent/references/task-workflow.md +104 -0
  1022. package/skills/loop-agent/references/verification-and-failure-handling.md +148 -0
  1023. package/skills/playwright-cli/SKILL.md +93 -0
  1024. package/skills/playwright-cli/references/element-attributes.md +23 -0
  1025. package/skills/playwright-cli/references/playwright-tests.md +39 -0
  1026. package/skills/playwright-cli/references/request-mocking.md +87 -0
  1027. package/skills/playwright-cli/references/running-code.md +241 -0
  1028. package/skills/playwright-cli/references/session-management.md +225 -0
  1029. package/skills/playwright-cli/references/storage-state.md +275 -0
  1030. package/skills/playwright-cli/references/test-generation.md +433 -0
  1031. package/skills/playwright-cli/references/tracing.md +5 -0
  1032. package/skills/playwright-cli/references/video-recording.md +5 -0
  1033. package/skills/playwright-cli-case-generator/SKILL.md +108 -0
  1034. package/skills/requesting-code-review/SKILL.md +101 -0
  1035. package/skills/requesting-code-review/code-reviewer.md +168 -0
  1036. package/skills/systematic-debugging/CREATION-LOG.md +119 -0
  1037. package/skills/systematic-debugging/SKILL.md +312 -0
  1038. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  1039. package/skills/systematic-debugging/condition-based-waiting.md +115 -0
  1040. package/skills/systematic-debugging/defense-in-depth.md +122 -0
  1041. package/skills/systematic-debugging/find-polluter.sh +63 -0
  1042. package/skills/systematic-debugging/root-cause-tracing.md +169 -0
  1043. package/skills/systematic-debugging/test-academic.md +14 -0
  1044. package/skills/systematic-debugging/test-pressure-1.md +58 -0
  1045. package/skills/systematic-debugging/test-pressure-2.md +68 -0
  1046. package/skills/systematic-debugging/test-pressure-3.md +69 -0
  1047. package/skills/test-driven-development/SKILL.md +27 -0
  1048. package/skills/using-git-worktrees/SKILL.md +215 -0
  1049. package/skills/verification-before-completion/SKILL.md +154 -0
  1050. package/skills/webapp-testing/SKILL.md +19 -0
@@ -0,0 +1,3055 @@
1
+ import { spawn } from "node:child_process";
2
+ import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
3
+ import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { writeDagNodeJsonArtifact, writeDagNodeTextArtifact, } from "../infrastructure/harness/artifact-store.js";
6
+ import { captureWorkspaceCheckpoint } from "../workflows/dag/workspace-checkpoint.js";
7
+ import { truncateOutput } from "../shared/output-truncation.js";
8
+ import { processTreeSpawnOptions, terminateProcessTree, } from "./process-tree.js";
9
+ import { createPlaywrightCliTool, defaultPlaywrightCliCommandRunner, } from "./pi-playwright-cli-tool.js";
10
+ import { resolvePiBackend } from "./pi-executor.js";
11
+ import { checkPiSdkCustomToolCapability } from "./pi-sdk-executor.js";
12
+ import { resolvePlaywrightCliLauncher } from "./playwright-cli-launcher.js";
13
+ import { buildRequirementCoverageGateShellCommand, expandShellPreset, buildVerdictGateShellCommand, } from "./shell-presets.js";
14
+ import { materializeBackendTestAnalysisContract } from "../workflows/dag/backend-test-analysis-contract.js";
15
+ import { extractBackendTestContractEnvelope } from "../workflows/dag/backend-test-contract-envelope.js";
16
+ import { materializeFrontendImplementationContract } from "../workflows/dag/frontend-implementation-contract.js";
17
+ import { materializeFrontendTestResult, validateFrontendCaseEvidence, } from "../workflows/dag/frontend-test-result-contract.js";
18
+ import { renderFrontendTestL5Report } from "../workflows/dag/frontend-test-l5-report.js";
19
+ import { validateFrontendCaseChecklist } from "../workflows/dag/frontend-test-case-checklist.js";
20
+ import { materializeFrontendTestCaseManifest } from "../workflows/dag/frontend-test-case-manifest.js";
21
+ import { renderFrontendTestHtmlReport } from "../workflows/dag/frontend-test-html-report.js";
22
+ import { formatFrontendVerificationTraceStdout, runFrontendVerificationTraceGate, } from "../workflows/dag/frontend-verification-trace.js";
23
+ import { formatFrontendWorktreeDiffStdout, runFrontendWorktreeDiffGate, } from "../workflows/dag/frontend-worktree-diff.js";
24
+ import { formatFrontendFailureAssessStdout, formatFrontendRepairContractStdout, runFrontendFailureAssessGate, runFrontendRepairContractGate, } from "../workflows/dag/frontend-repair.js";
25
+ import { formatFrontendPrewriteGateStdout, runFrontendPrewriteGate, } from "../workflows/dag/frontend-prewrite-gate.js";
26
+ import { formatFrontendReviewContextStdout, runFrontendReviewContextGate, } from "../workflows/dag/frontend-review-context.js";
27
+ import { materializeFrontendLintAssessment, materializeFrontendLintBaseline, } from "../workflows/dag/frontend-lint-baseline.js";
28
+ import { formatTraceabilityGateStdout, materializeBackendTestCaseManifest, runBackendTestTraceabilityGate, } from "../workflows/dag/backend-test-case-manifest.js";
29
+ import { materializeBackendTestExecutionContract } from "../workflows/dag/backend-test-execution-contract.js";
30
+ import { resolveBackendTestLayout } from "../workflows/dag/backend-test-layout.js";
31
+ import { backendTestGapDocSchema, ingestBackendTestGap, } from "../workflows/dag/backend-test-gap-fill.js";
32
+ import { analyzeBackendTestCaseCoverage, analyzeBackendTestMarkdownPytestCorrespondence, materializeBackendTestCaseManifestFromFacts, } from "../workflows/dag/backend-test-case-coverage-analysis.js";
33
+ import { materializeBackendTestResultFromPytestHtml, materializeBackendTestResultFromRunDir, parsePytestHtmlReport, } from "../workflows/dag/backend-test-result-contract.js";
34
+ import { collectBackendTestHumanCaseCatalog, collectBackendTestMappedPytestScripts, resolveBackendTestMappedPytestScripts, collectJacocoCoverage, hasBlockingBackendMarkdownSafetyFindings, hasBlockingBackendMarkdownModuleStemFindings, inspectBackendTestEnvironment, markdownFiles, requiredBackendMarkdownCaseAcIds, renderBackendTestFacts, renderBackendTestHtml, renderBackendTestL5Dashboard, redactBackendTestOutput, validateBackendMarkdownCases, validateBackendMarkdownTraceability, writeRunReport, } from "../workflows/dag/backend-test-markdown-workflow.js";
35
+ import { applyDeterministicScenarioParamRepairs, assessBackendScenarioParamConsistency, classifyBackendTestFailureWithScenarioParam, readBackendScenarioParamFacts, renderBackendTestFailureAnalysis, writeBackendScenarioParamArtifacts, writeScenarioParamRepairAudit, } from "../workflows/dag/backend-test-scenario-param.js";
36
+ import { assessBackendPytestCollection, assessMissingBackendPytestScripts, assessPriorityOnlyBackendPytestModules, assertBackendTestExecutionReadinessFresh, buildBackendPytestAssetInventory, buildBackendTestItemEligibility, materializeBackendTestExecutionReadiness, materializeEffectiveBackendPytestCollection, readBackendPytestCollectionFacts, readBackendTestExecutionReadiness, writeBackendPytestCollectionArtifacts, } from "../workflows/dag/backend-test-pytest-collection.js";
37
+ import { computeL5ReportMetrics } from "../workflows/dag/l5-report-metrics.js";
38
+ import { buildBackendTestCanonicalResultFromInitialShellSnippet, materializeBackendTestClassification, } from "../workflows/dag/backend-test-classification-contract.js";
39
+ import { backendTestSemanticReviewSchema, materializeBackendTestSemanticReview, } from "../workflows/dag/backend-test-semantic-review-contract.js";
40
+ import { pathsChangedDuringRun, readGitStatusPorcelain, snapshotGitStatusPorcelain, validateShellWriteGuard, } from "./shell-write-guard.js";
41
+ import { parseAndValidateFinalWriteSetApproval, } from "../workflows/dag/node-execution.js";
42
+ import { buildShellProcessEnv } from "./shell-verification.js";
43
+ import { readRunState } from "../workflows/dag/run-store.js";
44
+ import { readProjectGovernanceContext } from "../workflows/dag/project-governance-context.js";
45
+ import { assertMavenPlanFresh, MavenPlanStaleError, } from "../verification/maven/index.js";
46
+ async function runBackendScenarioParamPreflight(input) {
47
+ const outputs = [];
48
+ const initial = await assessBackendScenarioParamConsistency({
49
+ workspaceRoot: input.workspaceRoot,
50
+ phase: "initial",
51
+ repairAttempt: 0,
52
+ strictScenarioParamGate: input.strictScenarioParamGate,
53
+ layout: input.layout,
54
+ });
55
+ const initialArtifacts = await writeBackendScenarioParamArtifacts({
56
+ runDir: input.runDir,
57
+ facts: initial.facts,
58
+ markdown: initial.markdown,
59
+ });
60
+ outputs.push(`scenarioParamInitial=${initialArtifacts.reportPath}`);
61
+ let finalFacts;
62
+ let finalMarkdown;
63
+ if (initial.facts.repairEligible) {
64
+ const repair = await applyDeterministicScenarioParamRepairs({
65
+ workspaceRoot: input.workspaceRoot,
66
+ facts: initial.facts,
67
+ });
68
+ const auditPath = await writeScenarioParamRepairAudit({
69
+ runDir: input.runDir,
70
+ audit: repair.audit,
71
+ changedFiles: repair.changedFiles,
72
+ });
73
+ outputs.push(`scenarioParamRepairAudit=${auditPath}`, `scenarioParamRepaired=${repair.repaired.join(",") || "(none)"}`);
74
+ const reassessed = await assessBackendScenarioParamConsistency({
75
+ workspaceRoot: input.workspaceRoot,
76
+ phase: "final",
77
+ repairAttempt: 1,
78
+ strictScenarioParamGate: input.strictScenarioParamGate,
79
+ layout: input.layout,
80
+ });
81
+ finalFacts = reassessed.facts;
82
+ finalMarkdown = reassessed.markdown;
83
+ }
84
+ else {
85
+ finalFacts = { ...initial.facts, phase: "final", repairAttempt: 0, repairEligible: false };
86
+ finalMarkdown = initial.markdown.replace("Phase: initial", "Phase: final");
87
+ }
88
+ const finalArtifacts = await writeBackendScenarioParamArtifacts({
89
+ runDir: input.runDir,
90
+ facts: finalFacts,
91
+ markdown: finalMarkdown,
92
+ });
93
+ outputs.push(`scenarioParamFinal=${finalArtifacts.reportPath}`, `scenarioParamStatus=${finalFacts.overallStatus}`);
94
+ if (input.strictScenarioParamGate && ["FAIL", "UNAVAILABLE"].includes(finalFacts.overallStatus)) {
95
+ throw new Error(`backend-test strict scenario-param gate blocked ${finalFacts.overallStatus} before collection`);
96
+ }
97
+ return { facts: finalFacts, outputs };
98
+ }
99
+ /** In-memory same-run success cache. Never shared across runIds. */
100
+ const sameRunShellCommandCaches = new Map();
101
+ function shellCommandCacheKey(runId, workspaceFingerprint, contractKey) {
102
+ return `${runId}::${workspaceFingerprint}::${contractKey}`;
103
+ }
104
+ function buildShellCommandContractKey(input) {
105
+ const environment = Object.fromEntries((input.envAllowlist ?? []).sort().map((name) => [name, process.env[name] ?? null]));
106
+ return JSON.stringify({
107
+ cwd: input.cwd,
108
+ timeoutMs: input.timeoutMs,
109
+ environment,
110
+ failFast: input.failFast,
111
+ nonZeroExitPolicy: input.nonZeroExitPolicy ?? "fail",
112
+ controllerInput: input.controllerInput ?? null,
113
+ });
114
+ }
115
+ export function clearSameRunShellCommandCachesForTests() {
116
+ sameRunShellCommandCaches.clear();
117
+ }
118
+ function shouldReuseSuccessfulShellCommands(shell) {
119
+ return shell.verifyEvidence?.phase === "intermediate" || shell.verifyEvidence?.phase === "final";
120
+ }
121
+ const DEFAULT_SHELL_TIMEOUT_MS = 300_000;
122
+ const SUMMARY_STDOUT_MAX = 4_000;
123
+ const SUMMARY_STDERR_MAX = 2_000;
124
+ const NODE_STDOUT_MAX = 12_000;
125
+ const NODE_STDERR_MAX = 8_000;
126
+ const MAX_BUFFERED_OUTPUT_BYTES = 64 * 1024;
127
+ function resolveBashExecutable() {
128
+ const envCandidate = process.env.LOOP_AGENT_BASH ?? process.env.GIT_BASH;
129
+ if (envCandidate && existsSync(envCandidate))
130
+ return envCandidate;
131
+ if (process.platform !== "win32")
132
+ return envCandidate || "bash";
133
+ const candidates = [
134
+ path.join(process.env.ProgramFiles ?? "C:\\Program Files", "Git", "bin", "bash.exe"),
135
+ path.join(process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)", "Git", "bin", "bash.exe"),
136
+ path.join(process.env.LocalAppData ?? "", "Programs", "Git", "bin", "bash.exe"),
137
+ ].filter(Boolean);
138
+ return (candidates.find((candidate) => existsSync(candidate)) ??
139
+ envCandidate ??
140
+ "bash");
141
+ }
142
+ /** POSIX-style runDir for bash `${HARNESS_DAG_RUN_DIR}` expansions on Windows. */
143
+ function normalizeHarnessDagRunDir(runDir) {
144
+ return runDir.replaceAll(path.sep, "/");
145
+ }
146
+ const BACKEND_TEST_EXECUTE_DIAGNOSTICS_REL = "reports/backend-test-execute-diagnostics.md";
147
+ /**
148
+ * Write diagnostics *before* any bash spawn so Windows STATUS_DLL_INIT_FAILED
149
+ * (0xC0000142) / empty-shell-output failures still leave an auditable file.
150
+ * Shell steps append under `## shell-steps`; they must not overwrite this head.
151
+ */
152
+ async function writeBackendTestExecuteDiagnosticsPreSpawn(input) {
153
+ const harnessDagRunDir = normalizeHarnessDagRunDir(input.runDir);
154
+ if (!harnessDagRunDir.trim()) {
155
+ throw new Error("HARNESS_DAG_RUN_DIR would be empty; refusing backend-test execute (would write under /reports)");
156
+ }
157
+ const reportsDir = path.join(input.runDir, "reports");
158
+ await mkdir(reportsDir, { recursive: true });
159
+ const diagnosticsPath = path.join(reportsDir, "backend-test-execute-diagnostics.md");
160
+ const lines = [
161
+ "# Backend Test Execute Diagnostics",
162
+ "",
163
+ "## pre-spawn",
164
+ "",
165
+ `- phase: pre-spawn`,
166
+ `- startedAt: ${new Date().toISOString()}`,
167
+ `- cwd: ${input.cwd}`,
168
+ `- runId: ${input.runId}`,
169
+ `- runDir: ${input.runDir}`,
170
+ `- HARNESS_DAG_RUN_DIR: ${harnessDagRunDir}`,
171
+ `- bashExecutable: ${resolveBashExecutable()}`,
172
+ `- platform: ${process.platform}`,
173
+ `- mappedScriptCount: ${input.mappedScripts.length}`,
174
+ "- mappedScripts:",
175
+ ...(input.mappedScripts.length > 0
176
+ ? input.mappedScripts.map((script) => ` - ${script}`)
177
+ : [" - <none>"]),
178
+ `- commandPlanCount: ${input.commandPlan.length}`,
179
+ "- commandPlan:",
180
+ ...input.commandPlan.map((label, index) => ` - ${index + 1}. ${label}`),
181
+ "",
182
+ "## shell-steps",
183
+ "",
184
+ "_Shell commands append below. If this section stays empty, bash never ran successfully._",
185
+ "",
186
+ ];
187
+ await writeFile(diagnosticsPath, `${lines.join("\n")}\n`, "utf8");
188
+ return { diagnosticsPath, harnessDagRunDir };
189
+ }
190
+ function isWithinRoot(root, candidate) {
191
+ const relative = path.relative(root, candidate);
192
+ return (relative === "" ||
193
+ (!relative.startsWith("..") && !path.isAbsolute(relative)));
194
+ }
195
+ export function resolveShellCwd(root, requestedCwd) {
196
+ const resolved = requestedCwd ? path.resolve(root, requestedCwd) : root;
197
+ if (!isWithinRoot(root, resolved)) {
198
+ throw new Error(`shell cwd must stay within DAG cwd: ${requestedCwd}`);
199
+ }
200
+ return resolved;
201
+ }
202
+ async function resolveJsonArtifactSourceNodeId(input) {
203
+ const candidates = [
204
+ input.fromNodeId,
205
+ ...new Set((input.fallbackFromNodeIds ?? []).filter((nodeId) => nodeId !== input.fromNodeId)),
206
+ ];
207
+ for (const nodeId of candidates) {
208
+ try {
209
+ await stat(path.join(input.runDir, `${nodeId}.json`));
210
+ return nodeId;
211
+ }
212
+ catch (error) {
213
+ if (error &&
214
+ typeof error === "object" &&
215
+ "code" in error &&
216
+ error.code === "ENOENT") {
217
+ continue;
218
+ }
219
+ throw error;
220
+ }
221
+ }
222
+ throw new Error(`missing jsonArtifactGate node output (tried: ${candidates.join(", ")})`);
223
+ }
224
+ /** Preset commands first, then explicit shell.commands, then verdictGate (deterministic ordering). */
225
+ export function resolveShellCommands(shell) {
226
+ const fromPreset = shell.preset ? expandShellPreset(shell.preset) : [];
227
+ const explicit = (shell.commands ?? []).filter((command) => command.trim().length > 0);
228
+ const fromVerdictGate = shell.verdictGate
229
+ ? [buildVerdictGateShellCommand(shell.verdictGate)]
230
+ : [];
231
+ const fromRequirementCoverageGate = shell.requirementCoverageGate
232
+ ? [buildRequirementCoverageGateShellCommand(shell.requirementCoverageGate)]
233
+ : [];
234
+ return [
235
+ ...fromPreset,
236
+ ...explicit,
237
+ ...fromVerdictGate,
238
+ ...fromRequirementCoverageGate,
239
+ ];
240
+ }
241
+ export async function executeShellCommand(input) {
242
+ return new Promise((resolve) => {
243
+ const startedAt = Date.now();
244
+ const startedAtIso = new Date(startedAt).toISOString();
245
+ let lastOutputActivityAt = 0;
246
+ const reportOutputActivity = () => {
247
+ const now = Date.now();
248
+ if (now - lastOutputActivityAt < 500)
249
+ return;
250
+ lastOutputActivityAt = now;
251
+ input.reportActivity?.({ attempt: 1, kind: "output", at: new Date(now).toISOString() });
252
+ };
253
+ const injectedEnv = input.dagRunMeta
254
+ ? {
255
+ // Git Bash treats backslashes as escape characters inside double
256
+ // quotes. On Windows, path.join() produces backslash runDirs like
257
+ // D:\\...\\active\\<runId>, which corrupt "${HARNESS_DAG_RUN_DIR}/..."
258
+ // expansions. Normalize to POSIX separators so bash sees a stable
259
+ // path regardless of host platform.
260
+ HARNESS_DAG_RUN_DIR: input.dagRunMeta.runDir.replaceAll(path.sep, "/"),
261
+ HARNESS_DAG_RUN_ID: input.dagRunMeta.runId,
262
+ }
263
+ : undefined;
264
+ const child = spawn(resolveBashExecutable(), ["-c", input.command], {
265
+ cwd: input.cwd,
266
+ env: buildShellProcessEnv(input.envAllowlist, injectedEnv),
267
+ ...processTreeSpawnOptions(),
268
+ stdio: ["ignore", "pipe", "pipe"],
269
+ });
270
+ let stdout = createBoundedOutput();
271
+ let stderr = createBoundedOutput();
272
+ let timedOut = false;
273
+ let settled = false;
274
+ let timeoutHandle;
275
+ let sigkillHandle;
276
+ function finish(result) {
277
+ if (settled)
278
+ return;
279
+ settled = true;
280
+ if (timeoutHandle)
281
+ clearTimeout(timeoutHandle);
282
+ if (sigkillHandle)
283
+ clearTimeout(sigkillHandle);
284
+ resolve({
285
+ command: input.command,
286
+ cwd: input.cwd,
287
+ durationMs: Date.now() - startedAt,
288
+ wallDurationMs: Date.now() - startedAt,
289
+ startedAt: startedAtIso,
290
+ finishedAt: new Date().toISOString(),
291
+ stdout: formatBoundedOutput(stdout),
292
+ stdoutBytes: stdout.bytes,
293
+ stdoutTruncated: stdout.truncated,
294
+ stderr: formatBoundedOutput(stderr),
295
+ stderrBytes: stderr.bytes,
296
+ stderrTruncated: stderr.truncated,
297
+ ...(input.outputArtifacts
298
+ ? {
299
+ stdoutArtifactPath: input.outputArtifacts.stdoutPath,
300
+ stderrArtifactPath: input.outputArtifacts.stderrPath,
301
+ }
302
+ : {}),
303
+ timedOut,
304
+ ...result,
305
+ });
306
+ }
307
+ child.stdout.setEncoding("utf-8");
308
+ child.stderr.setEncoding("utf-8");
309
+ initializeOutputArtifacts(input.outputArtifacts);
310
+ child.stdout.on("data", (chunk) => {
311
+ stdout = appendBoundedOutput(stdout, chunk);
312
+ appendOutputArtifact(input.outputArtifacts?.stdoutPath, chunk);
313
+ reportOutputActivity();
314
+ });
315
+ child.stderr.on("data", (chunk) => {
316
+ stderr = appendBoundedOutput(stderr, chunk);
317
+ appendOutputArtifact(input.outputArtifacts?.stderrPath, chunk);
318
+ reportOutputActivity();
319
+ });
320
+ child.on("error", (error) => {
321
+ stderr = appendBoundedOutput(stderr, `${stderr.text ? "\n" : ""}${error.message}`);
322
+ appendOutputArtifact(input.outputArtifacts?.stderrPath, error.message);
323
+ finish({
324
+ exitCode: null,
325
+ failureCategory: "spawn-error",
326
+ ok: false,
327
+ });
328
+ });
329
+ child.on("close", (code) => {
330
+ let failureCategory = "nonzero-exit";
331
+ if (timedOut) {
332
+ failureCategory = "timeout";
333
+ }
334
+ else if (code === 0) {
335
+ failureCategory = "success";
336
+ }
337
+ finish({
338
+ exitCode: code,
339
+ failureCategory,
340
+ ok: code === 0 && !timedOut,
341
+ });
342
+ });
343
+ if (input.timeoutMs > 0) {
344
+ timeoutHandle = setTimeout(() => {
345
+ timedOut = true;
346
+ terminateProcessTree(child, "SIGTERM");
347
+ sigkillHandle = setTimeout(() => {
348
+ if (child.exitCode === null) {
349
+ terminateProcessTree(child, "SIGKILL");
350
+ }
351
+ }, 5_000);
352
+ }, input.timeoutMs);
353
+ }
354
+ });
355
+ }
356
+ function createBoundedOutput() {
357
+ return { bytes: 0, text: "", truncated: false };
358
+ }
359
+ function appendBoundedOutput(current, chunk) {
360
+ const bytes = current.bytes + Buffer.byteLength(chunk);
361
+ const combined = current.text + chunk;
362
+ if (Buffer.byteLength(combined) <= MAX_BUFFERED_OUTPUT_BYTES) {
363
+ return { bytes, text: combined, truncated: current.truncated };
364
+ }
365
+ const tail = Buffer.from(combined)
366
+ .subarray(-MAX_BUFFERED_OUTPUT_BYTES)
367
+ .toString("utf-8")
368
+ .replace(/^\uFFFD/, "");
369
+ return { bytes, text: tail, truncated: true };
370
+ }
371
+ function formatBoundedOutput(output) {
372
+ return output.truncated ? `${output.text}\n...[truncated]` : output.text;
373
+ }
374
+ function initializeOutputArtifacts(paths) {
375
+ if (!paths)
376
+ return;
377
+ try {
378
+ mkdirSync(path.dirname(paths.stdoutPath), { recursive: true });
379
+ writeFileSync(paths.stdoutPath, "", "utf-8");
380
+ writeFileSync(paths.stderrPath, "", "utf-8");
381
+ }
382
+ catch {
383
+ // Artifact persistence is best-effort; the bounded result remains available.
384
+ }
385
+ }
386
+ function appendOutputArtifact(filePath, chunk) {
387
+ if (!filePath)
388
+ return;
389
+ try {
390
+ appendFileSync(filePath, chunk, "utf-8");
391
+ }
392
+ catch {
393
+ // Keep command execution independent from a transient artifact write failure.
394
+ }
395
+ }
396
+ function summarizeCommandResults(results) {
397
+ const stdout = results
398
+ .map((result) => [`$ ${result.command}`, result.stdout.trim()].filter(Boolean).join("\n"))
399
+ .join("\n\n");
400
+ const stderr = results
401
+ .filter((result) => result.stderr.trim().length > 0)
402
+ .map((result) => [`$ ${result.command}`, result.stderr.trim()].join("\n"))
403
+ .join("\n\n");
404
+ return {
405
+ stdout: truncateOutput(stdout, NODE_STDOUT_MAX).text,
406
+ stderr: truncateOutput(stderr, NODE_STDERR_MAX).text,
407
+ };
408
+ }
409
+ function parseCheckRepoSubcheckDurations(result) {
410
+ if (!/scripts\/check-repo\.sh/.test(result.command))
411
+ return [];
412
+ const durations = [];
413
+ for (const line of `${result.stdout}\n${result.stderr}`.split(/\r?\n/)) {
414
+ const match = /^CHECK_REPO_SUBCHECK_END name=(\S+) status=(success|failed) durationMs=(\d+)$/.exec(line.trim());
415
+ if (match)
416
+ durations.push({ name: match[1], status: match[2], durationMs: Number(match[3]) });
417
+ }
418
+ return durations;
419
+ }
420
+ export function buildShellResultSummaryMarkdown(input) {
421
+ const lines = [
422
+ "# Shell execution summary",
423
+ "",
424
+ "## Run metadata",
425
+ "",
426
+ "| Field | Value |",
427
+ "|-------|-------|",
428
+ `| dagRunId | ${input.runId} |`,
429
+ `| dagNodeId | ${input.nodeId} |`,
430
+ `| cwd | ${input.rootCwd} |`,
431
+ `| commandCount | ${input.results.length} |`,
432
+ "",
433
+ "## Commands",
434
+ "",
435
+ "| # | ok | exitCode | failureCategory | startedAt | finishedAt | wallDurationMs | executionDurationMs | reused | cwd | command |",
436
+ "|---|----|----------|-----------------|-----------|------------|----------------|---------------------|--------|-----|---------|",
437
+ ];
438
+ input.results.forEach((result, index) => {
439
+ lines.push(`| ${index + 1} | ${result.ok} | ${result.exitCode ?? "null"} | ${result.failureCategory} | ${result.startedAt ?? "(unknown)"} | ${result.finishedAt ?? "(unknown)"} | ${result.wallDurationMs ?? result.durationMs} | ${result.executionDurationMs ?? result.durationMs} | ${result.reused === true} | ${result.cwd} | ${result.command.replace(/\|/g, "\\|")} |`);
440
+ });
441
+ if (input.writeGuardViolations && input.writeGuardViolations.length > 0) {
442
+ lines.push("", "## Write guard violations", "");
443
+ for (const violation of input.writeGuardViolations) {
444
+ lines.push(`- ${violation}`);
445
+ }
446
+ }
447
+ for (const [index, result] of input.results.entries()) {
448
+ const stdout = truncateOutput(result.stdout, SUMMARY_STDOUT_MAX);
449
+ const stderr = truncateOutput(result.stderr, SUMMARY_STDERR_MAX);
450
+ lines.push("", `## Command ${index + 1}: ${result.command}`, "", "### stdout", "", "```text", stdout.text, "```");
451
+ if (stdout.truncated)
452
+ lines.push("", "_(stdout truncated)_");
453
+ if (result.stdoutArtifactPath) {
454
+ lines.push("", `Full stdout artifact: ${result.stdoutArtifactPath}`);
455
+ }
456
+ if (stderr.text.trim()) {
457
+ lines.push("", "### stderr", "", "```text", stderr.text, "```");
458
+ if (stderr.truncated)
459
+ lines.push("", "_(stderr truncated)_");
460
+ }
461
+ if (result.stderrArtifactPath) {
462
+ lines.push("", `Full stderr artifact: ${result.stderrArtifactPath}`);
463
+ }
464
+ }
465
+ return `${lines.join("\n")}\n`;
466
+ }
467
+ async function runShellWriteGuard(input) {
468
+ const afterStatus = await readGitStatusPorcelain(input.rootCwd);
469
+ const beforeSnapshot = snapshotGitStatusPorcelain(input.beforeStatus);
470
+ const afterSnapshot = snapshotGitStatusPorcelain(afterStatus);
471
+ const changedPaths = pathsChangedDuringRun(beforeSnapshot, afterSnapshot);
472
+ return validateShellWriteGuard({
473
+ changedPaths,
474
+ writePolicy: input.task.writePolicy,
475
+ writeSet: input.task.writeSet,
476
+ allowedPaths: input.task.allowedPaths,
477
+ forbiddenPaths: input.task.forbiddenPaths,
478
+ });
479
+ }
480
+ async function readRequiredRunReport(reportsDir, filename) {
481
+ try {
482
+ return await readFile(path.join(reportsDir, filename), "utf8");
483
+ }
484
+ catch {
485
+ throw new Error(`missing required upstream report: reports/${filename}`);
486
+ }
487
+ }
488
+ async function readAdvisoryRunReport(reportsDir, filename, title) {
489
+ try {
490
+ return await readFile(path.join(reportsDir, filename), "utf8");
491
+ }
492
+ catch {
493
+ return `# ${title}\n\n## Status\n\nUNAVAILABLE\n\n## Findings\n\n- Missing advisory upstream report: reports/${filename}\n`;
494
+ }
495
+ }
496
+ async function executeBackendTestPipeline(input, meta) {
497
+ const pipeline = input.task.shell?.backendTestPipeline;
498
+ const started = Date.now();
499
+ // Plan A: prefer the frozen layout carried by the compiled spec; fall back
500
+ // to the historical default for hand-written legacy DAGs.
501
+ const backendLayout = resolveBackendTestLayout(meta.spec.backendTestLayout
502
+ ? {
503
+ testRoot: meta.spec.backendTestLayout.testRoot,
504
+ markdownRel: meta.spec.backendTestLayout.markdownRel,
505
+ scriptRel: meta.spec.backendTestLayout.scriptRel,
506
+ }
507
+ : undefined);
508
+ try {
509
+ if (!pipeline)
510
+ throw new Error("missing backend-test pipeline id");
511
+ const outputs = [];
512
+ if (pipeline === "ingest-backend-test-gap") {
513
+ // Plan D: deterministic gap ingest. Identity (taskId + requirement
514
+ // sha256) is fail-closed; a mismatched plan blocks every writer.
515
+ const gapDocRel = meta.spec.globalConstraints
516
+ ?.map((item) => /^backendTestGapDoc=(.+)$/i.exec(item.trim())?.[1])
517
+ .find(Boolean);
518
+ const binding = meta.spec.sourceBinding;
519
+ const requirementSha256 = binding?.sources.find((s) => s.kind === "requirement")?.sha256 ?? "";
520
+ if (!gapDocRel || !binding || !requirementSha256) {
521
+ return {
522
+ ok: false,
523
+ stdout: outputs.join("\n"),
524
+ stderr: "backend-test gap ingest requires the bound gap document path and the task source binding with a requirement sha256",
525
+ failureCategory: "invalid-output",
526
+ durationMs: Date.now() - started,
527
+ };
528
+ }
529
+ const gapDocAbs = path.join(input.cwd, ...gapDocRel.split("/"));
530
+ let parsed;
531
+ try {
532
+ parsed = JSON.parse(await readFile(gapDocAbs, "utf8"));
533
+ }
534
+ catch (error) {
535
+ return {
536
+ ok: false,
537
+ stdout: outputs.join("\n"),
538
+ stderr: `backend-test gap document is unreadable JSON at ${gapDocRel}: ${error instanceof Error ? error.message : String(error)}`,
539
+ failureCategory: "invalid-output",
540
+ durationMs: Date.now() - started,
541
+ };
542
+ }
543
+ const doc = backendTestGapDocSchema.safeParse(parsed);
544
+ if (!doc.success) {
545
+ return {
546
+ ok: false,
547
+ stdout: outputs.join("\n"),
548
+ stderr: `invalid backend-test-gap-v1 document: ${doc.error.issues
549
+ .map((issue) => `${issue.path.join(".")}: ${issue.message}`)
550
+ .join("; ")}`,
551
+ failureCategory: "invalid-output",
552
+ durationMs: Date.now() - started,
553
+ };
554
+ }
555
+ // previous N6 facts (may be absent on a fresh checkout)
556
+ const factsPath = path.join(input.cwd, "contracts", "backend-test-case-coverage-facts.json");
557
+ let previousMissingSlots;
558
+ let previousCoveredSlots;
559
+ try {
560
+ const facts = JSON.parse(await readFile(factsPath, "utf8"));
561
+ previousMissingSlots = facts.scenarioPartitions?.missingSlots;
562
+ previousCoveredSlots = (facts.rules ?? []).flatMap((rule) => rule.coveredTestPoints ?? []);
563
+ }
564
+ catch {
565
+ // no previous facts in the workspace: the gap doc alone drives the plan
566
+ }
567
+ // module index comes from the README on disk
568
+ let existingModuleStems = [];
569
+ try {
570
+ const files = await markdownFiles(input.cwd, backendLayout);
571
+ existingModuleStems = files
572
+ .map((file) => path.basename(file, ".md"))
573
+ .filter((stem) => stem.toLowerCase() !== "readme");
574
+ }
575
+ catch {
576
+ // no markdown tree: every slot becomes a conflict
577
+ }
578
+ const plan = ingestBackendTestGap({
579
+ gapDoc: doc.data,
580
+ previousMissingSlots,
581
+ previousCoveredSlots,
582
+ existingModuleStems,
583
+ sharedSetupPath: meta.spec.backendTestSharedSetup?.path ?? null,
584
+ layout: backendLayout,
585
+ taskId: binding.taskId,
586
+ requirementSha256,
587
+ });
588
+ const contractsDir = path.join(meta.runDir, "contracts");
589
+ await mkdir(contractsDir, { recursive: true });
590
+ const planPath = path.join(contractsDir, "backend-test-gap-plan-v1.json");
591
+ await writeFile(planPath, JSON.stringify(plan, null, 2), "utf8");
592
+ outputs.push(`gapPlan=${planPath}`, `gapPlanStatus=${plan.status}`);
593
+ if (plan.status === "error") {
594
+ return {
595
+ ok: false,
596
+ stdout: outputs.join("\n"),
597
+ stderr: [
598
+ "backend-test gap ingest failed closed (identity/conflicts):",
599
+ ...plan.conflicts,
600
+ ].join("\n"),
601
+ failureCategory: "invalid-output",
602
+ durationMs: Date.now() - started,
603
+ };
604
+ }
605
+ }
606
+ else if (pipeline === "markdown-environment") {
607
+ const results = await executePipelineCommands(input, meta);
608
+ if (!results.every((result) => result.ok)) {
609
+ const failure = results.find((result) => !result.ok);
610
+ return {
611
+ ok: false,
612
+ stdout: results.map((result) => result.stdout).join("\n"),
613
+ stderr: failure.stderr,
614
+ failureCategory: failure.failureCategory,
615
+ durationMs: Date.now() - started,
616
+ };
617
+ }
618
+ const environment = await inspectBackendTestEnvironment({
619
+ workspaceRoot: input.cwd,
620
+ pythonVersion: results[0]?.stdout ?? "unknown",
621
+ pytestVersion: results[1]?.stdout ?? "unknown",
622
+ pytestHelp: results[2]?.stdout ?? "",
623
+ });
624
+ const reportPath = await writeRunReport(meta.runDir, "backend-test-environment.md", environment.markdown);
625
+ outputs.push(`environment=${reportPath}`, environment.markdown);
626
+ }
627
+ else if (pipeline === "markdown-cases") {
628
+ const environmentMarkdown = await readFile(path.join(meta.runDir, "reports", "backend-test-environment.md"), "utf8");
629
+ const requiredRequirementIds = requiredBackendMarkdownCaseAcIds([
630
+ meta.spec.objective ?? "",
631
+ ...(meta.spec.successCriteria ?? []),
632
+ ]);
633
+ let report;
634
+ try {
635
+ report = await validateBackendMarkdownCases({
636
+ workspaceRoot: input.cwd,
637
+ environmentMarkdown,
638
+ requiredRequirementIds,
639
+ sourceBinding: meta.spec.sourceBinding,
640
+ layout: backendLayout,
641
+ sharedSetup: meta.spec.backendTestSharedSetup ?? null,
642
+ });
643
+ }
644
+ catch (error) {
645
+ report = `# Backend Markdown Case Validation\n\n## Status\n\nFAIL\n\n## Findings\n\n- Validator could not complete: ${error instanceof Error ? error.message : String(error)}\n`;
646
+ }
647
+ const reportPath = await writeRunReport(meta.runDir, "backend-md-case-validation.md", report);
648
+ outputs.push(`caseValidation=${reportPath}`, report);
649
+ if (hasBlockingBackendMarkdownSafetyFindings(report)) {
650
+ return {
651
+ ok: false,
652
+ stdout: outputs.join("\n"),
653
+ stderr: "backend Markdown safety gate blocked: secret-shaped values detected",
654
+ failureCategory: "invalid-output",
655
+ durationMs: Date.now() - started,
656
+ };
657
+ }
658
+ if (hasBlockingBackendMarkdownModuleStemFindings(report)) {
659
+ return {
660
+ ok: false,
661
+ stdout: outputs.join("\n"),
662
+ stderr: "backend Markdown module stem gate blocked: priority-only-module-stem",
663
+ failureCategory: "invalid-output",
664
+ durationMs: Date.now() - started,
665
+ };
666
+ }
667
+ const sourceBinding = meta.spec.sourceBinding;
668
+ if (!sourceBinding) {
669
+ const coverage = "# Backend Test Case Coverage Analysis\n\n## Status\n\nUNAVAILABLE\n\n## Findings\n\n- Coverage analysis requires spec.sourceBinding; node 4 and node 7 fail closed on the same task/source binding contract.\n";
670
+ const coveragePath = await writeRunReport(meta.runDir, "backend-test-case-coverage-analysis.md", coverage);
671
+ outputs.push(`caseCoverage=${coveragePath}`, coverage);
672
+ return {
673
+ ok: false,
674
+ stdout: outputs.join("\n\n"),
675
+ stderr: "backend-test markdown-cases requires spec.sourceBinding",
676
+ failureCategory: "invalid-output",
677
+ durationMs: Date.now() - started,
678
+ };
679
+ }
680
+ const normalizedBinding = {
681
+ taskId: sourceBinding.taskId,
682
+ requirementPath: sourceBinding.sources.find((s) => s.kind === "requirement")?.path ??
683
+ "source/requirement.md",
684
+ requirementSha256: sourceBinding.sources.find((s) => s.kind === "requirement")?.sha256 ??
685
+ "0".repeat(64),
686
+ referencePaths: sourceBinding.sources
687
+ .filter((s) => s.kind === "reference")
688
+ .map((s) => s.path),
689
+ requirementIds: sourceBinding.requirementIds,
690
+ };
691
+ try {
692
+ const coverage = await analyzeBackendTestCaseCoverage({
693
+ workspaceRoot: input.cwd,
694
+ sourceBinding: normalizedBinding,
695
+ layout: backendLayout,
696
+ });
697
+ const coveragePath = await writeRunReport(meta.runDir, "backend-test-case-coverage-analysis.md", coverage.markdown);
698
+ const contractsDir = path.join(meta.runDir, "contracts");
699
+ await mkdir(contractsDir, { recursive: true });
700
+ const factsPath = path.join(contractsDir, "backend-test-case-coverage-facts.json");
701
+ await writeFile(factsPath, JSON.stringify(coverage.facts, null, 2), "utf8");
702
+ outputs.push(`caseCoverage=${coveragePath}`, `caseCoverageFacts=${factsPath}`, coverage.markdown);
703
+ // Plan C: scenario-partition slot coverage is fail-closed. General
704
+ // coverage findings stay advisory, but a declared filter axis missing
705
+ // an each-value/omitted slot or its complement slot must block pytest
706
+ // generation until the Markdown declares the slot.
707
+ const partitionBlocking = coverage.facts.findings.filter((finding) => /^(?:MISSING_PARTITION|MISSING_PARTITION_SLOT|MISSING_COMPLEMENT_SLOT|MISSING_PARTITION_TABLE)/.test(finding));
708
+ if (partitionBlocking.length > 0) {
709
+ return {
710
+ ok: false,
711
+ stdout: outputs.join("\n\n"),
712
+ stderr: [
713
+ "backend-test scenario partitions are incomplete (fail-closed):",
714
+ ...partitionBlocking,
715
+ ].join("\n"),
716
+ failureCategory: "invalid-output",
717
+ durationMs: Date.now() - started,
718
+ };
719
+ }
720
+ }
721
+ catch (error) {
722
+ const message = error instanceof Error ? error.message : String(error);
723
+ const coverage = `# Backend Test Case Coverage Analysis\n\n## Status\n\nUNAVAILABLE\n\n## Findings\n\n- Coverage analysis crashed and is treated as an infrastructure failure: ${message}\n`;
724
+ const coveragePath = await writeRunReport(meta.runDir, "backend-test-case-coverage-analysis.md", coverage);
725
+ outputs.push(`caseCoverage=${coveragePath}`, coverage);
726
+ return {
727
+ ok: false,
728
+ stdout: outputs.join("\n\n"),
729
+ stderr: `backend-test coverage analysis crashed: ${message}`,
730
+ failureCategory: "invalid-output",
731
+ durationMs: Date.now() - started,
732
+ };
733
+ }
734
+ }
735
+ else if (pipeline === "markdown-collection-assess") {
736
+ const resolution = await resolveBackendTestMappedPytestScripts(input.cwd, backendLayout);
737
+ let facts;
738
+ const preflightInventory = await buildBackendPytestAssetInventory(input.cwd, resolution.existingScripts, { requireMappedScripts: false, layout: backendLayout });
739
+ const priorityOnlyFacts = assessPriorityOnlyBackendPytestModules({
740
+ phase: "initial",
741
+ mappedScripts: resolution.expectedScripts,
742
+ inventory: preflightInventory,
743
+ });
744
+ if (priorityOnlyFacts) {
745
+ facts = priorityOnlyFacts;
746
+ }
747
+ else if (resolution.missingScripts.length > 0) {
748
+ const inventory = await buildBackendPytestAssetInventory(input.cwd, resolution.existingScripts, { requireMappedScripts: false, layout: backendLayout });
749
+ facts = assessMissingBackendPytestScripts({
750
+ mappedScripts: resolution.expectedScripts,
751
+ existingMappedScripts: resolution.existingScripts,
752
+ missingMappedScripts: resolution.missingScripts,
753
+ assetFiles: inventory.assetFiles,
754
+ inputHashes: inventory.inputHashes,
755
+ });
756
+ }
757
+ else {
758
+ const mappedScripts = resolution.existingScripts;
759
+ const scenario = await runBackendScenarioParamPreflight({
760
+ workspaceRoot: input.cwd,
761
+ runDir: meta.runDir,
762
+ layout: backendLayout,
763
+ strictScenarioParamGate: Boolean((meta.spec.globalConstraints ?? []).some((item) => /strictScenarioParamGate\s*=\s*true/i.test(item))),
764
+ });
765
+ outputs.push(...scenario.outputs);
766
+ const inventory = await buildBackendPytestAssetInventory(input.cwd, mappedScripts, { layout: backendLayout });
767
+ const shellQuote = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
768
+ const targets = mappedScripts.map(shellQuote).join(" ");
769
+ const command = `PYTHONDONTWRITEBYTECODE=1 python -m pytest --collect-only -q -p no:cacheprovider ${targets}`;
770
+ const [result] = await executePipelineCommands(input, meta, [command]);
771
+ if (!result)
772
+ throw new Error("backend pytest collection command did not produce a result");
773
+ if (["spawn-error", "timeout", "termination-unconfirmed"].includes(result.failureCategory ?? "")) {
774
+ return {
775
+ ok: false,
776
+ stdout: result.stdout,
777
+ stderr: result.stderr || "backend pytest collection could not start",
778
+ failureCategory: result.failureCategory,
779
+ durationMs: Date.now() - started,
780
+ };
781
+ }
782
+ let fixtureResolution;
783
+ if ((result.exitCode ?? 2) === 0) {
784
+ const fixtureCommand = `PYTHONDONTWRITEBYTECODE=1 python -m pytest --setup-plan -q -p no:cacheprovider ${targets}`;
785
+ const [fixtureResult] = await executePipelineCommands(input, meta, [fixtureCommand]);
786
+ if (!fixtureResult)
787
+ throw new Error("backend pytest fixture-resolution command did not produce a result");
788
+ if (["spawn-error", "timeout", "termination-unconfirmed"].includes(fixtureResult.failureCategory ?? "")) {
789
+ return {
790
+ ok: false,
791
+ stdout: fixtureResult.stdout,
792
+ stderr: fixtureResult.stderr || "backend pytest fixture resolution could not start",
793
+ failureCategory: fixtureResult.failureCategory,
794
+ durationMs: Date.now() - started,
795
+ };
796
+ }
797
+ fixtureResolution = { exitCode: fixtureResult.exitCode ?? 2, stdout: fixtureResult.stdout, stderr: fixtureResult.stderr };
798
+ }
799
+ facts = assessBackendPytestCollection({
800
+ layout: backendLayout,
801
+ phase: "initial",
802
+ mappedScripts,
803
+ inventory,
804
+ exitCode: result.exitCode ?? 2,
805
+ stdout: result.stdout,
806
+ stderr: result.stderr,
807
+ fixtureResolution,
808
+ });
809
+ }
810
+ try {
811
+ const correspondence = await analyzeBackendTestMarkdownPytestCorrespondence({
812
+ workspaceRoot: input.cwd,
813
+ layout: backendLayout,
814
+ taskId: meta.spec.sourceBinding?.taskId ?? "unbound",
815
+ });
816
+ const correspondenceReportPath = await writeRunReport(meta.runDir, "backend-test-markdown-pytest-correspondence-initial.md", correspondence.markdown);
817
+ const contractsDir = path.join(meta.runDir, "contracts");
818
+ await mkdir(contractsDir, { recursive: true });
819
+ const correspondenceFactsPath = path.join(contractsDir, "backend-test-markdown-pytest-correspondence-initial.json");
820
+ await writeFile(correspondenceFactsPath, JSON.stringify(correspondence.facts, null, 2), "utf8");
821
+ outputs.push(`initialCorrespondence=${correspondenceReportPath}`, `initialCorrespondenceFacts=${correspondenceFactsPath}`);
822
+ if (correspondence.facts.status === "FAIL" && facts.status === "PASS") {
823
+ const correspondenceRepairPaths = Array.from(new Set(correspondence.facts.entries
824
+ .filter((entry) => entry.status !== "EXACT_1_TO_1")
825
+ .flatMap((entry) => [entry.expectedScript, ...entry.actualScripts])
826
+ .filter((candidate) => candidate.startsWith("testcase/") && candidate.endsWith(".py")))).sort();
827
+ if (correspondenceRepairPaths.length > 0) {
828
+ facts.status = "REPAIRABLE";
829
+ facts.repairEligible = true;
830
+ facts.repairPaths = Array.from(new Set([...facts.repairPaths, ...correspondenceRepairPaths])).sort();
831
+ facts.findings.push({
832
+ kind: "markdown-pytest-correspondence",
833
+ classification: "test-asset-defect",
834
+ repairability: "repairable",
835
+ detail: correspondence.facts.entries
836
+ .filter((entry) => entry.status !== "EXACT_1_TO_1")
837
+ .flatMap((entry) => entry.findings)
838
+ .join("; ")
839
+ .slice(0, 12_000) || "Markdown-to-pytest correspondence is incomplete",
840
+ });
841
+ }
842
+ }
843
+ }
844
+ catch (error) {
845
+ outputs.push(`initialCorrespondence=UNAVAILABLE: ${error instanceof Error ? error.message : String(error)}`);
846
+ }
847
+ const artifacts = await writeBackendPytestCollectionArtifacts({
848
+ runDir: meta.runDir,
849
+ stem: "initial",
850
+ facts,
851
+ });
852
+ outputs.push(`collectionFacts=${artifacts.factsPath}`, `collectionReport=${artifacts.reportPath}`);
853
+ return {
854
+ ok: true,
855
+ stdout: JSON.stringify({
856
+ status: facts.status,
857
+ repairEligible: facts.repairEligible,
858
+ collectionAttempted: facts.collectionAttempted,
859
+ pytestExitCode: facts.pytestExitCode,
860
+ fixtureResolutionStatus: facts.fixtureResolutionStatus,
861
+ collectedItemCount: facts.collectedItemCount,
862
+ missingMappedScripts: facts.missingMappedScripts,
863
+ repairPaths: facts.repairPaths,
864
+ factsPath: "contracts/backend-test-pytest-collection-initial.json",
865
+ }),
866
+ stderr: "",
867
+ durationMs: Date.now() - started,
868
+ };
869
+ }
870
+ else if (pipeline === "markdown-collection-effective") {
871
+ const initial = await readBackendPytestCollectionFacts(path.join(meta.runDir, "contracts", "backend-test-pytest-collection-initial.json"));
872
+ let effective;
873
+ if (initial.status === "PASS") {
874
+ effective = await materializeEffectiveBackendPytestCollection({
875
+ workspaceRoot: input.cwd,
876
+ initial,
877
+ });
878
+ }
879
+ else if (initial.status === "REPAIRABLE") {
880
+ const mappedScripts = await collectBackendTestMappedPytestScripts(input.cwd);
881
+ const scenario = await runBackendScenarioParamPreflight({
882
+ workspaceRoot: input.cwd,
883
+ runDir: meta.runDir,
884
+ layout: backendLayout,
885
+ strictScenarioParamGate: Boolean((meta.spec.globalConstraints ?? []).some((item) => /strictScenarioParamGate\s*=\s*true/i.test(item))),
886
+ });
887
+ outputs.push(...scenario.outputs);
888
+ const inventory = await buildBackendPytestAssetInventory(input.cwd, mappedScripts, { layout: backendLayout });
889
+ const shellQuote = (value) => `'${value.replaceAll("'", `'"'"'`)}'`;
890
+ const targets = mappedScripts.map(shellQuote).join(" ");
891
+ const command = `PYTHONDONTWRITEBYTECODE=1 python -m pytest --collect-only -q -p no:cacheprovider ${targets}`;
892
+ const [result] = await executePipelineCommands(input, meta, [command]);
893
+ if (!result)
894
+ throw new Error("backend pytest final collection command did not produce a result");
895
+ if (["spawn-error", "timeout", "termination-unconfirmed"].includes(result.failureCategory ?? "")) {
896
+ return {
897
+ ok: false,
898
+ stdout: result.stdout,
899
+ stderr: result.stderr ||
900
+ "backend pytest final collection could not start",
901
+ failureCategory: result.failureCategory,
902
+ durationMs: Date.now() - started,
903
+ };
904
+ }
905
+ let fixtureResolution;
906
+ if ((result.exitCode ?? 2) === 0) {
907
+ const fixtureCommand = `PYTHONDONTWRITEBYTECODE=1 python -m pytest --setup-plan -q -p no:cacheprovider ${targets}`;
908
+ const [fixtureResult] = await executePipelineCommands(input, meta, [fixtureCommand]);
909
+ if (!fixtureResult)
910
+ throw new Error("backend pytest final fixture-resolution command did not produce a result");
911
+ if (["spawn-error", "timeout", "termination-unconfirmed"].includes(fixtureResult.failureCategory ?? "")) {
912
+ return {
913
+ ok: false,
914
+ stdout: fixtureResult.stdout,
915
+ stderr: fixtureResult.stderr || "backend pytest final fixture resolution could not start",
916
+ failureCategory: fixtureResult.failureCategory,
917
+ durationMs: Date.now() - started,
918
+ };
919
+ }
920
+ fixtureResolution = { exitCode: fixtureResult.exitCode ?? 2, stdout: fixtureResult.stdout, stderr: fixtureResult.stderr };
921
+ }
922
+ const finalFacts = assessBackendPytestCollection({
923
+ layout: backendLayout,
924
+ phase: "final",
925
+ mappedScripts,
926
+ inventory,
927
+ exitCode: result.exitCode ?? 2,
928
+ stdout: result.stdout,
929
+ stderr: result.stderr,
930
+ fixtureResolution,
931
+ });
932
+ effective = await materializeEffectiveBackendPytestCollection({
933
+ workspaceRoot: input.cwd,
934
+ initial,
935
+ final: finalFacts,
936
+ });
937
+ }
938
+ else {
939
+ throw new Error("backend pytest collection is blocked and does not authorize repair");
940
+ }
941
+ const artifacts = await writeBackendPytestCollectionArtifacts({
942
+ runDir: meta.runDir,
943
+ stem: "effective",
944
+ facts: effective,
945
+ });
946
+ const scenarioFacts = await readBackendScenarioParamFacts(path.join(meta.runDir, "contracts", "backend-test-scenario-param-consistency-facts.json"));
947
+ const correspondence = meta.spec.sourceBinding
948
+ ? await analyzeBackendTestMarkdownPytestCorrespondence({
949
+ workspaceRoot: input.cwd,
950
+ layout: backendLayout,
951
+ taskId: meta.spec.sourceBinding.taskId,
952
+ })
953
+ : undefined;
954
+ const eligibility = correspondence
955
+ ? buildBackendTestItemEligibility(effective.collectedItemIds, {
956
+ correspondenceEntries: correspondence.facts.entries,
957
+ scenarioEntries: scenarioFacts.entries,
958
+ })
959
+ : { eligibleItemIds: effective.collectedItemIds, excludedItems: [] };
960
+ const eligibilityInputHashes = Object.fromEntries((correspondence?.facts.inputFiles ?? []).map((item) => [item.path, item.sha256]));
961
+ const eligibilityFactsPath = path.join(meta.runDir, "contracts", "backend-test-execution-eligibility.json");
962
+ await mkdir(path.dirname(eligibilityFactsPath), { recursive: true });
963
+ await writeFile(eligibilityFactsPath, `${JSON.stringify({
964
+ schemaId: "backend-test-execution-eligibility-v1",
965
+ status: eligibility.eligibleItemIds.length === 0 ? "BLOCKED" : eligibility.excludedItems.length > 0 ? "PARTIAL" : "PASS",
966
+ collectedItemCount: effective.collectedItemIds.length,
967
+ eligibleItemCount: eligibility.eligibleItemIds.length,
968
+ excludedItemCount: eligibility.excludedItems.length,
969
+ eligibleItemIds: eligibility.eligibleItemIds,
970
+ excludedItems: eligibility.excludedItems,
971
+ inputHashes: eligibilityInputHashes,
972
+ }, null, 2)}\n`, "utf8");
973
+ const eligibilityReportPath = await writeRunReport(meta.runDir, "backend-test-execution-eligibility.md", [
974
+ "# Backend Test Execution Eligibility",
975
+ "",
976
+ "## Status",
977
+ "",
978
+ eligibility.eligibleItemIds.length === 0 ? "BLOCKED" : eligibility.excludedItems.length > 0 ? "PARTIAL" : "PASS",
979
+ "",
980
+ `- Collected items: ${effective.collectedItemIds.length}`,
981
+ `- Eligible items: ${eligibility.eligibleItemIds.length}`,
982
+ `- Excluded items: ${eligibility.excludedItems.length}`,
983
+ "",
984
+ "## Excluded Items",
985
+ "",
986
+ ...(eligibility.excludedItems.length > 0 ? eligibility.excludedItems.map((item) => `- \`${item.itemId}\` (${item.caseId ?? "unknown Case"}): ${item.reasons.join("; ")}`) : ["- None"]),
987
+ "",
988
+ ].join("\n"));
989
+ outputs.push(`executionEligibility=${eligibilityReportPath}`, `executionEligibilityFacts=${eligibilityFactsPath}`);
990
+ const readiness = await materializeBackendTestExecutionReadiness({
991
+ runDir: meta.runDir,
992
+ workspaceRoot: input.cwd,
993
+ effective,
994
+ scenarioParamStatus: scenarioFacts.overallStatus,
995
+ scenarioParamRepairAttempt: scenarioFacts.repairAttempt,
996
+ eligibility,
997
+ eligibilityInputHashes,
998
+ });
999
+ return {
1000
+ ok: true,
1001
+ stdout: JSON.stringify({
1002
+ status: effective.status,
1003
+ collectionSource: effective.collectionSource,
1004
+ repairAttempt: effective.repairAttempt,
1005
+ collectedItemCount: effective.collectedItemCount,
1006
+ fixtureResolutionStatus: effective.fixtureResolutionStatus,
1007
+ executionReadinessStatus: readiness.status,
1008
+ eligibleItemCount: readiness.eligibleItemIds.length,
1009
+ excludedItemCount: readiness.excludedItems.length,
1010
+ factsPath: "contracts/backend-test-pytest-collection-effective.json",
1011
+ readinessPath: "contracts/backend-test-execution-readiness.json",
1012
+ reportPath: artifacts.reportPath,
1013
+ }),
1014
+ stderr: "",
1015
+ durationMs: Date.now() - started,
1016
+ };
1017
+ }
1018
+ else if (pipeline === "markdown-traceability") {
1019
+ let report;
1020
+ try {
1021
+ report = await validateBackendMarkdownTraceability(input.cwd);
1022
+ }
1023
+ catch (error) {
1024
+ report = `# Backend Test Traceability\n\n## Status\n\nFAIL\n\n## Findings\n\n- Traceability validation could not complete: ${error instanceof Error ? error.message : String(error)}\n`;
1025
+ }
1026
+ const reportPath = await writeRunReport(meta.runDir, "backend-test-traceability.md", report);
1027
+ outputs.push(`traceability=${reportPath}`, report);
1028
+ const sourceBinding = meta.spec.sourceBinding;
1029
+ if (!sourceBinding) {
1030
+ const correspondence = "# Backend Test Markdown → pytest Correspondence\n\n## Status\n\nUNAVAILABLE\n\n## Findings\n\n- Correspondence analysis requires spec.sourceBinding; nodes 4, 6 and 7 fail closed on the same task/source binding contract.\n";
1031
+ const correspondencePath = await writeRunReport(meta.runDir, "backend-test-markdown-pytest-correspondence.md", correspondence);
1032
+ outputs.push(`correspondence=${correspondencePath}`, correspondence);
1033
+ return {
1034
+ ok: false,
1035
+ stdout: outputs.join("\n\n"),
1036
+ stderr: "backend-test markdown-traceability requires spec.sourceBinding",
1037
+ failureCategory: "invalid-output",
1038
+ durationMs: Date.now() - started,
1039
+ };
1040
+ }
1041
+ try {
1042
+ const correspondence = await analyzeBackendTestMarkdownPytestCorrespondence({
1043
+ workspaceRoot: input.cwd,
1044
+ layout: backendLayout,
1045
+ taskId: sourceBinding.taskId,
1046
+ });
1047
+ const correspondencePath = await writeRunReport(meta.runDir, "backend-test-markdown-pytest-correspondence.md", correspondence.markdown);
1048
+ const contractsDir = path.join(meta.runDir, "contracts");
1049
+ await mkdir(contractsDir, { recursive: true });
1050
+ const factsPath = path.join(contractsDir, "backend-test-markdown-pytest-correspondence-facts.json");
1051
+ await writeFile(factsPath, JSON.stringify(correspondence.facts, null, 2), "utf8");
1052
+ outputs.push(`correspondence=${correspondencePath}`, `correspondenceFacts=${factsPath}`, correspondence.markdown);
1053
+ }
1054
+ catch (error) {
1055
+ const message = error instanceof Error ? error.message : String(error);
1056
+ const correspondence = `# Backend Test Markdown → pytest Correspondence\n\n## Status\n\nUNAVAILABLE\n\n## Findings\n\n- Correspondence analysis crashed and is treated as an infrastructure failure: ${message}\n`;
1057
+ const correspondencePath = await writeRunReport(meta.runDir, "backend-test-markdown-pytest-correspondence.md", correspondence);
1058
+ outputs.push(`correspondence=${correspondencePath}`, correspondence);
1059
+ return {
1060
+ ok: false,
1061
+ stdout: outputs.join("\n\n"),
1062
+ stderr: `backend-test correspondence analysis crashed: ${message}`,
1063
+ failureCategory: "invalid-output",
1064
+ durationMs: Date.now() - started,
1065
+ };
1066
+ }
1067
+ // Scenario-param assessment and deterministic repair run before collection,
1068
+ // so effective collection/fixture facts bind the final executable assets.
1069
+ try {
1070
+ const scenarioFactsPath = path.join(meta.runDir, "contracts", "backend-test-scenario-param-consistency-facts.json");
1071
+ const scenarioReportPath = path.join(meta.runDir, "reports", "backend-test-scenario-param-consistency.md");
1072
+ const scenarioFacts = await readBackendScenarioParamFacts(scenarioFactsPath);
1073
+ const scenarioReport = await readFile(scenarioReportPath, "utf8");
1074
+ outputs.push(`scenarioParamFinal=${scenarioReportPath}`, `scenarioParamFinalFacts=${scenarioFactsPath}`, `scenarioParamStatus=${scenarioFacts.overallStatus}`, scenarioReport);
1075
+ }
1076
+ catch (error) {
1077
+ const message = error instanceof Error ? error.message : String(error);
1078
+ outputs.push("# Backend Test Scenario-Param Consistency\n\n## Status\n\nUNAVAILABLE\n\n## Findings\n\n- Pre-collection scenario-param evidence missing: " + message + "\n");
1079
+ }
1080
+ }
1081
+ else if (pipeline === "markdown-manifest") {
1082
+ const sourceBinding = meta.spec.sourceBinding;
1083
+ if (!sourceBinding)
1084
+ throw new Error("backend-test markdown-manifest requires spec.sourceBinding");
1085
+ const manifest = await materializeBackendTestCaseManifestFromFacts({
1086
+ runDir: meta.runDir,
1087
+ workspaceRoot: input.cwd,
1088
+ sourceBinding: {
1089
+ taskId: sourceBinding.taskId,
1090
+ requirementPath: sourceBinding.sources.find((s) => s.kind === "requirement")?.path ??
1091
+ "source/requirement.md",
1092
+ requirementSha256: sourceBinding.sources.find((s) => s.kind === "requirement")
1093
+ ?.sha256 ?? "0".repeat(64),
1094
+ referencePaths: sourceBinding.sources
1095
+ .filter((s) => s.kind === "reference")
1096
+ .map((s) => s.path),
1097
+ requirementIds: sourceBinding.requirementIds,
1098
+ },
1099
+ });
1100
+ const manifestPath = path.join(meta.runDir, "contracts", "backend-test-case-manifest.json");
1101
+ const summary = manifest.coverageSummary;
1102
+ outputs.push(`manifest=${manifestPath}`, `materializationStatus=${manifest.materializationStatus ?? "available"}`, `coverageSummary.explicitAcCount=${summary?.explicitAcCount ?? "unavailable"}`, `coverageSummary.coveredAcCount=${summary?.coveredAcCount ?? "unavailable"}`, `coverageSummary.caseCount=${summary?.caseCount ?? "unavailable"}`, `coverageSummary.generatedCount=${summary?.generatedCount ?? "unavailable"}`, `ruleCoverageSummary.ruleCount=${manifest.ruleCoverageSummary?.ruleCount ?? "unavailable"}`, `correspondenceSummary.exactCorrespondenceCount=${manifest.correspondenceSummary?.exactCorrespondenceCount ?? "unavailable"}`, `correspondenceSummary.primarySymbolCount=${manifest.correspondenceSummary?.primarySymbolCount ?? "unavailable"}`, `correspondenceSummary.testPoints=${manifest.correspondenceSummary?.mappedTestPointCount ?? "unavailable"}/${manifest.correspondenceSummary?.testPointCount ?? "unavailable"}`, `correspondenceSummary.variantTestPointCount=${manifest.correspondenceSummary?.variantTestPointCount ?? "unavailable"}`, `correspondenceSummary.assertionTestPointCount=${manifest.correspondenceSummary?.assertionTestPointCount ?? "unavailable"}`, `correspondenceSummary.crossCuttingTestPointCount=${manifest.correspondenceSummary?.crossCuttingTestPointCount ?? "unavailable"}`, `correspondenceSummary.unclassifiedTestPointCount=${manifest.correspondenceSummary?.unclassifiedTestPointCount ?? "unavailable"}`, `correspondenceSummary.duplicateBindingTestPointCount=${manifest.correspondenceSummary?.duplicateBindingTestPointCount ?? "unavailable"}`);
1103
+ }
1104
+ else if (pipeline === "markdown-execute-html") {
1105
+ const executionReadiness = await readBackendTestExecutionReadiness(path.join(meta.runDir, "contracts", "backend-test-execution-readiness.json"));
1106
+ await assertBackendTestExecutionReadinessFresh(input.cwd, executionReadiness);
1107
+ const mappedScripts = await collectBackendTestMappedPytestScripts(input.cwd, backendLayout);
1108
+ let eligibleItemIds = executionReadiness.eligibleItemIds;
1109
+ // Plan D gap-fill executeScope=affected: when a gap plan exists in
1110
+ // this run, prefer the newly planned items (pytest node ids contain
1111
+ // the TP slot id) plus every collected item of the patched modules;
1112
+ // scope=all keeps the full eligible set.
1113
+ const affectedScope = meta.spec.globalConstraints?.some((item) => /^backendTestExecuteScope=affected$/i.test(item.trim())) ?? false;
1114
+ if (affectedScope) {
1115
+ try {
1116
+ const plan = JSON.parse(await readFile(path.join(meta.runDir, "contracts", "backend-test-gap-plan-v1.json"), "utf8"));
1117
+ const newPoints = new Set(plan.newTestPoints ?? []);
1118
+ const modules = new Set((plan.targetModules ?? []).map((stem) => `test_${stem}.py`));
1119
+ const affected = eligibleItemIds.filter((id) => modules.size === 0 ||
1120
+ [...modules].some((script) => id.includes(script)) ||
1121
+ [...newPoints].some((point) => id.includes(point)));
1122
+ if (affected.length > 0)
1123
+ eligibleItemIds = affected;
1124
+ }
1125
+ catch {
1126
+ // no gap plan in this run: execute the full eligible set
1127
+ }
1128
+ }
1129
+ const pytestTargetsRelative = "reports/backend-test-pytest-targets.txt";
1130
+ const pytestTargetsAbsolute = path.join(meta.runDir, pytestTargetsRelative);
1131
+ await writeFile(pytestTargetsAbsolute, `${eligibleItemIds.join("\n")}\n`, "utf8");
1132
+ const pytestTargetsArg = `@\"\${HARNESS_DAG_RUN_DIR}/${pytestTargetsRelative}\"`;
1133
+ // Split into short bash -c commands (aligned with markdown-environment).
1134
+ // A single ultra-long compound command has been observed on Windows to
1135
+ // exit 0xC0000142 (STATUS_DLL_INIT_FAILED) in ~40ms with empty stdout/
1136
+ // stderr and no diagnostics file. Keep pytest exactly once.
1137
+ const commandPlan = [
1138
+ "shell-bootstrap",
1139
+ "python-identity",
1140
+ "pytest-version",
1141
+ "pytest-run",
1142
+ ];
1143
+ let preSpawnOk = false;
1144
+ try {
1145
+ await writeBackendTestExecuteDiagnosticsPreSpawn({
1146
+ runDir: meta.runDir,
1147
+ runId: meta.runId,
1148
+ cwd: input.cwd,
1149
+ mappedScripts,
1150
+ commandPlan: [...commandPlan],
1151
+ });
1152
+ preSpawnOk = true;
1153
+ }
1154
+ catch (error) {
1155
+ const message = error instanceof Error ? error.message : String(error);
1156
+ return {
1157
+ ok: false,
1158
+ stdout: "",
1159
+ stderr: [
1160
+ "backend-test markdown-execute-html shell failed",
1161
+ "failureCategory=invalid-output",
1162
+ `mappedScripts=${mappedScripts.join(",") || "<none>"}`,
1163
+ `preSpawnDiagnostics=failed`,
1164
+ `diagnostics=${BACKEND_TEST_EXECUTE_DIAGNOSTICS_REL}`,
1165
+ message,
1166
+ ].join("\n"),
1167
+ failureCategory: "invalid-output",
1168
+ durationMs: Date.now() - started,
1169
+ };
1170
+ }
1171
+ // Append-only shell snippets; never overwrite the JS pre-spawn head.
1172
+ const shellBootstrapCommand = [
1173
+ 'if [ -z "${HARNESS_DAG_RUN_DIR:-}" ]; then echo "HARNESS_DAG_RUN_DIR is empty; refusing to write diagnostics under /reports" >&2; exit 2; fi',
1174
+ 'mkdir -p "${HARNESS_DAG_RUN_DIR}/reports"',
1175
+ 'DIAG_FILE="${HARNESS_DAG_RUN_DIR}/reports/backend-test-execute-diagnostics.md"',
1176
+ 'echo "STEP=shell-bootstrap"',
1177
+ 'echo "- shellBootstrapAt: $(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date)" >> "${DIAG_FILE}"',
1178
+ 'echo "- shellPwd: $(pwd)" >> "${DIAG_FILE}"',
1179
+ 'echo "- shellHARNESS_DAG_RUN_DIR: ${HARNESS_DAG_RUN_DIR}" >> "${DIAG_FILE}"',
1180
+ 'echo "- shell: ${BASH:-bash} (${BASH_VERSION:-unknown})" >> "${DIAG_FILE}"',
1181
+ ].join("; ");
1182
+ const pythonIdentityCommand = [
1183
+ 'if [ -z "${HARNESS_DAG_RUN_DIR:-}" ]; then echo "HARNESS_DAG_RUN_DIR is empty" >&2; exit 2; fi',
1184
+ 'DIAG_FILE="${HARNESS_DAG_RUN_DIR}/reports/backend-test-execute-diagnostics.md"',
1185
+ 'echo "STEP=resolve-python"',
1186
+ 'PYTHON_BIN="$(command -v python || command -v python3 || true)"',
1187
+ 'echo "- command_v_python: $(command -v python || true)" >> "${DIAG_FILE}"',
1188
+ 'echo "- command_v_python3: $(command -v python3 || true)" >> "${DIAG_FILE}"',
1189
+ 'echo "- PYTHON_BIN: ${PYTHON_BIN:-<empty>}" >> "${DIAG_FILE}"',
1190
+ 'if [ -z "${PYTHON_BIN}" ]; then echo "python/python3 is required for backend-test execution" | tee -a "${DIAG_FILE}" >&2; exit 127; fi',
1191
+ 'echo "STEP=python-identity"',
1192
+ 'if ! PYTHONUNBUFFERED=1 "${PYTHON_BIN}" -c "import sys; print(sys.executable); print(sys.version.splitlines()[0])" >> "${DIAG_FILE}" 2>&1; then echo "STEP=python-identity-failed" | tee -a "${DIAG_FILE}" >&2; exit 127; fi',
1193
+ ].join("; ");
1194
+ const pytestVersionCommand = [
1195
+ 'if [ -z "${HARNESS_DAG_RUN_DIR:-}" ]; then echo "HARNESS_DAG_RUN_DIR is empty" >&2; exit 2; fi',
1196
+ 'DIAG_FILE="${HARNESS_DAG_RUN_DIR}/reports/backend-test-execute-diagnostics.md"',
1197
+ 'PYTHON_BIN="$(command -v python || command -v python3 || true)"',
1198
+ 'if [ -z "${PYTHON_BIN}" ]; then echo "python/python3 is required for backend-test execution" | tee -a "${DIAG_FILE}" >&2; exit 127; fi',
1199
+ 'echo "STEP=pytest-version"',
1200
+ 'if ! PYTHONUNBUFFERED=1 "${PYTHON_BIN}" -m pytest --version >> "${DIAG_FILE}" 2>&1; then echo "STEP=pytest-version-failed" | tee -a "${DIAG_FILE}" >&2; exit 127; fi',
1201
+ ].join("; ");
1202
+ // Single pytest invocation; exit 0/1 + non-empty html are reportable.
1203
+ const pytestRunCommand = [
1204
+ 'if [ -z "${HARNESS_DAG_RUN_DIR:-}" ]; then echo "HARNESS_DAG_RUN_DIR is empty" >&2; exit 2; fi',
1205
+ 'DIAG_FILE="${HARNESS_DAG_RUN_DIR}/reports/backend-test-execute-diagnostics.md"',
1206
+ 'PYTHON_BIN="$(command -v python || command -v python3 || true)"',
1207
+ 'if [ -z "${PYTHON_BIN}" ]; then echo "python/python3 is required for backend-test execution" | tee -a "${DIAG_FILE}" >&2; exit 127; fi',
1208
+ 'echo "STEP=pytest-run"',
1209
+ `echo "- pytestCommand: python -m pytest <${eligibleItemIds.length} eligibility-authorized-items> -v -p no:cacheprovider --html=reports/backend-test.html --self-contained-html" >> "\${DIAG_FILE}"`,
1210
+ `echo "- eligibleItemCount: ${eligibleItemIds.length}" >> "\${DIAG_FILE}"`,
1211
+ `echo "- excludedItemCount: ${executionReadiness.excludedItems.length}" >> "\${DIAG_FILE}"`,
1212
+ `PYTHONUTF8=1 PYTHONIOENCODING=utf-8 PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 "\${PYTHON_BIN}" -m pytest ${pytestTargetsArg} -v -p no:cacheprovider --html="\${HARNESS_DAG_RUN_DIR}/reports/backend-test.html" --self-contained-html`,
1213
+ "STATUS=$?",
1214
+ 'printf "%s" "${STATUS}" > "${HARNESS_DAG_RUN_DIR}/reports/backend-test-pytest-exit.txt"',
1215
+ 'echo "- pytestExitCode: ${STATUS}" >> "${DIAG_FILE}"',
1216
+ 'if [ -f "${HARNESS_DAG_RUN_DIR}/reports/backend-test.html" ]; then echo "- pytestHtml: present ($(wc -c < "${HARNESS_DAG_RUN_DIR}/reports/backend-test.html" | tr -d " ") bytes)" >> "${DIAG_FILE}"; else echo "- pytestHtml: missing" >> "${DIAG_FILE}"; fi',
1217
+ 'echo "- finishedAt: $(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date)" >> "${DIAG_FILE}"',
1218
+ 'echo "STEP=pytest-finished status=${STATUS}"',
1219
+ 'if { [ "${STATUS}" -eq 0 ] || [ "${STATUS}" -eq 1 ]; } && [ -s "${HARNESS_DAG_RUN_DIR}/reports/backend-test.html" ]; then exit 0; fi',
1220
+ 'echo "STEP=pipeline-failed status=${STATUS} html=$([ -s "${HARNESS_DAG_RUN_DIR}/reports/backend-test.html" ] && echo present || echo missing)" >&2',
1221
+ 'exit "${STATUS}"',
1222
+ ].join("; ");
1223
+ const pipelineCommands = [
1224
+ shellBootstrapCommand,
1225
+ pythonIdentityCommand,
1226
+ pytestVersionCommand,
1227
+ pytestRunCommand,
1228
+ ];
1229
+ const results = await executePipelineCommands(input, meta, pipelineCommands);
1230
+ if (!results.every((result) => result.ok)) {
1231
+ const failureIndex = results.findIndex((result) => !result.ok);
1232
+ const failure = results[failureIndex];
1233
+ const combinedStdout = results
1234
+ .map((result) => result.stdout)
1235
+ .join("\n");
1236
+ let diagnosticsText = "";
1237
+ try {
1238
+ diagnosticsText = await readFile(path.join(meta.runDir, "reports", "backend-test-execute-diagnostics.md"), "utf8");
1239
+ }
1240
+ catch {
1241
+ diagnosticsText = "";
1242
+ }
1243
+ const failedStep = failureIndex >= 0 && failureIndex < commandPlan.length
1244
+ ? commandPlan[failureIndex]
1245
+ : "unknown";
1246
+ const stderrSummary = [
1247
+ "backend-test markdown-execute-html shell failed",
1248
+ `failureCategory=${failure.failureCategory}`,
1249
+ `exitCode=${failure.exitCode ?? "null"}`,
1250
+ `durationMs=${failure.durationMs}`,
1251
+ `failedCommandIndex=${failureIndex + 1}`,
1252
+ `failedStep=${failedStep}`,
1253
+ `commandPlanCount=${pipelineCommands.length}`,
1254
+ `preSpawnDiagnostics=${preSpawnOk ? "written" : "failed"}`,
1255
+ `mappedScripts=${mappedScripts.join(",") || "<none>"}`,
1256
+ `shellStdoutEmpty=${combinedStdout.trim().length === 0}`,
1257
+ `shellStderrEmpty=${failure.stderr.trim().length === 0}`,
1258
+ failure.stdoutArtifactPath
1259
+ ? `commandStdoutArtifact=${failure.stdoutArtifactPath}`
1260
+ : undefined,
1261
+ failure.stderrArtifactPath
1262
+ ? `commandStderrArtifact=${failure.stderrArtifactPath}`
1263
+ : undefined,
1264
+ `diagnostics=${BACKEND_TEST_EXECUTE_DIAGNOSTICS_REL}`,
1265
+ failure.stderr.trim() || "(shell stderr empty)",
1266
+ diagnosticsText.trim()
1267
+ ? `--- diagnostics ---\n${diagnosticsText.trim()}`
1268
+ : "(diagnostics file missing or empty)",
1269
+ ]
1270
+ .filter((line) => Boolean(line))
1271
+ .join("\n");
1272
+ return {
1273
+ ok: false,
1274
+ stdout: combinedStdout,
1275
+ stderr: stderrSummary,
1276
+ failureCategory: failure.failureCategory,
1277
+ durationMs: Date.now() - started,
1278
+ };
1279
+ }
1280
+ const reportsDir = path.join(meta.runDir, "reports");
1281
+ const pytestHtmlContent = await readFile(path.join(reportsDir, "backend-test.html"), "utf8");
1282
+ const pytestExitCode = Number.parseInt((await readFile(path.join(reportsDir, "backend-test-pytest-exit.txt"), "utf8")).trim(), 10);
1283
+ if (![0, 1].includes(pytestExitCode)) {
1284
+ throw new Error(`pytest did not complete with a reportable exit code: ${pytestExitCode}`);
1285
+ }
1286
+ const parsed = parsePytestHtmlReport(pytestHtmlContent);
1287
+ // Keep native pytest-html (data-jsonblob) for audit before styled overwrite.
1288
+ const nativeHtmlPath = await writeRunReport(meta.runDir, "backend-test-pytest-native.html", pytestHtmlContent);
1289
+ // Bind Result v1 from the native pytest-html report BEFORE overwriting with the
1290
+ // styled renderer (which drops the data-jsonblob island).
1291
+ const resultArtifact = await materializeBackendTestResultFromPytestHtml({
1292
+ runDir: meta.runDir,
1293
+ htmlRelativePath: "reports/backend-test-pytest-native.html",
1294
+ htmlContent: pytestHtmlContent,
1295
+ pytestExitCode,
1296
+ });
1297
+ const cases = await collectBackendTestHumanCaseCatalog(input.cwd, backendLayout);
1298
+ const caseValidationSummary = await readRequiredRunReport(reportsDir, "backend-md-case-validation.md");
1299
+ const caseCoverageSummary = await readAdvisoryRunReport(reportsDir, "backend-test-case-coverage-analysis.md", "Backend Test Case Coverage Analysis");
1300
+ const traceabilitySummary = await readRequiredRunReport(reportsDir, "backend-test-traceability.md");
1301
+ const correspondenceSummaryBase = await readAdvisoryRunReport(reportsDir, "backend-test-markdown-pytest-correspondence.md", "Backend Test Markdown → pytest Correspondence");
1302
+ const eligibilitySummary = await readAdvisoryRunReport(reportsDir, "backend-test-execution-eligibility.md", "Backend Test Execution Eligibility");
1303
+ const correspondenceSummary = `${correspondenceSummaryBase.trim()}\n\n${eligibilitySummary.trim()}\n`;
1304
+ const htmlContent = renderBackendTestHtml({
1305
+ title: meta.spec.title,
1306
+ parsed,
1307
+ cases,
1308
+ environmentSummary: await readFile(path.join(reportsDir, "backend-test-environment.md"), "utf8"),
1309
+ caseValidationSummary,
1310
+ caseCoverageSummary,
1311
+ traceabilitySummary,
1312
+ correspondenceSummary,
1313
+ });
1314
+ const htmlPath = await writeRunReport(meta.runDir, "backend-test.html", htmlContent);
1315
+ const facts = renderBackendTestFacts({
1316
+ parsed,
1317
+ cases,
1318
+ pytestExitCode,
1319
+ htmlRelativePath: "reports/backend-test.html",
1320
+ htmlContent,
1321
+ caseValidationSummary,
1322
+ caseCoverageSummary,
1323
+ traceabilitySummary,
1324
+ correspondenceSummary,
1325
+ });
1326
+ const markdownPath = await writeRunReport(meta.runDir, "backend-test.md", facts);
1327
+ const factsPath = await writeRunReport(meta.runDir, "backend-test-facts.md", facts);
1328
+ // Structured failure analysis report (P2); emitted even when there are zero failures.
1329
+ let scenarioParamFinalStatus = "UNAVAILABLE";
1330
+ let scenarioParamRepairAttempt = 0;
1331
+ const scenarioParamStatusByToken = new Map();
1332
+ try {
1333
+ const scenarioFacts = await readBackendScenarioParamFacts(path.join(meta.runDir, "contracts", "backend-test-scenario-param-consistency-facts.json"));
1334
+ scenarioParamFinalStatus = scenarioFacts.overallStatus;
1335
+ scenarioParamRepairAttempt = scenarioFacts.repairAttempt;
1336
+ for (const entry of scenarioFacts.entries) {
1337
+ scenarioParamStatusByToken.set(entry.caseId, entry.status);
1338
+ scenarioParamStatusByToken.set(entry.tpId, entry.status);
1339
+ }
1340
+ }
1341
+ catch {
1342
+ // optional facts
1343
+ }
1344
+ const failureCases = parsed.cases.filter((item) => item.status === "failure" || item.status === "error");
1345
+ const failureAnalysisMarkdown = renderBackendTestFailureAnalysis({
1346
+ generatedAt: new Date().toISOString(),
1347
+ htmlReportPath: "reports/backend-test.html",
1348
+ total: parsed.tests,
1349
+ passed: parsed.passed,
1350
+ failed: parsed.failed,
1351
+ errors: parsed.errors,
1352
+ durationLabel: `${parsed.durationMs ?? failureCases.reduce((sum, item) => sum + (item.durationMs ?? 0), 0)}ms`,
1353
+ environmentSummary: await readFile(path.join(reportsDir, "backend-test-environment.md"), "utf8").catch(() => "unavailable"),
1354
+ scenarioParamFinalStatus,
1355
+ repairAttempt: scenarioParamRepairAttempt,
1356
+ failures: failureCases.map((result) => {
1357
+ const identityText = `${result.nodeId ?? ""} ${result.name}`;
1358
+ const caseToken = identityText.match(/BE[-_][A-Z0-9_-]+[-_]\d{2,3}/i)?.[0];
1359
+ const caseId = caseToken
1360
+ ? caseToken.replace(/^BE_/i, "BE-").replaceAll("_", "-").toUpperCase()
1361
+ : result.name;
1362
+ const tpToken = identityText.match(/TP-[A-Z0-9-]+/i)?.[0]?.toUpperCase();
1363
+ const scenarioParamStatus = (tpToken && scenarioParamStatusByToken.get(tpToken)) ||
1364
+ scenarioParamStatusByToken.get(caseId);
1365
+ const evidence = [result.message, result.details]
1366
+ .filter(Boolean)
1367
+ .join("\n");
1368
+ const statusMatch = /assert\s+(\d{3})\s*==\s*(\d{3})/i.exec(evidence) ||
1369
+ /expected[^\d]*(\d{3})[\s\S]{0,40}actual[^\d]*(\d{3})/i.exec(evidence);
1370
+ return {
1371
+ name: tpToken ?? result.name,
1372
+ caseId,
1373
+ scenario: cases.find((item) => item.id === caseId)?.scenario ??
1374
+ cases.find((item) => item.id === caseId)?.title ??
1375
+ result.name,
1376
+ expectedCode: statusMatch?.[2] ?? statusMatch?.[1],
1377
+ actualCode: statusMatch?.[1] ?? statusMatch?.[2],
1378
+ message: result.message || result.status,
1379
+ scriptPath: result.nodeId ?? result.filePath ?? result.classname,
1380
+ durationLabel: result.durationMs !== undefined
1381
+ ? `${result.durationMs}ms`
1382
+ : undefined,
1383
+ scenarioParamStatus,
1384
+ classification: classifyBackendTestFailureWithScenarioParam({
1385
+ message: result.message || "",
1386
+ details: result.details,
1387
+ scenarioParamStatus,
1388
+ }),
1389
+ };
1390
+ }),
1391
+ });
1392
+ const failureAnalysisPath = await writeRunReport(meta.runDir, "backend-test-failure-analysis.md", failureAnalysisMarkdown);
1393
+ outputs.push(`failureAnalysis=${failureAnalysisPath}`);
1394
+ // Deterministic L-5 dashboard: machine-computed metrics (not Pi-generated).
1395
+ // manifest is produced by an upstream finalize node; tolerate its absence
1396
+ // so a minimal DAG without manifest still gets a degraded dashboard.
1397
+ let manifestForL5 = {};
1398
+ try {
1399
+ const manifestPath = path.join(meta.runDir, "contracts", "backend-test-case-manifest.json");
1400
+ manifestForL5 = JSON.parse(await readFile(manifestPath, "utf8"));
1401
+ }
1402
+ catch {
1403
+ // manifest missing: L5 AC/automation metrics degrade to unavailable.
1404
+ }
1405
+ // Optional JaCoCo coverage collection (Java services). When jacocoCoverage
1406
+ // is configured on the shell node, dump exec data over TCP from the
1407
+ // JaCoCo tcpserver agent, convert via jacococli.jar, and parse into a
1408
+ // code-coverage-v1 contract. Failure-safe: any error → coverage stays
1409
+ // null and the L-5 dashboard degrades line/branch to unavailable.
1410
+ let coverageContract = null;
1411
+ const jacocoCfg = input.task.shell?.jacocoCoverage;
1412
+ if (jacocoCfg) {
1413
+ const sourceBinding = meta.spec.sourceBinding;
1414
+ const requirementIds = sourceBinding?.requirementIds ?? [];
1415
+ try {
1416
+ coverageContract = await collectJacocoCoverage({
1417
+ endpoint: jacocoCfg.endpoint,
1418
+ cliJarPath: jacocoCfg.cliJarPath,
1419
+ includes: jacocoCfg.includes ?? "*",
1420
+ connectTimeoutMs: jacocoCfg.connectTimeoutMs ?? 5000,
1421
+ workDir: reportsDir,
1422
+ sourceScope: { requirementIds, paths: [] },
1423
+ commitSha: null,
1424
+ });
1425
+ }
1426
+ catch {
1427
+ // coverage unavailable: JaCoCo endpoint unreachable or parse failed.
1428
+ }
1429
+ if (coverageContract) {
1430
+ const contractsDir = path.join(meta.runDir, "contracts");
1431
+ await mkdir(contractsDir, { recursive: true });
1432
+ await writeFile(path.join(contractsDir, "code-coverage-v1.json"), JSON.stringify(coverageContract, null, 2), "utf8");
1433
+ }
1434
+ }
1435
+ const l5Metrics = computeL5ReportMetrics({
1436
+ result: {
1437
+ passed: parsed.passed,
1438
+ failed: parsed.failed,
1439
+ error: parsed.errors,
1440
+ skipped: parsed.skipped,
1441
+ },
1442
+ manifest: manifestForL5,
1443
+ coverage: coverageContract,
1444
+ criticalRiskCount: parsed.failed + parsed.errors > 0 ? 1 : 0,
1445
+ });
1446
+ const failureSummaries = parsed.cases
1447
+ .filter((c) => c.status !== "passed")
1448
+ .map((c) => {
1449
+ const id = c.name.match(/BE-[A-Z0-9]+(?:-[A-Z0-9]+)*-\d{3}/)?.[0] ?? c.name;
1450
+ return {
1451
+ caseId: id,
1452
+ title: c.name,
1453
+ message: c.message ?? c.status,
1454
+ script: `${c.classname.split("::")[0] ?? c.classname}`,
1455
+ };
1456
+ });
1457
+ const l5Html = renderBackendTestL5Dashboard({
1458
+ title: meta.spec.title,
1459
+ objective: meta.spec.objective,
1460
+ parsed,
1461
+ metrics: l5Metrics,
1462
+ caseValidationSummary,
1463
+ caseCoverageSummary,
1464
+ traceabilitySummary,
1465
+ correspondenceSummary,
1466
+ failures: failureSummaries.length > 0 ? failureSummaries : undefined,
1467
+ });
1468
+ const l5Path = await writeRunReport(meta.runDir, "backend-test-l5-dashboard.html", l5Html);
1469
+ const sanitizedOutputs = results.map((result) => redactBackendTestOutput(result.stdout));
1470
+ outputs.push(...sanitizedOutputs, `html=${htmlPath}`, `nativeHtml=${nativeHtmlPath}`, `markdown=${markdownPath}`, `facts=${factsPath}`, `l5-dashboard=${l5Path}`, `result=${resultArtifact.path}`, facts);
1471
+ }
1472
+ else if (pipeline === "contracts") {
1473
+ const wrapperPath = path.join(meta.runDir, "analyze-and-discover-backend-test-pi.json");
1474
+ const wrapper = JSON.parse(await readFile(wrapperPath, "utf8"));
1475
+ const raw = wrapper.assistantText?.trim() || wrapper.stdout?.trim() || "";
1476
+ const envelope = extractBackendTestContractEnvelope(raw);
1477
+ await writeFile(path.join(meta.runDir, "backend-test-analysis-envelope.json"), JSON.stringify({ assistantText: JSON.stringify(envelope.analysis) }));
1478
+ await writeFile(path.join(meta.runDir, "backend-test-execution-envelope.json"), JSON.stringify({ assistantText: JSON.stringify(envelope.execution) }));
1479
+ const analysis = await materializeBackendTestAnalysisContract({
1480
+ runDir: meta.runDir,
1481
+ fromNodeId: "backend-test-analysis-envelope",
1482
+ artifactName: "backend-test-analysis.json",
1483
+ outputDir: "contracts",
1484
+ sourceBinding: meta.spec.sourceBinding,
1485
+ });
1486
+ const execution = await materializeBackendTestExecutionContract({
1487
+ runDir: meta.runDir,
1488
+ fromNodeId: "backend-test-execution-envelope",
1489
+ artifactName: "backend-test-execution.json",
1490
+ outputDir: "contracts",
1491
+ allowedTestRoot: backendLayout.testRoot,
1492
+ });
1493
+ outputs.push(`analysis=${analysis.path}`, `execution=${execution.path}`);
1494
+ }
1495
+ else if (pipeline === "semantic-initial") {
1496
+ // Same pipeline id for node 09 (materialize+trace) and node 10 (pass-only authorize).
1497
+ // Branch only on task.id so request-revision remains materializable at 09 and fail-closed at 10.
1498
+ if (input.task.id === "backend-test-semantic-gate-shell") {
1499
+ const artifactPath = path.join(meta.runDir, "contracts", "backend-test-semantic-review.json");
1500
+ let raw;
1501
+ try {
1502
+ raw = await readFile(artifactPath, "utf8");
1503
+ }
1504
+ catch {
1505
+ return {
1506
+ ok: false,
1507
+ stdout: "",
1508
+ stderr: "backend-test semantic authorize: missing canonical artifact contracts/backend-test-semantic-review.json",
1509
+ failureCategory: "invalid-output",
1510
+ durationMs: Date.now() - started,
1511
+ };
1512
+ }
1513
+ let parsed;
1514
+ try {
1515
+ parsed = JSON.parse(raw);
1516
+ }
1517
+ catch {
1518
+ return {
1519
+ ok: false,
1520
+ stdout: "",
1521
+ stderr: "backend-test semantic authorize: malformed canonical artifact contracts/backend-test-semantic-review.json",
1522
+ failureCategory: "invalid-output",
1523
+ durationMs: Date.now() - started,
1524
+ };
1525
+ }
1526
+ if (!parsed ||
1527
+ typeof parsed !== "object" ||
1528
+ Array.isArray(parsed) ||
1529
+ !["verdict", "findings", "summary"].every((key) => Object.hasOwn(parsed, key))) {
1530
+ return {
1531
+ ok: false,
1532
+ stdout: "",
1533
+ stderr: "backend-test semantic authorize: malformed canonical artifact (missing required canonical fields)",
1534
+ failureCategory: "invalid-output",
1535
+ durationMs: Date.now() - started,
1536
+ };
1537
+ }
1538
+ const validated = backendTestSemanticReviewSchema.safeParse(parsed);
1539
+ if (!validated.success) {
1540
+ const detail = validated.error.issues
1541
+ .map((issue) => `${issue.path.join(".") || "artifact"}: ${issue.message}`)
1542
+ .join("; ");
1543
+ return {
1544
+ ok: false,
1545
+ stdout: "",
1546
+ stderr: `backend-test semantic authorize: malformed canonical artifact (strict schema validation failed: ${detail})`,
1547
+ failureCategory: "invalid-output",
1548
+ durationMs: Date.now() - started,
1549
+ };
1550
+ }
1551
+ const review = validated.data;
1552
+ if (review.verdict === "pass") {
1553
+ return {
1554
+ ok: true,
1555
+ stdout: JSON.stringify(review),
1556
+ stderr: "",
1557
+ failureCategory: "success",
1558
+ durationMs: Date.now() - started,
1559
+ };
1560
+ }
1561
+ return {
1562
+ ok: false,
1563
+ stdout: JSON.stringify(review),
1564
+ stderr: "backend-test semantic authorize blocked: verdict=request-revision",
1565
+ failureCategory: "invalid-output",
1566
+ durationMs: Date.now() - started,
1567
+ };
1568
+ }
1569
+ const review = await materializeBackendTestSemanticReview({
1570
+ runDir: meta.runDir,
1571
+ fromNodeId: "review-generated-backend-pytest-pi",
1572
+ artifactName: "backend-test-semantic-review.json",
1573
+ outputDir: "contracts",
1574
+ });
1575
+ const trace = await runBackendTestTraceabilityGate({
1576
+ runDir: meta.runDir,
1577
+ workspaceRoot: input.cwd,
1578
+ });
1579
+ outputs.push(`semantic=${review.path}`, formatTraceabilityGateStdout(trace));
1580
+ const parsed = JSON.parse(await readFile(review.path, "utf8"));
1581
+ return {
1582
+ ok: true,
1583
+ stdout: JSON.stringify(parsed),
1584
+ stderr: "",
1585
+ failureCategory: "success",
1586
+ durationMs: Date.now() - started,
1587
+ };
1588
+ }
1589
+ else if (pipeline === "execute-parse-initial") {
1590
+ const results = await executePipelineCommands(input, meta);
1591
+ if (!results.every((result) => result.ok)) {
1592
+ const failure = results.find((result) => !result.ok);
1593
+ return {
1594
+ ok: false,
1595
+ stdout: results.map((result) => result.stdout).join("\n"),
1596
+ stderr: failure.stderr,
1597
+ failureCategory: failure.failureCategory,
1598
+ durationMs: Date.now() - started,
1599
+ };
1600
+ }
1601
+ const artifact = await materializeBackendTestResultFromRunDir({
1602
+ runDir: meta.runDir,
1603
+ fromNodeId: input.task.id,
1604
+ artifactName: "backend-test-result-initial.json",
1605
+ outputDir: "contracts",
1606
+ junitRelativePath: "reports/backend-test-initial-junit.xml",
1607
+ testRoot: backendLayout.testRoot,
1608
+ });
1609
+ outputs.push(...results.map((result) => result.stdout), `result=${artifact.path}`);
1610
+ }
1611
+ else if (pipeline === "classification-result-context") {
1612
+ const classification = await materializeBackendTestClassification({
1613
+ runDir: meta.runDir,
1614
+ fromNodeId: "classify-backend-test-result-pi",
1615
+ artifactName: "backend-test-classification.json",
1616
+ outputDir: "contracts",
1617
+ });
1618
+ const selected = await executePipelineCommands(input, meta, [
1619
+ buildBackendTestCanonicalResultFromInitialShellSnippet(),
1620
+ ]);
1621
+ if (!selected.every((result) => result.ok))
1622
+ throw new Error(selected.find((result) => !result.ok)?.stderr ||
1623
+ "canonical result materialization failed");
1624
+ const readNodeOutput = async (nodeId) => {
1625
+ const record = JSON.parse(await readFile(path.join(meta.runDir, `${nodeId}.json`), "utf8"));
1626
+ const text = record.assistantText?.trim() || record.stdout?.trim() || "";
1627
+ if (!text)
1628
+ throw new Error(`missing auditable node output: ${nodeId}`);
1629
+ return text;
1630
+ };
1631
+ const caseReview = await readNodeOutput("review-backend-cases-pi");
1632
+ const reviewVerdicts = caseReview
1633
+ .split(/\r?\n/)
1634
+ .map((line) => line.trim())
1635
+ .filter((line) => line.startsWith("VERDICT:"));
1636
+ if (reviewVerdicts.length !== 1 ||
1637
+ !["VERDICT: pass", "VERDICT: request-revision"].includes(reviewVerdicts[0])) {
1638
+ throw new Error("invalid advisory case review evidence: expected exactly one auditable VERDICT line");
1639
+ }
1640
+ const traceability = await readNodeOutput("backend-test-traceability-gate-shell");
1641
+ if (!traceability.includes("Traceability gate: pass") ||
1642
+ !/Matched symbols:\s*\d+/.test(traceability)) {
1643
+ throw new Error("invalid traceability evidence: deterministic pass summary is missing");
1644
+ }
1645
+ const contracts = path.join(meta.runDir, "contracts");
1646
+ const context = {
1647
+ schemaVersion: 1,
1648
+ result: JSON.parse(await readFile(path.join(contracts, "backend-test-result.json"), "utf8")),
1649
+ manifest: JSON.parse(await readFile(path.join(contracts, "backend-test-case-manifest.json"), "utf8")),
1650
+ classification: JSON.parse(await readFile(path.join(contracts, "backend-test-classification.json"), "utf8")),
1651
+ caseReview: {
1652
+ verdict: reviewVerdicts[0].slice("VERDICT: ".length),
1653
+ evidence: caseReview,
1654
+ },
1655
+ traceability: { status: "pass", evidence: traceability },
1656
+ };
1657
+ outputs.push(`classification=${classification.path}`, ...selected.map((result) => result.stdout), JSON.stringify(context, null, 2));
1658
+ }
1659
+ else {
1660
+ throw new Error(`unsupported backend-test pipeline: ${pipeline}`);
1661
+ }
1662
+ return {
1663
+ ok: true,
1664
+ stdout: outputs.filter(Boolean).join("\n"),
1665
+ stderr: "",
1666
+ failureCategory: "success",
1667
+ durationMs: Date.now() - started,
1668
+ };
1669
+ }
1670
+ catch (error) {
1671
+ return {
1672
+ ok: false,
1673
+ stdout: "",
1674
+ stderr: error instanceof Error ? error.message : String(error),
1675
+ failureCategory: "invalid-output",
1676
+ durationMs: Date.now() - started,
1677
+ };
1678
+ }
1679
+ }
1680
+ async function executeBackendTestPipelineWithWriteGuard(input, meta) {
1681
+ let beforeStatus;
1682
+ try {
1683
+ beforeStatus = await readGitStatusPorcelain(input.cwd);
1684
+ }
1685
+ catch {
1686
+ // Non-git cwd: keep the same documented limitation as normal shell nodes.
1687
+ beforeStatus = undefined;
1688
+ }
1689
+ const result = await executeBackendTestPipeline(input, meta);
1690
+ if (beforeStatus === undefined)
1691
+ return result;
1692
+ try {
1693
+ const guard = await runShellWriteGuard({
1694
+ rootCwd: input.cwd,
1695
+ task: input.task,
1696
+ beforeStatus,
1697
+ });
1698
+ if (guard.ok)
1699
+ return result;
1700
+ const detail = `write guard failed: ${guard.violations.join(", ")}`;
1701
+ return {
1702
+ ...result,
1703
+ ok: false,
1704
+ stderr: [result.stderr, detail].filter(Boolean).join("\n\n"),
1705
+ failureCategory: "write-guard",
1706
+ };
1707
+ }
1708
+ catch (error) {
1709
+ const detail = `write guard failed: git status unavailable: ${error instanceof Error ? error.message : String(error)}`;
1710
+ return {
1711
+ ...result,
1712
+ ok: false,
1713
+ stderr: [result.stderr, detail].filter(Boolean).join("\n\n"),
1714
+ failureCategory: "write-guard",
1715
+ };
1716
+ }
1717
+ }
1718
+ async function executePipelineCommands(input, meta, overrideCommands) {
1719
+ const shell = input.task.shell;
1720
+ const cwd = resolveShellCwd(input.cwd, shell.cwd);
1721
+ const commands = overrideCommands ?? shell.commands;
1722
+ const results = [];
1723
+ for (const command of commands) {
1724
+ const commandNumber = results.length + 1;
1725
+ const result = await executeShellCommand({
1726
+ command,
1727
+ cwd,
1728
+ timeoutMs: shell.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS,
1729
+ envAllowlist: shell.envAllowlist,
1730
+ dagRunMeta: { runDir: meta.runDir, runId: meta.runId },
1731
+ reportActivity: input.reportActivity,
1732
+ outputArtifacts: {
1733
+ stdoutPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stdout.txt`),
1734
+ stderrPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stderr.txt`),
1735
+ },
1736
+ });
1737
+ results.push(result);
1738
+ if (!result.ok)
1739
+ break;
1740
+ }
1741
+ return results;
1742
+ }
1743
+ async function executeFrontendVerificationBundle(input, meta) {
1744
+ const started = Date.now();
1745
+ const shell = input.task.shell;
1746
+ const bundle = shell.frontendVerificationBundle;
1747
+ const cwd = resolveShellCwd(input.cwd, shell.cwd);
1748
+ const results = [];
1749
+ let beforeStatus;
1750
+ try {
1751
+ beforeStatus = await readGitStatusPorcelain(input.cwd);
1752
+ }
1753
+ catch {
1754
+ beforeStatus = undefined;
1755
+ }
1756
+ const lintResults = [];
1757
+ for (const command of bundle.lintCommands ?? []) {
1758
+ const commandNumber = results.length + 1;
1759
+ const result = await executeShellCommand({
1760
+ command,
1761
+ cwd,
1762
+ timeoutMs: shell.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS,
1763
+ envAllowlist: shell.envAllowlist,
1764
+ dagRunMeta: { runDir: meta.runDir, runId: meta.runId },
1765
+ reportActivity: input.reportActivity,
1766
+ outputArtifacts: {
1767
+ stdoutPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stdout.txt`),
1768
+ stderrPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stderr.txt`),
1769
+ },
1770
+ });
1771
+ lintResults.push(result);
1772
+ results.push(result);
1773
+ }
1774
+ let lintAssessment;
1775
+ if ((bundle.lintCommands?.length ?? 0) > 0 &&
1776
+ bundle.lintBaselineNodeId &&
1777
+ (bundle.writerNodeIds?.length ?? 0) > 0) {
1778
+ lintAssessment = await materializeFrontendLintAssessment({
1779
+ runDir: meta.runDir,
1780
+ workspaceRoot: input.cwd,
1781
+ commands: bundle.lintCommands,
1782
+ results: lintResults,
1783
+ baselineNodeId: bundle.lintBaselineNodeId,
1784
+ writerNodeIds: bundle.writerNodeIds,
1785
+ });
1786
+ }
1787
+ const lintBlocked = lintAssessment?.status === "failed" ||
1788
+ lintAssessment?.status === "unavailable";
1789
+ const groups = [
1790
+ {
1791
+ name: "mock",
1792
+ commands: bundle.mockCommands,
1793
+ labels: bundle.mockEvidence?.commandLabels ?? [],
1794
+ },
1795
+ {
1796
+ name: "static",
1797
+ commands: bundle.staticCommands,
1798
+ labels: bundle.staticEvidence.commandLabels,
1799
+ },
1800
+ {
1801
+ name: "behavior",
1802
+ commands: bundle.behaviorCommands,
1803
+ labels: bundle.behaviorEvidence.commandLabels,
1804
+ },
1805
+ ];
1806
+ const successfulLabels = new Map();
1807
+ for (const group of lintBlocked ? [] : groups) {
1808
+ for (const [index, command] of group.commands.entries()) {
1809
+ const commandNumber = results.length + 1;
1810
+ const result = await executeShellCommand({
1811
+ command,
1812
+ cwd,
1813
+ timeoutMs: shell.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS,
1814
+ envAllowlist: shell.envAllowlist,
1815
+ dagRunMeta: { runDir: meta.runDir, runId: meta.runId },
1816
+ reportActivity: input.reportActivity,
1817
+ outputArtifacts: {
1818
+ stdoutPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stdout.txt`),
1819
+ stderrPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stderr.txt`),
1820
+ },
1821
+ });
1822
+ results.push(result);
1823
+ if (result.ok && group.labels[index]) {
1824
+ const labels = successfulLabels.get(group.name) ?? [];
1825
+ labels.push(group.labels[index]);
1826
+ successfulLabels.set(group.name, labels);
1827
+ }
1828
+ if (!result.ok)
1829
+ break;
1830
+ }
1831
+ }
1832
+ if (beforeStatus !== undefined) {
1833
+ const guard = await runShellWriteGuard({
1834
+ rootCwd: input.cwd,
1835
+ task: input.task,
1836
+ beforeStatus,
1837
+ });
1838
+ if (!guard.ok) {
1839
+ return {
1840
+ ok: false,
1841
+ stdout: "",
1842
+ stderr: `write guard failed: ${guard.violations.join(", ")}`,
1843
+ failureCategory: "write-guard",
1844
+ durationMs: Date.now() - started,
1845
+ };
1846
+ }
1847
+ }
1848
+ await writeDagNodeTextArtifact(meta.runDir, input.task.id, "result.summary.md", buildShellResultSummaryMarkdown({
1849
+ nodeId: input.task.id,
1850
+ runId: meta.runId,
1851
+ rootCwd: input.cwd,
1852
+ results,
1853
+ }));
1854
+ const commandResults = results.map((result) => ({
1855
+ ok: result.ok,
1856
+ exitCode: result.exitCode,
1857
+ failureCategory: result.failureCategory,
1858
+ command: result.command,
1859
+ }));
1860
+ const firstFailure = lintBlocked
1861
+ ? lintResults.find((result) => !result.ok)
1862
+ : results
1863
+ .filter((result) => !lintResults.includes(result))
1864
+ .find((result) => !result.ok);
1865
+ const lintSyntheticFailure = lintBlocked && !firstFailure
1866
+ ? {
1867
+ failureCategory: "invalid-output",
1868
+ stderr: lintAssessment?.blockingReasons.join("; ") ??
1869
+ "frontend lint assessment failed",
1870
+ }
1871
+ : undefined;
1872
+ let traceError;
1873
+ try {
1874
+ await runFrontendVerificationTraceGate({
1875
+ runDir: meta.runDir,
1876
+ workspaceRoot: input.cwd,
1877
+ evidence: {
1878
+ mock: {
1879
+ nodeId: input.task.id,
1880
+ commandLabels: successfulLabels.get("mock") ?? [],
1881
+ commandTexts: bundle.mockCommands.slice(0, successfulLabels.get("mock")?.length ?? 0),
1882
+ },
1883
+ static: {
1884
+ nodeId: input.task.id,
1885
+ commandLabels: successfulLabels.get("static") ?? [],
1886
+ },
1887
+ behavior: {
1888
+ nodeId: input.task.id,
1889
+ commandLabels: successfulLabels.get("behavior") ?? [],
1890
+ },
1891
+ },
1892
+ });
1893
+ }
1894
+ catch (error) {
1895
+ traceError = error instanceof Error ? error : new Error(String(error));
1896
+ }
1897
+ if (bundle.mode === "repair") {
1898
+ if (firstFailure || lintSyntheticFailure || traceError) {
1899
+ return {
1900
+ ok: false,
1901
+ stdout: summarizeCommandResults(results).stdout,
1902
+ stderr: firstFailure?.stderr ||
1903
+ lintSyntheticFailure?.stderr ||
1904
+ traceError?.message ||
1905
+ "frontend reverify failed",
1906
+ failureCategory: firstFailure?.failureCategory ??
1907
+ lintSyntheticFailure?.failureCategory ??
1908
+ "invalid-output",
1909
+ durationMs: Date.now() - started,
1910
+ ...{ commandResults },
1911
+ };
1912
+ }
1913
+ return {
1914
+ ok: true,
1915
+ stdout: lintAssessment?.status === "baseline-debt"
1916
+ ? "Frontend reverify bundle: pass with lint baseline-debt"
1917
+ : "Frontend reverify bundle: pass",
1918
+ stderr: "",
1919
+ failureCategory: "success",
1920
+ durationMs: Date.now() - started,
1921
+ ...{ commandResults },
1922
+ };
1923
+ }
1924
+ const failureFacts = [];
1925
+ if (firstFailure || lintSyntheticFailure) {
1926
+ const failureStdout = firstFailure
1927
+ ? [firstFailure.command, firstFailure.stdout].filter(Boolean).join("\n")
1928
+ : "";
1929
+ failureFacts.push({
1930
+ nodeId: input.task.id,
1931
+ record: {
1932
+ status: "FINISHED",
1933
+ failureCategory: firstFailure?.failureCategory ??
1934
+ lintSyntheticFailure?.failureCategory,
1935
+ // Keep classification scoped to the failed command. Aggregate
1936
+ // successful output may contain unrelated writeSet-like JSON.
1937
+ stdout: failureStdout,
1938
+ stderr: firstFailure?.stderr ?? lintSyntheticFailure?.stderr,
1939
+ commandResults,
1940
+ },
1941
+ });
1942
+ }
1943
+ if (traceError) {
1944
+ failureFacts.push({
1945
+ nodeId: "frontend-verification-trace-shell",
1946
+ record: {
1947
+ status: "FINISHED",
1948
+ failureCategory: "invalid-output",
1949
+ stderr: traceError.message,
1950
+ },
1951
+ });
1952
+ }
1953
+ try {
1954
+ const assessment = await runFrontendFailureAssessGate({
1955
+ runDir: meta.runDir,
1956
+ failureFacts,
1957
+ });
1958
+ await runFrontendRepairContractGate({ runDir: meta.runDir });
1959
+ return {
1960
+ ok: true,
1961
+ stdout: formatFrontendFailureAssessStdout(assessment),
1962
+ stderr: "",
1963
+ failureCategory: "success",
1964
+ durationMs: Date.now() - started,
1965
+ ...{ commandResults },
1966
+ };
1967
+ }
1968
+ catch (error) {
1969
+ return {
1970
+ ok: false,
1971
+ stdout: "",
1972
+ stderr: error instanceof Error ? error.message : String(error),
1973
+ failureCategory: "invalid-output",
1974
+ durationMs: Date.now() - started,
1975
+ ...{ commandResults },
1976
+ };
1977
+ }
1978
+ }
1979
+ async function executeFrontendTestCaseChecklist(input, meta) {
1980
+ const started = Date.now();
1981
+ try {
1982
+ const declaredAcIds = (meta.spec.sourceBinding?.requirementIds ?? []).filter((id) => /^AC(?:-[A-Z0-9]+)+$/i.test(id));
1983
+ const result = await validateFrontendCaseChecklist({
1984
+ workspaceRoot: input.cwd,
1985
+ declaredAcIds,
1986
+ });
1987
+ if (result.issues.length) {
1988
+ return {
1989
+ ok: false,
1990
+ stdout: "",
1991
+ stderr: `frontend-test checklist blocked: ${JSON.stringify(result.issues)}`,
1992
+ failureCategory: "invalid-output",
1993
+ durationMs: Date.now() - started,
1994
+ };
1995
+ }
1996
+ return {
1997
+ ok: true,
1998
+ stdout: `frontend-test checklist ok cases=${result.caseCount} source=${path.basename(result.manifestPath)}`,
1999
+ stderr: "",
2000
+ failureCategory: "success",
2001
+ durationMs: Date.now() - started,
2002
+ };
2003
+ }
2004
+ catch (error) {
2005
+ return {
2006
+ ok: false,
2007
+ stdout: "",
2008
+ stderr: error instanceof Error ? error.message : String(error),
2009
+ failureCategory: "invalid-output",
2010
+ durationMs: Date.now() - started,
2011
+ };
2012
+ }
2013
+ }
2014
+ async function executeFrontendTestL5Report(input, meta) {
2015
+ const started = Date.now();
2016
+ try {
2017
+ const output = await renderFrontendTestL5Report({
2018
+ workspaceRoot: input.cwd,
2019
+ runDir: meta.runDir,
2020
+ });
2021
+ return {
2022
+ ok: true,
2023
+ stdout: `Frontend L-5 report: ${output.htmlPath}\nMarkdown: ${output.markdownPath}\nStatus: ${output.metrics.status}`,
2024
+ stderr: "",
2025
+ failureCategory: "success",
2026
+ durationMs: Date.now() - started,
2027
+ };
2028
+ }
2029
+ catch (error) {
2030
+ return {
2031
+ ok: false,
2032
+ stdout: "",
2033
+ stderr: error instanceof Error ? error.message : String(error),
2034
+ failureCategory: "invalid-output",
2035
+ durationMs: Date.now() - started,
2036
+ };
2037
+ }
2038
+ }
2039
+ async function executeFrontendTestHtmlReport(input, meta) {
2040
+ const started = Date.now();
2041
+ try {
2042
+ const output = await renderFrontendTestHtmlReport({
2043
+ workspaceRoot: input.cwd,
2044
+ runDir: meta.runDir,
2045
+ });
2046
+ return {
2047
+ ok: true,
2048
+ stdout: `Frontend test report: ${output.htmlPath}\nMarkdown: ${output.markdownPath}\nOutcome: ${output.outcome}\nCases: ${output.caseCount}`,
2049
+ stderr: "",
2050
+ failureCategory: "success",
2051
+ durationMs: Date.now() - started,
2052
+ };
2053
+ }
2054
+ catch (error) {
2055
+ return {
2056
+ ok: false,
2057
+ stdout: "",
2058
+ stderr: error instanceof Error ? error.message : String(error),
2059
+ failureCategory: "invalid-output",
2060
+ durationMs: Date.now() - started,
2061
+ };
2062
+ }
2063
+ }
2064
+ async function executeFrontendTestEvidenceValidation(input) {
2065
+ const started = Date.now();
2066
+ try {
2067
+ const result = await validateFrontendCaseEvidence({
2068
+ workspaceRoot: input.cwd,
2069
+ });
2070
+ const output = `frontend case evidence validation cases=${result.cases} findings=${result.issues.length}${result.issues.length ? ` issues=${JSON.stringify(result.issues)}` : ""}`;
2071
+ if (result.hardFail) {
2072
+ return {
2073
+ ok: false,
2074
+ stdout: "",
2075
+ stderr: `frontend-test evidence hard-fail: ${JSON.stringify(result.issues)}`,
2076
+ failureCategory: "nonzero-exit",
2077
+ durationMs: Date.now() - started,
2078
+ };
2079
+ }
2080
+ return {
2081
+ ok: true,
2082
+ stdout: output,
2083
+ stderr: "",
2084
+ failureCategory: "success",
2085
+ durationMs: Date.now() - started,
2086
+ };
2087
+ }
2088
+ catch (error) {
2089
+ return {
2090
+ ok: false,
2091
+ stdout: "",
2092
+ stderr: error instanceof Error ? error.message : String(error),
2093
+ failureCategory: "invalid-output",
2094
+ durationMs: Date.now() - started,
2095
+ };
2096
+ }
2097
+ }
2098
+ async function executeFrontendTestCaseManifest(input, meta) {
2099
+ const started = Date.now();
2100
+ try {
2101
+ const gate = input.task.shell?.frontendTestCaseManifest ?? {};
2102
+ const declaredAcIds = gate.declaredAcIds ??
2103
+ (meta.spec.sourceBinding?.requirementIds ?? []).filter((id) => /^AC(?:-[A-Z0-9]+)+$/i.test(id));
2104
+ const checklist = await validateFrontendCaseChecklist({
2105
+ workspaceRoot: input.cwd,
2106
+ declaredAcIds,
2107
+ });
2108
+ if (checklist.issues.length) {
2109
+ return {
2110
+ ok: false,
2111
+ stdout: "",
2112
+ stderr: `frontend-test checklist blocked: ${JSON.stringify(checklist.issues)}`,
2113
+ failureCategory: "invalid-output",
2114
+ durationMs: Date.now() - started,
2115
+ };
2116
+ }
2117
+ const materialized = await materializeFrontendTestCaseManifest({
2118
+ workspaceRoot: input.cwd,
2119
+ maxCases: gate.maxCases,
2120
+ declaredAcIds,
2121
+ });
2122
+ return {
2123
+ ok: true,
2124
+ stdout: `${JSON.stringify({ cases: materialized.cases })}
2125
+ `,
2126
+ stderr: "",
2127
+ failureCategory: "success",
2128
+ durationMs: Date.now() - started,
2129
+ };
2130
+ }
2131
+ catch (error) {
2132
+ return {
2133
+ ok: false,
2134
+ stdout: "",
2135
+ stderr: error instanceof Error ? error.message : String(error),
2136
+ failureCategory: "invalid-output",
2137
+ durationMs: Date.now() - started,
2138
+ };
2139
+ }
2140
+ }
2141
+ /** Path-escape / cross-case evidence errors must hard-fail finalize (same class as validate.hardFail). */
2142
+ function isFrontendTestEvidencePathHardError(error) {
2143
+ const message = error instanceof Error ? error.message : String(error);
2144
+ return /unsafe evidence|escapes (?:evidence root|its allowed root)|evidence realpath|cross-case/i.test(message);
2145
+ }
2146
+ /**
2147
+ * Lean finalize: evidence validate + result materialize in one shell.
2148
+ * - Case-level missing/malformed evidence is advisory (issues / advisoryFindings);
2149
+ * the node still succeeds so reports can render incomplete/failed outcomes.
2150
+ * - Path escape / unsafe evidence roots hard-fail the node.
2151
+ * - Controller/contract errors (missing sourceBinding, empty manifest) still fail closed.
2152
+ */
2153
+ async function executeFrontendTestResultFinalize(input, meta) {
2154
+ const started = Date.now();
2155
+ let advisoryStdout = "";
2156
+ try {
2157
+ const evidence = await validateFrontendCaseEvidence({
2158
+ workspaceRoot: input.cwd,
2159
+ });
2160
+ advisoryStdout = `frontend case evidence validation cases=${evidence.cases} findings=${evidence.issues.length}${evidence.issues.length ? ` issues=${JSON.stringify(evidence.issues)}` : ""}`;
2161
+ // Advisory findings must not block materialize; only hardFail (path escape) does.
2162
+ if (evidence.hardFail) {
2163
+ return {
2164
+ ok: false,
2165
+ stdout: advisoryStdout,
2166
+ stderr: `frontend-test evidence hard-fail: ${JSON.stringify(evidence.issues)}`,
2167
+ failureCategory: "nonzero-exit",
2168
+ durationMs: Date.now() - started,
2169
+ };
2170
+ }
2171
+ const artifact = await materializeFrontendTestResult({
2172
+ runDir: meta.runDir,
2173
+ workspaceRoot: input.cwd,
2174
+ artifactName: "frontend-test-result.json",
2175
+ outputDir: "contracts",
2176
+ sourceBinding: meta.spec.sourceBinding,
2177
+ });
2178
+ return {
2179
+ ok: true,
2180
+ stdout: `${advisoryStdout}\nStructured artifact: ${artifact.path}\nSchema: ${artifact.schemaId}\nSHA-256: ${artifact.sha256}`,
2181
+ stderr: "",
2182
+ failureCategory: "success",
2183
+ durationMs: Date.now() - started,
2184
+ };
2185
+ }
2186
+ catch (error) {
2187
+ const detail = error instanceof Error ? error.message : String(error);
2188
+ if (isFrontendTestEvidencePathHardError(error)) {
2189
+ return {
2190
+ ok: false,
2191
+ stdout: advisoryStdout,
2192
+ stderr: `frontend-test evidence hard-fail: ${detail}`,
2193
+ failureCategory: "nonzero-exit",
2194
+ durationMs: Date.now() - started,
2195
+ };
2196
+ }
2197
+ return {
2198
+ ok: false,
2199
+ stdout: advisoryStdout,
2200
+ stderr: detail,
2201
+ failureCategory: "invalid-output",
2202
+ durationMs: Date.now() - started,
2203
+ };
2204
+ }
2205
+ }
2206
+ async function executeFrontendTestReports(input, meta) {
2207
+ const started = Date.now();
2208
+ try {
2209
+ const writeL5 = input.task.shell?.frontendTestReports?.l5 !== false;
2210
+ const lines = [];
2211
+ if (writeL5) {
2212
+ const l5 = await renderFrontendTestL5Report({
2213
+ workspaceRoot: input.cwd,
2214
+ runDir: meta.runDir,
2215
+ });
2216
+ lines.push(`Frontend L-5 report: ${l5.htmlPath}\nMarkdown: ${l5.markdownPath}\nStatus: ${l5.metrics.status}`);
2217
+ }
2218
+ const report = await renderFrontendTestHtmlReport({
2219
+ workspaceRoot: input.cwd,
2220
+ runDir: meta.runDir,
2221
+ });
2222
+ lines.push(`Frontend test report: ${report.htmlPath}\nMarkdown: ${report.markdownPath}\nOutcome: ${report.outcome}\nCases: ${report.caseCount}`);
2223
+ return {
2224
+ ok: true,
2225
+ stdout: lines.join("\n"),
2226
+ stderr: "",
2227
+ failureCategory: "success",
2228
+ durationMs: Date.now() - started,
2229
+ };
2230
+ }
2231
+ catch (error) {
2232
+ return {
2233
+ ok: false,
2234
+ stdout: "",
2235
+ stderr: error instanceof Error ? error.message : String(error),
2236
+ failureCategory: "invalid-output",
2237
+ durationMs: Date.now() - started,
2238
+ };
2239
+ }
2240
+ }
2241
+ async function executeFrontendLintBaseline(input, meta) {
2242
+ const started = Date.now();
2243
+ const shell = input.task.shell;
2244
+ const baseline = shell.frontendLintBaseline;
2245
+ const cwd = resolveShellCwd(input.cwd, shell.cwd);
2246
+ let beforeStatus;
2247
+ try {
2248
+ beforeStatus = await readGitStatusPorcelain(input.cwd);
2249
+ }
2250
+ catch {
2251
+ beforeStatus = undefined;
2252
+ }
2253
+ const results = [];
2254
+ for (const command of baseline.lintCommands) {
2255
+ const commandNumber = results.length + 1;
2256
+ results.push(await executeShellCommand({
2257
+ command,
2258
+ cwd,
2259
+ timeoutMs: shell.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS,
2260
+ envAllowlist: shell.envAllowlist,
2261
+ dagRunMeta: { runDir: meta.runDir, runId: meta.runId },
2262
+ outputArtifacts: {
2263
+ stdoutPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stdout.txt`),
2264
+ stderrPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stderr.txt`),
2265
+ },
2266
+ }));
2267
+ }
2268
+ let afterStatus;
2269
+ try {
2270
+ afterStatus = await readGitStatusPorcelain(input.cwd);
2271
+ }
2272
+ catch {
2273
+ afterStatus = undefined;
2274
+ }
2275
+ try {
2276
+ const materialized = await materializeFrontendLintBaseline({
2277
+ runDir: meta.runDir,
2278
+ workspaceRoot: input.cwd,
2279
+ commands: baseline.lintCommands,
2280
+ results,
2281
+ worktreeChanged: beforeStatus === undefined ||
2282
+ afterStatus === undefined ||
2283
+ beforeStatus !== afterStatus,
2284
+ });
2285
+ return {
2286
+ ok: true,
2287
+ stdout: `Frontend lint baseline: ${materialized.artifact.status}\nArtifact: ${materialized.ref.path}\nDiagnostics: ${materialized.artifact.diagnostics.length}`,
2288
+ stderr: materialized.artifact.reason ?? "",
2289
+ failureCategory: "success",
2290
+ durationMs: Date.now() - started,
2291
+ };
2292
+ }
2293
+ catch (error) {
2294
+ return {
2295
+ ok: false,
2296
+ stdout: "",
2297
+ stderr: error instanceof Error ? error.message : String(error),
2298
+ failureCategory: "invalid-output",
2299
+ durationMs: Date.now() - started,
2300
+ };
2301
+ }
2302
+ }
2303
+ export async function executeDagShellNode(input, meta) {
2304
+ const shell = input.task.shell;
2305
+ if (shell?.finalWriteSetApprovalGate) {
2306
+ const started = Date.now();
2307
+ try {
2308
+ const state = await readRunState(meta.runDir);
2309
+ const writer = meta.spec.tasks.find((task) => task.id === shell.finalWriteSetApprovalGate.writerNodeId);
2310
+ if (!writer)
2311
+ throw new Error("final write-set approval gate writer is missing");
2312
+ const authorization = parseAndValidateFinalWriteSetApproval({
2313
+ task: writer,
2314
+ spec: meta.spec,
2315
+ state,
2316
+ });
2317
+ if (!authorization.ok)
2318
+ throw new Error(authorization.reason);
2319
+ return {
2320
+ ok: true,
2321
+ stdout: JSON.stringify({
2322
+ status: "approved",
2323
+ writerNodeId: writer.id,
2324
+ approvalDigest: authorization.approvalDigest,
2325
+ effectiveWriteSet: authorization.effectiveWriteSet,
2326
+ }),
2327
+ stderr: "",
2328
+ failureCategory: "success",
2329
+ durationMs: Date.now() - started,
2330
+ };
2331
+ }
2332
+ catch (error) {
2333
+ return {
2334
+ ok: false,
2335
+ stdout: "",
2336
+ stderr: `final-write-set-approval-invalid: ${error instanceof Error ? error.message : String(error)}`,
2337
+ failureCategory: "invalid-output",
2338
+ durationMs: Date.now() - started,
2339
+ };
2340
+ }
2341
+ }
2342
+ if (shell?.frontendBrowserToolPreflight) {
2343
+ const started = Date.now();
2344
+ try {
2345
+ if (resolvePiBackend() === "cli-only") {
2346
+ throw new Error("browser-command-capability-unavailable: CODE_AGENT_PI_BACKEND=cli-only rejects SDK-only playwright_cli");
2347
+ }
2348
+ const sdkCapability = await checkPiSdkCustomToolCapability(input.cwd);
2349
+ if (!sdkCapability.ok) {
2350
+ throw new Error(`browser-command-capability-unavailable: ${sdkCapability.detail}`);
2351
+ }
2352
+ const toolProbe = await createPlaywrightCliTool({
2353
+ repoRoot: input.cwd,
2354
+ runDir: meta.runDir,
2355
+ nodeId: input.task.id,
2356
+ caseId: "frontend-browser-preflight",
2357
+ evidenceDir: "testcase/frontend/evidence/frontend-browser-preflight",
2358
+ // Capability-only registration probe. The tested URL is selected later by
2359
+ // retrieve-frontend-test-context-pi and validated by the environment shell.
2360
+ baseUrl: "http://localhost:5173/",
2361
+ inputRoot: "testcase/frontend/fixtures",
2362
+ });
2363
+ if (toolProbe.name !== "playwright_cli") {
2364
+ throw new Error("browser-command-capability-unavailable: structured playwright_cli tool registration failed");
2365
+ }
2366
+ const launcher = resolvePlaywrightCliLauncher(input.cwd);
2367
+ const result = await defaultPlaywrightCliCommandRunner({
2368
+ executable: launcher.executable,
2369
+ argv: [...launcher.argvPrefix, "--help"],
2370
+ cwd: input.cwd,
2371
+ timeoutMs: 30_000,
2372
+ env: { ...process.env, CI: process.env.CI ?? "1" },
2373
+ });
2374
+ const help = `${result.stdout}\n${result.stderr}`;
2375
+ if (result.exitCode !== 0 ||
2376
+ result.timedOut ||
2377
+ !["open", "close", "find", "snapshot", "click"].every((command) => new RegExp(`\\b${command}\\b`, "i").test(help))) {
2378
+ throw new Error("playwright-cli-contract-incompatible");
2379
+ }
2380
+ const capability = {
2381
+ schemaVersion: 1,
2382
+ status: "ready",
2383
+ tool: "playwright-cli",
2384
+ launcher: "verified-js-entry",
2385
+ sdkCustomToolCapability: true,
2386
+ structuredToolName: toolProbe.name,
2387
+ targetUrlAuthority: "retrieve-context-and-environment-probe",
2388
+ };
2389
+ await writeDagNodeJsonArtifact(meta.runDir, input.task.id, "frontend-browser-capability.json", capability);
2390
+ return {
2391
+ ok: true,
2392
+ stdout: JSON.stringify(capability),
2393
+ stderr: "",
2394
+ failureCategory: "success",
2395
+ durationMs: Date.now() - started,
2396
+ };
2397
+ }
2398
+ catch (error) {
2399
+ return {
2400
+ ok: false,
2401
+ stdout: "",
2402
+ stderr: `playwright-cli-unavailable: ${error instanceof Error ? error.message : String(error)}`,
2403
+ failureCategory: "tool-policy",
2404
+ durationMs: Date.now() - started,
2405
+ };
2406
+ }
2407
+ }
2408
+ if (shell?.frontendPrewriteGate) {
2409
+ const started = Date.now();
2410
+ try {
2411
+ const result = await runFrontendPrewriteGate({
2412
+ runDir: meta.runDir,
2413
+ config: shell.frontendPrewriteGate,
2414
+ sourceBinding: meta.spec.sourceBinding,
2415
+ workspaceRoot: input.cwd,
2416
+ repoRoot: input.cwd,
2417
+ });
2418
+ return {
2419
+ ok: true,
2420
+ stdout: formatFrontendPrewriteGateStdout(result),
2421
+ stderr: "",
2422
+ failureCategory: "success",
2423
+ durationMs: Date.now() - started,
2424
+ };
2425
+ }
2426
+ catch (error) {
2427
+ return {
2428
+ ok: false,
2429
+ stdout: "",
2430
+ stderr: error instanceof Error ? error.message : String(error),
2431
+ failureCategory: "invalid-output",
2432
+ durationMs: Date.now() - started,
2433
+ };
2434
+ }
2435
+ }
2436
+ if (shell?.frontendLintBaseline) {
2437
+ return executeFrontendLintBaseline(input, meta);
2438
+ }
2439
+ if (shell?.frontendVerificationBundle) {
2440
+ return executeFrontendVerificationBundle(input, meta);
2441
+ }
2442
+ if (shell?.frontendReviewContext) {
2443
+ const started = Date.now();
2444
+ try {
2445
+ const result = await runFrontendReviewContextGate({
2446
+ runDir: meta.runDir,
2447
+ workspaceRoot: input.cwd,
2448
+ requireBaseline: shell.frontendReviewContext.requireBaseline,
2449
+ });
2450
+ return {
2451
+ ok: true,
2452
+ stdout: formatFrontendReviewContextStdout(result),
2453
+ stderr: "",
2454
+ failureCategory: "success",
2455
+ durationMs: Date.now() - started,
2456
+ };
2457
+ }
2458
+ catch (error) {
2459
+ return {
2460
+ ok: false,
2461
+ stdout: "",
2462
+ stderr: error instanceof Error ? error.message : String(error),
2463
+ failureCategory: "invalid-output",
2464
+ durationMs: Date.now() - started,
2465
+ };
2466
+ }
2467
+ }
2468
+ if (shell?.frontendTestCaseChecklist) {
2469
+ return executeFrontendTestCaseChecklist(input, meta);
2470
+ }
2471
+ if (shell?.frontendTestCaseManifest) {
2472
+ return executeFrontendTestCaseManifest(input, meta);
2473
+ }
2474
+ if (shell?.frontendTestEvidenceValidation) {
2475
+ return executeFrontendTestEvidenceValidation(input);
2476
+ }
2477
+ if (shell?.frontendTestResultFinalize) {
2478
+ return executeFrontendTestResultFinalize(input, meta);
2479
+ }
2480
+ if (shell?.frontendTestL5Report) {
2481
+ return executeFrontendTestL5Report(input, meta);
2482
+ }
2483
+ if (shell?.frontendTestHtmlReport) {
2484
+ return executeFrontendTestHtmlReport(input, meta);
2485
+ }
2486
+ if (shell?.frontendTestReports) {
2487
+ return executeFrontendTestReports(input, meta);
2488
+ }
2489
+ if (shell?.backendTestPipeline) {
2490
+ return executeBackendTestPipelineWithWriteGuard(input, meta);
2491
+ }
2492
+ if (shell?.jsonArtifactGate) {
2493
+ const started = Date.now();
2494
+ const backendLayout = resolveBackendTestLayout(meta.spec.backendTestLayout
2495
+ ? {
2496
+ testRoot: meta.spec.backendTestLayout.testRoot,
2497
+ markdownRel: meta.spec.backendTestLayout.markdownRel,
2498
+ scriptRel: meta.spec.backendTestLayout.scriptRel,
2499
+ }
2500
+ : undefined);
2501
+ try {
2502
+ const gate = shell.jsonArtifactGate;
2503
+ const sourceNodeId = gate.fallbackFromNodeIds?.length
2504
+ ? await resolveJsonArtifactSourceNodeId({
2505
+ runDir: meta.runDir,
2506
+ fromNodeId: gate.fromNodeId,
2507
+ fallbackFromNodeIds: gate.fallbackFromNodeIds,
2508
+ })
2509
+ : gate.fromNodeId;
2510
+ let artifact;
2511
+ switch (gate.schemaId) {
2512
+ case "backend-test-analysis-v1":
2513
+ case "backend-test-analysis-v2":
2514
+ artifact = await materializeBackendTestAnalysisContract({
2515
+ runDir: meta.runDir,
2516
+ fromNodeId: sourceNodeId,
2517
+ artifactName: gate.artifactName,
2518
+ outputDir: gate.outputDir,
2519
+ sourceBinding: meta.spec.sourceBinding,
2520
+ });
2521
+ break;
2522
+ case "backend-test-execution-v1":
2523
+ artifact = await materializeBackendTestExecutionContract({
2524
+ runDir: meta.runDir,
2525
+ fromNodeId: sourceNodeId,
2526
+ artifactName: gate.artifactName,
2527
+ outputDir: gate.outputDir,
2528
+ allowedTestRoot: backendLayout.testRoot,
2529
+ });
2530
+ break;
2531
+ case "backend-test-result-v1":
2532
+ artifact = await materializeBackendTestResultFromRunDir({
2533
+ runDir: meta.runDir,
2534
+ fromNodeId: sourceNodeId,
2535
+ artifactName: gate.artifactName,
2536
+ outputDir: gate.outputDir,
2537
+ junitRelativePath: gate.junitRelativePath,
2538
+ testRoot: backendLayout.testRoot,
2539
+ });
2540
+ break;
2541
+ case "backend-test-classification-v1":
2542
+ artifact = await materializeBackendTestClassification({
2543
+ runDir: meta.runDir,
2544
+ fromNodeId: sourceNodeId,
2545
+ artifactName: gate.artifactName,
2546
+ outputDir: gate.outputDir,
2547
+ });
2548
+ break;
2549
+ case "backend-test-semantic-review-v1":
2550
+ artifact = await materializeBackendTestSemanticReview({
2551
+ runDir: meta.runDir,
2552
+ fromNodeId: sourceNodeId,
2553
+ artifactName: gate.artifactName,
2554
+ outputDir: gate.outputDir,
2555
+ });
2556
+ break;
2557
+ case "backend-test-case-manifest-v1":
2558
+ artifact = await materializeBackendTestCaseManifest({
2559
+ runDir: meta.runDir,
2560
+ fromNodeId: sourceNodeId,
2561
+ artifactName: gate.artifactName,
2562
+ outputDir: gate.outputDir,
2563
+ sourceBinding: meta.spec.sourceBinding,
2564
+ });
2565
+ break;
2566
+ case "frontend-implementation-contract-v1":
2567
+ artifact = await materializeFrontendImplementationContract({
2568
+ runDir: meta.runDir,
2569
+ fromNodeId: sourceNodeId,
2570
+ artifactName: gate.artifactName,
2571
+ outputDir: gate.outputDir,
2572
+ sourceBinding: meta.spec.sourceBinding,
2573
+ });
2574
+ break;
2575
+ case "frontend-test-result-v1":
2576
+ artifact = await materializeFrontendTestResult({
2577
+ runDir: meta.runDir,
2578
+ workspaceRoot: input.cwd,
2579
+ artifactName: gate.artifactName,
2580
+ outputDir: gate.outputDir,
2581
+ sourceBinding: meta.spec.sourceBinding,
2582
+ });
2583
+ break;
2584
+ default:
2585
+ throw new Error(`unsupported jsonArtifactGate.schemaId: ${String(gate.schemaId)}`);
2586
+ }
2587
+ return {
2588
+ ok: true,
2589
+ stdout: `Structured artifact: ${artifact.path}\nSchema: ${artifact.schemaId}\nSHA-256: ${artifact.sha256}`,
2590
+ stderr: "",
2591
+ failureCategory: "success",
2592
+ durationMs: Date.now() - started,
2593
+ };
2594
+ }
2595
+ catch (error) {
2596
+ return {
2597
+ ok: false,
2598
+ stdout: "",
2599
+ stderr: error instanceof Error ? error.message : String(error),
2600
+ failureCategory: "invalid-output",
2601
+ durationMs: Date.now() - started,
2602
+ };
2603
+ }
2604
+ }
2605
+ if (shell?.commands?.length === 1 &&
2606
+ shell.commands[0] === "backend-test-traceability-gate") {
2607
+ const started = Date.now();
2608
+ try {
2609
+ const result = await runBackendTestTraceabilityGate({
2610
+ runDir: meta.runDir,
2611
+ workspaceRoot: input.cwd,
2612
+ });
2613
+ return {
2614
+ ok: true,
2615
+ stdout: formatTraceabilityGateStdout(result),
2616
+ stderr: "",
2617
+ failureCategory: "success",
2618
+ durationMs: Date.now() - started,
2619
+ };
2620
+ }
2621
+ catch (error) {
2622
+ return {
2623
+ ok: false,
2624
+ stdout: "",
2625
+ stderr: error instanceof Error ? error.message : String(error),
2626
+ failureCategory: "invalid-output",
2627
+ durationMs: Date.now() - started,
2628
+ };
2629
+ }
2630
+ }
2631
+ if (shell?.commands?.length === 1 &&
2632
+ shell.commands[0] === "frontend-verification-trace-gate") {
2633
+ const started = Date.now();
2634
+ try {
2635
+ const result = await runFrontendVerificationTraceGate({
2636
+ runDir: meta.runDir,
2637
+ workspaceRoot: input.cwd,
2638
+ });
2639
+ return {
2640
+ ok: true,
2641
+ stdout: formatFrontendVerificationTraceStdout(result),
2642
+ stderr: "",
2643
+ failureCategory: "success",
2644
+ durationMs: Date.now() - started,
2645
+ };
2646
+ }
2647
+ catch (error) {
2648
+ const stderr = error instanceof Error ? error.message : String(error);
2649
+ const recordNonZero = shell.nonZeroExitPolicy === "record";
2650
+ return {
2651
+ ok: recordNonZero,
2652
+ stdout: recordNonZero
2653
+ ? "nonZeroExitPolicy=record failureCategory=invalid-output\n"
2654
+ : "",
2655
+ stderr,
2656
+ failureCategory: "invalid-output",
2657
+ durationMs: Date.now() - started,
2658
+ };
2659
+ }
2660
+ }
2661
+ if (shell?.commands?.length === 1 &&
2662
+ shell.commands[0] === "frontend-worktree-diff-gate") {
2663
+ const started = Date.now();
2664
+ try {
2665
+ const result = await runFrontendWorktreeDiffGate({
2666
+ runDir: meta.runDir,
2667
+ workspaceRoot: input.cwd,
2668
+ });
2669
+ return {
2670
+ ok: true,
2671
+ stdout: formatFrontendWorktreeDiffStdout(result),
2672
+ stderr: "",
2673
+ failureCategory: "success",
2674
+ durationMs: Date.now() - started,
2675
+ };
2676
+ }
2677
+ catch (error) {
2678
+ return {
2679
+ ok: false,
2680
+ stdout: "",
2681
+ stderr: error instanceof Error ? error.message : String(error),
2682
+ failureCategory: "invalid-output",
2683
+ durationMs: Date.now() - started,
2684
+ };
2685
+ }
2686
+ }
2687
+ if (shell?.commands?.length === 1 &&
2688
+ shell.commands[0] === "frontend-failure-assess-gate") {
2689
+ const started = Date.now();
2690
+ try {
2691
+ const result = await runFrontendFailureAssessGate({
2692
+ runDir: meta.runDir,
2693
+ });
2694
+ return {
2695
+ ok: true,
2696
+ stdout: formatFrontendFailureAssessStdout(result),
2697
+ stderr: "",
2698
+ failureCategory: "success",
2699
+ durationMs: Date.now() - started,
2700
+ };
2701
+ }
2702
+ catch (error) {
2703
+ return {
2704
+ ok: false,
2705
+ stdout: "",
2706
+ stderr: error instanceof Error ? error.message : String(error),
2707
+ failureCategory: "invalid-output",
2708
+ durationMs: Date.now() - started,
2709
+ };
2710
+ }
2711
+ }
2712
+ if (shell?.commands?.length === 1 &&
2713
+ shell.commands[0] === "frontend-repair-contract-gate") {
2714
+ const started = Date.now();
2715
+ try {
2716
+ const result = await runFrontendRepairContractGate({
2717
+ runDir: meta.runDir,
2718
+ });
2719
+ return {
2720
+ ok: true,
2721
+ stdout: formatFrontendRepairContractStdout(result.assessment),
2722
+ stderr: "",
2723
+ failureCategory: "success",
2724
+ durationMs: Date.now() - started,
2725
+ };
2726
+ }
2727
+ catch (error) {
2728
+ return {
2729
+ ok: false,
2730
+ stdout: "",
2731
+ stderr: error instanceof Error ? error.message : String(error),
2732
+ failureCategory: "invalid-output",
2733
+ durationMs: Date.now() - started,
2734
+ };
2735
+ }
2736
+ }
2737
+ if (shell?.projectGovernanceGate) {
2738
+ const started = Date.now();
2739
+ try {
2740
+ const state = await readRunState(meta.runDir);
2741
+ if (!state.projectGovernanceContextRef) {
2742
+ throw new Error("project governance gate requires projectGovernanceContextRef in run state");
2743
+ }
2744
+ const context = await readProjectGovernanceContext(meta.runDir, state.projectGovernanceContextRef, { expectedRunId: meta.runId });
2745
+ if (!context.applicable) {
2746
+ return {
2747
+ ok: true,
2748
+ stdout: "Project governance gate: not applicable for this writer changeset.",
2749
+ stderr: "",
2750
+ failureCategory: "success",
2751
+ durationMs: Date.now() - started,
2752
+ };
2753
+ }
2754
+ }
2755
+ catch (error) {
2756
+ return {
2757
+ ok: false,
2758
+ stdout: "",
2759
+ stderr: error instanceof Error ? error.message : String(error),
2760
+ failureCategory: "invalid-output",
2761
+ durationMs: Date.now() - started,
2762
+ };
2763
+ }
2764
+ }
2765
+ const commands = shell ? resolveShellCommands(shell) : [];
2766
+ if (!shell || commands.length === 0) {
2767
+ throw new Error(`shell task ${input.task.id} requires shell.preset, shell.verdictGate, and/or non-empty shell.commands`);
2768
+ }
2769
+ const started = Date.now();
2770
+ if (shell.mavenVerificationPlan) {
2771
+ try {
2772
+ assertMavenPlanFresh(input.cwd, shell.mavenVerificationPlan, { shellCommands: commands });
2773
+ }
2774
+ catch (error) {
2775
+ const message = error instanceof MavenPlanStaleError
2776
+ ? error.message
2777
+ : error instanceof Error
2778
+ ? error.message
2779
+ : String(error);
2780
+ const staleMessage = message.includes("verification-plan-stale")
2781
+ ? message
2782
+ : `verification-plan-stale: ${message}`;
2783
+ const staleResults = commands.map((command) => ({
2784
+ command,
2785
+ cwd: input.cwd,
2786
+ durationMs: 0,
2787
+ wallDurationMs: 0,
2788
+ startedAt: new Date().toISOString(),
2789
+ finishedAt: new Date().toISOString(),
2790
+ exitCode: null,
2791
+ failureCategory: "verification-plan-stale",
2792
+ ok: false,
2793
+ stderr: staleMessage,
2794
+ stderrBytes: Buffer.byteLength(staleMessage),
2795
+ stderrTruncated: false,
2796
+ stdout: "",
2797
+ stdoutBytes: 0,
2798
+ stdoutTruncated: false,
2799
+ timedOut: false,
2800
+ }));
2801
+ await writeDagNodeTextArtifact(meta.runDir, input.task.id, "result.summary.md", buildShellResultSummaryMarkdown({
2802
+ nodeId: input.task.id,
2803
+ runId: meta.runId,
2804
+ rootCwd: input.cwd,
2805
+ results: staleResults,
2806
+ }));
2807
+ return {
2808
+ ok: false,
2809
+ stdout: "",
2810
+ stderr: `${staleMessage}\nRegenerate/validate the DAG before running Maven verification.`,
2811
+ failureCategory: "verification-plan-stale",
2812
+ durationMs: Date.now() - started,
2813
+ ...{
2814
+ commandResults: staleResults.map((result) => ({
2815
+ ok: result.ok,
2816
+ exitCode: result.exitCode,
2817
+ failureCategory: result.failureCategory,
2818
+ command: result.command,
2819
+ })),
2820
+ },
2821
+ };
2822
+ }
2823
+ }
2824
+ const cwd = resolveShellCwd(input.cwd, shell.cwd);
2825
+ const timeoutMs = shell.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
2826
+ const failFast = shell.failFast !== false;
2827
+ const results = [];
2828
+ const executedCommandKeys = new Set();
2829
+ let beforeStatus;
2830
+ let workspaceFingerprint;
2831
+ try {
2832
+ beforeStatus = await readGitStatusPorcelain(input.cwd);
2833
+ }
2834
+ catch {
2835
+ // Non-git cwd: skip write guard (documented limitation).
2836
+ beforeStatus = undefined;
2837
+ }
2838
+ const reuseEligible = shouldReuseSuccessfulShellCommands(shell);
2839
+ let commandContractKey;
2840
+ let cache;
2841
+ if (reuseEligible) {
2842
+ try {
2843
+ const governanceTaskId = meta.spec.sourceBinding?.taskId?.trim();
2844
+ const checkpoint = await captureWorkspaceCheckpoint(input.cwd, {
2845
+ ...(governanceTaskId ? { taskId: governanceTaskId } : {}),
2846
+ });
2847
+ workspaceFingerprint = checkpoint.fingerprint;
2848
+ }
2849
+ catch {
2850
+ // Fingerprint unavailable: do not create cache / do not reuse.
2851
+ workspaceFingerprint = undefined;
2852
+ }
2853
+ commandContractKey = buildShellCommandContractKey({
2854
+ cwd,
2855
+ timeoutMs,
2856
+ envAllowlist: shell.envAllowlist,
2857
+ failFast,
2858
+ nonZeroExitPolicy: shell.nonZeroExitPolicy,
2859
+ controllerInput: {
2860
+ sourceBinding: meta.spec.sourceBinding,
2861
+ taskContractBinding: meta.spec.taskContractBinding,
2862
+ runtimeContract: meta.spec.runtimeContract,
2863
+ },
2864
+ });
2865
+ const cacheKey = workspaceFingerprint
2866
+ ? shellCommandCacheKey(meta.runId, workspaceFingerprint, commandContractKey)
2867
+ : undefined;
2868
+ const existing = cacheKey ? sameRunShellCommandCaches.get(cacheKey) : undefined;
2869
+ if (existing && existing.runId === meta.runId && existing.contractKey === commandContractKey) {
2870
+ cache = existing;
2871
+ }
2872
+ else if (cacheKey && workspaceFingerprint) {
2873
+ cache = {
2874
+ runId: meta.runId,
2875
+ contractKey: commandContractKey,
2876
+ workspaceFingerprint,
2877
+ byCommand: new Map(),
2878
+ };
2879
+ sameRunShellCommandCaches.set(cacheKey, cache);
2880
+ }
2881
+ }
2882
+ for (const command of commands) {
2883
+ const commandNumber = results.length + 1;
2884
+ // Cache each contractual occurrence independently. A result written for an
2885
+ // earlier duplicate in this round must not satisfy a later occurrence.
2886
+ const commandCacheKey = command;
2887
+ const cached = cache?.byCommand.get(commandCacheKey);
2888
+ if (cached && !executedCommandKeys.has(commandCacheKey)) {
2889
+ const reusedAt = new Date().toISOString();
2890
+ results.push({
2891
+ command,
2892
+ cwd: cached.cwd,
2893
+ durationMs: 0,
2894
+ wallDurationMs: 0,
2895
+ executionDurationMs: cached.durationMs,
2896
+ startedAt: reusedAt,
2897
+ finishedAt: reusedAt,
2898
+ sourceNodeId: cached.sourceNodeId,
2899
+ sourceReceiptNodeId: cached.sourceReceiptNodeId,
2900
+ exitCode: cached.exitCode,
2901
+ failureCategory: cached.failureCategory,
2902
+ ok: true,
2903
+ stderr: cached.stderr,
2904
+ stderrBytes: cached.stderrBytes,
2905
+ stderrTruncated: cached.stderrTruncated,
2906
+ stdout: cached.stdout,
2907
+ stdoutBytes: cached.stdoutBytes,
2908
+ stdoutTruncated: cached.stdoutTruncated,
2909
+ timedOut: cached.timedOut,
2910
+ reused: true,
2911
+ });
2912
+ continue;
2913
+ }
2914
+ const result = await executeShellCommand({
2915
+ command,
2916
+ cwd,
2917
+ timeoutMs,
2918
+ envAllowlist: shell.envAllowlist,
2919
+ dagRunMeta: { runDir: meta.runDir, runId: meta.runId },
2920
+ reportActivity: input.reportActivity,
2921
+ outputArtifacts: {
2922
+ stdoutPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stdout.txt`),
2923
+ stderrPath: path.join(meta.runDir, input.task.id, "commands", `${commandNumber}.stderr.txt`),
2924
+ },
2925
+ });
2926
+ results.push(result);
2927
+ executedCommandKeys.add(commandCacheKey);
2928
+ if (result.ok && cache) {
2929
+ cache.byCommand.set(commandCacheKey, {
2930
+ ok: true,
2931
+ exitCode: result.exitCode,
2932
+ failureCategory: "success",
2933
+ stdout: result.stdout,
2934
+ stderr: result.stderr,
2935
+ stdoutBytes: result.stdoutBytes,
2936
+ stderrBytes: result.stderrBytes,
2937
+ stdoutTruncated: result.stdoutTruncated,
2938
+ stderrTruncated: result.stderrTruncated,
2939
+ timedOut: result.timedOut,
2940
+ durationMs: result.durationMs,
2941
+ startedAt: result.startedAt ?? new Date().toISOString(),
2942
+ finishedAt: result.finishedAt ?? new Date().toISOString(),
2943
+ sourceNodeId: input.task.id,
2944
+ sourceReceiptNodeId: input.task.id,
2945
+ cwd: result.cwd,
2946
+ });
2947
+ }
2948
+ if (!result.ok && failFast)
2949
+ break;
2950
+ }
2951
+ let writeGuardViolations = [];
2952
+ let writeGuardOk = true;
2953
+ if (beforeStatus !== undefined) {
2954
+ try {
2955
+ const guard = await runShellWriteGuard({
2956
+ rootCwd: input.cwd,
2957
+ task: input.task,
2958
+ beforeStatus,
2959
+ });
2960
+ writeGuardOk = guard.ok;
2961
+ writeGuardViolations = guard.violations;
2962
+ }
2963
+ catch (error) {
2964
+ writeGuardOk = false;
2965
+ writeGuardViolations = [
2966
+ `git status unavailable: ${error instanceof Error ? error.message : String(error)}`,
2967
+ ];
2968
+ }
2969
+ }
2970
+ // Write-guard or workspace mutation invalidates same-run success reuse.
2971
+ if (!writeGuardOk && reuseEligible && workspaceFingerprint && commandContractKey) {
2972
+ sameRunShellCommandCaches.delete(shellCommandCacheKey(meta.runId, workspaceFingerprint, commandContractKey));
2973
+ }
2974
+ const commandsOk = results.every((result) => result.ok);
2975
+ const recordNonZero = shell.nonZeroExitPolicy === "record";
2976
+ const ok = (commandsOk || recordNonZero) && writeGuardOk;
2977
+ const firstFailure = results.find((result) => !result.ok);
2978
+ const receipt = {
2979
+ schemaVersion: 1,
2980
+ runId: meta.runId,
2981
+ nodeId: input.task.id,
2982
+ workspaceFingerprint: workspaceFingerprint ?? null,
2983
+ failFast,
2984
+ commands: results.map((result) => ({
2985
+ command: result.command,
2986
+ ok: result.ok,
2987
+ exitCode: result.exitCode,
2988
+ failureCategory: result.failureCategory,
2989
+ startedAt: result.startedAt,
2990
+ finishedAt: result.finishedAt,
2991
+ wallDurationMs: result.wallDurationMs,
2992
+ executionDurationMs: result.executionDurationMs ?? result.durationMs,
2993
+ reused: result.reused === true,
2994
+ reuseSource: result.reused
2995
+ ? { nodeId: result.sourceNodeId, receiptNodeId: result.sourceReceiptNodeId }
2996
+ : null,
2997
+ subchecks: parseCheckRepoSubcheckDurations(result),
2998
+ })),
2999
+ };
3000
+ await writeDagNodeJsonArtifact(meta.runDir, input.task.id, "shell-command-receipt.json", receipt);
3001
+ const summaryMarkdown = buildShellResultSummaryMarkdown({
3002
+ nodeId: input.task.id,
3003
+ runId: meta.runId,
3004
+ rootCwd: input.cwd,
3005
+ results,
3006
+ writeGuardViolations: writeGuardOk ? undefined : writeGuardViolations,
3007
+ });
3008
+ const receiptMarkdown = [
3009
+ summaryMarkdown,
3010
+ "",
3011
+ "## Same-run reuse receipt",
3012
+ "",
3013
+ `| workspaceFingerprint | ${workspaceFingerprint ?? "(unavailable)"} |`,
3014
+ `| failFast | ${failFast} |`,
3015
+ "",
3016
+ "| # | ok | exitCode | reused | command |",
3017
+ "|---|----|----------|--------|---------|",
3018
+ ...results.map((result, index) => `| ${index + 1} | ${result.ok} | ${result.exitCode ?? "null"} | ${result.reused === true} | ${result.command.replace(/\|/g, "\\|")} |`),
3019
+ ].join("\n");
3020
+ await writeDagNodeTextArtifact(meta.runDir, input.task.id, "result.summary.md", receiptMarkdown);
3021
+ const aggregate = summarizeCommandResults(results);
3022
+ const stderrParts = [aggregate.stderr];
3023
+ if (!writeGuardOk) {
3024
+ stderrParts.push(`write guard failed: ${writeGuardViolations.join(", ")}`);
3025
+ }
3026
+ let failureCategory = "success";
3027
+ if (!commandsOk) {
3028
+ failureCategory = firstFailure?.failureCategory ?? "nonzero-exit";
3029
+ }
3030
+ else if (!writeGuardOk) {
3031
+ failureCategory = "write-guard";
3032
+ }
3033
+ const commandResults = results.map((result) => ({
3034
+ ok: result.ok,
3035
+ exitCode: result.exitCode,
3036
+ failureCategory: result.failureCategory,
3037
+ command: result.command,
3038
+ reused: result.reused === true,
3039
+ }));
3040
+ const recordPrefix = recordNonZero && !commandsOk
3041
+ ? `nonZeroExitPolicy=record exitCode=${firstFailure?.exitCode ?? "null"}\n`
3042
+ : "";
3043
+ const reuseNote = results.some((result) => result.reused)
3044
+ ? `same-run-reuse workspaceFingerprint=${workspaceFingerprint ?? "unknown"}\n`
3045
+ : "";
3046
+ return {
3047
+ ok,
3048
+ stdout: `${recordPrefix}${reuseNote}${aggregate.stdout}`,
3049
+ stderr: stderrParts.filter(Boolean).join("\n\n"),
3050
+ failureCategory,
3051
+ durationMs: Date.now() - started,
3052
+ // extra field consumed by node-execution if supported; also embedded in stdout marker
3053
+ ...{ commandResults },
3054
+ };
3055
+ }