@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,3085 @@
1
+ import { execFile, spawn } from "node:child_process";
2
+ import { lstatSync, watch } from "node:fs";
3
+ import { createHash, randomUUID } from "node:crypto";
4
+ import { lstat, mkdtemp, mkdir, readFile, readdir, readlink, realpath, rename, rm, stat, writeFile, } from "node:fs/promises";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { resolveAdapter } from "../adapters/index.js";
9
+ import { createNpmUpdateClient } from "../cli/update/npm-client.js";
10
+ import { auditDocs } from "../governance/checks.js";
11
+ import { harnessManifestSchema } from "../governance/manifest-types.js";
12
+ import { isInitRuntimeActive, } from "../shared/runtime-activity.js";
13
+ import { MAX_OVERFLOW_RECOVERIES, MAX_SESSION_RETRIES, OPENCODE_CONTEXT_OVERFLOW_COMPACT_PLUGIN_PATH, OPENCODE_TRANSIENT_RETRY_PLUGIN_PATH, PI_CONTEXT_OVERFLOW_EXTENSION_PATH, PI_PROJECT_SETTINGS_PATH, inspectProjectPiSettings, } from "./client-recovery.js";
14
+ import { applyInitUpdate, checkInitUpdate, initializeLoopAgentProject, runInitDoctor, } from "./init.js";
15
+ const RUN_ROOT = ".harness/init-upgrades";
16
+ const GLOBAL_LOCK_NAME = ".writer-lock";
17
+ const INIT_SURFACE_STATE_PATH = ".harness/init-surface.json";
18
+ const RECOVERY_PATHS = [
19
+ OPENCODE_TRANSIENT_RETRY_PLUGIN_PATH,
20
+ OPENCODE_CONTEXT_OVERFLOW_COMPACT_PLUGIN_PATH,
21
+ PI_CONTEXT_OVERFLOW_EXTENSION_PATH,
22
+ PI_PROJECT_SETTINGS_PATH,
23
+ ];
24
+ function sha256(value) {
25
+ return createHash("sha256").update(value).digest("hex");
26
+ }
27
+ function validRunId(runId) {
28
+ return /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(runId);
29
+ }
30
+ function isRecord(value) {
31
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
32
+ }
33
+ function isSha256(value) {
34
+ return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
35
+ }
36
+ function isHomeFingerprint(value) {
37
+ if (!isRecord(value))
38
+ return false;
39
+ const keys = Object.keys(value).sort();
40
+ if (value.present === false) {
41
+ return keys.length === 1 && keys[0] === "present";
42
+ }
43
+ return (value.present === true &&
44
+ keys.length === 2 &&
45
+ keys[0] === "present" &&
46
+ keys[1] === "sha256" &&
47
+ isSha256(value.sha256));
48
+ }
49
+ /**
50
+ * Shape guard for the controller-owned gitignore migration assessment.
51
+ * `assessGitignoreMigration` is the single schema fact source; status/report
52
+ * reads fail closed on any shape that the writer could not have produced
53
+ * (AC-005). `queryError` may be absent and arrays may be empty; extra keys are
54
+ * tolerated for forward compatibility.
55
+ */
56
+ function isGitignoreMigrationAssessment(value) {
57
+ if (!isRecord(value))
58
+ return false;
59
+ const assessment = value;
60
+ if (assessment.schemaVersion !== 1)
61
+ return false;
62
+ if (typeof assessment.managedBlockRefreshed !== "boolean")
63
+ return false;
64
+ if (typeof assessment.needsAgentsReview !== "boolean")
65
+ return false;
66
+ if (!isRecord(assessment.git))
67
+ return false;
68
+ const git = assessment.git;
69
+ if (typeof git.isRepo !== "boolean")
70
+ return false;
71
+ if (git.queryError !== undefined && typeof git.queryError !== "string") {
72
+ return false;
73
+ }
74
+ if (!Array.isArray(assessment.harnessTracked) ||
75
+ !assessment.harnessTracked.every((entry) => typeof entry === "string")) {
76
+ return false;
77
+ }
78
+ if (!Array.isArray(assessment.agentsTracked) ||
79
+ !assessment.agentsTracked.every((entry) => typeof entry === "string")) {
80
+ return false;
81
+ }
82
+ if (!Array.isArray(assessment.stagedRisk) ||
83
+ !assessment.stagedRisk.every((entry) => typeof entry === "string")) {
84
+ return false;
85
+ }
86
+ if (!Array.isArray(assessment.followUp) ||
87
+ !assessment.followUp.every((entry) => typeof entry === "string")) {
88
+ return false;
89
+ }
90
+ if (!Array.isArray(assessment.recommendedCommands))
91
+ return false;
92
+ for (const entry of assessment.recommendedCommands) {
93
+ if (!isRecord(entry))
94
+ return false;
95
+ if (typeof entry.command !== "string" || typeof entry.note !== "string") {
96
+ return false;
97
+ }
98
+ }
99
+ return (assessment.status === "clean" ||
100
+ assessment.status === "recommended" ||
101
+ assessment.status === "blocked");
102
+ }
103
+ function cloneJson(value) {
104
+ return JSON.parse(JSON.stringify(value));
105
+ }
106
+ function serializeJson(value) {
107
+ return `${JSON.stringify(value, null, 2)}\n`;
108
+ }
109
+ /** Stable hash for controller authority records, independent of JSON key order. */
110
+ function canonicalJson(value) {
111
+ if (value === null || typeof value !== "object")
112
+ return JSON.stringify(value);
113
+ if (Array.isArray(value))
114
+ return `[${value.map(canonicalJson).join(",")}]`;
115
+ const record = value;
116
+ return `{${Object.keys(record)
117
+ .sort()
118
+ .map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)
119
+ .join(",")}}`;
120
+ }
121
+ function canonicalSha256(value) {
122
+ return sha256(canonicalJson(value));
123
+ }
124
+ function controllerStateProjection(state) {
125
+ return {
126
+ phase: state.phase,
127
+ status: state.status,
128
+ mergeTasks: cloneJson(state.mergeTasks),
129
+ ...(state.mergeGuard ? { mergeGuard: cloneJson(state.mergeGuard) } : {}),
130
+ mergeReceipts: cloneJson(state.mergeReceipts),
131
+ humanDecisions: cloneJson(state.humanDecisions),
132
+ actionReceipts: cloneJson(state.actionReceipts),
133
+ };
134
+ }
135
+ function sameJsonValue(left, right) {
136
+ if (left === right)
137
+ return true;
138
+ if (Array.isArray(left) || Array.isArray(right)) {
139
+ return (Array.isArray(left) &&
140
+ Array.isArray(right) &&
141
+ left.length === right.length &&
142
+ left.every((entry, index) => sameJsonValue(entry, right[index])));
143
+ }
144
+ if (!isRecord(left) || !isRecord(right))
145
+ return false;
146
+ const leftKeys = Object.keys(left).sort();
147
+ const rightKeys = Object.keys(right).sort();
148
+ return (leftKeys.length === rightKeys.length &&
149
+ leftKeys.every((key, index) => key === rightKeys[index] && sameJsonValue(left[key], right[key])));
150
+ }
151
+ function isControllerIdentity(value) {
152
+ if (!isRecord(value))
153
+ return false;
154
+ const identity = value;
155
+ return (identity.packageName === "@tea-agent/loop-agent" &&
156
+ typeof identity.version === "string" &&
157
+ identity.version.length > 0 &&
158
+ typeof identity.packageRoot === "string" &&
159
+ path.isAbsolute(identity.packageRoot) &&
160
+ typeof identity.cliEntry === "string" &&
161
+ path.isAbsolute(identity.cliEntry) &&
162
+ isSha256(identity.cliEntrySha256) &&
163
+ identity.portableIdentity ===
164
+ `@tea-agent/loop-agent@${identity.version}:${identity.cliEntrySha256}`);
165
+ }
166
+ function sameControllerIdentity(left, right) {
167
+ return (left.packageName === right.packageName &&
168
+ left.version === right.version &&
169
+ left.packageRoot === right.packageRoot &&
170
+ left.cliEntry === right.cliEntry &&
171
+ left.cliEntrySha256 === right.cliEntrySha256 &&
172
+ left.portableIdentity === right.portableIdentity);
173
+ }
174
+ function assertStateShape(state, repoRoot, runId) {
175
+ if (!isRecord(state))
176
+ throw new Error("invalid init upgrade state");
177
+ const candidate = state;
178
+ const phases = [
179
+ "DISCOVER",
180
+ "VERSION_GATE",
181
+ "PLAN",
182
+ "APPLY_SAFE",
183
+ "MODEL_MERGE",
184
+ "VERIFY",
185
+ "COMPLETED",
186
+ ];
187
+ const statuses = [
188
+ "in-progress",
189
+ "completed",
190
+ "needs-human-decision",
191
+ "failed",
192
+ "cancelled",
193
+ ];
194
+ if (candidate.schemaVersion !== 1 ||
195
+ candidate.runId !== runId ||
196
+ candidate.repoRoot !== repoRoot ||
197
+ !phases.includes(candidate.phase) ||
198
+ !statuses.includes(candidate.status) ||
199
+ typeof candidate.createdAt !== "string" ||
200
+ typeof candidate.updatedAt !== "string" ||
201
+ typeof candidate.nextAction !== "string" ||
202
+ !isControllerIdentity(candidate.controllerIdentity) ||
203
+ !isRecord(candidate.versionGate) ||
204
+ !Array.isArray(candidate.mergeTasks) ||
205
+ !Array.isArray(candidate.mergeReceipts) ||
206
+ !Array.isArray(candidate.humanDecisions) ||
207
+ !Array.isArray(candidate.actionReceipts) ||
208
+ !isHomeFingerprint(candidate.homeFingerprint)) {
209
+ throw new Error("invalid init upgrade state or repoRoot authority");
210
+ }
211
+ }
212
+ function assertTerminalCoherence(state) {
213
+ if (state.status === "completed" && state.phase !== "COMPLETED") {
214
+ throw new Error("invalid init upgrade terminal status/phase coherence");
215
+ }
216
+ if (state.status === "cancelled" && state.phase !== "VERSION_GATE") {
217
+ throw new Error("invalid init upgrade terminal status/phase coherence");
218
+ }
219
+ if (state.phase === "COMPLETED" && state.status !== "completed") {
220
+ throw new Error("invalid init upgrade status/phase coherence");
221
+ }
222
+ if (state.status === "needs-human-decision" &&
223
+ state.phase !== "VERSION_GATE" &&
224
+ state.phase !== "MODEL_MERGE") {
225
+ throw new Error("invalid init upgrade status/phase coherence");
226
+ }
227
+ }
228
+ /** Read-only git query; never mutates the user index (no status refresh, no write). */
229
+ function runGitQuery(cwd, args) {
230
+ return new Promise((resolve) => {
231
+ const child = spawn("git", ["-C", cwd, ...args], {
232
+ stdio: ["ignore", "pipe", "pipe"],
233
+ });
234
+ const stdoutChunks = [];
235
+ const stderrChunks = [];
236
+ child.stdout?.on("data", (chunk) => stdoutChunks.push(chunk));
237
+ child.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
238
+ child.on("error", (error) => {
239
+ resolve({ ok: false, stdout: "", stderr: error.message });
240
+ });
241
+ child.on("close", (code) => {
242
+ resolve({
243
+ ok: code === 0,
244
+ stdout: Buffer.concat(stdoutChunks).toString("utf8"),
245
+ stderr: Buffer.concat(stderrChunks).toString("utf8"),
246
+ });
247
+ });
248
+ });
249
+ }
250
+ function splitNulPaths(output) {
251
+ return output.split("\0").filter((entry) => entry.length > 0);
252
+ }
253
+ /**
254
+ * Gitignore migration assessment (PRD 2026-08-13): inventories tracked
255
+ * `.harness/**` / `.agents/**` with read-only git queries after the managed
256
+ * block has converged, and recommends index-only untrack commands. The
257
+ * controller never mutates the user Git index and never deletes local files.
258
+ */
259
+ export async function assessGitignoreMigration(repoRoot, input) {
260
+ const base = {
261
+ schemaVersion: 1,
262
+ managedBlockRefreshed: input.managedBlockRefreshed,
263
+ harnessTracked: [],
264
+ agentsTracked: [],
265
+ stagedRisk: [],
266
+ needsAgentsReview: false,
267
+ followUp: [
268
+ "run `git status --short` and confirm only the intended untrack changes",
269
+ "confirm the .harness/ and .agents/ directories still exist locally after untracking",
270
+ "run `loop-agent init doctor` to re-check the gitignore loop-agent block",
271
+ ],
272
+ };
273
+ const inside = await runGitQuery(repoRoot, ["rev-parse", "--is-inside-work-tree"]);
274
+ if (!inside.ok) {
275
+ return {
276
+ ...base,
277
+ git: {
278
+ isRepo: false,
279
+ queryError: inside.stderr.trim() || "not inside a Git work tree",
280
+ },
281
+ recommendedCommands: [],
282
+ status: "blocked",
283
+ };
284
+ }
285
+ const tracked = await runGitQuery(repoRoot, [
286
+ "ls-files",
287
+ "-z",
288
+ "--cached",
289
+ "--",
290
+ ".harness",
291
+ ".agents",
292
+ ]);
293
+ if (!tracked.ok) {
294
+ return {
295
+ ...base,
296
+ git: {
297
+ isRepo: true,
298
+ queryError: tracked.stderr.trim() || "git ls-files query failed",
299
+ },
300
+ recommendedCommands: [],
301
+ status: "blocked",
302
+ };
303
+ }
304
+ const staged = await runGitQuery(repoRoot, [
305
+ "diff",
306
+ "--cached",
307
+ "--name-only",
308
+ "-z",
309
+ ]);
310
+ if (!staged.ok) {
311
+ return {
312
+ ...base,
313
+ git: {
314
+ isRepo: true,
315
+ queryError: staged.stderr.trim() || "git diff --cached query failed",
316
+ },
317
+ recommendedCommands: [],
318
+ status: "blocked",
319
+ };
320
+ }
321
+ const trackedPaths = splitNulPaths(tracked.stdout);
322
+ const harnessTracked = trackedPaths.filter((entry) => entry.startsWith(".harness/"));
323
+ const agentsTracked = trackedPaths.filter((entry) => entry.startsWith(".agents/"));
324
+ const stagedRisk = splitNulPaths(staged.stdout);
325
+ const needsAgentsReview = agentsTracked.length > 0;
326
+ if (stagedRisk.length > 0) {
327
+ // Fail closed: staged entries make any untrack advice ambiguous.
328
+ return {
329
+ ...base,
330
+ git: { isRepo: true },
331
+ harnessTracked,
332
+ agentsTracked,
333
+ stagedRisk,
334
+ needsAgentsReview,
335
+ recommendedCommands: [],
336
+ status: "blocked",
337
+ };
338
+ }
339
+ const recommendedCommands = [];
340
+ if (harnessTracked.length > 0) {
341
+ recommendedCommands.push({
342
+ command: "git rm -r --cached --ignore-unmatch .harness",
343
+ note: "untracks tracked .harness runtime facts; index-only, working-tree files are kept",
344
+ });
345
+ }
346
+ if (agentsTracked.length > 0) {
347
+ recommendedCommands.push({
348
+ command: "git rm -r --cached --ignore-unmatch .agents",
349
+ note: "untracks tracked .agents local skills; index-only, working-tree files are kept; review team-shareable customizations first",
350
+ });
351
+ }
352
+ return {
353
+ ...base,
354
+ git: { isRepo: true },
355
+ harnessTracked,
356
+ agentsTracked,
357
+ stagedRisk,
358
+ needsAgentsReview,
359
+ recommendedCommands,
360
+ status: harnessTracked.length > 0 || agentsTracked.length > 0
361
+ ? "recommended"
362
+ : "clean",
363
+ };
364
+ }
365
+ function runRoot(repoRoot) {
366
+ return path.join(repoRoot, RUN_ROOT);
367
+ }
368
+ function runDirectory(repoRoot, runId) {
369
+ if (!validRunId(runId))
370
+ throw new Error("invalid init upgrade run id");
371
+ return path.join(runRoot(repoRoot), runId);
372
+ }
373
+ async function exists(filePath) {
374
+ return Boolean(await stat(filePath).catch(() => undefined));
375
+ }
376
+ async function writeJsonAtomic(filePath, value) {
377
+ await mkdir(path.dirname(filePath), { recursive: true });
378
+ const temp = `${filePath}.${process.pid}.${randomUUID()}.tmp`;
379
+ try {
380
+ await writeFile(temp, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
381
+ await rename(temp, filePath);
382
+ }
383
+ catch (error) {
384
+ await rm(temp, { force: true }).catch(() => undefined);
385
+ throw error;
386
+ }
387
+ }
388
+ async function controllerAnchorPath(input) {
389
+ const repoRoot = await realpath(input.repoRoot).catch(() => {
390
+ throw new Error("init upgrade controller authority requires a readable Git repository");
391
+ });
392
+ const marker = path.join(repoRoot, ".git");
393
+ const markerStat = await lstat(marker).catch(() => undefined);
394
+ let gitDirectory;
395
+ if (markerStat?.isDirectory()) {
396
+ gitDirectory = await realpath(marker);
397
+ }
398
+ else if (markerStat?.isFile()) {
399
+ const text = await readFile(marker, "utf-8").catch(() => "");
400
+ const match = /^gitdir:\s*(.+?)\s*\r?\n?$/.exec(text);
401
+ if (!match || !match[1]) {
402
+ throw new Error("init upgrade controller authority rejected an invalid Git worktree file");
403
+ }
404
+ gitDirectory = await realpath(path.resolve(path.dirname(marker), match[1]));
405
+ }
406
+ else {
407
+ throw new Error("init upgrade controller authority requires a Git repository");
408
+ }
409
+ const gitStat = await stat(gitDirectory).catch(() => undefined);
410
+ if (!gitStat?.isDirectory() || !(await stat(path.join(gitDirectory, "HEAD")).catch(() => undefined))?.isFile()) {
411
+ throw new Error("init upgrade controller authority Git directory is unusable");
412
+ }
413
+ let anchorRoot = path.join(gitDirectory, "loop-agent-init-upgrades");
414
+ if (await lstat(anchorRoot).catch(() => undefined)) {
415
+ anchorRoot = await realpath(anchorRoot);
416
+ if (!isWithin(gitDirectory, anchorRoot)) {
417
+ throw new Error("init upgrade controller authority anchor path escapes Git directory");
418
+ }
419
+ }
420
+ const file = path.join(anchorRoot, `${sha256(`${repoRoot}\0${input.runId}`)}.json`);
421
+ if (!isWithin(anchorRoot, file)) {
422
+ throw new Error("init upgrade controller authority anchor path escapes Git directory");
423
+ }
424
+ return { repoRoot, gitDirectory, anchorRoot, file };
425
+ }
426
+ async function writeControllerAnchor(input) {
427
+ let location = await controllerAnchorPath(input.state);
428
+ await mkdir(location.anchorRoot, { recursive: true });
429
+ location = await controllerAnchorPath(input.state);
430
+ if (!isWithin(location.gitDirectory, location.anchorRoot)) {
431
+ throw new Error("init upgrade controller authority anchor path escapes Git directory");
432
+ }
433
+ let authoritativeHomeFingerprint;
434
+ let authoritativeGitignoreMigrationSha256;
435
+ if (input.initialize) {
436
+ if (await exists(location.file)) {
437
+ throw new Error("init upgrade controller authority anchor already exists");
438
+ }
439
+ if (!isHomeFingerprint(input.state.homeFingerprint)) {
440
+ throw new Error("init upgrade controller home fingerprint is invalid");
441
+ }
442
+ authoritativeHomeFingerprint = cloneJson(input.state.homeFingerprint);
443
+ }
444
+ else {
445
+ const current = await readControllerAnchor(location.repoRoot, input.state);
446
+ authoritativeHomeFingerprint = current.homeFingerprint;
447
+ // Preserve the gitignore migration artifact binding across later
448
+ // mergeAuthority/completion writes; fail closed on a corrupt or
449
+ // non-SHA-256 binding instead of silently dropping or propagating it.
450
+ const digest = input.gitignoreMigrationSha256 ?? current.gitignoreMigrationSha256;
451
+ if (digest !== undefined && !isSha256(digest)) {
452
+ throw new Error("init upgrade controller authority gitignore migration digest binding is invalid");
453
+ }
454
+ authoritativeGitignoreMigrationSha256 = digest;
455
+ }
456
+ await writeJsonAtomic(location.file, {
457
+ schemaVersion: 1,
458
+ repoRoot: location.repoRoot,
459
+ runId: input.state.runId,
460
+ controllerIdentity: input.state.controllerIdentity,
461
+ homeFingerprint: authoritativeHomeFingerprint,
462
+ ...(authoritativeGitignoreMigrationSha256 !== undefined
463
+ ? { gitignoreMigrationSha256: authoritativeGitignoreMigrationSha256 }
464
+ : {}),
465
+ ...(input.mergeAuthority ? { mergeAuthority: input.mergeAuthority } : {}),
466
+ ...(input.completion ? { completion: input.completion } : {}),
467
+ });
468
+ }
469
+ async function readControllerAnchor(repoRoot, state) {
470
+ const location = await controllerAnchorPath({ repoRoot, runId: state.runId });
471
+ let parsed;
472
+ try {
473
+ parsed = JSON.parse(await readFile(location.file, "utf-8"));
474
+ }
475
+ catch {
476
+ throw new Error("init upgrade controller authority anchor is missing or invalid");
477
+ }
478
+ if (!isRecord(parsed) ||
479
+ parsed.schemaVersion !== 1 ||
480
+ parsed.repoRoot !== location.repoRoot ||
481
+ parsed.runId !== state.runId ||
482
+ !isControllerIdentity(parsed.controllerIdentity) ||
483
+ !sameControllerIdentity(parsed.controllerIdentity, state.controllerIdentity)) {
484
+ throw new Error("init upgrade controller authority anchor binding is invalid");
485
+ }
486
+ if (!isHomeFingerprint(parsed.homeFingerprint) ||
487
+ !sameJsonValue(parsed.homeFingerprint, state.homeFingerprint)) {
488
+ throw new Error("init upgrade controller authority home fingerprint binding is invalid");
489
+ }
490
+ return parsed;
491
+ }
492
+ function mergeAuthorityFromAnchor(anchor, state) {
493
+ const authority = anchor.mergeAuthority;
494
+ if (!authority ||
495
+ !isRecord(authority.baselineWorkspace) ||
496
+ !Number.isInteger(authority.priorMergeReceiptCount) ||
497
+ authority.priorMergeReceiptCount < 0 ||
498
+ !isSha256(authority.priorMergeReceiptsSha256) ||
499
+ !isRecord(authority.stateProjection) ||
500
+ !isSha256(authority.stateProjectionSha256) ||
501
+ canonicalSha256(authority.stateProjection) !== authority.stateProjectionSha256 ||
502
+ !isFrozenVerificationSurface(authority.verificationSurface)) {
503
+ throw new Error("init upgrade controller merge baseline authority is missing or inconsistent");
504
+ }
505
+ validateTaskBoundary(authority.task);
506
+ if (authority.pending) {
507
+ assertPendingAcceptance(authority.pending, state, authority.task);
508
+ if (authority.pending.transition.priorMergeReceiptCount !==
509
+ authority.priorMergeReceiptCount) {
510
+ throw new Error("pending merge acceptance receipt count differs from controller authority");
511
+ }
512
+ }
513
+ return authority;
514
+ }
515
+ function expectedPostAcceptanceProjection(authority) {
516
+ if (!authority.pending) {
517
+ throw new Error("post-acceptance merge state projection lacks controller merge authority for pending acceptance");
518
+ }
519
+ const projection = cloneJson(authority.stateProjection);
520
+ projection.phase = "PLAN";
521
+ projection.status = "in-progress";
522
+ projection.mergeTasks = [];
523
+ delete projection.mergeGuard;
524
+ projection.mergeReceipts.push(cloneJson(authority.pending.receipt));
525
+ return projection;
526
+ }
527
+ function mergeAuthorityProjection(state, authority) {
528
+ const preAcceptance = state.phase === "MODEL_MERGE";
529
+ if (preAcceptance) {
530
+ if (state.mergeTasks.length !== 1) {
531
+ throw new Error("init upgrade merge state must contain exactly one canonical current merge task");
532
+ }
533
+ if (!sameJsonValue(state.mergeTasks[0], authority.task)) {
534
+ throw new Error("init upgrade merge state is inconsistent with the canonical current merge task");
535
+ }
536
+ if (!state.mergeGuard) {
537
+ throw new Error("init upgrade merge guard is missing; refusing to continue an unbounded model merge");
538
+ }
539
+ if (!sameJsonValue(state.mergeGuard.task, authority.task)) {
540
+ throw new Error("init upgrade merge guard is inconsistent with the canonical current merge task");
541
+ }
542
+ if (!sameJsonValue(state.mergeGuard.baselineWorkspace, authority.baselineWorkspace)) {
543
+ throw new Error("init upgrade merge guard projection differs from controller baseline authority");
544
+ }
545
+ if (!sameJsonValue(state.mergeGuard.verificationAuthority, authority.verificationAuthority)) {
546
+ throw new Error("init upgrade merge guard verification authority differs from controller baseline authority");
547
+ }
548
+ if (state.mergeReceipts.length !== authority.priorMergeReceiptCount) {
549
+ throw new Error("persisted pre-acceptance merge receipt count differs from controller authority");
550
+ }
551
+ }
552
+ const priorReceipts = state.mergeReceipts.slice(0, authority.priorMergeReceiptCount);
553
+ if (priorReceipts.length !== authority.priorMergeReceiptCount ||
554
+ sha256(serializeJson(priorReceipts)) !== authority.priorMergeReceiptsSha256) {
555
+ throw new Error("persisted merge receipt prefix differs from controller authority");
556
+ }
557
+ const currentProjection = controllerStateProjection(state);
558
+ const expectedProjection = preAcceptance
559
+ ? authority.stateProjection
560
+ : expectedPostAcceptanceProjection(authority);
561
+ if (canonicalSha256(currentProjection) !== canonicalSha256(expectedProjection) ||
562
+ !sameJsonValue(currentProjection, expectedProjection)) {
563
+ throw new Error("persisted merge state projection differs from controller merge authority");
564
+ }
565
+ if (preAcceptance)
566
+ return "pre-acceptance";
567
+ const postAcceptance = Boolean(authority.pending) &&
568
+ state.phase === "PLAN" &&
569
+ state.mergeTasks.length === 0 &&
570
+ state.mergeGuard === undefined &&
571
+ state.mergeReceipts.length === authority.priorMergeReceiptCount + 1 &&
572
+ sameJsonValue(state.mergeReceipts[authority.priorMergeReceiptCount], authority.pending?.receipt);
573
+ if (postAcceptance)
574
+ return "post-acceptance";
575
+ throw new Error("persisted merge state projection differs from controller merge authority");
576
+ }
577
+ async function packageRoot() {
578
+ let current = path.dirname(fileURLToPath(import.meta.url));
579
+ while (true) {
580
+ if (await exists(path.join(current, "package.json")))
581
+ return current;
582
+ const parent = path.dirname(current);
583
+ if (parent === current)
584
+ throw new Error("unable to resolve loop-agent package root");
585
+ current = parent;
586
+ }
587
+ }
588
+ async function readIdentity() {
589
+ const root = await packageRoot();
590
+ const pkg = JSON.parse(await readFile(path.join(root, "package.json"), "utf-8"));
591
+ if (typeof pkg.version !== "string")
592
+ throw new Error("loop-agent package version is missing");
593
+ const cliEntry = path.resolve(process.argv[1] ?? path.join(root, "src", "cli.ts"));
594
+ const cliBytes = await readFile(cliEntry).catch(() => Buffer.from("unreadable-cli-entry"));
595
+ const cliEntrySha256 = sha256(cliBytes);
596
+ return {
597
+ packageName: "@tea-agent/loop-agent",
598
+ version: pkg.version,
599
+ packageRoot: root,
600
+ cliEntry,
601
+ cliEntrySha256,
602
+ portableIdentity: `@tea-agent/loop-agent@${pkg.version}:${cliEntrySha256}`,
603
+ };
604
+ }
605
+ async function homeFingerprint() {
606
+ const homeSettings = path.join(os.homedir(), ".pi", "agent", "settings.json");
607
+ try {
608
+ return { present: true, sha256: sha256(await readFile(homeSettings)) };
609
+ }
610
+ catch {
611
+ return { present: false };
612
+ }
613
+ }
614
+ function versionsNewer(candidate, current) {
615
+ const parse = (value) => value.match(/^(\d+)\.(\d+)\.(\d+)$/)?.slice(1).map(Number);
616
+ const left = parse(candidate);
617
+ const right = parse(current);
618
+ if (!left || !right)
619
+ return false;
620
+ for (let index = 0; index < left.length; index += 1) {
621
+ if (left[index] === right[index])
622
+ continue;
623
+ return left[index] > right[index];
624
+ }
625
+ return false;
626
+ }
627
+ async function readState(repoRoot, runId) {
628
+ const file = path.join(runDirectory(repoRoot, runId), "state.json");
629
+ const parsed = JSON.parse(await readFile(file, "utf-8"));
630
+ assertStateShape(parsed, repoRoot, runId);
631
+ return parsed;
632
+ }
633
+ async function frozenControllerMatches(directory, state) {
634
+ const parsed = JSON.parse(await readFile(path.join(directory, "controller-identity.json"), "utf-8"));
635
+ if (!isRecord(parsed) ||
636
+ parsed.schemaVersion !== 1 ||
637
+ parsed.runId !== state.runId ||
638
+ parsed.repoRoot !== state.repoRoot ||
639
+ !isControllerIdentity(parsed.controllerIdentity)) {
640
+ throw new Error("invalid frozen init upgrade controller identity");
641
+ }
642
+ const frozen = {
643
+ schemaVersion: 1,
644
+ runId: parsed.runId,
645
+ repoRoot: parsed.repoRoot,
646
+ controllerIdentity: parsed.controllerIdentity,
647
+ };
648
+ if (!sameControllerIdentity(frozen.controllerIdentity, state.controllerIdentity)) {
649
+ throw new Error("persisted init upgrade controller identity is inconsistent");
650
+ }
651
+ return sameControllerIdentity(await readIdentity(), frozen.controllerIdentity);
652
+ }
653
+ async function saveState(directory, state) {
654
+ state.updatedAt = new Date().toISOString();
655
+ await writeJsonAtomic(path.join(directory, "state.json"), state);
656
+ }
657
+ function toResult(state, verification, gitignoreMigration) {
658
+ return {
659
+ runId: state.runId,
660
+ status: state.status,
661
+ phase: state.phase,
662
+ controllerIdentity: state.controllerIdentity,
663
+ deterministicActions: state.actionReceipts,
664
+ modelMergeTasks: state.mergeTasks,
665
+ humanDecisions: state.humanDecisions,
666
+ nextAction: state.nextAction,
667
+ ...(verification ? { verification } : {}),
668
+ ...(gitignoreMigration ? { gitignoreMigration } : {}),
669
+ };
670
+ }
671
+ function renderReport(state, verification, gitignoreMigration) {
672
+ const lines = [
673
+ "# loop-agent init upgrade report",
674
+ "",
675
+ `- runId: \`${state.runId}\``,
676
+ `- status: \`${state.status}\``,
677
+ `- phase: \`${state.phase}\``,
678
+ `- controller: \`${state.controllerIdentity.portableIdentity}\``,
679
+ `- version gate: \`${state.versionGate.outcome}\``,
680
+ `- version choice: \`${state.versionGate.choice ?? "pending"}\``,
681
+ `- deterministic actions applied: ${state.actionReceipts.length}`,
682
+ `- semantic merges accepted: ${state.mergeReceipts.length}`,
683
+ `- preserved user modifications: ${state.mergeReceipts.length}`,
684
+ `- retired path actions: ${state.actionReceipts.filter((action) => action.type === "migrate-owned-file" || action.type === "remove-owned-file" || action.type === "remove-empty-directory").length}`,
685
+ `- remaining model merge tasks: ${state.mergeTasks.length}`,
686
+ `- remaining human decisions: ${state.humanDecisions.length}`,
687
+ `- gitignore migration assessment: \`.harness/init-upgrades/${state.runId}/gitignore-migration.json\` (index-only untrack guidance; the controller never modifies the Git index)`,
688
+ ...(gitignoreMigration
689
+ ? [
690
+ "",
691
+ "## Gitignore migration assessment",
692
+ "",
693
+ `- status: \`${gitignoreMigration.status}\``,
694
+ `- managed block refreshed: \`${gitignoreMigration.managedBlockRefreshed}\``,
695
+ `- tracked \`.harness\` inventory: ${gitignoreMigration.harnessTracked.length > 0
696
+ ? gitignoreMigration.harnessTracked
697
+ .map((entry) => `\`${entry}\``)
698
+ .join(", ")
699
+ : "(none)"}`,
700
+ `- tracked \`.agents\` inventory: ${gitignoreMigration.agentsTracked.length > 0
701
+ ? gitignoreMigration.agentsTracked
702
+ .map((entry) => `\`${entry}\``)
703
+ .join(", ")
704
+ : "(none)"}`,
705
+ ...(gitignoreMigration.stagedRisk.length > 0
706
+ ? [
707
+ `- staged risk: ${gitignoreMigration.stagedRisk
708
+ .map((entry) => `\`${entry}\``)
709
+ .join(", ")}`,
710
+ ]
711
+ : []),
712
+ ...(gitignoreMigration.git.queryError
713
+ ? [
714
+ `- git query blocker: \`${gitignoreMigration.git.queryError}\``,
715
+ ]
716
+ : []),
717
+ `- \`.agents\` review required: \`${gitignoreMigration.needsAgentsReview}\``,
718
+ "- recommended commands:",
719
+ ...(gitignoreMigration.recommendedCommands.length > 0
720
+ ? gitignoreMigration.recommendedCommands.map((entry) => ` - \`${entry.command}\` — ${entry.note}`)
721
+ : [" - (none)"]),
722
+ "- follow-up:",
723
+ ...(gitignoreMigration.followUp.length > 0
724
+ ? gitignoreMigration.followUp.map((entry) => ` - ${entry}`)
725
+ : [" - (none)"]),
726
+ ]
727
+ : []),
728
+ "",
729
+ "## Next action",
730
+ "",
731
+ state.nextAction,
732
+ "",
733
+ "## Project Pi trust",
734
+ "",
735
+ "Pi must trust the target project before loading `.pi/settings.json` and `.pi/extensions/`. This upgrade never writes `~/.pi/agent/settings.json`.",
736
+ "",
737
+ ...(verification
738
+ ? [
739
+ "## Verification",
740
+ "",
741
+ "```json",
742
+ JSON.stringify(verification, null, 2),
743
+ "```",
744
+ ]
745
+ : []),
746
+ ];
747
+ return `${lines.join("\n")}\n`;
748
+ }
749
+ async function writeReport(directory, state, verification, gitignoreMigration) {
750
+ await writeFile(path.join(directory, "final-report.md"), renderReport(state, verification, gitignoreMigration), "utf-8");
751
+ }
752
+ async function assertInactive(input) {
753
+ let activity;
754
+ try {
755
+ activity = await input.readRuntimeActivity(path.resolve(input.repoRoot));
756
+ }
757
+ catch (error) {
758
+ throw new Error(`init upgrade blocked: runtime activity is unknown (${error instanceof Error ? error.message : String(error)})`);
759
+ }
760
+ if (isInitRuntimeActive(activity)) {
761
+ throw new Error("init upgrade blocked: active DAG, Worker, or exclusive writer is present");
762
+ }
763
+ return activity;
764
+ }
765
+ function processLiveness(pid) {
766
+ if (!Number.isInteger(pid) || pid <= 0)
767
+ return "unknown";
768
+ try {
769
+ process.kill(pid, 0);
770
+ return "active";
771
+ }
772
+ catch (error) {
773
+ const code = error.code;
774
+ if (code === "ESRCH")
775
+ return "stale";
776
+ return "unknown";
777
+ }
778
+ }
779
+ async function acquireGlobalLock(input) {
780
+ const root = runRoot(input.repoRoot);
781
+ await mkdir(root, { recursive: true });
782
+ const lock = path.join(root, GLOBAL_LOCK_NAME);
783
+ const token = randomUUID();
784
+ const owner = {
785
+ schemaVersion: 1,
786
+ token,
787
+ runId: input.runId,
788
+ pid: process.pid,
789
+ hostname: os.hostname(),
790
+ acquiredAt: new Date().toISOString(),
791
+ };
792
+ const tryCreate = async () => {
793
+ try {
794
+ await mkdir(lock);
795
+ await writeJsonAtomic(path.join(lock, "owner.json"), owner);
796
+ return true;
797
+ }
798
+ catch (error) {
799
+ if (error.code === "EEXIST")
800
+ return false;
801
+ throw error;
802
+ }
803
+ };
804
+ if (!(await tryCreate())) {
805
+ let existing;
806
+ try {
807
+ existing = JSON.parse(await readFile(path.join(lock, "owner.json"), "utf-8"));
808
+ }
809
+ catch {
810
+ throw new Error("init upgrade blocked: global upgrade owner is unreadable; lock state is unknown");
811
+ }
812
+ if (existing.hostname !== os.hostname() ||
813
+ typeof existing.pid !== "number") {
814
+ throw new Error(`init upgrade blocked: global upgrade owner is active or unknown (runId=${String(existing.runId ?? "unknown")})`);
815
+ }
816
+ const liveness = processLiveness(existing.pid);
817
+ if (liveness !== "stale") {
818
+ throw new Error(`init upgrade blocked: global upgrade owner is ${liveness} (runId=${String(existing.runId ?? "unknown")})`);
819
+ }
820
+ await rm(lock, { recursive: true, force: true });
821
+ if (!(await tryCreate())) {
822
+ throw new Error("init upgrade blocked: another upgrade writer acquired the global lock");
823
+ }
824
+ }
825
+ return async () => {
826
+ try {
827
+ const current = JSON.parse(await readFile(path.join(lock, "owner.json"), "utf-8"));
828
+ if (current.token === token) {
829
+ await rm(lock, { recursive: true, force: true });
830
+ }
831
+ }
832
+ catch {
833
+ // Fail closed: never remove a lock whose ownership can no longer be proven.
834
+ }
835
+ };
836
+ }
837
+ async function currentVersionGate(input, identity) {
838
+ try {
839
+ const latest = input.latestVersion
840
+ ? await input.latestVersion()
841
+ : await createNpmUpdateClient({
842
+ currentVersion: identity.version,
843
+ packageRoot: identity.packageRoot,
844
+ }).latestVersion();
845
+ if (!latest)
846
+ return { outcome: "registry-unavailable" };
847
+ return versionsNewer(latest, identity.version)
848
+ ? { outcome: "newer-available", latestVersion: latest }
849
+ : { outcome: "latest", latestVersion: latest };
850
+ }
851
+ catch {
852
+ return { outcome: "registry-unavailable" };
853
+ }
854
+ }
855
+ function toRepoPath(repoRoot, absolutePath) {
856
+ return path.relative(repoRoot, absolutePath).split(path.sep).join("/");
857
+ }
858
+ function isWithin(root, candidate) {
859
+ const relative = path.relative(root, candidate);
860
+ return (relative === "" ||
861
+ (!relative.startsWith("..") && !path.isAbsolute(relative)));
862
+ }
863
+ function normalizeAllowedPath(relativePath) {
864
+ if (!relativePath ||
865
+ path.isAbsolute(relativePath) ||
866
+ /[?*[\]{}!]/.test(relativePath)) {
867
+ throw new Error(`init upgrade merge guard rejected non-concrete allowedPath: ${relativePath}`);
868
+ }
869
+ const normalized = path.posix.normalize(relativePath.replace(/\\/g, "/"));
870
+ if (normalized === "." || normalized.startsWith("../")) {
871
+ throw new Error(`init upgrade merge guard rejected path outside repo: ${relativePath}`);
872
+ }
873
+ return normalized;
874
+ }
875
+ async function assertSafeMergePath(repoRoot, relativePath) {
876
+ const normalized = normalizeAllowedPath(relativePath);
877
+ const rootReal = await realpath(repoRoot);
878
+ const target = path.resolve(repoRoot, normalized);
879
+ if (!isWithin(path.resolve(repoRoot), target)) {
880
+ throw new Error(`init upgrade merge path escapes repo: ${normalized}`);
881
+ }
882
+ const targetStat = await lstat(target).catch((error) => {
883
+ if (error.code === "ENOENT")
884
+ return undefined;
885
+ throw error;
886
+ });
887
+ if (targetStat?.isSymbolicLink()) {
888
+ throw new Error(`init upgrade merge path is a symlink: ${normalized}`);
889
+ }
890
+ if (targetStat && !targetStat.isFile()) {
891
+ throw new Error(`init upgrade merge path is not a file: ${normalized}`);
892
+ }
893
+ const containmentTarget = targetStat ? target : path.dirname(target);
894
+ const containedReal = await realpath(containmentTarget);
895
+ if (!isWithin(rootReal, containedReal)) {
896
+ throw new Error(`init upgrade merge realpath escapes repo: ${normalized}`);
897
+ }
898
+ }
899
+ async function snapshotWorkspace(repoRoot) {
900
+ const snapshot = {};
901
+ async function visit(directory) {
902
+ const entries = await readdir(directory, { withFileTypes: true });
903
+ entries.sort((left, right) => left.name.localeCompare(right.name));
904
+ for (const entry of entries) {
905
+ // Git may expose the same temp repository through a short/long Windows
906
+ // path pair, making path.relative() lose the leading `.git` segment.
907
+ // Exclude the top-level Git administration directory by entry identity;
908
+ // init-upgrade protects its own controller anchors separately.
909
+ if (directory === repoRoot && entry.name === ".git")
910
+ continue;
911
+ const absolute = path.join(directory, entry.name);
912
+ const relative = toRepoPath(repoRoot, absolute);
913
+ if (relative === ".git" || relative.startsWith(".git/"))
914
+ continue;
915
+ if (relative === RUN_ROOT || relative.startsWith(`${RUN_ROOT}/`))
916
+ continue;
917
+ if (entry.isSymbolicLink()) {
918
+ snapshot[relative] = `symlink:${await readlink(absolute)}`;
919
+ continue;
920
+ }
921
+ if (entry.isDirectory()) {
922
+ snapshot[relative] = "directory";
923
+ await visit(absolute);
924
+ continue;
925
+ }
926
+ if (entry.isFile()) {
927
+ snapshot[relative] = `file:${sha256(await readFile(absolute))}`;
928
+ continue;
929
+ }
930
+ snapshot[relative] = "special";
931
+ }
932
+ }
933
+ await visit(repoRoot);
934
+ return snapshot;
935
+ }
936
+ function changedWorkspacePaths(before, after) {
937
+ return [...new Set([...Object.keys(before), ...Object.keys(after)])]
938
+ .filter((relativePath) => before[relativePath] !== after[relativePath])
939
+ .sort();
940
+ }
941
+ /**
942
+ * Generated-output prefixes treated as verification noise when git ignore
943
+ * rules are unavailable (non-git target). In a git repo the authoritative
944
+ * filter is `.gitignore` via `git check-ignore`; this list only covers the
945
+ * common case where a verification command such as `npm run build` writes
946
+ * untracked artifacts (build/dist/coverage/...) into the workspace.
947
+ */
948
+ const WORKSPACE_NOISE_PREFIXES = [
949
+ "node_modules/",
950
+ "build/",
951
+ "dist/",
952
+ "coverage/",
953
+ ".turbo/",
954
+ ".next/",
955
+ ".nuxt/",
956
+ ".cache/",
957
+ ".parcel-cache/",
958
+ ".vite/",
959
+ "out/",
960
+ "target/",
961
+ "__pycache__/",
962
+ ];
963
+ function isWorkspaceNoisePath(relative) {
964
+ if (relative === ".git" || relative.startsWith(".git/"))
965
+ return true;
966
+ if (relative === RUN_ROOT || relative.startsWith(`${RUN_ROOT}/`))
967
+ return true;
968
+ return WORKSPACE_NOISE_PREFIXES.some((prefix) => relative === prefix.slice(0, -1) || relative.startsWith(prefix));
969
+ }
970
+ /**
971
+ * Filters changed workspace paths down to meaningful (non-generated) changes
972
+ * so a verification command that legitimately writes git-ignored artifacts is
973
+ * not misjudged as an unauthorized workspace mutation. Git ignore rules are
974
+ * authoritative when the target is a git repo; otherwise the built-in
975
+ * generated-output prefixes apply.
976
+ */
977
+ async function filterWorkspaceNoise(repoRoot, relativePaths) {
978
+ const builtinFiltered = relativePaths.filter((relative) => !isWorkspaceNoisePath(relative));
979
+ if (builtinFiltered.length === 0)
980
+ return builtinFiltered;
981
+ const ignored = await gitIgnoredPaths(repoRoot, builtinFiltered);
982
+ if (ignored === null)
983
+ return builtinFiltered;
984
+ return builtinFiltered.filter((relative) => !ignored.has(relative));
985
+ }
986
+ /**
987
+ * Returns the subset of paths matched by git ignore rules, or null when the
988
+ * target is not a git repository (or git is unavailable), in which case the
989
+ * caller falls back to the built-in noise prefixes.
990
+ */
991
+ async function gitIgnoredPaths(repoRoot, relativePaths) {
992
+ return await new Promise((resolve) => {
993
+ execFile("git", ["-C", repoRoot, "check-ignore", ...relativePaths], {
994
+ timeout: 15_000,
995
+ maxBuffer: 4 * 1024 * 1024,
996
+ windowsHide: true,
997
+ }, (error, stdout) => {
998
+ if (error) {
999
+ // `git check-ignore` exits 1 when no path is ignored: that is a
1000
+ // successful lookup with an empty result set. Any other failure
1001
+ // (e.g. 128 outside a repo) means the git filter is unavailable.
1002
+ if (error.code === 1 && stdout.length === 0)
1003
+ resolve(new Set());
1004
+ else
1005
+ resolve(null);
1006
+ return;
1007
+ }
1008
+ resolve(new Set(stdout
1009
+ .split("\n")
1010
+ .filter(Boolean)
1011
+ .map((entry) => entry.split(path.sep).join("/"))));
1012
+ });
1013
+ });
1014
+ }
1015
+ function isFrozenVerificationSurface(value) {
1016
+ return (isRecord(value) &&
1017
+ value.schemaVersion === 1 &&
1018
+ Array.isArray(value.files) &&
1019
+ value.files.length > 0 &&
1020
+ value.files.every((entry) => isRecord(entry) &&
1021
+ typeof entry.path === "string" &&
1022
+ entry.path === normalizeAllowedPath(entry.path) &&
1023
+ isSha256(entry.sha256)) &&
1024
+ new Set(value.files.map((entry) => entry.path)).size === value.files.length);
1025
+ }
1026
+ async function assertSafeRegularRepoFile(repoRoot, relativePath) {
1027
+ const normalized = normalizeAllowedPath(relativePath);
1028
+ await assertSafeMergePath(repoRoot, normalized);
1029
+ const target = path.join(repoRoot, normalized);
1030
+ const targetStat = await lstat(target).catch(() => undefined);
1031
+ if (!targetStat?.isFile() || targetStat.isSymbolicLink()) {
1032
+ throw new Error(`verification authority input is not a regular file: ${normalized}`);
1033
+ }
1034
+ return normalized;
1035
+ }
1036
+ async function staticScriptDependencies(repoRoot, relativePath, collected) {
1037
+ const normalized = await assertSafeRegularRepoFile(repoRoot, relativePath);
1038
+ if (collected.has(normalized))
1039
+ return;
1040
+ collected.add(normalized);
1041
+ const content = await readFile(path.join(repoRoot, normalized), "utf-8");
1042
+ const dependencies = new Set();
1043
+ for (const match of content.matchAll(/(?:^|[;\s])(?:bash|source|\.)\s+["']?([A-Za-z0-9_./-]+\.sh)["']?/gm)) {
1044
+ if (match[1])
1045
+ dependencies.add(match[1]);
1046
+ }
1047
+ // check-repo-style arrays dispatch quoted script paths indirectly through a variable.
1048
+ for (const match of content.matchAll(/["'](scripts\/[A-Za-z0-9_./-]+\.sh)["']/g)) {
1049
+ if (match[1])
1050
+ dependencies.add(match[1]);
1051
+ }
1052
+ for (const dependency of dependencies) {
1053
+ if (!path.isAbsolute(dependency) && !dependency.startsWith("../")) {
1054
+ await staticScriptDependencies(repoRoot, dependency, collected);
1055
+ }
1056
+ }
1057
+ // Direct interpreter inputs are verification authority too. Only existing,
1058
+ // concrete repo files are accepted; optional absent fallback branches are not.
1059
+ for (const match of content.matchAll(/(?:scripts|src)\/[A-Za-z0-9_./-]+\.(?:mjs|cjs|js|ts)/g)) {
1060
+ const dependency = match[0];
1061
+ if (await exists(path.join(repoRoot, dependency))) {
1062
+ collected.add(await assertSafeRegularRepoFile(repoRoot, dependency));
1063
+ }
1064
+ }
1065
+ }
1066
+ async function existingRepoFileTokens(repoRoot, tokens) {
1067
+ const files = [];
1068
+ for (const token of tokens) {
1069
+ const candidate = token.includes("=") ? token.slice(token.indexOf("=") + 1) : token;
1070
+ if (!candidate || candidate.startsWith("-") || path.isAbsolute(candidate)) {
1071
+ continue;
1072
+ }
1073
+ let normalized;
1074
+ try {
1075
+ normalized = normalizeAllowedPath(candidate);
1076
+ }
1077
+ catch {
1078
+ continue;
1079
+ }
1080
+ if (await exists(path.join(repoRoot, normalized))) {
1081
+ files.push(await assertSafeRegularRepoFile(repoRoot, normalized));
1082
+ }
1083
+ }
1084
+ return [...new Set(files)];
1085
+ }
1086
+ async function commandRepositoryInputs(repoRoot, commandText, npmScriptStack = []) {
1087
+ const tokens = tokenizeCommand(commandText);
1088
+ const command = tokens[0];
1089
+ if (!command)
1090
+ throw new Error("empty verification command");
1091
+ if (command === "node" || command === "node.exe") {
1092
+ if (tokens.some((token) => ["-e", "--eval", "-p", "--print"].includes(token))) {
1093
+ throw new Error("verification authority rejects direct node code evaluation");
1094
+ }
1095
+ const files = await existingRepoFileTokens(repoRoot, tokens.slice(1));
1096
+ if (files.length === 0) {
1097
+ throw new Error("verification authority requires node to execute a concrete repo-local entry");
1098
+ }
1099
+ return files;
1100
+ }
1101
+ if (command === "bash") {
1102
+ if (tokens.length < 2 || !tokens[1]?.endsWith(".sh")) {
1103
+ throw new Error("verification authority requires bash to execute one concrete repo-local script");
1104
+ }
1105
+ return [await assertSafeRegularRepoFile(repoRoot, tokens[1])];
1106
+ }
1107
+ if (command === "npm" || command === "npm.cmd") {
1108
+ if (tokens.some((token) => ["-e", "--eval", "-p", "--print"].includes(token))) {
1109
+ throw new Error("verification authority rejects package command code evaluation");
1110
+ }
1111
+ let scriptName;
1112
+ if (tokens[1] === "run" || tokens[1] === "run-script")
1113
+ scriptName = tokens[2];
1114
+ else if (["test", "start", "stop", "restart"].includes(tokens[1] ?? ""))
1115
+ scriptName = tokens[1];
1116
+ if (!scriptName || scriptName.startsWith("-")) {
1117
+ throw new Error("verification authority requires npm to select one concrete package script");
1118
+ }
1119
+ if (npmScriptStack.includes(scriptName)) {
1120
+ throw new Error(`verification authority rejected recursive npm script: ${scriptName}`);
1121
+ }
1122
+ const packagePath = await assertSafeRegularRepoFile(repoRoot, "package.json");
1123
+ const packageJson = JSON.parse(await readFile(path.join(repoRoot, packagePath), "utf-8"));
1124
+ const script = packageJson.scripts?.[scriptName];
1125
+ if (typeof script !== "string" || !script.trim()) {
1126
+ throw new Error(`verification authority package script is missing: ${scriptName}`);
1127
+ }
1128
+ const nested = [];
1129
+ for (const lifecycleName of [`pre${scriptName}`, scriptName, `post${scriptName}`]) {
1130
+ const lifecycleScript = packageJson.scripts?.[lifecycleName];
1131
+ if (typeof lifecycleScript !== "string" || !lifecycleScript.trim())
1132
+ continue;
1133
+ nested.push(...(await commandRepositoryInputs(repoRoot, lifecycleScript, [
1134
+ ...npmScriptStack,
1135
+ scriptName,
1136
+ lifecycleName,
1137
+ ])));
1138
+ }
1139
+ return [...new Set([packagePath, ...nested])];
1140
+ }
1141
+ if (command === "npx" || command === "npx.cmd") {
1142
+ if (tokens.some((token) => ["-e", "--eval", "-p", "--print", "-c", "--call"].includes(token))) {
1143
+ throw new Error("verification authority rejects package command code evaluation");
1144
+ }
1145
+ let runnerIndex = 1;
1146
+ while (["-y", "--yes", "--no-install"].includes(tokens[runnerIndex] ?? "")) {
1147
+ runnerIndex += 1;
1148
+ }
1149
+ const runner = tokens[runnerIndex];
1150
+ if (!runner || runner.startsWith("-")) {
1151
+ throw new Error("verification authority requires npx to select one concrete runner");
1152
+ }
1153
+ const files = await existingRepoFileTokens(repoRoot, tokens.slice(runnerIndex));
1154
+ if (files.length === 0) {
1155
+ throw new Error("verification authority requires npx to bind a concrete repo-local entry");
1156
+ }
1157
+ const inputs = [...files];
1158
+ if (await exists(path.join(repoRoot, "package.json"))) {
1159
+ inputs.unshift(await assertSafeRegularRepoFile(repoRoot, "package.json"));
1160
+ }
1161
+ return [...new Set(inputs)];
1162
+ }
1163
+ if (command.includes("/") || command.startsWith(".")) {
1164
+ return [await assertSafeRegularRepoFile(repoRoot, command)];
1165
+ }
1166
+ const directInputs = await existingRepoFileTokens(repoRoot, tokens.slice(1));
1167
+ if (directInputs.length > 0)
1168
+ return directInputs;
1169
+ throw new Error(`verification authority rejects unbound executable: ${command}`);
1170
+ }
1171
+ async function freezeVerificationSurface(repoRoot) {
1172
+ const collected = new Set();
1173
+ const add = async (relativePath) => {
1174
+ collected.add(await assertSafeRegularRepoFile(repoRoot, relativePath));
1175
+ };
1176
+ await add("harness.json");
1177
+ const harness = await readVerificationHarness(repoRoot);
1178
+ const matrixPath = verificationMatrixPath(harness);
1179
+ await add(matrixPath);
1180
+ const commands = ["bash scripts/check-repo.sh", await discoverQuickVerification(repoRoot)];
1181
+ for (const command of commands) {
1182
+ for (const input of await commandRepositoryInputs(repoRoot, command)) {
1183
+ const normalized = await assertSafeRegularRepoFile(repoRoot, input);
1184
+ if (normalized.endsWith(".sh"))
1185
+ await staticScriptDependencies(repoRoot, normalized, collected);
1186
+ else
1187
+ collected.add(normalized);
1188
+ }
1189
+ }
1190
+ const files = await Promise.all([...collected].sort().map(async (relativePath) => ({
1191
+ path: relativePath,
1192
+ sha256: sha256(await readFile(path.join(repoRoot, relativePath))),
1193
+ })));
1194
+ return { schemaVersion: 1, files };
1195
+ }
1196
+ async function assertFrozenVerificationSurface(repoRoot, surface) {
1197
+ if (!isFrozenVerificationSurface(surface)) {
1198
+ throw new Error("verification authority surface manifest is invalid");
1199
+ }
1200
+ for (const entry of surface.files) {
1201
+ const normalized = await assertSafeRegularRepoFile(repoRoot, entry.path);
1202
+ if (normalized !== entry.path || sha256(await readFile(path.join(repoRoot, normalized))) !== entry.sha256) {
1203
+ throw new Error(`verification authority surface changed: ${entry.path}`);
1204
+ }
1205
+ }
1206
+ }
1207
+ function frozenVerificationMatrixPath(surface) {
1208
+ const paths = surface.files
1209
+ .map((entry) => entry.path)
1210
+ .filter((pathName) => pathName === "verification-matrix.md" ||
1211
+ pathName.endsWith("/verification-matrix.md"));
1212
+ if (paths.length !== 1) {
1213
+ throw new Error("verification authority surface must contain exactly one verification-matrix.md");
1214
+ }
1215
+ return paths[0];
1216
+ }
1217
+ function isControllerAuthorizedVerificationMergeTask(task, surface) {
1218
+ const taskPath = normalizeAllowedPath(task.path);
1219
+ if (taskPath === "harness.json")
1220
+ return true;
1221
+ return ((taskPath === "verification-matrix.md" ||
1222
+ taskPath.endsWith("/verification-matrix.md")) &&
1223
+ Boolean(surface?.files.some((entry) => entry.path === taskPath)));
1224
+ }
1225
+ async function assertFrozenVerificationSurfaceExcept(repoRoot, surface, exemptPath) {
1226
+ if (!isFrozenVerificationSurface(surface)) {
1227
+ throw new Error("verification authority surface manifest is invalid");
1228
+ }
1229
+ const canonicalExemptPath = exemptPath
1230
+ ? normalizeAllowedPath(exemptPath)
1231
+ : undefined;
1232
+ for (const entry of surface.files) {
1233
+ if (entry.path === canonicalExemptPath)
1234
+ continue;
1235
+ const normalized = await assertSafeRegularRepoFile(repoRoot, entry.path);
1236
+ if (normalized !== entry.path || sha256(await readFile(path.join(repoRoot, normalized))) !== entry.sha256) {
1237
+ throw new Error(`verification authority surface changed: ${entry.path}`);
1238
+ }
1239
+ }
1240
+ }
1241
+ async function validateControllerAuthorizedVerificationMerge(input) {
1242
+ const taskPath = normalizeAllowedPath(input.task.path);
1243
+ if (!isControllerAuthorizedVerificationMergeTask(input.task, input.surface))
1244
+ return;
1245
+ const harness = await readVerificationHarness(input.repoRoot);
1246
+ const matrixPath = normalizeAllowedPath(verificationMatrixPath(harness));
1247
+ const frozenMatrixPath = frozenVerificationMatrixPath(input.surface);
1248
+ if (matrixPath !== frozenMatrixPath ||
1249
+ (taskPath !== "harness.json" && taskPath !== frozenMatrixPath)) {
1250
+ throw new Error("verification authority merge must retain the controller-frozen verification matrix path");
1251
+ }
1252
+ if (taskPath === "harness.json") {
1253
+ harnessManifestSchema.parse(harness);
1254
+ const authority = input.guard?.verificationAuthority;
1255
+ if (authority &&
1256
+ (!sameJsonValue(harness.executors ?? null, authority.executors) ||
1257
+ !sameJsonValue(harness.workflowPolicy ?? null, authority.workflowPolicy))) {
1258
+ throw new Error("verification authority harness.json merge cannot change controller executor or workflow authority");
1259
+ }
1260
+ }
1261
+ const command = await discoverQuickVerification(input.repoRoot);
1262
+ tokenizeCommand(command);
1263
+ await commandRepositoryInputs(input.repoRoot, command);
1264
+ }
1265
+ function assertTaskOutsideVerificationSurface(task, surface) {
1266
+ const taskPath = normalizeAllowedPath(task.path);
1267
+ if (surface.files.some((entry) => entry.path === taskPath) &&
1268
+ !isControllerAuthorizedVerificationMergeTask(task, surface)) {
1269
+ throw new Error(`init upgrade merge task targets authority-sensitive verification input: ${taskPath}`);
1270
+ }
1271
+ }
1272
+ async function snapshotDirectory(directory) {
1273
+ const snapshot = {};
1274
+ const visit = async (absolute, relative) => {
1275
+ const entries = await readdir(absolute, { withFileTypes: true });
1276
+ entries.sort((left, right) => left.name.localeCompare(right.name));
1277
+ for (const entry of entries) {
1278
+ const childRelative = relative ? `${relative}/${entry.name}` : entry.name;
1279
+ const child = path.join(absolute, entry.name);
1280
+ if (entry.isSymbolicLink())
1281
+ snapshot[childRelative] = `symlink:${await readlink(child)}`;
1282
+ else if (entry.isDirectory()) {
1283
+ snapshot[childRelative] = "directory";
1284
+ await visit(child, childRelative);
1285
+ }
1286
+ else if (entry.isFile())
1287
+ snapshot[childRelative] = `file:${sha256(await readFile(child))}`;
1288
+ else
1289
+ snapshot[childRelative] = "special";
1290
+ }
1291
+ };
1292
+ await visit(directory, "");
1293
+ return snapshot;
1294
+ }
1295
+ async function controllerAnchorSnapshot(repoRoot, state) {
1296
+ const location = await controllerAnchorPath({ repoRoot, runId: state.runId });
1297
+ return await snapshotDirectory(location.anchorRoot);
1298
+ }
1299
+ function validateTaskBoundary(task) {
1300
+ const allowed = task.allowedPaths.map(normalizeAllowedPath);
1301
+ if (allowed.length !== 1 || allowed[0] !== normalizeAllowedPath(task.path)) {
1302
+ throw new Error(`init upgrade merge task ${task.taskId} must declare exactly its concrete single-file path`);
1303
+ }
1304
+ return allowed;
1305
+ }
1306
+ async function harnessVerificationAuthority(repoRoot, task) {
1307
+ if (normalizeAllowedPath(task.path) !== "harness.json")
1308
+ return undefined;
1309
+ const harness = await readVerificationHarness(repoRoot);
1310
+ return {
1311
+ executors: cloneJson(harness.executors ?? null),
1312
+ workflowPolicy: cloneJson(harness.workflowPolicy ?? null),
1313
+ };
1314
+ }
1315
+ async function createMergeGuard(repoRoot, task) {
1316
+ for (const allowedPath of validateTaskBoundary(task)) {
1317
+ await assertSafeMergePath(repoRoot, allowedPath);
1318
+ }
1319
+ return {
1320
+ task,
1321
+ baselineWorkspace: await snapshotWorkspace(repoRoot),
1322
+ verificationAuthority: await harnessVerificationAuthority(repoRoot, task),
1323
+ };
1324
+ }
1325
+ function isMergeReceipt(value) {
1326
+ if (!isRecord(value))
1327
+ return false;
1328
+ return (typeof value.taskId === "string" &&
1329
+ typeof value.path === "string" &&
1330
+ isSha256(value.currentSha256) &&
1331
+ isSha256(value.desiredSha256) &&
1332
+ typeof value.acceptedAt === "string" &&
1333
+ !Number.isNaN(Date.parse(value.acceptedAt)));
1334
+ }
1335
+ function assertPendingAcceptance(value, state, task) {
1336
+ if (!isRecord(value) ||
1337
+ value.schemaVersion !== 1 ||
1338
+ !isRecord(value.guard) ||
1339
+ !isRecord(value.transition) ||
1340
+ !isRecord(value.surfaceTransition)) {
1341
+ throw new Error("invalid pending merge acceptance record");
1342
+ }
1343
+ const pending = value;
1344
+ const desiredSha256 = task.evidence.desiredSha256;
1345
+ const sourceAnchorSha256 = task.evidence.sourceAnchorSha256;
1346
+ const guard = pending.guard;
1347
+ const receipt = pending.receipt;
1348
+ const transition = pending.transition;
1349
+ const surfaceTransition = pending.surfaceTransition;
1350
+ const canonicalPath = normalizeAllowedPath(task.path);
1351
+ if (pending.runId !== state.runId ||
1352
+ pending.repoRoot !== state.repoRoot ||
1353
+ guard.taskId !== task.taskId ||
1354
+ guard.path !== canonicalPath ||
1355
+ !Array.isArray(guard.allowedPaths) ||
1356
+ guard.allowedPaths.length !== 1 ||
1357
+ guard.allowedPaths[0] !== canonicalPath ||
1358
+ !isSha256(guard.currentSha256) ||
1359
+ !isSha256(guard.desiredSha256) ||
1360
+ !isSha256(desiredSha256) ||
1361
+ guard.desiredSha256 !== desiredSha256 ||
1362
+ (sourceAnchorSha256 === undefined
1363
+ ? guard.sourceAnchorSha256 !== undefined
1364
+ : guard.sourceAnchorSha256 !== sourceAnchorSha256) ||
1365
+ !isMergeReceipt(receipt) ||
1366
+ receipt.taskId !== guard.taskId ||
1367
+ receipt.path !== guard.path ||
1368
+ receipt.currentSha256 !== guard.currentSha256 ||
1369
+ receipt.desiredSha256 !== guard.desiredSha256 ||
1370
+ transition.fromPhase !== "MODEL_MERGE" ||
1371
+ transition.toPhase !== "PLAN" ||
1372
+ !Number.isInteger(transition.priorMergeReceiptCount) ||
1373
+ transition.priorMergeReceiptCount < 0 ||
1374
+ surfaceTransition.path !== canonicalPath ||
1375
+ !isSha256(surfaceTransition.beforeSha256) ||
1376
+ !isSha256(surfaceTransition.acceptedSha256) ||
1377
+ !isRecord(surfaceTransition.beforeEntry)) {
1378
+ throw new Error("pending merge acceptance binding is invalid");
1379
+ }
1380
+ }
1381
+ function acceptedSurface(input) {
1382
+ if (!isRecord(input.surface) || !isRecord(input.surface.files)) {
1383
+ throw new Error("init upgrade merge surface transition is invalid");
1384
+ }
1385
+ const canonicalPath = normalizeAllowedPath(input.task.path);
1386
+ const entry = input.surface.files[canonicalPath];
1387
+ if (!isRecord(entry)) {
1388
+ throw new Error(`init upgrade cannot accept ${canonicalPath}: recorded surface entry is missing`);
1389
+ }
1390
+ const accepted = cloneJson(input.surface);
1391
+ const acceptedFiles = accepted.files;
1392
+ const acceptedEntry = isRecord(acceptedFiles)
1393
+ ? acceptedFiles[canonicalPath]
1394
+ : undefined;
1395
+ if (!isRecord(acceptedEntry)) {
1396
+ throw new Error("init upgrade merge surface transition is invalid");
1397
+ }
1398
+ acceptedEntry.currentSha256 = input.currentSha256;
1399
+ acceptedEntry.acceptedMerge = {
1400
+ currentSha256: input.currentSha256,
1401
+ desiredSha256: input.desiredSha256,
1402
+ ...(typeof input.task.evidence.sourceAnchorSha256 === "string"
1403
+ ? { sourceAnchorSha256: input.task.evidence.sourceAnchorSha256 }
1404
+ : {}),
1405
+ };
1406
+ return { beforeEntry: cloneJson(entry), accepted };
1407
+ }
1408
+ async function validatePendingSurfaceTransition(input) {
1409
+ const transition = input.pending.surfaceTransition;
1410
+ const baselineSurface = input.guard.baselineWorkspace[INIT_SURFACE_STATE_PATH];
1411
+ if (baselineSurface !== `file:${transition.beforeSha256}`) {
1412
+ throw new Error("pending merge acceptance surface transition is not bound to the merge baseline");
1413
+ }
1414
+ const surfacePath = path.join(input.repoRoot, INIT_SURFACE_STATE_PATH);
1415
+ let surfaceText;
1416
+ let surface;
1417
+ try {
1418
+ surfaceText = await readFile(surfacePath, "utf-8");
1419
+ surface = JSON.parse(surfaceText);
1420
+ }
1421
+ catch {
1422
+ throw new Error("pending merge acceptance init-surface is invalid");
1423
+ }
1424
+ const actualSha256 = sha256(surfaceText);
1425
+ if (actualSha256 === transition.beforeSha256) {
1426
+ if (!isRecord(surface) ||
1427
+ !isRecord(surface.files) ||
1428
+ !sameJsonValue(surface.files[normalizeAllowedPath(input.task.path)], transition.beforeEntry)) {
1429
+ throw new Error("pending merge acceptance before surface transition is inconsistent");
1430
+ }
1431
+ const expected = acceptedSurface({
1432
+ surface,
1433
+ task: input.task,
1434
+ currentSha256: input.pending.guard.currentSha256,
1435
+ desiredSha256: input.pending.guard.desiredSha256,
1436
+ }).accepted;
1437
+ if (sha256(serializeJson(expected)) !== transition.acceptedSha256) {
1438
+ throw new Error("pending merge acceptance accepted surface transition is inconsistent");
1439
+ }
1440
+ return "before";
1441
+ }
1442
+ if (actualSha256 !== transition.acceptedSha256) {
1443
+ throw new Error("pending merge acceptance init-surface contains an unrelated modification");
1444
+ }
1445
+ if (!isRecord(surface) || !isRecord(surface.files)) {
1446
+ throw new Error("pending merge acceptance init-surface is invalid");
1447
+ }
1448
+ const reconstructedBefore = cloneJson(surface);
1449
+ const reconstructedFiles = reconstructedBefore.files;
1450
+ if (!isRecord(reconstructedFiles)) {
1451
+ throw new Error("pending merge acceptance init-surface is invalid");
1452
+ }
1453
+ reconstructedFiles[normalizeAllowedPath(input.task.path)] = cloneJson(transition.beforeEntry);
1454
+ if (sha256(serializeJson(reconstructedBefore)) !== transition.beforeSha256) {
1455
+ throw new Error("pending merge acceptance surface transition changed unrelated facts");
1456
+ }
1457
+ const expectedAccepted = acceptedSurface({
1458
+ surface: reconstructedBefore,
1459
+ task: input.task,
1460
+ currentSha256: input.pending.guard.currentSha256,
1461
+ desiredSha256: input.pending.guard.desiredSha256,
1462
+ }).accepted;
1463
+ if (serializeJson(expectedAccepted) !== surfaceText) {
1464
+ throw new Error("pending merge acceptance accepted surface transition is not exact");
1465
+ }
1466
+ return "accepted";
1467
+ }
1468
+ async function readPendingAcceptance(input) {
1469
+ const file = path.join(input.directory, "merge-acceptance-pending.json");
1470
+ if (!(await exists(file)))
1471
+ return input.authoritativePending;
1472
+ let parsed;
1473
+ try {
1474
+ parsed = JSON.parse(await readFile(file, "utf-8"));
1475
+ }
1476
+ catch {
1477
+ throw new Error("invalid pending merge acceptance record");
1478
+ }
1479
+ assertPendingAcceptance(parsed, input.state, input.task);
1480
+ if (!input.authoritativePending || !sameJsonValue(parsed, input.authoritativePending)) {
1481
+ throw new Error("pending merge acceptance is not authorized by the controller anchor");
1482
+ }
1483
+ return input.authoritativePending;
1484
+ }
1485
+ async function verifyMergedFileInvariants(repoRoot, task) {
1486
+ await assertSafeMergePath(repoRoot, task.path);
1487
+ const content = await readFile(path.join(repoRoot, task.path), "utf-8");
1488
+ if (!content.trim())
1489
+ throw new Error(`init upgrade merge invariant failed: ${task.path} is empty`);
1490
+ if (task.path.endsWith(".json")) {
1491
+ const parsed = JSON.parse(content);
1492
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1493
+ throw new Error(`init upgrade merge invariant failed: ${task.path} must contain a JSON object`);
1494
+ }
1495
+ }
1496
+ if (task.path === OPENCODE_TRANSIENT_RETRY_PLUGIN_PATH) {
1497
+ for (const token of [
1498
+ `MAX_RETRIES = ${MAX_SESSION_RETRIES}`,
1499
+ "plugin-ignore-permanent-error",
1500
+ "recoveryWorkers",
1501
+ "readSessionStatus",
1502
+ ]) {
1503
+ if (!content.includes(token))
1504
+ throw new Error(`init upgrade merge invariant failed: ${task.path} is missing ${token}`);
1505
+ }
1506
+ }
1507
+ if (task.path === OPENCODE_CONTEXT_OVERFLOW_COMPACT_PLUGIN_PATH) {
1508
+ for (const token of [
1509
+ `MAX_OVERFLOW_RECOVERIES = ${MAX_OVERFLOW_RECOVERIES}`,
1510
+ "pendingOverflow",
1511
+ "deferredResume",
1512
+ "recoveryWorkers",
1513
+ "compactOrSummarize",
1514
+ ]) {
1515
+ if (!content.includes(token))
1516
+ throw new Error(`init upgrade merge invariant failed: ${task.path} is missing ${token}`);
1517
+ }
1518
+ }
1519
+ if (task.path === PI_CONTEXT_OVERFLOW_EXTENSION_PATH) {
1520
+ for (const token of ["message_end", "context_length_exceeded:"]) {
1521
+ if (!content.includes(token))
1522
+ throw new Error(`init upgrade merge invariant failed: ${task.path} is missing ${token}`);
1523
+ }
1524
+ }
1525
+ if (task.path === "AGENTS.md" ||
1526
+ task.path.endsWith("/loop-agent/SKILL.md")) {
1527
+ for (const token of ["init upgrade", "init check-update"]) {
1528
+ if (!content.includes(token))
1529
+ throw new Error(`init upgrade merge invariant failed: ${task.path} is missing ${token}`);
1530
+ }
1531
+ }
1532
+ return sha256(content);
1533
+ }
1534
+ async function recordAcceptedMerge(input) {
1535
+ const desiredSha256 = input.task.evidence.desiredSha256;
1536
+ if (!desiredSha256) {
1537
+ throw new Error(`init upgrade cannot accept ${input.task.path}: desired hash evidence is missing`);
1538
+ }
1539
+ const sourceAnchorSha256 = input.task.evidence.sourceAnchorSha256;
1540
+ const canonicalPath = normalizeAllowedPath(input.task.path);
1541
+ const surfacePath = path.join(input.state.repoRoot, INIT_SURFACE_STATE_PATH);
1542
+ let pending = input.pending;
1543
+ let receipt;
1544
+ let accepted;
1545
+ if (pending) {
1546
+ receipt = pending.receipt;
1547
+ if (input.pendingSurfaceState !== "before" &&
1548
+ input.pendingSurfaceState !== "accepted") {
1549
+ throw new Error("pending merge acceptance surface transition was not validated");
1550
+ }
1551
+ if (input.pendingSurfaceState === "before") {
1552
+ const surface = JSON.parse(await readFile(surfacePath, "utf-8"));
1553
+ accepted = acceptedSurface({
1554
+ surface,
1555
+ task: input.task,
1556
+ currentSha256: input.currentSha256,
1557
+ desiredSha256,
1558
+ }).accepted;
1559
+ }
1560
+ }
1561
+ else {
1562
+ const surfaceText = await readFile(surfacePath, "utf-8");
1563
+ const surface = JSON.parse(surfaceText);
1564
+ const transition = acceptedSurface({
1565
+ surface,
1566
+ task: input.task,
1567
+ currentSha256: input.currentSha256,
1568
+ desiredSha256,
1569
+ });
1570
+ const beforeSha256 = sha256(surfaceText);
1571
+ if (input.guard.baselineWorkspace[INIT_SURFACE_STATE_PATH] !==
1572
+ `file:${beforeSha256}`) {
1573
+ throw new Error("init upgrade merge surface no longer matches the guarded baseline");
1574
+ }
1575
+ accepted = transition.accepted;
1576
+ receipt = {
1577
+ taskId: input.task.taskId,
1578
+ path: canonicalPath,
1579
+ currentSha256: input.currentSha256,
1580
+ desiredSha256,
1581
+ acceptedAt: new Date().toISOString(),
1582
+ };
1583
+ pending = {
1584
+ schemaVersion: 1,
1585
+ runId: input.state.runId,
1586
+ repoRoot: input.state.repoRoot,
1587
+ guard: {
1588
+ taskId: input.task.taskId,
1589
+ path: canonicalPath,
1590
+ allowedPaths: [canonicalPath],
1591
+ currentSha256: input.currentSha256,
1592
+ desiredSha256,
1593
+ ...(sourceAnchorSha256 !== undefined
1594
+ ? { sourceAnchorSha256 }
1595
+ : {}),
1596
+ },
1597
+ receipt,
1598
+ transition: {
1599
+ fromPhase: "MODEL_MERGE",
1600
+ toPhase: "PLAN",
1601
+ priorMergeReceiptCount: input.state.mergeReceipts.length,
1602
+ },
1603
+ surfaceTransition: {
1604
+ path: canonicalPath,
1605
+ beforeSha256,
1606
+ acceptedSha256: sha256(serializeJson(accepted)),
1607
+ beforeEntry: transition.beforeEntry,
1608
+ },
1609
+ };
1610
+ await writeControllerAnchor({
1611
+ state: input.state,
1612
+ mergeAuthority: {
1613
+ task: input.task,
1614
+ baselineWorkspace: input.guard.baselineWorkspace,
1615
+ verificationAuthority: input.guard.verificationAuthority,
1616
+ priorMergeReceiptCount: input.state.mergeReceipts.length,
1617
+ priorMergeReceiptsSha256: sha256(serializeJson(input.state.mergeReceipts)),
1618
+ stateProjection: controllerStateProjection(input.state),
1619
+ stateProjectionSha256: canonicalSha256(controllerStateProjection(input.state)),
1620
+ verificationSurface: input.verificationSurface,
1621
+ pending,
1622
+ },
1623
+ });
1624
+ await writeJsonAtomic(path.join(input.directory, "merge-acceptance-pending.json"), pending);
1625
+ }
1626
+ if (accepted)
1627
+ await writeJsonAtomic(surfacePath, accepted);
1628
+ if (!pending) {
1629
+ throw new Error("pending merge acceptance was not established");
1630
+ }
1631
+ const priorMergeReceiptCount = pending.transition.priorMergeReceiptCount;
1632
+ if (input.state.mergeReceipts.length === priorMergeReceiptCount) {
1633
+ input.state.mergeReceipts.push(receipt);
1634
+ }
1635
+ else if (input.state.mergeReceipts.length !== priorMergeReceiptCount + 1 ||
1636
+ !sameJsonValue(input.state.mergeReceipts[priorMergeReceiptCount], receipt)) {
1637
+ throw new Error("pending merge acceptance receipt projection is inconsistent");
1638
+ }
1639
+ await writeJsonAtomic(path.join(input.directory, "model-merge-receipts.json"), input.state.mergeReceipts);
1640
+ }
1641
+ async function validateContinueMerge(directory, state, anchor) {
1642
+ if (!anchor.mergeAuthority) {
1643
+ if (state.phase === "MODEL_MERGE" && state.mergeTasks.length > 0) {
1644
+ throw new Error("init upgrade controller merge baseline authority is missing or inconsistent");
1645
+ }
1646
+ return "none";
1647
+ }
1648
+ const authority = mergeAuthorityFromAnchor(anchor, state);
1649
+ const projection = mergeAuthorityProjection(state, authority);
1650
+ const task = authority.task;
1651
+ await assertFrozenVerificationSurfaceExcept(state.repoRoot, authority.verificationSurface, isControllerAuthorizedVerificationMergeTask(task, authority.verificationSurface)
1652
+ ? task.path
1653
+ : undefined);
1654
+ const authoritativeGuard = {
1655
+ task,
1656
+ baselineWorkspace: authority.baselineWorkspace,
1657
+ verificationAuthority: authority.verificationAuthority,
1658
+ };
1659
+ const allowed = new Set(validateTaskBoundary(task));
1660
+ for (const allowedPath of allowed) {
1661
+ await assertSafeMergePath(state.repoRoot, allowedPath);
1662
+ }
1663
+ const current = await snapshotWorkspace(state.repoRoot);
1664
+ const rawChanged = changedWorkspacePaths(authoritativeGuard.baselineWorkspace, current);
1665
+ const pending = await readPendingAcceptance({
1666
+ directory,
1667
+ state,
1668
+ task,
1669
+ authoritativePending: authority.pending,
1670
+ });
1671
+ const pendingSurfaceState = pending
1672
+ ? await validatePendingSurfaceTransition({
1673
+ repoRoot: state.repoRoot,
1674
+ guard: authoritativeGuard,
1675
+ task,
1676
+ pending,
1677
+ })
1678
+ : undefined;
1679
+ // Only the out-of-bound drift check treats git-ignored/generated-output
1680
+ // changes as noise; allowed-path changes are merge content and must stay
1681
+ // intact even when the target is git-ignored (e.g. `.agents/skills/*`).
1682
+ const changed = await filterWorkspaceNoise(state.repoRoot, rawChanged);
1683
+ const unexpected = changed.filter((relativePath) => {
1684
+ if (allowed.has(relativePath))
1685
+ return false;
1686
+ return !(relativePath === INIT_SURFACE_STATE_PATH &&
1687
+ pendingSurfaceState === "accepted");
1688
+ });
1689
+ if (unexpected.length > 0) {
1690
+ throw new Error(`init upgrade merge write-guard blocked out-of-bound workspace changes: ${unexpected.join(", ")}`);
1691
+ }
1692
+ const changedAllowed = rawChanged.filter((relativePath) => allowed.has(relativePath));
1693
+ if (changedAllowed.length === 0) {
1694
+ if (pending) {
1695
+ throw new Error("pending merge acceptance cannot authorize a merge without an allowed workspace change");
1696
+ }
1697
+ state.nextAction = `No change was detected in allowedPaths=[${[...allowed].join(", ")}]. Complete the current merge task before calling --continue.`;
1698
+ await saveState(directory, state);
1699
+ await writeReport(directory, state);
1700
+ return "no-change";
1701
+ }
1702
+ const currentSha256 = await verifyMergedFileInvariants(state.repoRoot, task);
1703
+ await validateControllerAuthorizedVerificationMerge({
1704
+ repoRoot: state.repoRoot,
1705
+ task,
1706
+ surface: authority.verificationSurface,
1707
+ guard: authoritativeGuard,
1708
+ });
1709
+ if (pending && pending.guard.currentSha256 !== currentSha256) {
1710
+ throw new Error("pending merge acceptance current hash is inconsistent");
1711
+ }
1712
+ if (projection === "post-acceptance") {
1713
+ if (!pending || pendingSurfaceState !== "accepted") {
1714
+ throw new Error("post-acceptance merge state lacks the exact controller-authorized surface transition");
1715
+ }
1716
+ return "accepted";
1717
+ }
1718
+ await recordAcceptedMerge({
1719
+ directory,
1720
+ state,
1721
+ guard: authoritativeGuard,
1722
+ task,
1723
+ currentSha256,
1724
+ ...(pending && pendingSurfaceState
1725
+ ? { pending, pendingSurfaceState }
1726
+ : {}),
1727
+ verificationSurface: authority.verificationSurface,
1728
+ });
1729
+ state.mergeTasks = [];
1730
+ state.mergeGuard = undefined;
1731
+ state.phase = "PLAN";
1732
+ state.nextAction = "Accepted the bounded semantic merge; replan and verify the current workspace.";
1733
+ await saveState(directory, state);
1734
+ await rm(path.join(directory, "merge-acceptance-pending.json"), {
1735
+ force: true,
1736
+ });
1737
+ return "accepted";
1738
+ }
1739
+ function boundedOutput(value) {
1740
+ return value.length <= 8000 ? value : value.slice(value.length - 8000);
1741
+ }
1742
+ const VERIFICATION_COMMAND_DEADLINE_MS = 300_000;
1743
+ const VERIFICATION_STDIO_GRACE_MS = 500;
1744
+ const VERIFICATION_TREE_SETTLE_MS = 500;
1745
+ const VERIFICATION_QUIET_WINDOW_MS = 500;
1746
+ const VERIFICATION_OUTPUT_LIMIT = 4 * 1024 * 1024;
1747
+ const VERIFICATION_SUPERVISOR_SOURCE = `
1748
+ import { spawn } from "node:child_process";
1749
+ const specification = JSON.parse(Buffer.from(process.argv[2], "base64url").toString("utf8"));
1750
+ let worker;
1751
+ let workerClosed = false;
1752
+ let exitResult;
1753
+ let sent = false;
1754
+ const finish = (message) => {
1755
+ if (sent) return;
1756
+ sent = true;
1757
+ const afterSend = () => {
1758
+ if (specification.holdOpen) {
1759
+ setInterval(() => {}, 60000);
1760
+ return;
1761
+ }
1762
+ worker?.stdout?.destroy();
1763
+ worker?.stderr?.destroy();
1764
+ process.disconnect?.();
1765
+ process.exit(0);
1766
+ };
1767
+ if (typeof process.send !== "function") process.exit(97);
1768
+ process.send({ ...message, stdioOpen: !workerClosed }, afterSend);
1769
+ };
1770
+ try {
1771
+ worker = spawn(specification.command, specification.args, {
1772
+ cwd: process.cwd(),
1773
+ env: process.env,
1774
+ stdio: ["ignore", "pipe", "pipe"],
1775
+ windowsHide: true,
1776
+ shell: false,
1777
+ });
1778
+ worker.stdout?.pipe(process.stdout);
1779
+ worker.stderr?.pipe(process.stderr);
1780
+ worker.once("close", () => {
1781
+ workerClosed = true;
1782
+ if (exitResult) finish(exitResult);
1783
+ });
1784
+ worker.once("error", (error) => finish({
1785
+ type: "result",
1786
+ ok: false,
1787
+ exitCode: null,
1788
+ error: error instanceof Error ? error.message : String(error),
1789
+ }));
1790
+ worker.once("exit", (code, signal) => {
1791
+ exitResult = {
1792
+ type: "result",
1793
+ ok: code === 0 && signal === null,
1794
+ exitCode: typeof code === "number" ? code : 1,
1795
+ ...(signal ? { error: "verification command exited by signal " + signal } : {}),
1796
+ };
1797
+ if (workerClosed) finish(exitResult);
1798
+ else setTimeout(() => finish(exitResult), specification.stdioGraceMs);
1799
+ });
1800
+ } catch (error) {
1801
+ finish({
1802
+ type: "result",
1803
+ ok: false,
1804
+ exitCode: null,
1805
+ error: error instanceof Error ? error.message : String(error),
1806
+ });
1807
+ }
1808
+ process.once("disconnect", () => {
1809
+ if (!sent) worker?.kill("SIGTERM");
1810
+ process.exit(98);
1811
+ });
1812
+ `;
1813
+ class VerificationProcessBoundaryError extends Error {
1814
+ constructor(message) {
1815
+ super(message);
1816
+ this.name = "VerificationProcessBoundaryError";
1817
+ }
1818
+ }
1819
+ function tokenizeCommand(command) {
1820
+ if (!command.trim() || /[;&|><`$\r\n]/.test(command)) {
1821
+ throw new Error(`unsafe verification command: ${command}`);
1822
+ }
1823
+ const tokens = [];
1824
+ let token = "";
1825
+ let quote;
1826
+ for (let index = 0; index < command.length; index += 1) {
1827
+ const character = command[index];
1828
+ if (quote) {
1829
+ if (character === quote)
1830
+ quote = undefined;
1831
+ else
1832
+ token += character;
1833
+ continue;
1834
+ }
1835
+ if (character === '"' || character === "'") {
1836
+ quote = character;
1837
+ continue;
1838
+ }
1839
+ if (/\s/.test(character)) {
1840
+ if (token) {
1841
+ tokens.push(token);
1842
+ token = "";
1843
+ }
1844
+ continue;
1845
+ }
1846
+ token += character;
1847
+ }
1848
+ if (quote)
1849
+ throw new Error(`unterminated quote in verification command: ${command}`);
1850
+ if (token)
1851
+ tokens.push(token);
1852
+ if (tokens.length === 0)
1853
+ throw new Error("empty verification command");
1854
+ return tokens;
1855
+ }
1856
+ async function snapshotOptionalDirectory(directory) {
1857
+ const entry = await lstat(directory).catch((error) => {
1858
+ if (error.code === "ENOENT")
1859
+ return undefined;
1860
+ throw error;
1861
+ });
1862
+ if (!entry)
1863
+ return {};
1864
+ if (!entry.isDirectory() || entry.isSymbolicLink()) {
1865
+ throw new Error("real Pi home is not a regular directory");
1866
+ }
1867
+ return await snapshotDirectory(directory);
1868
+ }
1869
+ function verificationSandboxEnvironment(sandbox) {
1870
+ const home = path.join(sandbox, "home");
1871
+ const temporary = path.join(sandbox, "tmp");
1872
+ const config = path.join(sandbox, "config");
1873
+ const cache = path.join(sandbox, "cache");
1874
+ const pathValue = process.env.PATH;
1875
+ if (!pathValue)
1876
+ throw new Error("verification isolation requires PATH");
1877
+ const env = {
1878
+ PATH: pathValue,
1879
+ HOME: home,
1880
+ USERPROFILE: home,
1881
+ XDG_CONFIG_HOME: config,
1882
+ XDG_CACHE_HOME: cache,
1883
+ XDG_DATA_HOME: path.join(sandbox, "data"),
1884
+ TMPDIR: temporary,
1885
+ TMP: temporary,
1886
+ TEMP: temporary,
1887
+ PI_HOME: path.join(sandbox, "pi-home"),
1888
+ PI_CONFIG_DIR: path.join(config, "pi"),
1889
+ PI_CACHE_DIR: path.join(cache, "pi"),
1890
+ npm_config_cache: path.join(cache, "npm"),
1891
+ HARNESS_ALLOW_ACTIVE_DAG_RUNS: "1",
1892
+ };
1893
+ for (const name of ["SystemRoot", "WINDIR", "ComSpec", "PATHEXT", "LANG", "LC_ALL"]) {
1894
+ if (process.env[name])
1895
+ env[name] = process.env[name];
1896
+ }
1897
+ return env;
1898
+ }
1899
+ async function createVerificationSandbox() {
1900
+ const sandbox = await mkdtemp(path.join(os.tmpdir(), "loop-agent-init-verification-"));
1901
+ await Promise.all(["home", "tmp", "config", "cache", "data", "pi-home"].map((directory) => mkdir(path.join(sandbox, directory), { recursive: true })));
1902
+ return sandbox;
1903
+ }
1904
+ async function createVerificationWriteAudit(input) {
1905
+ const events = [];
1906
+ const failures = [];
1907
+ const watchers = [];
1908
+ const watched = new Set();
1909
+ const canonicalRepoRoot = await realpath(input.repoRoot);
1910
+ const piHome = path.join(os.homedir(), ".pi");
1911
+ const anchor = await controllerAnchorPath(input.state);
1912
+ const note = (scope, file) => {
1913
+ events.push(`${scope}=${file.split(path.sep).join("/")}`);
1914
+ };
1915
+ const ignoredWorkspacePath = (candidate) => isWorkspaceNoisePath(toRepoPath(canonicalRepoRoot, candidate));
1916
+ const install = async (scope, directory, ignore, scopeRoot) => {
1917
+ const canonical = await realpath(directory);
1918
+ if (watched.has(canonical))
1919
+ return;
1920
+ watched.add(canonical);
1921
+ let watcher;
1922
+ try {
1923
+ watcher = watch(canonical, { persistent: false }, (_eventType, filename) => {
1924
+ const candidate = filename
1925
+ ? path.join(canonical, filename.toString())
1926
+ : canonical;
1927
+ if (ignore?.(candidate))
1928
+ return;
1929
+ let existingDirectory = false;
1930
+ try {
1931
+ existingDirectory = lstatSync(candidate).isDirectory();
1932
+ }
1933
+ catch {
1934
+ // A removed or transient path is still a write-boundary event.
1935
+ }
1936
+ // Windows can report a parent-directory notification when a read-only
1937
+ // traversal opens an existing child directory. Nested watchers own real
1938
+ // mutations below that child; persistent changes are also snapshot-checked.
1939
+ if (!existingDirectory) {
1940
+ note(scope, path.relative(scopeRoot ?? directory, candidate) || ".");
1941
+ }
1942
+ // A parent rename can introduce a new directory. Installing its watcher
1943
+ // closes the delayed-descendant gap even when the container event is noise.
1944
+ void install(scope, candidate, ignore, scopeRoot).catch((error) => {
1945
+ const code = error.code;
1946
+ if (code !== "ENOENT" && code !== "ENOTDIR") {
1947
+ failures.push(`watcher refresh failed for ${scope}: ${error instanceof Error ? error.message : String(error)}`);
1948
+ }
1949
+ });
1950
+ });
1951
+ watcher.on("error", (error) => {
1952
+ failures.push(`watcher failed for ${scope}: ${error.message}`);
1953
+ });
1954
+ watchers.push(watcher);
1955
+ }
1956
+ catch (error) {
1957
+ throw new Error(`verification watcher is unavailable for ${scope}: ${error instanceof Error ? error.message : String(error)}`);
1958
+ }
1959
+ const entries = await readdir(canonical, { withFileTypes: true });
1960
+ for (const entry of entries) {
1961
+ const child = path.join(canonical, entry.name);
1962
+ if (ignore?.(child) || !entry.isDirectory() || entry.isSymbolicLink())
1963
+ continue;
1964
+ await install(scope, child, ignore, scopeRoot);
1965
+ }
1966
+ };
1967
+ await install("workspace", canonicalRepoRoot, ignoredWorkspacePath, canonicalRepoRoot);
1968
+ await install("controller", runRoot(input.repoRoot));
1969
+ await install("controller-anchor", anchor.anchorRoot);
1970
+ const piHomeEntry = await lstat(piHome).catch((error) => {
1971
+ if (error.code === "ENOENT")
1972
+ return undefined;
1973
+ throw error;
1974
+ });
1975
+ if (piHomeEntry) {
1976
+ if (!piHomeEntry.isDirectory() || piHomeEntry.isSymbolicLink()) {
1977
+ throw new Error("real Pi home is not a regular directory");
1978
+ }
1979
+ await install("pi-home", piHome);
1980
+ }
1981
+ else {
1982
+ const home = await realpath(os.homedir());
1983
+ let homeWatcher;
1984
+ try {
1985
+ homeWatcher = watch(home, { persistent: false }, (_eventType, filename) => {
1986
+ if (!filename || filename.toString() === ".pi")
1987
+ note("pi-home", ".");
1988
+ });
1989
+ homeWatcher.on("error", (error) => {
1990
+ failures.push(`watcher failed for pi-home parent: ${error.message}`);
1991
+ });
1992
+ watchers.push(homeWatcher);
1993
+ }
1994
+ catch (error) {
1995
+ throw new Error(`verification watcher is unavailable for pi-home: ${error instanceof Error ? error.message : String(error)}`);
1996
+ }
1997
+ }
1998
+ return {
1999
+ events,
2000
+ failures,
2001
+ close: () => {
2002
+ for (const watcher of watchers)
2003
+ watcher.close();
2004
+ },
2005
+ };
2006
+ }
2007
+ function appendVerificationOutput(current, chunk) {
2008
+ const combined = `${current}${chunk.toString()}`;
2009
+ return combined.length <= VERIFICATION_OUTPUT_LIMIT
2010
+ ? combined
2011
+ : combined.slice(combined.length - VERIFICATION_OUTPUT_LIMIT);
2012
+ }
2013
+ async function waitForBounded(promise, timeoutMs) {
2014
+ let timeout;
2015
+ const completed = await Promise.race([
2016
+ promise.then(() => true),
2017
+ new Promise((resolve) => {
2018
+ timeout = setTimeout(() => resolve(false), timeoutMs);
2019
+ }),
2020
+ ]);
2021
+ if (timeout)
2022
+ clearTimeout(timeout);
2023
+ return completed;
2024
+ }
2025
+ function processBoundaryExists(pid, group) {
2026
+ try {
2027
+ process.kill(group ? -pid : pid, 0);
2028
+ return true;
2029
+ }
2030
+ catch (error) {
2031
+ if (error.code === "ESRCH")
2032
+ return false;
2033
+ throw error;
2034
+ }
2035
+ }
2036
+ async function runSupervisedVerification(input) {
2037
+ const supervisorPath = path.join(input.sandbox, "verification-supervisor.mjs");
2038
+ await writeFile(supervisorPath, VERIFICATION_SUPERVISOR_SOURCE, "utf-8");
2039
+ // The fixed supervisor, not the repository command, is the direct detached
2040
+ // child. It reports worker exit over IPC and keeps the Windows tree root alive
2041
+ // until the controller performs bounded taskkill /T convergence.
2042
+ const specification = Buffer.from(JSON.stringify({
2043
+ command: input.command,
2044
+ args: input.args,
2045
+ stdioGraceMs: VERIFICATION_STDIO_GRACE_MS,
2046
+ holdOpen: process.platform === "win32",
2047
+ })).toString("base64url");
2048
+ let stdout = "";
2049
+ let stderr = "";
2050
+ const supervisor = spawn(process.execPath, [supervisorPath, specification], {
2051
+ cwd: input.repoRoot,
2052
+ env: verificationSandboxEnvironment(input.sandbox),
2053
+ stdio: ["ignore", "pipe", "pipe", "ipc"],
2054
+ detached: true,
2055
+ windowsHide: true,
2056
+ shell: false,
2057
+ });
2058
+ supervisor.stdout?.on("data", (chunk) => {
2059
+ stdout = appendVerificationOutput(stdout, chunk);
2060
+ });
2061
+ supervisor.stderr?.on("data", (chunk) => {
2062
+ stderr = appendVerificationOutput(stderr, chunk);
2063
+ });
2064
+ const waitForExit = new Promise((resolve) => {
2065
+ supervisor.once("exit", () => resolve());
2066
+ supervisor.once("error", () => resolve());
2067
+ });
2068
+ const outcome = await new Promise((resolve) => {
2069
+ let settled = false;
2070
+ const finish = (value) => {
2071
+ if (settled)
2072
+ return;
2073
+ settled = true;
2074
+ clearTimeout(deadline);
2075
+ resolve(value);
2076
+ };
2077
+ const deadline = setTimeout(() => {
2078
+ finish({
2079
+ reported: false,
2080
+ ok: false,
2081
+ exitCode: null,
2082
+ stdioOpen: true,
2083
+ timedOut: true,
2084
+ error: `verification command exceeded ${VERIFICATION_COMMAND_DEADLINE_MS}ms deadline`,
2085
+ });
2086
+ }, VERIFICATION_COMMAND_DEADLINE_MS);
2087
+ supervisor.once("error", (error) => {
2088
+ finish({
2089
+ reported: false,
2090
+ ok: false,
2091
+ exitCode: null,
2092
+ stdioOpen: false,
2093
+ timedOut: false,
2094
+ error: `verification supervisor failed to start: ${error.message}`,
2095
+ });
2096
+ });
2097
+ supervisor.on("message", (message) => {
2098
+ if (!isRecord(message) ||
2099
+ message.type !== "result" ||
2100
+ typeof message.ok !== "boolean" ||
2101
+ (message.exitCode !== null && typeof message.exitCode !== "number") ||
2102
+ typeof message.stdioOpen !== "boolean" ||
2103
+ (message.error !== undefined && typeof message.error !== "string")) {
2104
+ finish({
2105
+ reported: false,
2106
+ ok: false,
2107
+ exitCode: null,
2108
+ stdioOpen: false,
2109
+ timedOut: false,
2110
+ error: "verification supervisor returned an invalid protocol message",
2111
+ });
2112
+ return;
2113
+ }
2114
+ finish({
2115
+ reported: true,
2116
+ ok: message.ok,
2117
+ exitCode: message.exitCode,
2118
+ stdioOpen: message.stdioOpen,
2119
+ timedOut: false,
2120
+ ...(message.error ? { error: message.error } : {}),
2121
+ });
2122
+ });
2123
+ supervisor.once("exit", (code, signal) => {
2124
+ setTimeout(() => {
2125
+ finish({
2126
+ reported: false,
2127
+ ok: false,
2128
+ exitCode: typeof code === "number" ? code : null,
2129
+ stdioOpen: false,
2130
+ timedOut: false,
2131
+ error: `verification supervisor exited before reporting command completion${signal ? ` (${signal})` : ""}`,
2132
+ });
2133
+ }, 0);
2134
+ });
2135
+ });
2136
+ return {
2137
+ pid: supervisor.pid,
2138
+ outcome,
2139
+ waitForExit,
2140
+ readResult: () => ({
2141
+ ok: outcome.ok && !outcome.timedOut,
2142
+ command: input.commandText,
2143
+ exitCode: outcome.exitCode,
2144
+ stdout: boundedOutput(stdout),
2145
+ stderr: boundedOutput(`${stderr}${outcome.error ? `${stderr ? "\n" : ""}${outcome.error}` : ""}`),
2146
+ }),
2147
+ };
2148
+ }
2149
+ async function settleVerificationProcessTree(execution) {
2150
+ const pid = execution.pid;
2151
+ if (!pid || !Number.isInteger(pid) || pid <= 0)
2152
+ return false;
2153
+ if (process.platform === "win32") {
2154
+ // holdOpen keeps this exact boundary PID alive, avoiding a post-exit tree
2155
+ // lookup against a vanished direct process.
2156
+ const taskkillOutput = await new Promise((resolve, reject) => {
2157
+ execFile("taskkill", ["/pid", String(pid), "/t", "/f"], { windowsHide: true, timeout: 5_000, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
2158
+ if (error) {
2159
+ reject(new Error(`verification Windows process tree did not converge: ${boundedOutput(`${stdout}\n${stderr}\n${error.message}`)}`));
2160
+ return;
2161
+ }
2162
+ resolve(`${stdout}\n${stderr}`);
2163
+ });
2164
+ });
2165
+ await new Promise((resolve) => setTimeout(resolve, VERIFICATION_TREE_SETTLE_MS));
2166
+ if (processBoundaryExists(pid, false)) {
2167
+ throw new Error("verification Windows process tree did not converge");
2168
+ }
2169
+ const terminatedLines = taskkillOutput
2170
+ .split(/\r?\n/)
2171
+ .map((line) => line.trim())
2172
+ .filter(Boolean);
2173
+ return execution.outcome.stdioOpen || terminatedLines.length > 1;
2174
+ }
2175
+ if (execution.outcome.reported) {
2176
+ await waitForBounded(execution.waitForExit, VERIFICATION_TREE_SETTLE_MS);
2177
+ }
2178
+ let descendantsObserved = false;
2179
+ if (processBoundaryExists(pid, true)) {
2180
+ descendantsObserved = execution.outcome.reported;
2181
+ try {
2182
+ process.kill(-pid, "SIGTERM");
2183
+ }
2184
+ catch (error) {
2185
+ if (error.code !== "ESRCH")
2186
+ throw error;
2187
+ }
2188
+ }
2189
+ await new Promise((resolve) => setTimeout(resolve, VERIFICATION_TREE_SETTLE_MS));
2190
+ if (processBoundaryExists(pid, true)) {
2191
+ try {
2192
+ process.kill(-pid, "SIGKILL");
2193
+ }
2194
+ catch (error) {
2195
+ if (error.code !== "ESRCH")
2196
+ throw error;
2197
+ }
2198
+ await new Promise((resolve) => setTimeout(resolve, VERIFICATION_TREE_SETTLE_MS));
2199
+ if (processBoundaryExists(pid, true)) {
2200
+ throw new Error("verification descendant process group did not converge");
2201
+ }
2202
+ }
2203
+ return descendantsObserved || execution.outcome.stdioOpen;
2204
+ }
2205
+ function scheduleDeferredVerificationCleanup(input) {
2206
+ const retry = async () => {
2207
+ try {
2208
+ await settleVerificationProcessTree(input.execution);
2209
+ await new Promise((resolve) => setTimeout(resolve, VERIFICATION_QUIET_WINDOW_MS));
2210
+ input.audit.close();
2211
+ await rm(input.sandbox, { recursive: true, force: true });
2212
+ if (await exists(input.sandbox)) {
2213
+ throw new Error("verification sandbox cleanup was incomplete");
2214
+ }
2215
+ }
2216
+ catch {
2217
+ // Keep the controller event loop alive while the watcher remains open.
2218
+ // Exiting before boundary proof would abandon an unmonitored descendant.
2219
+ setTimeout(() => void retry(), VERIFICATION_TREE_SETTLE_MS);
2220
+ }
2221
+ };
2222
+ setTimeout(() => void retry(), VERIFICATION_TREE_SETTLE_MS);
2223
+ }
2224
+ async function runVerificationCommand(input) {
2225
+ const { state, surface, commandText } = input;
2226
+ const repoRoot = state.repoRoot;
2227
+ let sandbox;
2228
+ let audit;
2229
+ let execution;
2230
+ let boundaryConverged = false;
2231
+ let descendantsObserved = false;
2232
+ let failure;
2233
+ let boundaryFailure;
2234
+ let result;
2235
+ try {
2236
+ await assertFrozenVerificationSurface(repoRoot, surface);
2237
+ const workspaceBefore = await snapshotWorkspace(repoRoot);
2238
+ const controllerBefore = await snapshotDirectory(runRoot(repoRoot));
2239
+ const anchorBefore = await controllerAnchorSnapshot(repoRoot, state);
2240
+ const piHomeBefore = await snapshotOptionalDirectory(path.join(os.homedir(), ".pi"));
2241
+ const tokens = tokenizeCommand(commandText);
2242
+ await commandRepositoryInputs(repoRoot, commandText);
2243
+ let command = tokens.shift();
2244
+ if (process.platform === "win32" && command === "npm")
2245
+ command = "npm.cmd";
2246
+ if (process.platform === "win32" && command === "npx")
2247
+ command = "npx.cmd";
2248
+ if (command === "bash" && tokens[0])
2249
+ await assertSafeMergePath(repoRoot, tokens[0]);
2250
+ sandbox = await createVerificationSandbox();
2251
+ audit = await createVerificationWriteAudit({ repoRoot, state });
2252
+ execution = await runSupervisedVerification({
2253
+ repoRoot,
2254
+ sandbox,
2255
+ command,
2256
+ args: tokens,
2257
+ commandText,
2258
+ });
2259
+ descendantsObserved = await settleVerificationProcessTree(execution);
2260
+ boundaryConverged = true;
2261
+ // Keep event coverage through a bounded quiet window after tree convergence.
2262
+ await new Promise((resolve) => setTimeout(resolve, VERIFICATION_QUIET_WINDOW_MS));
2263
+ result = execution.readResult();
2264
+ if (audit.failures.length)
2265
+ throw new Error(audit.failures.join("; "));
2266
+ await assertFrozenVerificationSurface(repoRoot, surface);
2267
+ const changedWorkspace = await filterWorkspaceNoise(repoRoot, changedWorkspacePaths(workspaceBefore, await snapshotWorkspace(repoRoot)));
2268
+ const changedController = changedWorkspacePaths(controllerBefore, await snapshotDirectory(runRoot(repoRoot)));
2269
+ const changedAnchors = changedWorkspacePaths(anchorBefore, await controllerAnchorSnapshot(repoRoot, state));
2270
+ const changedPiHome = changedWorkspacePaths(piHomeBefore, await snapshotOptionalDirectory(path.join(os.homedir(), ".pi")));
2271
+ // Workspace watcher events are repo-root relative (see scopeRoot in
2272
+ // createVerificationWriteAudit); drop git-ignored/generated-output noise
2273
+ // before deciding whether the subprocess wrote the workspace.
2274
+ const workspaceEvents = audit.events.filter((event) => event.startsWith("workspace="));
2275
+ const remainingEvents = [
2276
+ ...audit.events.filter((event) => !event.startsWith("workspace=")),
2277
+ ...(await filterWorkspaceNoise(repoRoot, workspaceEvents.map((event) => event.slice("workspace=".length)))).map((relative) => `workspace=${relative}`),
2278
+ ];
2279
+ if (descendantsObserved || remainingEvents.length || changedWorkspace.length || changedController.length || changedAnchors.length || changedPiHome.length) {
2280
+ return {
2281
+ ...result,
2282
+ ok: false,
2283
+ stderr: boundedOutput(`${result.stderr}\nverification write guard blocked changes: ${[
2284
+ ...(descendantsObserved ? ["process-tree=descendant"] : []),
2285
+ ...(remainingEvents.length ? [`events=${remainingEvents.join(",")}`] : []),
2286
+ ...(changedWorkspace.length ? [`workspace=${changedWorkspace.join(",")}`] : []),
2287
+ ...(changedController.length ? [`controller=${changedController.join(",")}`] : []),
2288
+ ...(changedAnchors.length ? [`controller-anchor=${changedAnchors.join(",")}`] : []),
2289
+ ...(changedPiHome.length ? [`pi-home=${changedPiHome.join(",")}`] : []),
2290
+ ].join("; ")}`),
2291
+ };
2292
+ }
2293
+ }
2294
+ catch (error) {
2295
+ failure = error;
2296
+ }
2297
+ finally {
2298
+ if (execution && !boundaryConverged) {
2299
+ try {
2300
+ descendantsObserved =
2301
+ (await settleVerificationProcessTree(execution)) || descendantsObserved;
2302
+ boundaryConverged = true;
2303
+ await new Promise((resolve) => setTimeout(resolve, VERIFICATION_QUIET_WINDOW_MS));
2304
+ }
2305
+ catch (cleanupError) {
2306
+ boundaryFailure = new VerificationProcessBoundaryError(`init upgrade ${state.runId} verification process boundary termination is unconfirmed${execution.pid ? ` for pid ${execution.pid}` : ""}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
2307
+ if (audit && sandbox) {
2308
+ scheduleDeferredVerificationCleanup({ execution, audit, sandbox });
2309
+ }
2310
+ }
2311
+ }
2312
+ if (!boundaryFailure) {
2313
+ if (audit)
2314
+ audit.close();
2315
+ if (sandbox) {
2316
+ try {
2317
+ await rm(sandbox, { recursive: true, force: true });
2318
+ if (await exists(sandbox))
2319
+ throw new Error("verification sandbox cleanup was incomplete");
2320
+ }
2321
+ catch (error) {
2322
+ failure ??= error;
2323
+ }
2324
+ }
2325
+ }
2326
+ if (boundaryFailure)
2327
+ throw boundaryFailure;
2328
+ }
2329
+ if (failure || !result) {
2330
+ return {
2331
+ ok: false,
2332
+ command: commandText,
2333
+ exitCode: null,
2334
+ stdout: "",
2335
+ stderr: failure instanceof Error ? failure.message : String(failure ?? "verification command did not return a result"),
2336
+ };
2337
+ }
2338
+ return result;
2339
+ }
2340
+ function verificationMatrixPath(harness) {
2341
+ const governanceRoot = typeof harness.governanceRoot === "string"
2342
+ ? harness.governanceRoot
2343
+ : "ai_workspace/loop-agent";
2344
+ return typeof harness.entrypoints?.verificationMatrix === "string"
2345
+ ? harness.entrypoints.verificationMatrix
2346
+ : `${governanceRoot}/verification-matrix.md`;
2347
+ }
2348
+ async function readVerificationHarness(repoRoot) {
2349
+ const parsed = JSON.parse(await readFile(path.join(repoRoot, "harness.json"), "utf-8"));
2350
+ if (!isRecord(parsed)) {
2351
+ throw new Error("verification authority harness.json must contain a JSON object");
2352
+ }
2353
+ return parsed;
2354
+ }
2355
+ async function discoverQuickVerification(repoRoot) {
2356
+ const harness = await readVerificationHarness(repoRoot);
2357
+ const matrixPath = verificationMatrixPath(harness);
2358
+ await assertSafeMergePath(repoRoot, matrixPath);
2359
+ const matrix = await readFile(path.join(repoRoot, matrixPath), "utf-8");
2360
+ const targetTests = matrix.match(/^\|\s*target project tests are valid\s*\|\s*`([^`]+)`/im)?.[1];
2361
+ if (!targetTests) {
2362
+ throw new Error(`target verification matrix does not declare a concrete minimum project test command: ${matrixPath}`);
2363
+ }
2364
+ return targetTests.trim();
2365
+ }
2366
+ async function verifyRecoveryFiles(repoRoot) {
2367
+ const verification = {};
2368
+ for (const relativePath of RECOVERY_PATHS) {
2369
+ const present = await exists(path.join(repoRoot, relativePath));
2370
+ let invariant = present;
2371
+ if (present && relativePath !== PI_PROJECT_SETTINGS_PATH) {
2372
+ try {
2373
+ const content = await readFile(path.join(repoRoot, relativePath), "utf-8");
2374
+ if (relativePath === OPENCODE_TRANSIENT_RETRY_PLUGIN_PATH) {
2375
+ invariant =
2376
+ content.includes(`MAX_RETRIES = ${MAX_SESSION_RETRIES}`) &&
2377
+ content.includes("plugin-ignore-permanent-error") &&
2378
+ content.includes("recoveryWorkers");
2379
+ }
2380
+ else if (relativePath === OPENCODE_CONTEXT_OVERFLOW_COMPACT_PLUGIN_PATH) {
2381
+ invariant =
2382
+ content.includes(`MAX_OVERFLOW_RECOVERIES = ${MAX_OVERFLOW_RECOVERIES}`) &&
2383
+ content.includes("compactOrSummarize") &&
2384
+ content.includes("recoveryWorkers");
2385
+ }
2386
+ else {
2387
+ invariant =
2388
+ content.includes("message_end") &&
2389
+ content.includes("context_length_exceeded:");
2390
+ }
2391
+ }
2392
+ catch {
2393
+ invariant = false;
2394
+ }
2395
+ }
2396
+ verification[relativePath] = { exists: present, invariant };
2397
+ }
2398
+ return verification;
2399
+ }
2400
+ async function runFinalVerification(state, authoritativeHomeFingerprint, surface) {
2401
+ await assertFrozenVerificationSurface(state.repoRoot, surface);
2402
+ const finalCheck = await checkInitUpdate({ repoRoot: state.repoRoot });
2403
+ const doctor = await runInitDoctor({ repoRoot: state.repoRoot });
2404
+ let inspect;
2405
+ try {
2406
+ const adapter = await resolveAdapter(state.repoRoot);
2407
+ const manifest = await adapter.loadHarnessManifest(state.repoRoot);
2408
+ inspect = {
2409
+ ok: true,
2410
+ command: "loop-agent inspect --repo-root .",
2411
+ adapter: adapter.name,
2412
+ project: manifest.project,
2413
+ };
2414
+ }
2415
+ catch (error) {
2416
+ inspect = {
2417
+ ok: false,
2418
+ command: "loop-agent inspect --repo-root .",
2419
+ error: error instanceof Error ? error.message : String(error),
2420
+ };
2421
+ }
2422
+ let docsAudit;
2423
+ try {
2424
+ const audit = await auditDocs(state.repoRoot);
2425
+ docsAudit = {
2426
+ ok: audit.summary.errorCount === 0,
2427
+ command: "loop-agent docs audit --repo-root .",
2428
+ errorCount: audit.summary.errorCount,
2429
+ warningCount: audit.summary.warningCount,
2430
+ };
2431
+ }
2432
+ catch (error) {
2433
+ docsAudit = {
2434
+ ok: false,
2435
+ command: "loop-agent docs audit --repo-root .",
2436
+ error: error instanceof Error ? error.message : String(error),
2437
+ };
2438
+ }
2439
+ const checkRepo = await runVerificationCommand({
2440
+ state,
2441
+ surface,
2442
+ commandText: "bash scripts/check-repo.sh",
2443
+ });
2444
+ let quickVerification;
2445
+ try {
2446
+ const quickCommand = await discoverQuickVerification(state.repoRoot);
2447
+ quickVerification = await runVerificationCommand({
2448
+ state,
2449
+ surface,
2450
+ commandText: quickCommand,
2451
+ });
2452
+ }
2453
+ catch (error) {
2454
+ quickVerification = {
2455
+ ok: false,
2456
+ command: "verification-matrix minimum project test command",
2457
+ exitCode: null,
2458
+ stdout: "",
2459
+ stderr: error instanceof Error ? error.message : String(error),
2460
+ };
2461
+ }
2462
+ const recovery = await verifyRecoveryFiles(state.repoRoot);
2463
+ const projectPi = await inspectProjectPiSettings({ repoRoot: state.repoRoot });
2464
+ const homeAfter = await homeFingerprint();
2465
+ return {
2466
+ completion: {
2467
+ schemaVersion: 1,
2468
+ runId: state.runId,
2469
+ repoRoot: state.repoRoot,
2470
+ status: "completed",
2471
+ phase: "COMPLETED",
2472
+ controllerIdentity: state.controllerIdentity,
2473
+ actionReceiptCount: state.actionReceipts.length,
2474
+ mergeReceiptCount: state.mergeReceipts.length,
2475
+ },
2476
+ initSurfaceClean: finalCheck.ok,
2477
+ doctor: { ok: doctor.ok, checks: doctor.checks },
2478
+ inspect,
2479
+ docsAudit,
2480
+ checkRepo,
2481
+ quickVerification,
2482
+ recovery,
2483
+ projectPi: {
2484
+ reason: projectPi.reason,
2485
+ action: projectPi.action,
2486
+ trustRequired: true,
2487
+ valid: projectPi.reason === "matches" ||
2488
+ projectPi.reason === "enabled-false",
2489
+ },
2490
+ userHomeUnchanged: sameJsonValue(homeAfter, authoritativeHomeFingerprint),
2491
+ mergeBoundaryClean: state.mergeTasks.length === 0,
2492
+ humanDecisionClean: state.humanDecisions.length === 0,
2493
+ };
2494
+ }
2495
+ function authoritativeVerificationFailureReason(verification) {
2496
+ const sources = [
2497
+ verification.quickVerification,
2498
+ verification.checkRepo,
2499
+ verification.inspect,
2500
+ verification.docsAudit,
2501
+ ];
2502
+ for (const source of sources) {
2503
+ if (!isRecord(source))
2504
+ continue;
2505
+ const candidates = [
2506
+ typeof source.error === "string" ? source.error : undefined,
2507
+ source.exitCode === null && typeof source.stderr === "string"
2508
+ ? source.stderr
2509
+ : undefined,
2510
+ ];
2511
+ for (const value of candidates) {
2512
+ if (!value)
2513
+ continue;
2514
+ for (const line of value.split(/\r?\n/)) {
2515
+ const reason = line.trim();
2516
+ if (reason.length <= 500 &&
2517
+ (reason ===
2518
+ "verification authority harness.json merge cannot change controller executor or workflow authority" ||
2519
+ reason ===
2520
+ "verification authority rejects direct node code evaluation" ||
2521
+ reason ===
2522
+ "verification authority rejects package command code evaluation" ||
2523
+ /^verification authority (?:surface changed|input is not a regular file): [A-Za-z0-9_./-]+$/.test(reason) ||
2524
+ reason ===
2525
+ "verification authority merge must retain the controller-frozen verification matrix path")) {
2526
+ return reason;
2527
+ }
2528
+ }
2529
+ }
2530
+ }
2531
+ return undefined;
2532
+ }
2533
+ function verificationPassed(verification) {
2534
+ const doctor = verification.doctor;
2535
+ const inspect = verification.inspect;
2536
+ const docsAudit = verification.docsAudit;
2537
+ const checkRepo = verification.checkRepo;
2538
+ const quick = verification.quickVerification;
2539
+ const projectPi = verification.projectPi;
2540
+ const recovery = verification.recovery;
2541
+ return (verification.initSurfaceClean === true &&
2542
+ isRecord(doctor) &&
2543
+ doctor.ok === true &&
2544
+ isRecord(inspect) &&
2545
+ inspect.ok === true &&
2546
+ isRecord(docsAudit) &&
2547
+ docsAudit.ok === true &&
2548
+ isRecord(checkRepo) &&
2549
+ checkRepo.ok === true &&
2550
+ isRecord(quick) &&
2551
+ quick.ok === true &&
2552
+ isRecord(projectPi) &&
2553
+ projectPi.valid === true &&
2554
+ isRecord(recovery) &&
2555
+ RECOVERY_PATHS.every((recoveryPath) => {
2556
+ const entry = recovery[recoveryPath];
2557
+ return isRecord(entry) && entry.exists === true && entry.invariant === true;
2558
+ }) &&
2559
+ verification.userHomeUnchanged === true &&
2560
+ verification.mergeBoundaryClean === true &&
2561
+ verification.humanDecisionClean === true);
2562
+ }
2563
+ function assertCompletedVerification(state, verification) {
2564
+ if (!isRecord(verification) || !isRecord(verification.completion)) {
2565
+ throw new Error("completed init upgrade verification evidence is invalid");
2566
+ }
2567
+ const completion = verification.completion;
2568
+ if (completion.schemaVersion !== 1 ||
2569
+ completion.runId !== state.runId ||
2570
+ completion.repoRoot !== state.repoRoot ||
2571
+ completion.status !== "completed" ||
2572
+ completion.phase !== "COMPLETED" ||
2573
+ !isControllerIdentity(completion.controllerIdentity) ||
2574
+ !sameControllerIdentity(completion.controllerIdentity, state.controllerIdentity) ||
2575
+ completion.actionReceiptCount !== state.actionReceipts.length ||
2576
+ completion.mergeReceiptCount !== state.mergeReceipts.length ||
2577
+ !verificationPassed(verification)) {
2578
+ throw new Error("completed init upgrade verification evidence is inconsistent");
2579
+ }
2580
+ }
2581
+ function assertAnchorCompletion(anchor, state) {
2582
+ const completion = anchor.completion;
2583
+ if (!completion ||
2584
+ !sameJsonValue(anchor.homeFingerprint, state.homeFingerprint) ||
2585
+ completion.status !== "completed" ||
2586
+ completion.phase !== "COMPLETED" ||
2587
+ completion.actionReceiptCount !== state.actionReceipts.length ||
2588
+ completion.mergeReceiptCount !== state.mergeReceipts.length ||
2589
+ !isSha256(completion.actionReceiptsSha256) ||
2590
+ !isSha256(completion.mergeReceiptsSha256) ||
2591
+ !isSha256(completion.stateProjectionSha256) ||
2592
+ !isSha256(completion.finalReportSha256) ||
2593
+ !isFrozenVerificationSurface(completion.verificationSurface) ||
2594
+ completion.actionReceiptsSha256 !== sha256(serializeJson(state.actionReceipts)) ||
2595
+ completion.mergeReceiptsSha256 !== sha256(serializeJson(state.mergeReceipts)) ||
2596
+ completion.stateProjectionSha256 !== canonicalSha256(controllerStateProjection(state))) {
2597
+ throw new Error("completed init upgrade state is not authorized by the controller anchor");
2598
+ }
2599
+ }
2600
+ async function readFreshCompletedVerification(repoRoot, state) {
2601
+ const anchor = await readControllerAnchor(repoRoot, state);
2602
+ assertAnchorCompletion(anchor, state);
2603
+ const report = await readFile(path.join(runDirectory(repoRoot, state.runId), "final-report.md")).catch(() => {
2604
+ throw new Error("completed init upgrade report is missing or unreadable");
2605
+ });
2606
+ if (sha256(report) !== anchor.completion?.finalReportSha256) {
2607
+ throw new Error("completed init upgrade report is not authorized by the controller anchor");
2608
+ }
2609
+ if (!anchor.completion?.verificationSurface) {
2610
+ throw new Error("completed verification surface authority is missing");
2611
+ }
2612
+ const verification = await runFinalVerification(state, anchor.homeFingerprint, anchor.completion.verificationSurface);
2613
+ assertCompletedVerification(state, verification);
2614
+ return { verification, reportMarkdown: report.toString("utf-8") };
2615
+ }
2616
+ async function readValidatedStatusState(repoRoot, runId) {
2617
+ const state = await readState(repoRoot, runId);
2618
+ assertTerminalCoherence(state);
2619
+ const directory = runDirectory(repoRoot, runId);
2620
+ const anchor = await readControllerAnchor(repoRoot, state);
2621
+ if (anchor.mergeAuthority) {
2622
+ mergeAuthorityProjection(state, mergeAuthorityFromAnchor(anchor, state));
2623
+ }
2624
+ if (state.status === "completed" || state.status === "cancelled") {
2625
+ if (!(await frozenControllerMatches(directory, state))) {
2626
+ throw new Error("Controller identity drift detected for persisted init upgrade state");
2627
+ }
2628
+ }
2629
+ return { state, directory, anchor };
2630
+ }
2631
+ /**
2632
+ * Read-only projection of the controller-owned gitignore migration assessment
2633
+ * for status/report reads. The artifact is written by executeUpgrade after the
2634
+ * managed block has converged; a missing artifact is tolerated only while the
2635
+ * run has not yet reached that write point (DISCOVER/VERSION_GATE). Every
2636
+ * other missing, malformed, or shape-invalid artifact fails closed so
2637
+ * status/report never trust mutable or incomplete content (AC-005).
2638
+ */
2639
+ async function readGitignoreMigrationProjection(directory, state, gitignoreMigrationSha256) {
2640
+ const file = path.join(directory, "gitignore-migration.json");
2641
+ let rawBytes;
2642
+ let raw;
2643
+ try {
2644
+ rawBytes = await readFile(file);
2645
+ raw = rawBytes.toString("utf-8");
2646
+ }
2647
+ catch (error) {
2648
+ const cause = error;
2649
+ if (cause.code === "ENOENT") {
2650
+ if (state.phase === "DISCOVER" || state.phase === "VERSION_GATE") {
2651
+ return undefined;
2652
+ }
2653
+ throw new Error(`init upgrade integrity error: gitignore migration artifact is missing for run ${state.runId} (phase ${state.phase}); refusing to trust an incomplete run`);
2654
+ }
2655
+ throw error;
2656
+ }
2657
+ let parsed;
2658
+ try {
2659
+ parsed = JSON.parse(raw);
2660
+ }
2661
+ catch {
2662
+ throw new Error(`init upgrade integrity error: gitignore migration artifact is malformed JSON for run ${state.runId} (${file})`);
2663
+ }
2664
+ if (!isGitignoreMigrationAssessment(parsed)) {
2665
+ throw new Error(`init upgrade integrity error: gitignore migration artifact has an invalid shape for run ${state.runId} (${file})`);
2666
+ }
2667
+ // The artifact exists, so this run has generated an assessment; the
2668
+ // controller authority binding must exist, be a legal SHA-256, and match
2669
+ // the raw artifact bytes on disk (fail closed otherwise).
2670
+ if (gitignoreMigrationSha256 === undefined || !isSha256(gitignoreMigrationSha256)) {
2671
+ throw new Error(`init upgrade integrity error: gitignore migration artifact digest binding is missing or invalid for run ${state.runId}`);
2672
+ }
2673
+ if (sha256(rawBytes) !== gitignoreMigrationSha256) {
2674
+ throw new Error(`init upgrade integrity error: gitignore migration artifact digest does not match the controller authority binding for run ${state.runId}`);
2675
+ }
2676
+ return parsed;
2677
+ }
2678
+ async function readStatusResult(repoRoot, runId) {
2679
+ const { state, directory, anchor } = await readValidatedStatusState(repoRoot, runId);
2680
+ if (state.status !== "completed") {
2681
+ return toResult(state, undefined, await readGitignoreMigrationProjection(directory, state, anchor.gitignoreMigrationSha256));
2682
+ }
2683
+ const authenticated = await readFreshCompletedVerification(repoRoot, state);
2684
+ return toResult(state, authenticated.verification, await readGitignoreMigrationProjection(directory, state, anchor.gitignoreMigrationSha256));
2685
+ }
2686
+ async function executeUpgrade(directory, state, input) {
2687
+ const identity = await readIdentity();
2688
+ if (!sameControllerIdentity(identity, state.controllerIdentity)) {
2689
+ return {
2690
+ ...toResult(state),
2691
+ status: "failed",
2692
+ nextAction: "Controller identity drift detected. Re-run with the frozen controller; no target-project writes were made.",
2693
+ };
2694
+ }
2695
+ const recordedCurrentChoice = input.mode === "continue" &&
2696
+ input.versionChoice === undefined &&
2697
+ state.versionGate.choice === "current"
2698
+ ? "current"
2699
+ : undefined;
2700
+ if (!recordedCurrentChoice)
2701
+ state.phase = "VERSION_GATE";
2702
+ state.versionGate = await currentVersionGate(input, identity);
2703
+ const choice = input.versionChoice ?? recordedCurrentChoice;
2704
+ if (state.versionGate.outcome === "newer-available" && !choice) {
2705
+ state.status = "needs-human-decision";
2706
+ state.nextAction =
2707
+ "Choose --version-choice current, upgrade, or cancel; the controller identity remains frozen until a choice is recorded.";
2708
+ await saveState(directory, state);
2709
+ await writeReport(directory, state);
2710
+ return toResult(state);
2711
+ }
2712
+ if (state.versionGate.outcome === "registry-unavailable" && !choice) {
2713
+ state.status = "needs-human-decision";
2714
+ state.nextAction =
2715
+ "npm registry is unavailable. Choose --version-choice current, retry, or cancel.";
2716
+ await saveState(directory, state);
2717
+ await writeReport(directory, state);
2718
+ return toResult(state);
2719
+ }
2720
+ state.versionGate.choice = choice ?? "current";
2721
+ if (choice === "cancel") {
2722
+ state.status = "cancelled";
2723
+ state.nextAction = "Upgrade cancelled before init-surface mutation.";
2724
+ await saveState(directory, state);
2725
+ await writeReport(directory, state);
2726
+ return toResult(state);
2727
+ }
2728
+ if (choice === "upgrade") {
2729
+ if (state.versionGate.outcome !== "newer-available") {
2730
+ state.status = "needs-human-decision";
2731
+ state.nextAction =
2732
+ "No newer controller was confirmed. Choose current, retry the registry, or cancel.";
2733
+ }
2734
+ else {
2735
+ state.status = "cancelled";
2736
+ state.nextAction = `This frozen run is closed before mutation. Install and verify @tea-agent/loop-agent@${state.versionGate.latestVersion}, then start a new run without --run-id so the new controller freezes a new identity.`;
2737
+ }
2738
+ await saveState(directory, state);
2739
+ await writeReport(directory, state);
2740
+ return toResult(state);
2741
+ }
2742
+ if (choice === "retry" &&
2743
+ state.versionGate.outcome === "registry-unavailable") {
2744
+ state.status = "needs-human-decision";
2745
+ state.nextAction =
2746
+ "npm registry remains unavailable after retry. Choose --version-choice current, retry, or cancel.";
2747
+ await saveState(directory, state);
2748
+ await writeReport(directory, state);
2749
+ return toResult(state);
2750
+ }
2751
+ state.status = "in-progress";
2752
+ state.phase = "PLAN";
2753
+ const pre = await checkInitUpdate({ repoRoot: state.repoRoot });
2754
+ await writeJsonAtomic(path.join(directory, "pre-update-report.json"), pre);
2755
+ await writeJsonAtomic(path.join(directory, "upgrade-plan.json"), {
2756
+ deterministicActions: pre.deterministicActions,
2757
+ modelMergeTasks: pre.modelMergeTasks,
2758
+ humanDecisions: pre.humanDecisions,
2759
+ verificationPlan: [
2760
+ "init check-update",
2761
+ "init doctor",
2762
+ "loop-agent inspect",
2763
+ "loop-agent docs audit",
2764
+ "bash scripts/check-repo.sh",
2765
+ "verification-matrix minimum project test command",
2766
+ "project recovery invariants",
2767
+ "Pi home fingerprint",
2768
+ ],
2769
+ });
2770
+ state.phase = "APPLY_SAFE";
2771
+ await assertInactive(input);
2772
+ if (pre.surfaceState === "missing") {
2773
+ const initialized = await initializeLoopAgentProject({
2774
+ repoRoot: state.repoRoot,
2775
+ profile: "full",
2776
+ merge: true,
2777
+ clientRecovery: "project",
2778
+ });
2779
+ state.actionReceipts.push(...initialized.written.map((writtenPath) => ({
2780
+ type: writtenPath === INIT_SURFACE_STATE_PATH
2781
+ ? "bootstrap-surface"
2782
+ : "write-generated-missing",
2783
+ path: writtenPath,
2784
+ reason: writtenPath === INIT_SURFACE_STATE_PATH
2785
+ ? "recorded the complete fresh-project initialization surface"
2786
+ : "full-project initialization wrote the required project surface",
2787
+ })));
2788
+ }
2789
+ await assertInactive(input);
2790
+ const safe = await applyInitUpdate({
2791
+ repoRoot: state.repoRoot,
2792
+ applySafe: true,
2793
+ clientRecovery: "project",
2794
+ });
2795
+ state.actionReceipts.push(...safe.applied);
2796
+ await writeJsonAtomic(path.join(directory, "change-manifest.json"), state.actionReceipts);
2797
+ let post = await checkInitUpdate({ repoRoot: state.repoRoot });
2798
+ // Converge remaining safe deterministic init actions before the final check.
2799
+ // Project/governance-derived drift (e.g. a personalized project rename after
2800
+ // an accepted merge) settles here instead of failing finalCheck. Model merge
2801
+ // tasks and human decisions are never auto-applied.
2802
+ while (post.deterministicActions.length > 0) {
2803
+ const converged = await applyInitUpdate({
2804
+ repoRoot: state.repoRoot,
2805
+ applySafe: true,
2806
+ clientRecovery: "project",
2807
+ });
2808
+ state.actionReceipts.push(...converged.applied);
2809
+ await writeJsonAtomic(path.join(directory, "change-manifest.json"), state.actionReceipts);
2810
+ if (converged.applied.length === 0)
2811
+ break;
2812
+ post = await checkInitUpdate({ repoRoot: state.repoRoot });
2813
+ if (post.humanDecisions.length > 0 ||
2814
+ post.modelMergeTasks.length > 0) {
2815
+ break;
2816
+ }
2817
+ }
2818
+ state.humanDecisions = post.humanDecisions;
2819
+ // Gitignore migration assessment: after the managed block has converged,
2820
+ // inventory tracked .harness/.agents paths with read-only git queries and
2821
+ // recommend index-only untrack commands. The controller never mutates the
2822
+ // user Git index; suggested commands only change the index and keep the
2823
+ // working tree intact.
2824
+ const gitignoreMigration = await assessGitignoreMigration(state.repoRoot, {
2825
+ managedBlockRefreshed: !post.deterministicActions.some((action) => action.type === "refresh-managed-block" &&
2826
+ action.path === ".gitignore"),
2827
+ });
2828
+ await writeJsonAtomic(path.join(directory, "gitignore-migration.json"), gitignoreMigration);
2829
+ // Bind the raw on-disk artifact bytes to the Git-dir controller authority
2830
+ // before any needs-human-decision/model-merge/completed return; later
2831
+ // mergeAuthority/completion anchor writes preserve this binding.
2832
+ await writeControllerAnchor({
2833
+ state,
2834
+ gitignoreMigrationSha256: sha256(await readFile(path.join(directory, "gitignore-migration.json"))),
2835
+ });
2836
+ if (gitignoreMigration.status === "blocked" ||
2837
+ gitignoreMigration.needsAgentsReview) {
2838
+ // Route through the existing needs-human-decision pause: `.agents`
2839
+ // tracked content must be reviewed (PRD §7) and staged blockers must be
2840
+ // resolved before safe untrack guidance can be given (PRD §8).
2841
+ post.humanDecisions.push(gitignoreMigration.status === "blocked"
2842
+ ? {
2843
+ path: ".harness/** (gitignore migration)",
2844
+ reason: "staged changes or a failed git query block safe untrack guidance; resolve the blocker, then rerun the migration assessment",
2845
+ }
2846
+ : {
2847
+ path: ".agents/** (gitignore migration)",
2848
+ reason: "tracked .agents content may include team-shareable customizations; review before running the suggested untrack commands",
2849
+ });
2850
+ }
2851
+ state.humanDecisions = post.humanDecisions;
2852
+ if (post.humanDecisions.length > 0) {
2853
+ state.mergeTasks = [];
2854
+ state.mergeGuard = undefined;
2855
+ state.status = "needs-human-decision";
2856
+ state.phase = "MODEL_MERGE";
2857
+ state.nextAction =
2858
+ "Resolve the recorded destructive, credential, or ambiguous decisions; the controller will not overwrite uncertain user intent. For gitignore migration: review tracked .agents customizations or resolve the staged blocker first, then rerun the assessment so index-only untrack commands can be recommended.";
2859
+ await writeJsonAtomic(path.join(directory, "merge-tasks.json"), state.mergeTasks);
2860
+ await saveState(directory, state);
2861
+ await writeReport(directory, state, undefined, gitignoreMigration);
2862
+ return toResult(state, undefined, gitignoreMigration);
2863
+ }
2864
+ if (post.modelMergeTasks.length > 0) {
2865
+ const activeTask = post.modelMergeTasks[0];
2866
+ const verificationSurface = await freezeVerificationSurface(state.repoRoot);
2867
+ assertTaskOutsideVerificationSurface(activeTask, verificationSurface);
2868
+ if (verificationSurface.files.some((entry) => entry.path === normalizeAllowedPath(activeTask.path))) {
2869
+ await validateControllerAuthorizedVerificationMerge({
2870
+ repoRoot: state.repoRoot,
2871
+ task: activeTask,
2872
+ surface: verificationSurface,
2873
+ });
2874
+ }
2875
+ state.mergeTasks = [activeTask];
2876
+ state.mergeGuard = await createMergeGuard(state.repoRoot, activeTask);
2877
+ // Bind the Git-dir anchor to the state actually handed to the model.
2878
+ state.phase = "MODEL_MERGE";
2879
+ state.status = "in-progress";
2880
+ await writeControllerAnchor({
2881
+ state,
2882
+ mergeAuthority: {
2883
+ ...state.mergeGuard,
2884
+ priorMergeReceiptCount: state.mergeReceipts.length,
2885
+ priorMergeReceiptsSha256: sha256(serializeJson(state.mergeReceipts)),
2886
+ stateProjection: controllerStateProjection(state),
2887
+ stateProjectionSha256: canonicalSha256(controllerStateProjection(state)),
2888
+ verificationSurface,
2889
+ },
2890
+ });
2891
+ state.nextAction = `Complete only task ${activeTask.taskId} within allowedPaths=[${activeTask.allowedPaths.join(", ")}], then call init upgrade --continue. The controller will reject every other workspace change and all symlink/realpath escapes.`;
2892
+ await writeJsonAtomic(path.join(directory, "merge-tasks.json"), state.mergeTasks);
2893
+ await saveState(directory, state);
2894
+ await writeReport(directory, state, undefined, gitignoreMigration);
2895
+ return toResult(state, undefined, gitignoreMigration);
2896
+ }
2897
+ state.mergeTasks = [];
2898
+ state.mergeGuard = undefined;
2899
+ state.humanDecisions = [];
2900
+ state.phase = "VERIFY";
2901
+ const anchor = await readControllerAnchor(state.repoRoot, state);
2902
+ const verificationSurface = await freezeVerificationSurface(state.repoRoot);
2903
+ const verification = await runFinalVerification(state, anchor.homeFingerprint, verificationSurface);
2904
+ await writeJsonAtomic(path.join(directory, "verification.json"), verification);
2905
+ if (verificationPassed(verification)) {
2906
+ state.status = "completed";
2907
+ state.phase = "COMPLETED";
2908
+ state.nextAction =
2909
+ "Init upgrade completed; reload the host session so updated AGENTS, skills, and recovery extensions are discovered.";
2910
+ // The report is controller-produced before its exact bytes are anchored.
2911
+ await writeReport(directory, state, verification, gitignoreMigration);
2912
+ const finalReportSha256 = sha256(await readFile(path.join(directory, "final-report.md")));
2913
+ await writeControllerAnchor({
2914
+ state,
2915
+ completion: {
2916
+ status: "completed",
2917
+ phase: "COMPLETED",
2918
+ actionReceiptCount: state.actionReceipts.length,
2919
+ mergeReceiptCount: state.mergeReceipts.length,
2920
+ actionReceiptsSha256: sha256(serializeJson(state.actionReceipts)),
2921
+ mergeReceiptsSha256: sha256(serializeJson(state.mergeReceipts)),
2922
+ stateProjectionSha256: canonicalSha256(controllerStateProjection(state)),
2923
+ verificationSurface,
2924
+ finalReportSha256,
2925
+ },
2926
+ });
2927
+ }
2928
+ else {
2929
+ state.status = "failed";
2930
+ const authorityReason = authoritativeVerificationFailureReason(verification);
2931
+ state.nextAction = authorityReason
2932
+ ? `Verification failed: ${authorityReason}. Inspect verification.json, repair only the controller-returned concrete merge path, then use --continue.`
2933
+ : "Verification failed. Inspect verification.json; repair only the controller-returned concrete merge path or address the reported external blocker, then use --continue.";
2934
+ }
2935
+ await saveState(directory, state);
2936
+ await writeReport(directory, state, verification, gitignoreMigration);
2937
+ return toResult(state, verification, gitignoreMigration);
2938
+ }
2939
+ export async function runInitUpgrade(input) {
2940
+ const repoRoot = path.resolve(input.repoRoot);
2941
+ const mode = input.mode ?? "start";
2942
+ if ((mode === "status" || mode === "report") && !input.runId) {
2943
+ throw new Error("init upgrade --status/--report requires --run-id");
2944
+ }
2945
+ if (mode === "status")
2946
+ return await readStatusResult(repoRoot, input.runId);
2947
+ if (mode === "report") {
2948
+ const { state, directory, anchor } = await readValidatedStatusState(repoRoot, input.runId);
2949
+ if (state.status === "completed") {
2950
+ const authenticated = await readFreshCompletedVerification(repoRoot, state);
2951
+ // Completed report bytes are anchored by finalReportSha256; still
2952
+ // verify the gitignore migration artifact binding so shape-valid
2953
+ // tampering fails closed here as well.
2954
+ await readGitignoreMigrationProjection(directory, state, anchor.gitignoreMigrationSha256);
2955
+ return { markdown: authenticated.reportMarkdown };
2956
+ }
2957
+ // Non-completed reports are projections of the validated state, never an
2958
+ // unauthenticated read of a mutable run-local report file.
2959
+ return {
2960
+ markdown: renderReport(state, undefined, await readGitignoreMigrationProjection(directory, state, anchor.gitignoreMigrationSha256)),
2961
+ };
2962
+ }
2963
+ const runId = mode === "continue"
2964
+ ? input.runId
2965
+ : input.runId ??
2966
+ `init-upgrade-${new Date().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14)}-${randomUUID().slice(0, 8)}`;
2967
+ if (!runId)
2968
+ throw new Error("init upgrade --continue requires --run-id");
2969
+ if (!validRunId(runId))
2970
+ throw new Error("invalid init upgrade run id");
2971
+ if (mode === "continue") {
2972
+ const persisted = await readState(repoRoot, runId);
2973
+ try {
2974
+ assertTerminalCoherence(persisted);
2975
+ const anchor = await readControllerAnchor(repoRoot, persisted);
2976
+ if (anchor.mergeAuthority) {
2977
+ mergeAuthorityProjection(persisted, mergeAuthorityFromAnchor(anchor, persisted));
2978
+ }
2979
+ const directory = runDirectory(repoRoot, runId);
2980
+ if (persisted.status === "completed" || persisted.status === "cancelled") {
2981
+ if (!(await frozenControllerMatches(directory, persisted))) {
2982
+ throw new Error("Controller identity drift detected for persisted init upgrade state");
2983
+ }
2984
+ if (persisted.status !== "completed")
2985
+ return toResult(persisted);
2986
+ const authenticated = await readFreshCompletedVerification(repoRoot, persisted);
2987
+ return toResult(persisted, authenticated.verification);
2988
+ }
2989
+ }
2990
+ catch (error) {
2991
+ return {
2992
+ ...toResult(persisted),
2993
+ status: "failed",
2994
+ nextAction: error instanceof Error ? error.message : String(error),
2995
+ };
2996
+ }
2997
+ }
2998
+ await assertInactive({ ...input, repoRoot });
2999
+ if (mode === "start") {
3000
+ await controllerAnchorPath({ repoRoot, runId });
3001
+ }
3002
+ const release = await acquireGlobalLock({ repoRoot, runId });
3003
+ let directory = runDirectory(repoRoot, runId);
3004
+ let state;
3005
+ try {
3006
+ await assertInactive({ ...input, repoRoot });
3007
+ if (mode === "continue") {
3008
+ state = await readState(repoRoot, runId);
3009
+ directory = runDirectory(repoRoot, runId);
3010
+ const recoverableMergeState = state.phase === "MODEL_MERGE" && state.status === "in-progress"
3011
+ ? cloneJson(state)
3012
+ : undefined;
3013
+ let mergeOutcome;
3014
+ try {
3015
+ const anchor = await readControllerAnchor(repoRoot, state);
3016
+ mergeOutcome = await validateContinueMerge(directory, state, anchor);
3017
+ }
3018
+ catch (error) {
3019
+ if (!recoverableMergeState)
3020
+ throw error;
3021
+ return {
3022
+ ...toResult(recoverableMergeState),
3023
+ status: "failed",
3024
+ nextAction: error instanceof Error ? error.message : String(error),
3025
+ };
3026
+ }
3027
+ if (mergeOutcome === "no-change")
3028
+ return toResult(state);
3029
+ if (state.phase !== "MODEL_MERGE") {
3030
+ await rm(path.join(directory, "merge-acceptance-pending.json"), {
3031
+ force: true,
3032
+ });
3033
+ }
3034
+ }
3035
+ else {
3036
+ directory = runDirectory(repoRoot, runId);
3037
+ await mkdir(directory, { recursive: false });
3038
+ const identity = await readIdentity();
3039
+ const now = new Date().toISOString();
3040
+ state = {
3041
+ schemaVersion: 1,
3042
+ runId,
3043
+ repoRoot,
3044
+ phase: "DISCOVER",
3045
+ status: "in-progress",
3046
+ createdAt: now,
3047
+ updatedAt: now,
3048
+ controllerIdentity: identity,
3049
+ versionGate: { outcome: "latest" },
3050
+ nextAction: "Evaluate controller version and init surface.",
3051
+ mergeTasks: [],
3052
+ mergeReceipts: [],
3053
+ humanDecisions: [],
3054
+ actionReceipts: [],
3055
+ homeFingerprint: await homeFingerprint(),
3056
+ };
3057
+ await writeControllerAnchor({ state, initialize: true });
3058
+ await writeJsonAtomic(path.join(directory, "controller-identity.json"), {
3059
+ schemaVersion: 1,
3060
+ runId,
3061
+ repoRoot,
3062
+ controllerIdentity: identity,
3063
+ });
3064
+ await saveState(directory, state);
3065
+ }
3066
+ if (!state)
3067
+ throw new Error("init upgrade state was not initialized");
3068
+ return await executeUpgrade(directory, state, { ...input, repoRoot });
3069
+ }
3070
+ catch (error) {
3071
+ if (error instanceof VerificationProcessBoundaryError)
3072
+ throw error;
3073
+ if (!state)
3074
+ throw error;
3075
+ const nextAction = error instanceof Error ? error.message : String(error);
3076
+ state.status = "failed";
3077
+ state.nextAction = nextAction;
3078
+ await saveState(directory, state);
3079
+ await writeReport(directory, state);
3080
+ return toResult(state);
3081
+ }
3082
+ finally {
3083
+ await release();
3084
+ }
3085
+ }