@moretea-labs/forge 1.5.0-rc.1

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 (903) hide show
  1. package/AGENTS.md +120 -0
  2. package/CHANGELOG.md +36 -0
  3. package/CLAUDE.md +120 -0
  4. package/CODE_OF_CONDUCT.md +23 -0
  5. package/CONTRIBUTING.md +77 -0
  6. package/LICENSE +22 -0
  7. package/NOTICE +9 -0
  8. package/README.en.md +5 -0
  9. package/README.es.md +12 -0
  10. package/README.fr.md +12 -0
  11. package/README.ja.md +12 -0
  12. package/README.md +132 -0
  13. package/README.zh-CN.md +84 -0
  14. package/SECURITY.md +50 -0
  15. package/SKILL.md +431 -0
  16. package/SUPPORT.md +31 -0
  17. package/THIRD_PARTY_NOTICES.md +18 -0
  18. package/agents/openai.yaml +4 -0
  19. package/assets/AGENTS.md +68 -0
  20. package/assets/CLAUDE.md +68 -0
  21. package/assets/branding/README.md +10 -0
  22. package/assets/branding/forge-controller-flow.mmd +8 -0
  23. package/assets/hooks/AGENTS.md +75 -0
  24. package/assets/hooks/CLAUDE.md +75 -0
  25. package/assets/hooks/anti-simplification.sh +12 -0
  26. package/assets/hooks/changelog-guard.sh +80 -0
  27. package/assets/hooks/codex.hooks.template.json +60 -0
  28. package/assets/hooks/first-principles-guard.sh +72 -0
  29. package/assets/hooks/hook-input.sh +618 -0
  30. package/assets/hooks/lib/session-state.sh +106 -0
  31. package/assets/hooks/lib/workflow-state.sh +1730 -0
  32. package/assets/hooks/post-bash.sh +215 -0
  33. package/assets/hooks/post-edit-guard.sh +256 -0
  34. package/assets/hooks/post-tool-observer.sh +99 -0
  35. package/assets/hooks/pre-edit-guard.sh +240 -0
  36. package/assets/hooks/prompt-guard.sh +1077 -0
  37. package/assets/hooks/run-hook.sh +67 -0
  38. package/assets/hooks/security-sentinel.sh +115 -0
  39. package/assets/hooks/session-start-context.sh +593 -0
  40. package/assets/hooks/settings.template.json +60 -0
  41. package/assets/hooks/stop-orchestrator.sh +171 -0
  42. package/assets/hooks/subagent-return-channel-guard.sh +107 -0
  43. package/assets/hooks/worktree-guard.sh +39 -0
  44. package/assets/initializer-question-pack.v1.json +132 -0
  45. package/assets/initializer-question-pack.v1.schema.json +46 -0
  46. package/assets/initializer-question-pack.v2.json +174 -0
  47. package/assets/initializer-question-pack.v3.json +206 -0
  48. package/assets/initializer-question-pack.v3.schema.json +92 -0
  49. package/assets/initializer-question-pack.v4.json +557 -0
  50. package/assets/initializer-question-pack.v4.schema.json +169 -0
  51. package/assets/partials/01-header.partial.md +10 -0
  52. package/assets/partials/02-iron-rules.partial.md +30 -0
  53. package/assets/partials/03-philosophy.partial.md +32 -0
  54. package/assets/partials/04-project-structure.partial.md +56 -0
  55. package/assets/partials/05-workflow.partial.md +79 -0
  56. package/assets/partials/06-cloudflare.partial.md +20 -0
  57. package/assets/partials/07-footer.partial.md +38 -0
  58. package/assets/partials/08-orchestration.partial.md +49 -0
  59. package/assets/partials/09-compact-instructions.partial.md +11 -0
  60. package/assets/partials/_assembly-order.md +40 -0
  61. package/assets/partials-agents/01-header.partial.md +8 -0
  62. package/assets/partials-agents/02-operating-mode.partial.md +44 -0
  63. package/assets/partials-agents/03-orchestration.partial.md +45 -0
  64. package/assets/partials-agents/04-task-protocol.partial.md +69 -0
  65. package/assets/partials-agents/05-coding-constraints.partial.md +20 -0
  66. package/assets/partials-agents/06-quality-safety.partial.md +23 -0
  67. package/assets/partials-agents/07-cloudflare.partial.md +9 -0
  68. package/assets/partials-agents/08-deep-docs.partial.md +32 -0
  69. package/assets/plan-map.json +301 -0
  70. package/assets/plugin-registry.v1.json +8 -0
  71. package/assets/project-structures/ai-native-collaborative-editor.txt +25 -0
  72. package/assets/project-structures/ai-native-product-copilot.txt +21 -0
  73. package/assets/project-structures/ai-native-runtime-console.txt +25 -0
  74. package/assets/project-structures/ai-native-sidecar-kernel.txt +19 -0
  75. package/assets/project-structures/astro-ssr.txt +25 -0
  76. package/assets/project-structures/expo-nativewind.txt +52 -0
  77. package/assets/project-structures/monorepo.txt +63 -0
  78. package/assets/project-structures/remix.txt +50 -0
  79. package/assets/project-structures/tanstack-start-workers.txt +51 -0
  80. package/assets/project-structures/vite-tanstack.txt +58 -0
  81. package/assets/reference-configs/AGENTS.md +13 -0
  82. package/assets/reference-configs/CLAUDE.md +13 -0
  83. package/assets/reference-configs/agentic-development-flow.md +94 -0
  84. package/assets/reference-configs/ai-workflows.md +14 -0
  85. package/assets/reference-configs/changelog-versioning.md +13 -0
  86. package/assets/reference-configs/coding-standards.md +14 -0
  87. package/assets/reference-configs/development-protocol.md +21 -0
  88. package/assets/reference-configs/document-generation.md +38 -0
  89. package/assets/reference-configs/evaluator-rubric.md +19 -0
  90. package/assets/reference-configs/external-tooling.md +374 -0
  91. package/assets/reference-configs/git-strategy.md +14 -0
  92. package/assets/reference-configs/global-working-rules.md +64 -0
  93. package/assets/reference-configs/handoff-protocol.md +38 -0
  94. package/assets/reference-configs/harness-overview.md +124 -0
  95. package/assets/reference-configs/heartbeat-triage.md +57 -0
  96. package/assets/reference-configs/hook-operations.md +96 -0
  97. package/assets/reference-configs/release-deploy.md +40 -0
  98. package/assets/reference-configs/spa-day-protocol.md +14 -0
  99. package/assets/reference-configs/sprint-contracts.md +89 -0
  100. package/assets/reference-configs/workflow-orchestration.md +14 -0
  101. package/assets/skill-hooks.json +41 -0
  102. package/assets/skill-version.json +189 -0
  103. package/assets/skills/claude-review/SKILL.md +231 -0
  104. package/assets/skills/codex-review/SKILL.md +103 -0
  105. package/assets/templates/AGENTS.md +13 -0
  106. package/assets/templates/CLAUDE.md +13 -0
  107. package/assets/templates/architecture.template.md +80 -0
  108. package/assets/templates/brief.template.md +38 -0
  109. package/assets/templates/contract.template.md +104 -0
  110. package/assets/templates/decisions.template.md +85 -0
  111. package/assets/templates/factor-factory/factor-backtest-report.template.md +36 -0
  112. package/assets/templates/factor-factory/factor-hypothesis.template.md +30 -0
  113. package/assets/templates/factor-factory/factor-registry.template.json +5 -0
  114. package/assets/templates/gitignore.template +53 -0
  115. package/assets/templates/guides/metro-esm-gotchas.template.md +46 -0
  116. package/assets/templates/helpers/architecture-event.ts +822 -0
  117. package/assets/templates/helpers/architecture-queue.sh +647 -0
  118. package/assets/templates/helpers/archive-architecture-request.sh +244 -0
  119. package/assets/templates/helpers/archive-workflow.sh +295 -0
  120. package/assets/templates/helpers/capability-config.ts +394 -0
  121. package/assets/templates/helpers/capability-resolver.ts +505 -0
  122. package/assets/templates/helpers/capture-plan.sh +335 -0
  123. package/assets/templates/helpers/check-agent-tooling.sh +1533 -0
  124. package/assets/templates/helpers/check-architecture-sync.sh +287 -0
  125. package/assets/templates/helpers/check-brain-manifest.sh +255 -0
  126. package/assets/templates/helpers/check-context-files.sh +49 -0
  127. package/assets/templates/helpers/check-deploy-sql-order.sh +117 -0
  128. package/assets/templates/helpers/check-skill-version.ts +193 -0
  129. package/assets/templates/helpers/check-task-sync.sh +65 -0
  130. package/assets/templates/helpers/check-task-workflow.sh +1002 -0
  131. package/assets/templates/helpers/codex-handoff-resume.sh +269 -0
  132. package/assets/templates/helpers/context-contract-sync.sh +516 -0
  133. package/assets/templates/helpers/contract-run.ts +465 -0
  134. package/assets/templates/helpers/contract-worktree.sh +725 -0
  135. package/assets/templates/helpers/ensure-task-workflow.sh +1218 -0
  136. package/assets/templates/helpers/harness-trace-grade.sh +150 -0
  137. package/assets/templates/helpers/heartbeat-triage.sh +361 -0
  138. package/assets/templates/helpers/inspect-project-state.ts +280 -0
  139. package/assets/templates/helpers/maintenance-triage.sh +167 -0
  140. package/assets/templates/helpers/migrate-project-template.sh +54 -0
  141. package/assets/templates/helpers/migrate-workflow-docs.ts +413 -0
  142. package/assets/templates/helpers/new-plan.sh +183 -0
  143. package/assets/templates/helpers/new-spec.sh +50 -0
  144. package/assets/templates/helpers/new-sprint.sh +49 -0
  145. package/assets/templates/helpers/plan-to-todo.sh +799 -0
  146. package/assets/templates/helpers/prepare-codex-handoff.sh +203 -0
  147. package/assets/templates/helpers/prepare-handoff.sh +81 -0
  148. package/assets/templates/helpers/refresh-current-status.sh +469 -0
  149. package/assets/templates/helpers/select-agent-context-blocks.sh +66 -0
  150. package/assets/templates/helpers/ship-worktrees.sh +561 -0
  151. package/assets/templates/helpers/sprint-backlog.sh +837 -0
  152. package/assets/templates/helpers/summarize-failures.sh +125 -0
  153. package/assets/templates/helpers/switch-plan.sh +153 -0
  154. package/assets/templates/helpers/sync-brain-docs.sh +381 -0
  155. package/assets/templates/helpers/verify-contract.sh +705 -0
  156. package/assets/templates/helpers/verify-sprint.sh +680 -0
  157. package/assets/templates/helpers/workflow-contract.ts +202 -0
  158. package/assets/templates/helpers/workstream-sync.sh +263 -0
  159. package/assets/templates/implementation-notes.template.md +37 -0
  160. package/assets/templates/packages.template.md +45 -0
  161. package/assets/templates/plan.template.md +77 -0
  162. package/assets/templates/prd.template.md +165 -0
  163. package/assets/templates/research.template.md +25 -0
  164. package/assets/templates/review.template.md +78 -0
  165. package/assets/templates/spec.template.md +31 -0
  166. package/assets/templates/sprint.template.md +69 -0
  167. package/assets/templates/tech-stack.template.md +57 -0
  168. package/assets/versions.json +77 -0
  169. package/assets/workflow-contract.v1.json +671 -0
  170. package/bin/forge-desktop-helper.mjs +204 -0
  171. package/bin/forge-hook.mjs +32 -0
  172. package/bin/forge-runtime-service.mjs +34 -0
  173. package/bin/forge-runtime.mjs +30 -0
  174. package/bin/forge.mjs +32 -0
  175. package/docs/CHANGELOG.md +978 -0
  176. package/docs/README.md +72 -0
  177. package/docs/README.zh-CN.md +58 -0
  178. package/docs/architecture/chatgpt-handoff-facade.md +393 -0
  179. package/docs/architecture/current/README.md +158 -0
  180. package/docs/architecture/current/approved-target-architecture.zh-CN.md +1501 -0
  181. package/docs/architecture/current/architecture-invariants.md +331 -0
  182. package/docs/architecture/current/automation-and-schedule-engine.md +453 -0
  183. package/docs/architecture/current/codegraph-context-provider.md +226 -0
  184. package/docs/architecture/current/control-plane-authority-inventory.md +82 -0
  185. package/docs/architecture/current/controller-repository-decoupling.md +148 -0
  186. package/docs/architecture/current/dispatch-and-agent-strategy.md +354 -0
  187. package/docs/architecture/current/entity-model.md +619 -0
  188. package/docs/architecture/current/external-plugin-governance.md +396 -0
  189. package/docs/architecture/current/failure-recovery.md +471 -0
  190. package/docs/architecture/current/governance.md +213 -0
  191. package/docs/architecture/current/human-interaction-plane.md +87 -0
  192. package/docs/architecture/current/implementation-status.md +128 -0
  193. package/docs/architecture/current/job-and-run-lifecycle.md +413 -0
  194. package/docs/architecture/current/local-workspace-target-grants.md +70 -0
  195. package/docs/architecture/current/migration-roadmap.md +105 -0
  196. package/docs/architecture/current/multi-repository-execution.md +371 -0
  197. package/docs/architecture/current/operations-runbook.md +45 -0
  198. package/docs/architecture/current/peer-supercontroller-runtime.md +32 -0
  199. package/docs/architecture/current/personal-assistant-plugin-baseline.md +207 -0
  200. package/docs/architecture/current/repository-access-modes.md +102 -0
  201. package/docs/architecture/current/route-policy.md +15 -0
  202. package/docs/architecture/current/route-session-concurrency.md +85 -0
  203. package/docs/architecture/current/runtime-architecture-simplification.md +214 -0
  204. package/docs/architecture/current/runtime-directory-map.md +49 -0
  205. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +85 -0
  206. package/docs/architecture/current/scheduler-and-resource-claims.md +432 -0
  207. package/docs/architecture/current/session-aware-execution-and-authorization.md +77 -0
  208. package/docs/architecture/current/system-overview.md +144 -0
  209. package/docs/architecture/current/target-requirements-traceability.md +202 -0
  210. package/docs/architecture/current/thin-harness-governance-policy.md +32 -0
  211. package/docs/architecture/current/thin-harness-v1.md +313 -0
  212. package/docs/architecture/current/verification-and-release-gates.md +391 -0
  213. package/docs/architecture/decisions/20260801-controller-home-sqlite-state.md +100 -0
  214. package/docs/architecture/decisions/20260803-canonical-skill-surface.md +40 -0
  215. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +53 -0
  216. package/docs/architecture/diagrams/.gitkeep +1 -0
  217. package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +186 -0
  218. package/docs/architecture/domains/.gitkeep +0 -0
  219. package/docs/architecture/domains/public-surface.md +30 -0
  220. package/docs/architecture/domains/runtime-harness.md +33 -0
  221. package/docs/architecture/domains/verification.md +33 -0
  222. package/docs/architecture/domains/workflow-engine.md +30 -0
  223. package/docs/architecture/global-hook-runtime.md +162 -0
  224. package/docs/architecture/history.md +38 -0
  225. package/docs/architecture/index.md +114 -0
  226. package/docs/architecture/ios-semantic-automation-provider-v2.md +166 -0
  227. package/docs/architecture/modules/.gitkeep +0 -0
  228. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +24 -0
  229. package/docs/architecture/requests/.gitkeep +1 -0
  230. package/docs/architecture/requests/archive/2026/20260528-154740-ai-hooks-ai-hooks-hook-input-sh.md +59 -0
  231. package/docs/architecture/requests/archive/2026/20260528-154816-assets-hooks-assets-hooks-hook-input-sh.md +59 -0
  232. package/docs/architecture/requests/archive/2026/20260528-161851-ai-harness-workflow-contract-json-ai-harness-workflow-contract-json.md +59 -0
  233. package/docs/architecture/requests/archive/2026/20260528-161853-scripts-check-task-workflow-sh-scripts-check-task-workflow-sh.md +59 -0
  234. package/docs/architecture/requests/archive/2026/20260528-161911-assets-workflow-contract-v1-json-assets-workflow-contract-v1-json.md +59 -0
  235. package/docs/architecture/requests/archive/2026/20260528-222416-ai-hooks-ai-hooks-hook-input-sh.md +59 -0
  236. package/docs/architecture/requests/archive/2026/20260528-222444-ai-hooks-ai-hooks-pre-edit-guard-sh.md +59 -0
  237. package/docs/architecture/requests/archive/2026/20260528-222457-ai-hooks-ai-hooks-pre-edit-guard-sh.md +59 -0
  238. package/docs/architecture/requests/archive/2026/20260528-222648-ai-hooks-ai-hooks-pre-edit-guard-sh.md +59 -0
  239. package/docs/architecture/requests/archive/2026/20260528-222705-ai-hooks-ai-hooks-worktree-guard-sh.md +59 -0
  240. package/docs/architecture/requests/archive/2026/20260528-222726-ai-hooks-ai-hooks-prompt-guard-sh.md +59 -0
  241. package/docs/architecture/requests/archive/2026/20260528-222727-ai-hooks-ai-hooks-prompt-guard-sh.md +59 -0
  242. package/docs/architecture/requests/archive/2026/20260528-224736-ai-hooks-ai-hooks-hook-input-sh.md +59 -0
  243. package/docs/architecture/requests/archive/2026/20260528-233259-ai-hooks-ai-hooks-prompt-guard-sh.md +59 -0
  244. package/docs/architecture/requests/archive/2026/20260528-233335-assets-hooks-assets-hooks-prompt-guard-sh.md +59 -0
  245. package/docs/architecture/requests/archive/2026/20260528-233456-ai-hooks-ai-hooks-prompt-guard-sh.md +59 -0
  246. package/docs/architecture/requests/archive/2026/20260528-233457-assets-hooks-assets-hooks-prompt-guard-sh.md +59 -0
  247. package/docs/architecture/requests/archive/2026/20260528-233637-ai-hooks-ai-hooks-prompt-guard-sh.md +59 -0
  248. package/docs/architecture/requests/archive/2026/20260528-233638-assets-hooks-assets-hooks-prompt-guard-sh.md +59 -0
  249. package/docs/architecture/requests/archive/2026/20260529-000322-root-package-json.md +59 -0
  250. package/docs/architecture/requests/archive/2026/20260529-004238-ai-hooks-ai-hooks-lib-workflow-state-sh.md +59 -0
  251. package/docs/architecture/requests/archive/2026/20260529-020654-ai-harness-policy-json-ai-harness-policy-json.md +63 -0
  252. package/docs/architecture/requests/archive/2026/20260529-094236-ai-hooks-ai-hooks-prompt-guard-sh.md +60 -0
  253. package/docs/architecture/requests/archive/2026/20260529-094323-assets-hooks-assets-hooks-prompt-guard-sh.md +60 -0
  254. package/docs/architecture/requests/archive/2026/20260529-094331-ai-hooks-ai-hooks-prompt-guard-sh.md +60 -0
  255. package/docs/architecture/requests/archive/2026/20260529-094339-assets-hooks-assets-hooks-prompt-guard-sh.md +60 -0
  256. package/docs/architecture/requests/archive/2026/20260529-094430-assets-hooks-assets-hooks-session-start-context-sh.md +60 -0
  257. package/docs/architecture/requests/archive/2026/20260529-094446-ai-hooks-ai-hooks-session-start-context-sh.md +60 -0
  258. package/docs/architecture/requests/archive/2026/20260613-002040-runtime-harness-hook-adapters.md +68 -0
  259. package/docs/architecture/requests/archive/2026/root.md +68 -0
  260. package/docs/architecture/requests/archive/2026/runtime-harness-hook-adapters.md +76 -0
  261. package/docs/architecture/requests/archive/2026/verification-evals-checks.md +68 -0
  262. package/docs/architecture/requests/archive/2026/workflow-engine-contract-assets.md +69 -0
  263. package/docs/architecture/reviews/2026-07-11-access-surface-review.md +99 -0
  264. package/docs/architecture/snapshots/.gitkeep +1 -0
  265. package/docs/forge-autonomous-goal-loop.md +7 -0
  266. package/docs/forge-chatgpt-browser-engine.md +11 -0
  267. package/docs/forge-chatgpt-mcp-setup.md +239 -0
  268. package/docs/forge-chatgpt-supervised-automation.md +11 -0
  269. package/docs/forge-github-issue-launcher.md +7 -0
  270. package/docs/forge-plugin-management.md +95 -0
  271. package/docs/forge-provider-configuration.md +7 -0
  272. package/docs/forge-runtime-self-healing-loop.md +7 -0
  273. package/docs/images/forge-banner-cn.svg +1 -0
  274. package/docs/images/forge-banner.svg +1 -0
  275. package/docs/images/forge-controller-flow.svg +1 -0
  276. package/docs/images/image.png +0 -0
  277. package/docs/images/repo-harness-gptpro-cn.png +0 -0
  278. package/docs/images/repo-harness-gptpro.png +0 -0
  279. package/docs/images/repo-harness-hook-carrot.png +0 -0
  280. package/docs/operations/chatgpt-personal-assistant-runtime.md +83 -0
  281. package/docs/operations/checkout-route-integrity.md +22 -0
  282. package/docs/operations/controller-browser-plugin.md +299 -0
  283. package/docs/operations/controller-desktop-plugin.md +112 -0
  284. package/docs/operations/controller-performance-and-502.md +126 -0
  285. package/docs/operations/controller-reliability-runbook.md +134 -0
  286. package/docs/operations/environment-hardening.md +50 -0
  287. package/docs/operations/features.md +45 -0
  288. package/docs/operations/features.zh-CN.md +45 -0
  289. package/docs/operations/github-repository.md +60 -0
  290. package/docs/operations/gmail-assistant-routines.md +186 -0
  291. package/docs/operations/homebrew.md +26 -0
  292. package/docs/operations/local-system-assistant.md +46 -0
  293. package/docs/operations/local-system-assistant.zh-CN.md +46 -0
  294. package/docs/operations/mcp-tool-exposure.md +58 -0
  295. package/docs/operations/mobile-intents-shortcuts.md +117 -0
  296. package/docs/operations/open-source-release-hygiene.md +31 -0
  297. package/docs/operations/personal-assistant-operator-guide.md +61 -0
  298. package/docs/operations/platform-support.md +62 -0
  299. package/docs/operations/platform-support.zh-CN.md +61 -0
  300. package/docs/operations/provider-configuration.md +31 -0
  301. package/docs/operations/releasing.md +86 -0
  302. package/docs/operations/releasing.zh-CN.md +86 -0
  303. package/docs/operations/route-session-concurrency-baseline.json +893 -0
  304. package/docs/operations/route-session-concurrency-baseline.md +94 -0
  305. package/docs/operations/runtime-performance-diagnostics.md +14 -0
  306. package/docs/operations/sqlite-control-plane-recovery.md +65 -0
  307. package/docs/operations/standalone-disaster-recovery.md +136 -0
  308. package/docs/operations/troubleshooting.md +72 -0
  309. package/docs/operations/troubleshooting.zh-CN.md +70 -0
  310. package/docs/public-usage-guide.md +33 -0
  311. package/docs/public-usage-guide.zh-CN.md +33 -0
  312. package/docs/tutorials/01-install-and-start.md +93 -0
  313. package/docs/tutorials/01-install-and-start.zh-CN.md +93 -0
  314. package/docs/tutorials/02-connect-chatgpt.md +82 -0
  315. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +80 -0
  316. package/docs/tutorials/03-first-repository-task.md +57 -0
  317. package/docs/tutorials/03-first-repository-task.zh-CN.md +57 -0
  318. package/docs/tutorials/README.md +13 -0
  319. package/docs/tutorials/README.zh-CN.md +13 -0
  320. package/docs/wiki/Architecture.md +85 -0
  321. package/docs/wiki/Core-Concepts.md +27 -0
  322. package/docs/wiki/Home.md +36 -0
  323. package/docs/wiki/Implementation.md +172 -0
  324. package/docs/wiki/Installation.md +50 -0
  325. package/docs/wiki/Integrations.md +29 -0
  326. package/docs/wiki/Operations.md +26 -0
  327. package/docs/wiki/Quick-Start.md +46 -0
  328. package/docs/wiki/Releases-and-Upgrades.md +34 -0
  329. package/docs/wiki/Runtime-Architecture.md +50 -0
  330. package/docs/wiki/Security-Model.md +31 -0
  331. package/docs/wiki/Troubleshooting.md +38 -0
  332. package/docs/wiki/Work-Lifecycle.md +31 -0
  333. package/docs/wiki/_Sidebar.md +21 -0
  334. package/install.ps1 +48 -0
  335. package/install.sh +66 -0
  336. package/package.json +156 -0
  337. package/scripts/activate-source-baseline.ts +335 -0
  338. package/scripts/architecture-event.ts +822 -0
  339. package/scripts/architecture-queue.sh +647 -0
  340. package/scripts/archive-architecture-request.sh +244 -0
  341. package/scripts/archive-workflow.sh +295 -0
  342. package/scripts/assemble-template.ts +1172 -0
  343. package/scripts/benchmark-controller-hotpath.ts +212 -0
  344. package/scripts/benchmark-controller-round2.ts +139 -0
  345. package/scripts/benchmark-ios-agent-device.ts +514 -0
  346. package/scripts/benchmark-route-session-concurrency.ts +714 -0
  347. package/scripts/benchmark-thin-harness-gateway-ab.ts +632 -0
  348. package/scripts/benchmark-thin-harness-mcp-e2e.ts +479 -0
  349. package/scripts/benchmark-thin-harness.ts +251 -0
  350. package/scripts/bootstrap-runtime-maintenance-recovery.sh +183 -0
  351. package/scripts/canary-global-hook.sh +263 -0
  352. package/scripts/capability-config.ts +394 -0
  353. package/scripts/capability-resolver.ts +505 -0
  354. package/scripts/capture-plan.sh +335 -0
  355. package/scripts/check-agent-tooling.sh +1549 -0
  356. package/scripts/check-architecture-sync.sh +373 -0
  357. package/scripts/check-brain-manifest.sh +255 -0
  358. package/scripts/check-ci.sh +16 -0
  359. package/scripts/check-context-files.sh +49 -0
  360. package/scripts/check-deploy-sql-order.sh +117 -0
  361. package/scripts/check-mcp-compatibility.ts +93 -0
  362. package/scripts/check-npm-release.sh +32 -0
  363. package/scripts/check-open-source-tracked-surface.sh +146 -0
  364. package/scripts/check-package-identity.mjs +117 -0
  365. package/scripts/check-platform-support.mjs +79 -0
  366. package/scripts/check-public-docs.sh +105 -0
  367. package/scripts/check-public-release-surface.sh +19 -0
  368. package/scripts/check-release-published.sh +57 -0
  369. package/scripts/check-release-readiness.sh +43 -0
  370. package/scripts/check-release-version.mjs +74 -0
  371. package/scripts/check-runtime-architecture.mjs +593 -0
  372. package/scripts/check-runtime-env.mjs +89 -0
  373. package/scripts/check-skill-version.ts +193 -0
  374. package/scripts/check-tarball-install-smoke.sh +66 -0
  375. package/scripts/check-task-sync.sh +65 -0
  376. package/scripts/check-task-workflow.sh +1002 -0
  377. package/scripts/check-third-party-notices.mjs +36 -0
  378. package/scripts/codex-handoff-resume.sh +269 -0
  379. package/scripts/complete-control-plane-cutover.ts +126 -0
  380. package/scripts/context-contract-sync.sh +516 -0
  381. package/scripts/contract-run.ts +465 -0
  382. package/scripts/contract-worktree.sh +725 -0
  383. package/scripts/controller-ngrok-rotation.sh +678 -0
  384. package/scripts/create-project-dirs.sh +282 -0
  385. package/scripts/ensure-codegraph.sh +16 -0
  386. package/scripts/ensure-task-workflow.sh +1218 -0
  387. package/scripts/export-requirement-portfolio.ts +28 -0
  388. package/scripts/factor-lab-check.sh +73 -0
  389. package/scripts/factor-lab-new.sh +96 -0
  390. package/scripts/factor-lab-promote.sh +86 -0
  391. package/scripts/factor-lab-reject.sh +68 -0
  392. package/scripts/harness-trace-grade.sh +150 -0
  393. package/scripts/heartbeat-triage.sh +361 -0
  394. package/scripts/hook-dispatch-diet-report.ts +234 -0
  395. package/scripts/hook-shim.sh +93 -0
  396. package/scripts/init-project.sh +505 -0
  397. package/scripts/initializer-question-pack.ts +200 -0
  398. package/scripts/inspect-project-state.ts +280 -0
  399. package/scripts/install-desktop-operator-registration.ts +45 -0
  400. package/scripts/install-standalone-recovery.ts +74 -0
  401. package/scripts/lib/AGENTS.md +13 -0
  402. package/scripts/lib/CLAUDE.md +13 -0
  403. package/scripts/lib/controller-home-env.sh +69 -0
  404. package/scripts/lib/project-init-lib.sh +2759 -0
  405. package/scripts/load-standalone-recovery.sh +17 -0
  406. package/scripts/loop-engine-cutover-gate.ts +324 -0
  407. package/scripts/maintenance-triage.sh +167 -0
  408. package/scripts/migrate-project-template.sh +1189 -0
  409. package/scripts/migrate-requirement-portfolio.ts +86 -0
  410. package/scripts/migrate-workflow-docs.ts +413 -0
  411. package/scripts/new-plan.sh +183 -0
  412. package/scripts/new-spec.sh +50 -0
  413. package/scripts/new-sprint.sh +49 -0
  414. package/scripts/open-source-audit-allowlist.txt +46 -0
  415. package/scripts/package-source-archive.sh +147 -0
  416. package/scripts/plan-to-todo.sh +799 -0
  417. package/scripts/prepare-codex-handoff.sh +203 -0
  418. package/scripts/prepare-handoff.sh +81 -0
  419. package/scripts/public-release-files.txt +39 -0
  420. package/scripts/public-release.ts +174 -0
  421. package/scripts/publish-release-tarball.sh +27 -0
  422. package/scripts/refresh-current-status.sh +469 -0
  423. package/scripts/refresh-ios-physical-runtime.ts +196 -0
  424. package/scripts/repair-executable-modes.ts +8 -0
  425. package/scripts/rollout-all-registered-repos.sh +7 -0
  426. package/scripts/route-nl-vs-ts-eval.ts +679 -0
  427. package/scripts/run-bun-test-file.ts +182 -0
  428. package/scripts/run-governed-gate.ts +140 -0
  429. package/scripts/run-mobile-monitor-bridge.ts +71 -0
  430. package/scripts/run-skill-evals.ts +1022 -0
  431. package/scripts/run-skill-hook.ts +244 -0
  432. package/scripts/run-supervised-command.ts +24 -0
  433. package/scripts/run-tests-portable.sh +20 -0
  434. package/scripts/select-agent-context-blocks.sh +66 -0
  435. package/scripts/setup-plugins.sh +43 -0
  436. package/scripts/ship-worktrees.sh +561 -0
  437. package/scripts/smoke-browser-cdp-bridge.ts +93 -0
  438. package/scripts/smoke-chatgpt-supervised-automation.ts +208 -0
  439. package/scripts/smoke-ios-physical-device.ts +114 -0
  440. package/scripts/smoke-mcp-http-runtime.ts +216 -0
  441. package/scripts/smoke-mcp-tool-surface.ts +117 -0
  442. package/scripts/smoke-runtime-recovery.ts +220 -0
  443. package/scripts/smoke-schedule-engine.ts +283 -0
  444. package/scripts/sprint-backlog.sh +837 -0
  445. package/scripts/stage-runtime-release.ts +38 -0
  446. package/scripts/summarize-failures.sh +125 -0
  447. package/scripts/switch-plan.sh +153 -0
  448. package/scripts/sync-brain-docs.sh +381 -0
  449. package/scripts/sync-codex-installed-copies.sh +183 -0
  450. package/scripts/test-governance.ts +75 -0
  451. package/scripts/verify-contract.sh +705 -0
  452. package/scripts/verify-forge-runtime.sh +31 -0
  453. package/scripts/verify-sprint.sh +680 -0
  454. package/scripts/windows-paths.d.mts +7 -0
  455. package/scripts/windows-paths.mjs +28 -0
  456. package/scripts/workflow-contract.ts +206 -0
  457. package/scripts/workstream-sync.sh +263 -0
  458. package/src/cli/agent-jobs/executable-resolver.ts +289 -0
  459. package/src/cli/agent-jobs/executor-health.ts +252 -0
  460. package/src/cli/agent-jobs/integration.ts +963 -0
  461. package/src/cli/agent-jobs/job-manager.ts +2030 -0
  462. package/src/cli/agent-jobs/types.ts +219 -0
  463. package/src/cli/agent-jobs/worker-lifecycle.ts +288 -0
  464. package/src/cli/chatgpt-browser/bind-server.ts +504 -0
  465. package/src/cli/chatgpt-browser/binding.ts +140 -0
  466. package/src/cli/chatgpt-browser/bridge-extension.ts +221 -0
  467. package/src/cli/chatgpt-browser/bridge-provider.ts +268 -0
  468. package/src/cli/chatgpt-browser/engine.ts +569 -0
  469. package/src/cli/chatgpt-browser/file-policy.ts +158 -0
  470. package/src/cli/chatgpt-browser/native-provider.ts +553 -0
  471. package/src/cli/chatgpt-browser/oracle-provider.ts +437 -0
  472. package/src/cli/chatgpt-browser/prompt-assembler.ts +29 -0
  473. package/src/cli/chatgpt-browser/session-store.ts +301 -0
  474. package/src/cli/chatgpt-browser/types.ts +172 -0
  475. package/src/cli/commands/adopt-plan.ts +186 -0
  476. package/src/cli/commands/brain-root.ts +132 -0
  477. package/src/cli/commands/brain.ts +609 -0
  478. package/src/cli/commands/capability-context.ts +613 -0
  479. package/src/cli/commands/chatgpt.ts +424 -0
  480. package/src/cli/commands/controller.ts +680 -0
  481. package/src/cli/commands/docs.ts +103 -0
  482. package/src/cli/commands/doctor.ts +476 -0
  483. package/src/cli/commands/global-runtime.ts +323 -0
  484. package/src/cli/commands/hook.ts +34 -0
  485. package/src/cli/commands/init-hook.ts +870 -0
  486. package/src/cli/commands/init.ts +829 -0
  487. package/src/cli/commands/install.ts +86 -0
  488. package/src/cli/commands/mcp.ts +315 -0
  489. package/src/cli/commands/migrate.ts +154 -0
  490. package/src/cli/commands/plugin.ts +74 -0
  491. package/src/cli/commands/prompt-guard-decision.ts +238 -0
  492. package/src/cli/commands/recovery.ts +667 -0
  493. package/src/cli/commands/repository.ts +224 -0
  494. package/src/cli/commands/run.ts +23 -0
  495. package/src/cli/commands/runtime.ts +191 -0
  496. package/src/cli/commands/security.ts +440 -0
  497. package/src/cli/commands/status.ts +137 -0
  498. package/src/cli/commands/tools.ts +167 -0
  499. package/src/cli/controller/check-runner.ts +1022 -0
  500. package/src/cli/controller/codex-continuation.ts +153 -0
  501. package/src/cli/controller/completion-backlog.ts +380 -0
  502. package/src/cli/controller/completion-orchestrator.ts +1807 -0
  503. package/src/cli/controller/completion-target.ts +96 -0
  504. package/src/cli/controller/composite-operations.ts +196 -0
  505. package/src/cli/controller/composite-result.ts +130 -0
  506. package/src/cli/controller/context-pack.ts +652 -0
  507. package/src/cli/controller/execution-completion.ts +175 -0
  508. package/src/cli/controller/execution-policy.ts +392 -0
  509. package/src/cli/controller/governance.ts +594 -0
  510. package/src/cli/controller/issue-store.ts +1421 -0
  511. package/src/cli/controller/launch-agents.ts +410 -0
  512. package/src/cli/controller/legacy-issue-cutover.ts +415 -0
  513. package/src/cli/controller/lifecycle-authority.ts +16 -0
  514. package/src/cli/controller/migrated-issue-decision.ts +237 -0
  515. package/src/cli/controller/operational-plan.ts +226 -0
  516. package/src/cli/controller/postcondition.ts +134 -0
  517. package/src/cli/controller/progress.ts +656 -0
  518. package/src/cli/controller/project-state.ts +147 -0
  519. package/src/cli/controller/requirement-portfolio-export.ts +146 -0
  520. package/src/cli/controller/requirement-portfolio-migration.ts +613 -0
  521. package/src/cli/controller/run-evidence.ts +43 -0
  522. package/src/cli/controller/runtime-config.ts +92 -0
  523. package/src/cli/controller/stuck-state-migration.ts +396 -0
  524. package/src/cli/controller/task-ledger.ts +632 -0
  525. package/src/cli/controller/task-status-resolver.ts +403 -0
  526. package/src/cli/controller/types.ts +273 -0
  527. package/src/cli/controller/work-mode.ts +186 -0
  528. package/src/cli/controller/worklog.ts +201 -0
  529. package/src/cli/diagnostic-entry.ts +39 -0
  530. package/src/cli/editing/edit-session.ts +1606 -0
  531. package/src/cli/editing/executable-modes.ts +70 -0
  532. package/src/cli/github/github.ts +274 -0
  533. package/src/cli/github/plugin.ts +345 -0
  534. package/src/cli/github/session.ts +104 -0
  535. package/src/cli/hook/prompt-guard-decision.ts +238 -0
  536. package/src/cli/hook/prompt-intents.ts +534 -0
  537. package/src/cli/hook/route-registry.ts +107 -0
  538. package/src/cli/hook/runtime.ts +303 -0
  539. package/src/cli/hook/state-snapshot.ts +388 -0
  540. package/src/cli/hook-entry.ts +51 -0
  541. package/src/cli/index.ts +574 -0
  542. package/src/cli/installer/managed-entries.ts +78 -0
  543. package/src/cli/installer/shared.ts +65 -0
  544. package/src/cli/installer/targets/claude.ts +136 -0
  545. package/src/cli/installer/targets/codex.ts +174 -0
  546. package/src/cli/installer/targets/registry.ts +28 -0
  547. package/src/cli/installer/types.ts +72 -0
  548. package/src/cli/local-bridge/connector-freshness.ts +529 -0
  549. package/src/cli/local-bridge/console-view-models.ts +324 -0
  550. package/src/cli/local-bridge/dashboard.ts +1312 -0
  551. package/src/cli/local-bridge/facade-api.ts +1877 -0
  552. package/src/cli/local-bridge/job-store.ts +1451 -0
  553. package/src/cli/local-bridge/mobile-intents.ts +344 -0
  554. package/src/cli/local-bridge/mobile-monitor.ts +209 -0
  555. package/src/cli/local-bridge/server.ts +3183 -0
  556. package/src/cli/local-bridge/types.ts +176 -0
  557. package/src/cli/mcp/access-mode.ts +132 -0
  558. package/src/cli/mcp/access-tools.ts +387 -0
  559. package/src/cli/mcp/audit.ts +32 -0
  560. package/src/cli/mcp/auth.ts +402 -0
  561. package/src/cli/mcp/instructions.ts +33 -0
  562. package/src/cli/mcp/legacy-context.ts +16 -0
  563. package/src/cli/mcp/legacy-tool-service.ts +5516 -0
  564. package/src/cli/mcp/multi-repository.ts +423 -0
  565. package/src/cli/mcp/oauth.ts +226 -0
  566. package/src/cli/mcp/paths.ts +134 -0
  567. package/src/cli/mcp/policy.ts +230 -0
  568. package/src/cli/mcp/proxy-env.ts +62 -0
  569. package/src/cli/mcp/readiness-projection.ts +24 -0
  570. package/src/cli/mcp/redaction.ts +21 -0
  571. package/src/cli/mcp/repo.ts +24 -0
  572. package/src/cli/mcp/repository-tools.ts +1295 -0
  573. package/src/cli/mcp/server.ts +341 -0
  574. package/src/cli/mcp/setup.ts +793 -0
  575. package/src/cli/mcp/tool-capability-cache.ts +87 -0
  576. package/src/cli/mcp/tools.ts +9 -0
  577. package/src/cli/mcp/toolset-names.ts +70 -0
  578. package/src/cli/mcp/toolset-selection.ts +41 -0
  579. package/src/cli/mcp/toolset.ts +300 -0
  580. package/src/cli/mcp/transports/AGENTS.md +39 -0
  581. package/src/cli/mcp/transports/CLAUDE.md +39 -0
  582. package/src/cli/mcp/transports/http.ts +1234 -0
  583. package/src/cli/mcp/transports/session-registry.ts +428 -0
  584. package/src/cli/mcp/transports/stdio.ts +8 -0
  585. package/src/cli/mcp/types.ts +41 -0
  586. package/src/cli/mcp/umbrella-tools.ts +117 -0
  587. package/src/cli/repo-adoption/reclaim-runtime.ts +681 -0
  588. package/src/cli/repositories/branch-name-policy.ts +57 -0
  589. package/src/cli/repositories/command-classifier.ts +600 -0
  590. package/src/cli/repositories/command-executor.ts +1066 -0
  591. package/src/cli/repositories/command-normalization.ts +53 -0
  592. package/src/cli/repositories/command-scope.ts +264 -0
  593. package/src/cli/repositories/controller-home.ts +88 -0
  594. package/src/cli/repositories/entity-migration.ts +199 -0
  595. package/src/cli/repositories/ephemeral-workspace.ts +62 -0
  596. package/src/cli/repositories/git-command-executor.ts +301 -0
  597. package/src/cli/repositories/goal-registry.ts +239 -0
  598. package/src/cli/repositories/identity.ts +64 -0
  599. package/src/cli/repositories/local-project-onboarding.ts +539 -0
  600. package/src/cli/repositories/locks.ts +262 -0
  601. package/src/cli/repositories/registry.ts +1057 -0
  602. package/src/cli/repositories/runtime-storage.ts +411 -0
  603. package/src/cli/repositories/safe-patch.ts +421 -0
  604. package/src/cli/repositories/selected-path-actions.ts +341 -0
  605. package/src/cli/repositories/structured-git.ts +257 -0
  606. package/src/cli/repositories/types.ts +155 -0
  607. package/src/cli/repositories/umbrella.ts +134 -0
  608. package/src/cli/repositories/workbench.ts +140 -0
  609. package/src/cli/repositories/worktree-storage.ts +137 -0
  610. package/src/cli/repository/inspector.ts +637 -0
  611. package/src/cli/repository/session-cache.ts +453 -0
  612. package/src/cli/runtime/helper-runner.ts +172 -0
  613. package/src/cli/runtime-invocation.ts +318 -0
  614. package/src/cli/tools/codegraph.ts +676 -0
  615. package/src/cli/v81-entry.ts +29 -0
  616. package/src/core/adoption/gitignore-plan.ts +127 -0
  617. package/src/core/adoption/helper-wrapper-plan.ts +159 -0
  618. package/src/core/adoption/manifest-templates.ts +60 -0
  619. package/src/core/adoption/modes.ts +7 -0
  620. package/src/core/adoption/operations.ts +126 -0
  621. package/src/core/adoption/plan.ts +149 -0
  622. package/src/core/adoption/render.ts +70 -0
  623. package/src/core/adoption/rollback.ts +62 -0
  624. package/src/core/adoption/summary.ts +43 -0
  625. package/src/core/adoption/workflow-contract-asset.ts +14 -0
  626. package/src/core/adoption/workflow-contract-plan.ts +31 -0
  627. package/src/effects/fs-transaction.ts +540 -0
  628. package/src/effects/git-executable.ts +59 -0
  629. package/src/effects/managed-block.ts +86 -0
  630. package/src/effects/path-safety.ts +47 -0
  631. package/src/effects/process-runner.ts +192 -0
  632. package/src/runtime/assistant/action-proposals.ts +279 -0
  633. package/src/runtime/assistant/intent.ts +395 -0
  634. package/src/runtime/assistant/model-provider.ts +460 -0
  635. package/src/runtime/assistant/openapi.ts +223 -0
  636. package/src/runtime/assistant/policy.ts +89 -0
  637. package/src/runtime/assistant/readiness.ts +258 -0
  638. package/src/runtime/assistant/routine-runtime.ts +477 -0
  639. package/src/runtime/assistant/schedule-binding.ts +171 -0
  640. package/src/runtime/assistant/standing-grants.ts +375 -0
  641. package/src/runtime/assistant/store.ts +243 -0
  642. package/src/runtime/assistant/types.ts +135 -0
  643. package/src/runtime/context/codegraph-read-provider.ts +347 -0
  644. package/src/runtime/context/codegraph-sidecar.cjs +207 -0
  645. package/src/runtime/control-plane/dispatch-priority.ts +67 -0
  646. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +151 -0
  647. package/src/runtime/control-plane/execution/execution-identity.ts +456 -0
  648. package/src/runtime/control-plane/execution/session-store.ts +175 -0
  649. package/src/runtime/control-plane/execution/validation.ts +117 -0
  650. package/src/runtime/control-plane/execution/verification-evidence.ts +191 -0
  651. package/src/runtime/control-plane/execution/work-handle-store.ts +225 -0
  652. package/src/runtime/control-plane/execution/work-prepare-request-store.ts +159 -0
  653. package/src/runtime/control-plane/execution/work-task-receipt.ts +445 -0
  654. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +821 -0
  655. package/src/runtime/control-plane/facade/capability-registry.ts +279 -0
  656. package/src/runtime/control-plane/facade/check-normalization.ts +233 -0
  657. package/src/runtime/control-plane/facade/codex-delegation.ts +196 -0
  658. package/src/runtime/control-plane/facade/controller-session-store.ts +199 -0
  659. package/src/runtime/control-plane/facade/facade-result.ts +41 -0
  660. package/src/runtime/control-plane/facade/goal-workloop-access.ts +177 -0
  661. package/src/runtime/control-plane/facade/goal-workloop.ts +1216 -0
  662. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +221 -0
  663. package/src/runtime/control-plane/facade/index.ts +16 -0
  664. package/src/runtime/control-plane/facade/operation-digest.ts +528 -0
  665. package/src/runtime/control-plane/facade/plan-contract-store.ts +403 -0
  666. package/src/runtime/control-plane/facade/policy-gate.ts +149 -0
  667. package/src/runtime/control-plane/facade/requirement-board.ts +280 -0
  668. package/src/runtime/control-plane/facade/self-healing-loop.ts +555 -0
  669. package/src/runtime/control-plane/facade/suggested-actions.ts +108 -0
  670. package/src/runtime/control-plane/facade/types.ts +729 -0
  671. package/src/runtime/control-plane/facade/work-admission-policy.ts +93 -0
  672. package/src/runtime/control-plane/facade/work-continuation.ts +104 -0
  673. package/src/runtime/control-plane/facade/work-contract-store.ts +1118 -0
  674. package/src/runtime/control-plane/facade/work-task-projection.ts +66 -0
  675. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +365 -0
  676. package/src/runtime/control-plane/global-scheduler/scheduler.ts +920 -0
  677. package/src/runtime/control-plane/global-scheduler/wake-signal.ts +89 -0
  678. package/src/runtime/control-plane/goal-loop/config-facade.ts +700 -0
  679. package/src/runtime/control-plane/goal-loop/config-store.ts +570 -0
  680. package/src/runtime/control-plane/goal-loop/config-types.ts +213 -0
  681. package/src/runtime/control-plane/goal-loop/executor-router.ts +109 -0
  682. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +149 -0
  683. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +304 -0
  684. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +1001 -0
  685. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +160 -0
  686. package/src/runtime/control-plane/goal-loop/index.ts +13 -0
  687. package/src/runtime/control-plane/goal-loop/local-tools.ts +290 -0
  688. package/src/runtime/control-plane/goal-loop/policy-gates.ts +203 -0
  689. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +93 -0
  690. package/src/runtime/control-plane/goal-loop/provider-registry.ts +545 -0
  691. package/src/runtime/control-plane/goal-loop/types.ts +378 -0
  692. package/src/runtime/control-plane/governance/access-policy.ts +147 -0
  693. package/src/runtime/control-plane/governance/authorization.ts +195 -0
  694. package/src/runtime/control-plane/governance/external-effects.ts +43 -0
  695. package/src/runtime/control-plane/launcher/thin-launcher.ts +97 -0
  696. package/src/runtime/control-plane/persistence/metadata-payload-policy.ts +53 -0
  697. package/src/runtime/control-plane/persistence/requirement-store.ts +241 -0
  698. package/src/runtime/control-plane/persistence/sqlite-store.ts +539 -0
  699. package/src/runtime/control-plane/release-retention.ts +447 -0
  700. package/src/runtime/control-plane/repo-actor/actor.ts +181 -0
  701. package/src/runtime/control-plane/repo-actor/registry.ts +20 -0
  702. package/src/runtime/control-plane/routing/index.ts +1 -0
  703. package/src/runtime/control-plane/routing/route-policy.ts +379 -0
  704. package/src/runtime/control-plane/runtime-cleanup.ts +727 -0
  705. package/src/runtime/control-plane/runtime-generation.ts +509 -0
  706. package/src/runtime/control-plane/runtime-pid-ownership.ts +11 -0
  707. package/src/runtime/control-plane/runtime-status-client.ts +90 -0
  708. package/src/runtime/control-plane/startup-recovery.ts +198 -0
  709. package/src/runtime/diagnostics/connectivity-observability.ts +475 -0
  710. package/src/runtime/diagnostics/mcp-timing.ts +64 -0
  711. package/src/runtime/diagnostics/performance.ts +379 -0
  712. package/src/runtime/diagnostics/process-facade.ts +350 -0
  713. package/src/runtime/diagnostics/read-only-tool.ts +226 -0
  714. package/src/runtime/evidence/artifact-store.ts +154 -0
  715. package/src/runtime/evidence/event-ledger.ts +80 -0
  716. package/src/runtime/evidence/evidence-store.ts +68 -0
  717. package/src/runtime/evidence/process-check-receipt.ts +43 -0
  718. package/src/runtime/evidence/result-store.ts +209 -0
  719. package/src/runtime/evidence/sensitive-output.ts +353 -0
  720. package/src/runtime/execution/jobs/child-reference.ts +124 -0
  721. package/src/runtime/execution/jobs/legacy-adapter.ts +22 -0
  722. package/src/runtime/execution/jobs/receipt-recovery.ts +119 -0
  723. package/src/runtime/execution/jobs/receipt-store.ts +243 -0
  724. package/src/runtime/execution/jobs/store.ts +737 -0
  725. package/src/runtime/execution/jobs/timeouts.ts +109 -0
  726. package/src/runtime/execution/jobs/types.ts +230 -0
  727. package/src/runtime/execution/jobs/wait.ts +45 -0
  728. package/src/runtime/execution/process-runtime/canary.ts +26 -0
  729. package/src/runtime/execution/process-runtime/check-facade.ts +272 -0
  730. package/src/runtime/execution/process-runtime/check-receipt.ts +185 -0
  731. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +60 -0
  732. package/src/runtime/execution/process-runtime/command-facade.ts +321 -0
  733. package/src/runtime/execution/process-runtime/gc.ts +171 -0
  734. package/src/runtime/execution/process-runtime/index.ts +9 -0
  735. package/src/runtime/execution/process-runtime/interactive-admission.ts +23 -0
  736. package/src/runtime/execution/process-runtime/process-runner-entry.ts +466 -0
  737. package/src/runtime/execution/process-runtime/resource-claims.ts +440 -0
  738. package/src/runtime/execution/process-runtime/runtime.ts +1816 -0
  739. package/src/runtime/execution/process-runtime/store.ts +687 -0
  740. package/src/runtime/execution/process-runtime/types.ts +352 -0
  741. package/src/runtime/execution/thin-harness/async-process.ts +181 -0
  742. package/src/runtime/execution/thin-harness/batch-executor.ts +671 -0
  743. package/src/runtime/execution/thin-harness/execution-router.ts +700 -0
  744. package/src/runtime/execution/thin-harness/fast-executor.ts +1263 -0
  745. package/src/runtime/execution/thin-harness/fast-receipt.ts +235 -0
  746. package/src/runtime/execution/thin-harness/fingerprint-worker.ts +191 -0
  747. package/src/runtime/execution/thin-harness/index.ts +13 -0
  748. package/src/runtime/execution/thin-harness/latency-trace.ts +107 -0
  749. package/src/runtime/execution/thin-harness/lightweight-lanes.ts +745 -0
  750. package/src/runtime/execution/thin-harness/mutation-gate.ts +578 -0
  751. package/src/runtime/execution/thin-harness/proposal-store.ts +409 -0
  752. package/src/runtime/execution/thin-harness/request-ledger.ts +595 -0
  753. package/src/runtime/execution/thin-harness/search-worker.ts +114 -0
  754. package/src/runtime/execution/thin-harness/types.ts +313 -0
  755. package/src/runtime/execution/thin-harness/workspace-savepoint.ts +333 -0
  756. package/src/runtime/execution/workers/executor.ts +687 -0
  757. package/src/runtime/execution/workers/ownership.ts +94 -0
  758. package/src/runtime/execution/workers/worker-entry.ts +277 -0
  759. package/src/runtime/gateway/mcp/execution-tools.ts +2009 -0
  760. package/src/runtime/gateway/mcp/persisted-check-process.ts +255 -0
  761. package/src/runtime/gateway/mcp/process-tools.ts +230 -0
  762. package/src/runtime/gateway/mcp/resource-policy.ts +195 -0
  763. package/src/runtime/gateway/mcp/router.ts +1088 -0
  764. package/src/runtime/gateway/mcp/runtime-tools.ts +5994 -0
  765. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +219 -0
  766. package/src/runtime/health/evaluator.ts +474 -0
  767. package/src/runtime/health/index.ts +2 -0
  768. package/src/runtime/health/operational-view.ts +198 -0
  769. package/src/runtime/maintenance/cleanup.ts +419 -0
  770. package/src/runtime/model-clients/deepseek-adapter.ts +312 -0
  771. package/src/runtime/model-clients/index.ts +2 -0
  772. package/src/runtime/model-clients/types.ts +101 -0
  773. package/src/runtime/observability/gateway-contention-metrics.ts +129 -0
  774. package/src/runtime/observability/route-session-benchmark.ts +110 -0
  775. package/src/runtime/personal-assistant/gmail-triage-manager.ts +231 -0
  776. package/src/runtime/personal-assistant/reporting-runtime.ts +344 -0
  777. package/src/runtime/personal-assistant/triage-runtime.ts +748 -0
  778. package/src/runtime/plugins/app-store-connect-adapter.ts +868 -0
  779. package/src/runtime/plugins/browser-adapter.ts +2812 -0
  780. package/src/runtime/plugins/browser-automation-service.ts +46 -0
  781. package/src/runtime/plugins/browser-handoff-host.ts +304 -0
  782. package/src/runtime/plugins/browser-handoff.ts +371 -0
  783. package/src/runtime/plugins/browser-macos-bridge.ts +865 -0
  784. package/src/runtime/plugins/browser-node-bridge-host.ts +64 -0
  785. package/src/runtime/plugins/browser-node-bridge.ts +249 -0
  786. package/src/runtime/plugins/desktop-adapter.ts +374 -0
  787. package/src/runtime/plugins/desktop-operator-registration.ts +418 -0
  788. package/src/runtime/plugins/errors.ts +39 -0
  789. package/src/runtime/plugins/execution-origin.ts +26 -0
  790. package/src/runtime/plugins/external-adapter.ts +322 -0
  791. package/src/runtime/plugins/external-registration.ts +308 -0
  792. package/src/runtime/plugins/external-unix-socket-probe.cjs +79 -0
  793. package/src/runtime/plugins/external-unix-socket.ts +238 -0
  794. package/src/runtime/plugins/first-party-registry.ts +37 -0
  795. package/src/runtime/plugins/github-adapter.ts +266 -0
  796. package/src/runtime/plugins/gmail-adapter.ts +729 -0
  797. package/src/runtime/plugins/google-calendar-adapter.ts +468 -0
  798. package/src/runtime/plugins/google-shared.ts +578 -0
  799. package/src/runtime/plugins/google-tasks-adapter.ts +535 -0
  800. package/src/runtime/plugins/interaction-session.ts +252 -0
  801. package/src/runtime/plugins/ios/agent-device-capabilities.ts +258 -0
  802. package/src/runtime/plugins/ios/agent-device-failures.ts +95 -0
  803. package/src/runtime/plugins/ios/agent-device-provider.ts +63 -0
  804. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +268 -0
  805. package/src/runtime/plugins/ios/app-adapters.ts +122 -0
  806. package/src/runtime/plugins/ios-adapter.ts +509 -0
  807. package/src/runtime/plugins/ios-agent-device.ts +2311 -0
  808. package/src/runtime/plugins/ios-physical-device.ts +955 -0
  809. package/src/runtime/plugins/local-system-adapter.ts +1016 -0
  810. package/src/runtime/plugins/macos-capability-broker.ts +73 -0
  811. package/src/runtime/plugins/managed-process-adapter.ts +366 -0
  812. package/src/runtime/plugins/resend-adapter.ts +506 -0
  813. package/src/runtime/plugins/store.ts +1025 -0
  814. package/src/runtime/plugins/types.ts +138 -0
  815. package/src/runtime/projections/controller-context.ts +596 -0
  816. package/src/runtime/projections/git-status-sampler.ts +84 -0
  817. package/src/runtime/projections/invalidation.ts +250 -0
  818. package/src/runtime/projections/materialized-view.ts +694 -0
  819. package/src/runtime/recovery/actions.ts +158 -0
  820. package/src/runtime/recovery/audit.ts +38 -0
  821. package/src/runtime/recovery/classifier.ts +163 -0
  822. package/src/runtime/recovery/index.ts +9 -0
  823. package/src/runtime/recovery/local-jobs-repair.ts +393 -0
  824. package/src/runtime/recovery/maintenance-executor.ts +822 -0
  825. package/src/runtime/recovery/patch-handoff.ts +50 -0
  826. package/src/runtime/recovery/probe.ts +308 -0
  827. package/src/runtime/recovery/store.ts +35 -0
  828. package/src/runtime/recovery/types.ts +175 -0
  829. package/src/runtime/release/identity.ts +101 -0
  830. package/src/runtime/release/release-gate.ts +137 -0
  831. package/src/runtime/resources/claims/conflicts.ts +205 -0
  832. package/src/runtime/resources/index.ts +1 -0
  833. package/src/runtime/resources/leases/store.ts +539 -0
  834. package/src/runtime/resources/leases/types.ts +50 -0
  835. package/src/runtime/resources/ownership.ts +72 -0
  836. package/src/runtime/root/controller-services.ts +42 -0
  837. package/src/runtime/root/entry.ts +70 -0
  838. package/src/runtime/root/gateway-adapter.ts +46 -0
  839. package/src/runtime/root/local-bridge.ts +72 -0
  840. package/src/runtime/root/mcp-transport.ts +198 -0
  841. package/src/runtime/root/ownership.ts +94 -0
  842. package/src/runtime/root/readiness.ts +69 -0
  843. package/src/runtime/root/release-manifest.ts +147 -0
  844. package/src/runtime/root/release-materialize.ts +509 -0
  845. package/src/runtime/root/release-store.ts +236 -0
  846. package/src/runtime/root/runtime.ts +318 -0
  847. package/src/runtime/root/scheduler.ts +58 -0
  848. package/src/runtime/root/service-runner-entry.ts +20 -0
  849. package/src/runtime/root/service-runner.ts +69 -0
  850. package/src/runtime/root/service.ts +291 -0
  851. package/src/runtime/root/status.ts +127 -0
  852. package/src/runtime/root/types.ts +112 -0
  853. package/src/runtime/root/write-fence.ts +309 -0
  854. package/src/runtime/safe-tooling/browser-safe.ts +133 -0
  855. package/src/runtime/safe-tooling/external-filesystem.ts +304 -0
  856. package/src/runtime/safe-tooling/google-credential-store.ts +95 -0
  857. package/src/runtime/safe-tooling/google-oauth-broker.ts +286 -0
  858. package/src/runtime/safe-tooling/index.ts +11 -0
  859. package/src/runtime/safe-tooling/ios-development.ts +1039 -0
  860. package/src/runtime/safe-tooling/job-summary.ts +361 -0
  861. package/src/runtime/safe-tooling/plugin-summary.ts +53 -0
  862. package/src/runtime/safe-tooling/resend-oauth.ts +387 -0
  863. package/src/runtime/safe-tooling/review-artifacts.ts +133 -0
  864. package/src/runtime/safe-tooling/types.ts +103 -0
  865. package/src/runtime/safe-tooling/workspace-auth.ts +116 -0
  866. package/src/runtime/shared/bounded-child-supervisor.ts +235 -0
  867. package/src/runtime/shared/idempotent-retry.ts +128 -0
  868. package/src/runtime/shared/json-files.ts +43 -0
  869. package/src/runtime/shared/local-bridge-surface.ts +231 -0
  870. package/src/runtime/shared/managed-env.ts +154 -0
  871. package/src/runtime/shared/node-ts-loader.mjs +50 -0
  872. package/src/runtime/shared/process-environment.ts +97 -0
  873. package/src/runtime/shared/process-identity.ts +72 -0
  874. package/src/runtime/shared/process-tree.ts +227 -0
  875. package/src/runtime/shared/response-budget.ts +246 -0
  876. package/src/runtime/standalone-recovery/core.ts +2275 -0
  877. package/src/runtime/standalone-recovery/entry.ts +686 -0
  878. package/src/runtime/standalone-recovery/http-transport.ts +317 -0
  879. package/src/runtime/standalone-recovery/installer.ts +747 -0
  880. package/src/runtime/standalone-recovery/release.ts +241 -0
  881. package/src/runtime/watchdog/workflow-watchdog.ts +118 -0
  882. package/src/runtime/workflow/campaigns/cleanup.ts +263 -0
  883. package/src/runtime/workflow/campaigns/engine.ts +112 -0
  884. package/src/runtime/workflow/campaigns/normalize.ts +100 -0
  885. package/src/runtime/workflow/campaigns/review.ts +315 -0
  886. package/src/runtime/workflow/campaigns/store.ts +526 -0
  887. package/src/runtime/workflow/campaigns/supervisor.ts +112 -0
  888. package/src/runtime/workflow/campaigns/types.ts +290 -0
  889. package/src/runtime/workflow/campaigns/workspace-agent.ts +155 -0
  890. package/src/runtime/workflow/campaigns/workspace.ts +200 -0
  891. package/src/runtime/workflow/findings/store.ts +165 -0
  892. package/src/runtime/workflow/findings/types.ts +36 -0
  893. package/src/runtime/workflow/portfolio/engine.ts +20 -0
  894. package/src/runtime/workflow/portfolio/store.ts +185 -0
  895. package/src/runtime/workflow/portfolio/types.ts +29 -0
  896. package/src/runtime/workflow/schedules/engine.ts +620 -0
  897. package/src/runtime/workflow/schedules/settlement.ts +144 -0
  898. package/src/runtime/workflow/schedules/store.ts +417 -0
  899. package/src/runtime/workflow/schedules/types.ts +124 -0
  900. package/src/runtime/workspace-targets/index.ts +24 -0
  901. package/src/runtime/workspace-targets/target-grant.ts +721 -0
  902. package/src/testing/test-governance.ts +626 -0
  903. package/src/version.ts +21 -0
@@ -0,0 +1,2009 @@
1
+ import { createHash, randomUUID } from 'crypto';
2
+ import { existsSync, readFileSync } from 'fs';
3
+ import { spawnSync } from 'child_process';
4
+ import { basename, isAbsolute, relative, resolve } from 'path';
5
+ import type { McpToolDefinition, CallToolResult } from '../../../cli/mcp/tools';
6
+ import type { CompletionReceipt } from '../../../cli/controller/types';
7
+ import type { MultiRepositoryMcpToolContext } from '../../../cli/mcp/multi-repository';
8
+ import type { RepositoryRecord } from '../../../cli/repositories/types';
9
+ import {
10
+ getRepository,
11
+ listRepositories,
12
+ resolveRepositorySelection,
13
+ selectRepositoryCheckout,
14
+ setRepositoryCheckoutLifecycle,
15
+ } from '../../../cli/repositories/registry';
16
+ import { withControllerLock } from '../../../cli/repositories/locks';
17
+ import { repositoryGitCommit, repositoryGitDeleteBranch, repositoryGitFinishWorkflow, repositoryGitStatus, repositoryGitDiff } from '../../../cli/repositories/structured-git';
18
+ import { previewRepositoryCommandExecution } from '../../../cli/repositories/command-executor';
19
+ import { classifyRepositoryCommand } from '../../../cli/repositories/command-classifier';
20
+ import { globMatches } from '../../../cli/mcp/paths';
21
+ import { ensureManagedWorkspace } from '../../workflow/campaigns/workspace';
22
+ import { listControllerChecks } from '../../../cli/controller/check-runner';
23
+ import { readRepositoryAccessPolicy } from '../../control-plane/governance/access-policy';
24
+ import { appendWorkEvidence, createWorkContract, getWorkContract, recordWorkCompletionReceipt, updateWorkContract, appendVerificationRecord } from '../../control-plane/facade/work-contract-store';
25
+ import { completeRequirementFromWork } from '../../control-plane/persistence/requirement-store';
26
+ import { isTerminalWorkContractStatus, type WorkReconciliationRecord } from '../../control-plane/facade/types';
27
+ import { buildWorkContinuationSnapshot } from '../../control-plane/facade/work-continuation';
28
+ import { claimControllerSession, getControllerSession, releaseControllerSession, resumeControllerSession } from '../../control-plane/facade/controller-session-store';
29
+ import { currentControllerInstanceId, requireExecutionSession, startExecutionSession, updateExecutionSession, type ExecutionSessionContext, type SessionIdentity } from '../../control-plane/execution/session-store';
30
+ import { currentPermissionSnapshotVersion, validateWorkHandle } from '../../control-plane/execution/validation';
31
+ import { commandFingerprint, verificationInputFingerprint, workspaceValidationFingerprint, workValidationInputFingerprint } from '../../control-plane/execution/verification-evidence';
32
+ import { assertExecutionIdentity, executionIdentityForWork, executionIdentityFromCoordinates, resolveLegacyWorkContractIdentity } from '../../control-plane/execution/execution-identity';
33
+ import { withWorkPrepareRequest } from '../../control-plane/execution/work-prepare-request-store';
34
+ import { markWorkHandleFailed, newWorkId, readWorkHandle, transitionWorkHandle, writeWorkHandle, type WorkFinalizationStages, type WorkHandleState, type WorkTerminalOutcome } from '../../control-plane/execution/work-handle-store';
35
+ import { cleanupTerminalWork } from '../../control-plane/execution/work-terminal-cleanup';
36
+ import { assertResolvedAuthorization, createGoalDelegation, decideAuthorization, resolveAuthorizationRequest, type AuthorizationDecision, type AuthorizationRiskClass } from '../../control-plane/governance/authorization';
37
+ import { readControllerResult, searchControllerResult, writeControllerResult } from '../../evidence/result-store';
38
+ import { resumeExecutionJobAfterApproval } from '../../execution/jobs/store';
39
+ import { recordMcpTiming, type McpTimingTrace } from '../../diagnostics/mcp-timing';
40
+ import { commandValue, normalizeRepositoryCommand, type RepositoryCommandValue } from '../../../cli/repositories/command-normalization';
41
+ import { markRepositoryProjectionDirty } from '../../projections/invalidation';
42
+ import { executeRepositoryCommandViaProcessRuntime } from '../../execution/process-runtime/command-facade';
43
+ import { getCheckProcessHandle } from '../../execution/process-runtime/check-facade';
44
+ import { processCheckCompletionReceipt } from '../../execution/process-runtime/check-receipt';
45
+ import { claimProcessInvocation, getProcessRecord } from '../../execution/process-runtime/store';
46
+ import { runPersistedCheckViaProcessRuntime } from './persisted-check-process';
47
+ import { hasCurrentWorkValidationAuthority, markWorkValidationPending, projectWorkValidationOutcome, reconcileWorkValidation } from './work-validation-reconciler';
48
+
49
+ const MAX_INLINE_RESULT_BYTES = 64 * 1024;
50
+
51
+ function definition(name: string, description: string, properties: Record<string, unknown>, required: string[] = [], readOnlyHint = false, destructiveHint = false): McpToolDefinition {
52
+ return { name, description, inputSchema: { type: 'object', properties, ...(required.length ? { required } : {}), additionalProperties: false }, annotations: { readOnlyHint, openWorldHint: false, destructiveHint } };
53
+ }
54
+
55
+ const sessionId = { type: 'string', description: 'Controller-issued session id returned by session_start. Omit only when the MCP transport binds one.' };
56
+ const workId = { type: 'string', description: 'Controller-owned work handle id returned by work_prepare.' };
57
+ const repoId = { type: 'string', description: 'Stable repository id. Repository switching must be explicit through session_bind_repository.' };
58
+
59
+ export const executionToolDefinitions: McpToolDefinition[] = [
60
+ definition('session_start', 'Start or resume a controller-owned MCP execution session. Identity comes from the authenticated/controller-issued transport context.', {}, [], false),
61
+ definition('session_bind_repository', 'Explicitly bind the current session to one registered repository and checkout.', { session_id: sessionId, repo_id: repoId, checkout_id: { type: 'string' } }, ['repo_id'], false),
62
+ definition('work_prepare', 'Prepare or reuse one controller-owned work handle and bind it to a WorkContract, checkout, branch, and permission snapshot.', {
63
+ session_id: sessionId, repo_id: repoId, checkout_id: { type: 'string' }, work_id: workId,
64
+ objective: { type: 'string' }, goal_id: { type: 'string' }, acceptance_criteria: { type: 'array', items: { type: 'string' } }, allowed_paths: { type: 'array', items: { type: 'string' } }, checks: { type: 'array', items: { type: 'string' } },
65
+ isolation: { type: 'string', enum: ['reuse', 'new_worktree', 'auto'] }, base_ref: { type: 'string' },
66
+ expected_previous_head: { type: 'string', description: 'Explicit prior WorkHandle HEAD required for audited successor adoption of an existing work_id.' },
67
+ adopt_candidate_head: { type: 'string', description: 'Explicit current successor commit to adopt after exact identity, ownership, cleanliness, ancestry, and path-scope validation.' },
68
+ }, [], false),
69
+ definition('work_inspect', 'Collect bounded Git, WorkContract, path, check, and readiness evidence through one work handle.', { session_id: sessionId, repo_id: repoId, work_id: workId, detail: { type: 'string', enum: ['summary', 'detail'] } }, ['work_id'], true),
70
+ definition('work_execute', 'Execute approved, repository-scoped commands against a validated work handle while preserving the existing command policy and audit path.', {
71
+ session_id: sessionId, controller_id: { type: 'string', description: 'Controller identity that holds the Work lease. Defaults to the authenticated principal.' }, repo_id: repoId, work_id: workId,
72
+ command: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }] }, approval_token: { type: 'string' }, cwd: { type: 'string' }, timeout_ms: { type: 'number' }, max_output_bytes: { type: 'number' },
73
+ commands: { type: 'array', items: { type: 'object' } }, approval_request_id: { type: 'string' },
74
+ }, ['work_id'], false),
75
+ definition('work_validate', 'Run targeted checks or read-only validation commands against a work handle with full current-state validation.', {
76
+ session_id: sessionId, controller_id: { type: 'string', description: 'Controller identity that holds the Work lease. Defaults to the authenticated principal.' }, repo_id: repoId, work_id: workId, check_ids: { type: 'array', items: { type: 'string' } }, commands: { type: 'array', items: { type: 'object' } },
77
+ }, ['work_id'], false),
78
+ definition('work_finalize', 'Idempotently validate, commit, merge, clean a managed worktree, and complete the existing WorkContract in independently recorded stages.', {
79
+ session_id: sessionId, controller_id: { type: 'string', description: 'Controller identity that holds the Work lease. Defaults to the authenticated principal.' }, repo_id: repoId, work_id: workId, commit: { type: 'boolean' }, message: { type: 'string' }, merge: { type: 'boolean' }, target_branch: { type: 'string' }, delete_branch: { type: 'boolean' }, cleanup: { type: 'boolean' }, no_ff: { type: 'boolean' }, approval_request_id: { type: 'string' },
80
+ completion_outcome: { type: 'string', enum: ['completed_changed', 'completed_no_change'] }, no_change_evidence: { type: 'string', description: 'Objective-specific proof that the requested state already holds; required for completed_no_change.' },
81
+ }, ['work_id'], false, true),
82
+ definition('approval_resolve', 'Resolve a controller approval request from the current conversation; GUI approval is optional and not required for continuation.', { session_id: sessionId, repo_id: repoId, work_id: workId, approval_request_id: { type: 'string' }, confirm_authorization: { type: 'boolean' } }, ['approval_request_id', 'confirm_authorization'], false),
83
+ definition('result_read', 'Read a session-scoped result reference with bounded pagination.', { session_id: sessionId, result_ref: { type: 'string' }, work_id: workId, cursor: { type: 'number' }, limit: { type: 'number' } }, ['result_ref'], true),
84
+ definition('result_search', 'Search a session-scoped result reference without returning the full payload.', { session_id: sessionId, result_ref: { type: 'string' }, work_id: workId, query: { type: 'string' }, limit: { type: 'number' } }, ['result_ref', 'query'], true),
85
+ ];
86
+
87
+ const executionToolNames = new Set(executionToolDefinitions.map((tool) => tool.name));
88
+
89
+ /**
90
+ * Work mutation tool names remain grouped for compatibility with older Worker
91
+ * entrypoints. On the public MCP surface they execute directly through
92
+ * WorkContract + Process Runtime ownership rather than durable ExecutionJobs.
93
+ */
94
+ const DURABLE_WORK_OPERATION_NAMES = new Set([
95
+ 'work_execute',
96
+ 'work_validate',
97
+ 'work_finalize',
98
+ ]);
99
+
100
+ export function isDurableWorkOperation(name: string): boolean {
101
+ return DURABLE_WORK_OPERATION_NAMES.has(name);
102
+ }
103
+
104
+ function result(value: Record<string, unknown>, isError = false): CallToolResult {
105
+ return { content: [{ type: 'text', text: JSON.stringify(value, null, 2) }], structuredContent: value, ...(isError ? { isError: true } : {}) };
106
+ }
107
+
108
+ function failure(error: unknown): CallToolResult {
109
+ const message = error instanceof Error ? error.message : String(error);
110
+ const code = message.includes(':') ? message.slice(0, message.indexOf(':')) : 'EXECUTION_TOOL_FAILED';
111
+ return result({ error: { code, message } }, true);
112
+ }
113
+
114
+ function principalFor(ctx: MultiRepositoryMcpToolContext): string {
115
+ return ctx.principalId?.trim() || `controller-issued:${ctx.controllerInstanceId ?? currentControllerInstanceId()}`;
116
+ }
117
+
118
+ function identityFor(ctx: MultiRepositoryMcpToolContext, args: Record<string, unknown>): SessionIdentity {
119
+ return {
120
+ sessionId: typeof args.session_id === 'string' && args.session_id.trim() ? args.session_id.trim() : ctx.sessionId,
121
+ principalId: principalFor(ctx),
122
+ controllerInstanceId: ctx.controllerInstanceId ?? currentControllerInstanceId(),
123
+ };
124
+ }
125
+
126
+ function startOrResumeSession(ctx: MultiRepositoryMcpToolContext): ExecutionSessionContext {
127
+ const permissionVersion = ctx.explicitRepository ? currentPermissionSnapshotVersion(ctx.controllerHome, ctx.explicitRepository.repoId) : 0;
128
+ return startExecutionSession(ctx.controllerHome, {
129
+ sessionId: ctx.sessionId,
130
+ principalId: principalFor(ctx),
131
+ controllerInstanceId: ctx.controllerInstanceId ?? currentControllerInstanceId(),
132
+ permissionSnapshotVersion: permissionVersion,
133
+ capabilitySnapshotVersion: 1,
134
+ });
135
+ }
136
+
137
+ function requireSession(ctx: MultiRepositoryMcpToolContext, args: Record<string, unknown>): ExecutionSessionContext {
138
+ return requireExecutionSession(ctx.controllerHome, identityFor(ctx, args));
139
+ }
140
+
141
+ function requireExplicitRepoId(args: Record<string, unknown>): string {
142
+ const value = typeof args.repo_id === 'string' ? args.repo_id.trim() : '';
143
+ if (!value) throw new Error('REPOSITORY_ID_REQUIRED: repository selection must be explicit for session binding');
144
+ return value;
145
+ }
146
+
147
+ function selectedRepository(ctx: MultiRepositoryMcpToolContext, session: ExecutionSessionContext, args: Record<string, unknown>, allowSession = true) {
148
+ const requested = typeof args.repo_id === 'string' && args.repo_id.trim() ? args.repo_id.trim() : undefined;
149
+ const selectedRepoId = requested ?? (allowSession ? session.activeRepositoryId : undefined);
150
+ if (!selectedRepoId) throw new Error('SESSION_REPOSITORY_REQUIRED: bind a repository before using this work tool');
151
+ if (session.activeRepositoryId && requested && session.activeRepositoryId !== requested) {
152
+ throw new Error('SESSION_REPOSITORY_MISMATCH: call session_bind_repository before switching repositories');
153
+ }
154
+ return resolveRepositorySelection({ repoId: selectedRepoId, checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : session.activeCheckoutId, controllerHome: ctx.controllerHome, allowSoleRepository: false });
155
+ }
156
+
157
+ function compactHandle(handle: WorkHandleState): Record<string, unknown> {
158
+ return {
159
+ workId: handle.workId, sessionId: handle.sessionId, repoId: handle.repositoryId, checkoutId: handle.checkoutId,
160
+ worktreePath: handle.worktreePath, branch: handle.branch, sourceCheckoutId: handle.sourceCheckoutId, goalId: handle.goalId, delegationVersion: handle.delegationVersion,
161
+ workContractId: handle.workContractId, baseCommit: handle.baseCommit, expectedHead: handle.expectedHead,
162
+ permissionSnapshotVersion: handle.permissionSnapshotVersion, state: handle.state, managedWorktree: handle.managedWorktree,
163
+ createdAt: handle.createdAt, updatedAt: handle.updatedAt, finalization: handle.finalization,
164
+ ...(handle.failureReason ? { failureReason: handle.failureReason } : {}),
165
+ ...(handle.cleanupResponsibility ? { cleanupResponsibility: handle.cleanupResponsibility } : {}),
166
+ ...(handle.cleanupReceipt ? { cleanupReceipt: handle.cleanupReceipt } : {}),
167
+ };
168
+ }
169
+
170
+ function initialStage(): WorkFinalizationStages {
171
+ return { validation: 'pending', commit: 'pending', merge: 'pending', branchCleanup: 'pending', worktreeCleanup: 'pending' };
172
+ }
173
+
174
+ function gitHead(root: string): string | undefined {
175
+ const output = spawnSync('git', ['-C', root, 'rev-parse', '--verify', 'HEAD'], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000 });
176
+ return output.status === 0 && typeof output.stdout === 'string' ? output.stdout.trim() : undefined;
177
+ }
178
+
179
+ function gitCommit(root: string, revision: string, label: string): string {
180
+ const output = spawnSync('git', ['-C', root, 'rev-parse', '--verify', `${revision}^{commit}`], {
181
+ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
182
+ });
183
+ if (output.status !== 0 || output.error || typeof output.stdout !== 'string' || !output.stdout.trim()) {
184
+ throw new Error(`WORK_HEAD_ADOPTION_${label}_INVALID: ${revision}`);
185
+ }
186
+ return output.stdout.trim();
187
+ }
188
+
189
+ function gitChangedPaths(root: string, previousHead: string, candidateHead: string): string[] {
190
+ const output = spawnSync('git', ['-C', root, 'diff', '--name-only', '-z', previousHead, candidateHead], {
191
+ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000, maxBuffer: 8 * 1024 * 1024,
192
+ });
193
+ if (output.status !== 0 || output.error || typeof output.stdout !== 'string') {
194
+ throw new Error('WORK_HEAD_ADOPTION_CHANGED_PATHS_UNAVAILABLE');
195
+ }
196
+ return [...new Set(output.stdout.split('\0').filter(Boolean))].sort((left, right) => left.localeCompare(right));
197
+ }
198
+
199
+ function normalizedRequiredString(args: Record<string, unknown>, key: string): string | undefined {
200
+ return typeof args[key] === 'string' && args[key].trim() ? args[key].trim() : undefined;
201
+ }
202
+
203
+ function selectWorkFinalizationTarget(
204
+ repository: ReturnType<typeof getRepository>,
205
+ handle: Pick<WorkHandleState, 'sourceCheckoutId' | 'checkoutId'>,
206
+ ) {
207
+ const preferredCheckoutId = handle.sourceCheckoutId?.trim();
208
+ if (preferredCheckoutId) {
209
+ try {
210
+ return selectRepositoryCheckout(repository, preferredCheckoutId, { allowArchived: true });
211
+ } catch (error) {
212
+ const message = error instanceof Error ? error.message : String(error);
213
+ // Legacy Work can outlive the registry entry for the source checkout after
214
+ // an earlier partial cleanup. Git refs/worktree administration are
215
+ // repository-common operations, so fall back only for a positively
216
+ // identified missing/removed source checkout; all other errors fail closed.
217
+ if (!message.startsWith('CHECKOUT_NOT_ACTIVE:') && !message.startsWith('checkout not found for ')) throw error;
218
+ }
219
+ }
220
+ return repository;
221
+ }
222
+
223
+ function workReturnCheckoutId(
224
+ ctx: MultiRepositoryMcpToolContext,
225
+ handle: Pick<WorkHandleState, 'repositoryId' | 'sourceCheckoutId' | 'checkoutId'>,
226
+ fallbackCheckoutId?: string,
227
+ ): string | undefined {
228
+ const repository = getRepository(handle.repositoryId, ctx.controllerHome, { includeRemoved: true });
229
+ const target = selectWorkFinalizationTarget(repository, handle);
230
+ return target.activeCheckoutId ?? repository.activeCheckoutId ?? fallbackCheckoutId;
231
+ }
232
+
233
+ function completionReceiptForFinalizedWork(
234
+ ctx: MultiRepositoryMcpToolContext,
235
+ handle: WorkHandleState,
236
+ contract: ReturnType<typeof contractFor>,
237
+ args: Record<string, unknown>,
238
+ ): CompletionReceipt {
239
+ const repository = getRepository(handle.repositoryId, ctx.controllerHome, { includeRemoved: true });
240
+ const target = selectWorkFinalizationTarget(repository, handle);
241
+ const targetBranch = typeof args.target_branch === 'string' && args.target_branch.trim()
242
+ ? args.target_branch.trim()
243
+ : repository.defaultBranch || 'main';
244
+ const targetRevision = gitHead(target.canonicalRoot);
245
+ if (!targetRevision) throw new Error('WORK_COMPLETION_RECEIPT_TARGET_REQUIRED: target HEAD is unavailable');
246
+ const reachable = spawnSync('git', ['-C', target.canonicalRoot, 'merge-base', '--is-ancestor', targetRevision, targetBranch], {
247
+ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
248
+ }).status === 0;
249
+ if (!reachable) throw new Error(`WORK_COMPLETION_RECEIPT_DELIVERY_NOT_PROVEN: ${targetRevision} is not reachable from ${targetBranch}`);
250
+ const changedPaths = handle.baseCommit
251
+ ? Array.from(new Set(String(spawnSync('git', ['-C', target.canonicalRoot, 'diff', '--name-only', `${handle.baseCommit}..${targetRevision}`], {
252
+ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
253
+ }).stdout ?? '').split('\n').map((entry) => entry.trim()).filter(Boolean))).sort((left, right) => left.localeCompare(right))
254
+ : [];
255
+ const recordedAt = new Date().toISOString();
256
+ const noChange = args.completion_outcome === 'completed_no_change';
257
+ const warnings = [
258
+ ...(handle.finalization.branchCleanup === 'skipped' ? [{ code: 'cleanup_retained_by_request' as const, message: 'Branch cleanup was skipped by the finalization request.', resourceKind: 'branch' as const, resourceId: handle.branch, recordedAt }] : []),
259
+ ...(handle.finalization.worktreeCleanup === 'skipped' && handle.managedWorktree ? [{ code: 'cleanup_retained_by_request' as const, message: 'Managed worktree cleanup was skipped by the finalization request.', resourceKind: 'worktree' as const, resourceId: handle.worktreePath, recordedAt }] : []),
260
+ ];
261
+ return {
262
+ schemaVersion: 1,
263
+ receiptId: `REC-controller_work-${createHash('sha256').update(`${handle.workId}\0${targetRevision}`).digest('hex').slice(0, 16)}`,
264
+ source: 'controller_work',
265
+ issueId: contract?.issueId ?? 'work',
266
+ taskId: contract?.taskId ?? handle.workId,
267
+ workId: handle.workId,
268
+ targetBranch,
269
+ targetRevision,
270
+ sourceRevision: handle.baseCommit,
271
+ baseRevision: handle.baseCommit,
272
+ changedPaths,
273
+ delivery: {
274
+ kind: noChange ? 'no_change' : 'commit',
275
+ status: 'integrated',
276
+ strategy: noChange ? 'no_change' : handle.finalization.commit === 'done' ? 'edit_session_commit' : 'already_integrated',
277
+ reachable: true,
278
+ recordedAt,
279
+ },
280
+ cleanup: {
281
+ status: warnings.length > 0 ? 'maintenance_warning' : 'complete',
282
+ warnings,
283
+ blockers: [],
284
+ recordedAt,
285
+ },
286
+ verifiedAt: recordedAt,
287
+ recordedAt,
288
+ };
289
+ }
290
+
291
+ function boundedStringArray(value: unknown, limit: number): string[] {
292
+ return Array.isArray(value) ? value.map(String).slice(0, limit) : [];
293
+ }
294
+
295
+ function workPrepareFingerprint(input: {
296
+ repoId: string;
297
+ requestedCheckoutId?: string;
298
+ isolation: 'reuse' | 'new_worktree' | 'auto';
299
+ objective: string;
300
+ goalId?: string;
301
+ acceptanceCriteria: string[];
302
+ allowedPaths: string[];
303
+ checks: string[];
304
+ baseRef?: string;
305
+ }): string {
306
+ return createHash('sha256').update(JSON.stringify({ schemaVersion: 1, operation: 'work_prepare', ...input })).digest('hex');
307
+ }
308
+
309
+ function makeBoundedResult(ctx: MultiRepositoryMcpToolContext, session: ExecutionSessionContext, repoId: string, workIdValue: string | undefined, kind: 'inspection' | 'command' | 'validation' | 'finalization' | 'generic', value: Record<string, unknown>): Record<string, unknown> {
310
+ const serialized = JSON.stringify(value);
311
+ if (Buffer.byteLength(serialized, 'utf8') <= MAX_INLINE_RESULT_BYTES) return value;
312
+ const started = performance.now();
313
+ const stored = writeControllerResult({ controllerHome: ctx.controllerHome, repoId, sessionId: session.sessionId, principalId: session.principalId, workId: workIdValue, kind, value });
314
+ return {
315
+ summary: { itemCount: Array.isArray(value.items) ? value.items.length : undefined, truncated: true, warnings: ['Full result is available through the secure result reference.'] },
316
+ items: Array.isArray(value.items) ? value.items.slice(0, 25) : { preview: serialized.slice(0, 16_384) },
317
+ resultRef: stored.resultRef,
318
+ resultId: stored.resultId,
319
+ byteLength: stored.byteLength,
320
+ _resultPersistenceMs: Math.round((performance.now() - started) * 100) / 100,
321
+ };
322
+ }
323
+
324
+ function contractFor(ctx: MultiRepositoryMcpToolContext, handle: WorkHandleState) {
325
+ return handle.workContractId ? getWorkContract({ controllerHome: ctx.controllerHome, repoId: handle.repositoryId }, handle.workContractId) : undefined;
326
+ }
327
+
328
+ function findWorkHandle(
329
+ ctx: MultiRepositoryMcpToolContext,
330
+ session: ExecutionSessionContext,
331
+ args: Record<string, unknown>,
332
+ ): WorkHandleState {
333
+ const requested = typeof args.work_id === 'string' ? args.work_id.trim() : '';
334
+ const workIdValue = requested || session.activeWorkId || '';
335
+ if (!workIdValue) throw new Error('WORK_ID_REQUIRED: provide work_id or call work_prepare first');
336
+ const requestedRepoId = typeof args.repo_id === 'string' && args.repo_id.trim() ? args.repo_id.trim() : undefined;
337
+ const requestedCheckoutId = typeof args.checkout_id === 'string' && args.checkout_id.trim()
338
+ ? args.checkout_id.trim()
339
+ : undefined;
340
+ const repoCandidates = [
341
+ requestedRepoId,
342
+ session.activeRepositoryId,
343
+ ].filter((value, index, values): value is string => Boolean(value) && values.indexOf(value) === index);
344
+ for (const repositoryId of repoCandidates) {
345
+ const handle = readWorkHandle(ctx.controllerHome, repositoryId, workIdValue);
346
+ if (!handle) continue;
347
+ if (requestedCheckoutId && handle.checkoutId !== requestedCheckoutId) {
348
+ throw new Error(
349
+ `WORK_HANDLE_CHECKOUT_DRIFT: work ${workIdValue} is bound to checkout ${handle.checkoutId}, not ${requestedCheckoutId}`,
350
+ );
351
+ }
352
+ return handle;
353
+ }
354
+ const matches = listRepositories(ctx.controllerHome, { includeRemoved: true })
355
+ .map((repository) => readWorkHandle(ctx.controllerHome, repository.repoId, workIdValue))
356
+ .filter((handle): handle is WorkHandleState => Boolean(handle));
357
+ // Legacy incomplete identity: only a unique exact WorkHandle match may execute.
358
+ return resolveLegacyWorkContractIdentity({
359
+ workId: workIdValue,
360
+ repoId: requestedRepoId,
361
+ checkoutId: requestedCheckoutId,
362
+ candidates: matches,
363
+ });
364
+ }
365
+
366
+ function workForSession(
367
+ ctx: MultiRepositoryMcpToolContext,
368
+ session: ExecutionSessionContext,
369
+ args: Record<string, unknown>,
370
+ options: { reconcileValidation?: boolean } = {},
371
+ ): WorkHandleState {
372
+ let handle = findWorkHandle(ctx, session, args);
373
+ if (handle.principalId !== session.principalId) throw new Error('WORK_HANDLE_PRINCIPAL_MISMATCH: work handle belongs to another principal');
374
+ if (options.reconcileValidation !== false) handle = reconcileWorkValidation(ctx.controllerHome, handle).handle;
375
+ if (
376
+ session.activeRepositoryId !== handle.repositoryId
377
+ || session.activeCheckoutId !== handle.checkoutId
378
+ || session.activeWorkId !== handle.workId
379
+ ) {
380
+ updateExecutionSession(ctx.controllerHome, identityFor(ctx, args), {
381
+ activeRepositoryId: handle.repositoryId,
382
+ activeCheckoutId: handle.checkoutId,
383
+ activeWorkId: handle.workId,
384
+ permissionSnapshotVersion: handle.permissionSnapshotVersion,
385
+ lastValidatedAt: new Date().toISOString(),
386
+ });
387
+ }
388
+ return handle;
389
+ }
390
+
391
+ function assertWorkControllerOwnership(
392
+ ctx: MultiRepositoryMcpToolContext,
393
+ session: ExecutionSessionContext,
394
+ handle: WorkHandleState,
395
+ args: Record<string, unknown>,
396
+ ): void {
397
+ const workIdValue = handle.workContractId ?? handle.workId;
398
+ const owner = getControllerSession({ controllerHome: ctx.controllerHome, repoId: handle.repositoryId }, workIdValue);
399
+ const controllerId = typeof args.controller_id === 'string' && args.controller_id.trim()
400
+ ? args.controller_id.trim()
401
+ : session.principalId;
402
+ if (controllerId !== session.principalId) {
403
+ throw new Error('WORK_CONTROLLER_IDENTITY_MISMATCH: controller_id must match the authenticated principal');
404
+ }
405
+ const resumed = resumeControllerSession({ controllerHome: ctx.controllerHome, repoId: handle.repositoryId }, {
406
+ workId: workIdValue,
407
+ controllerId,
408
+ controllerType: owner?.controllerType ?? 'chatgpt',
409
+ sessionId: session.sessionId,
410
+ principalId: session.principalId,
411
+ controllerInstanceId: session.controllerInstanceId,
412
+ leaseMs: 3_600_000,
413
+ });
414
+ if (resumed.controllerId !== controllerId || resumed.sessionId !== session.sessionId) {
415
+ throw new Error(`WORK_CONTROLLER_OWNER_MISMATCH: ${workIdValue} is owned by ${resumed.controllerId}/${resumed.sessionId}`);
416
+ }
417
+ }
418
+
419
+ function claimPreparedWorkOwnership(
420
+ ctx: MultiRepositoryMcpToolContext,
421
+ session: ExecutionSessionContext,
422
+ handle: WorkHandleState,
423
+ args: Record<string, unknown>,
424
+ ): void {
425
+ const workIdValue = handle.workContractId ?? handle.workId;
426
+ const controllerId = typeof args.controller_id === 'string' && args.controller_id.trim()
427
+ ? args.controller_id.trim()
428
+ : session.principalId;
429
+ claimControllerSession({ controllerHome: ctx.controllerHome, repoId: handle.repositoryId }, {
430
+ workId: workIdValue,
431
+ controllerId,
432
+ controllerType: 'chatgpt',
433
+ sessionId: session.sessionId,
434
+ principalId: session.principalId,
435
+ controllerInstanceId: session.controllerInstanceId,
436
+ leaseMs: 3_600_000,
437
+ });
438
+ }
439
+
440
+ function claimHeadAdoptionOwnership(
441
+ ctx: MultiRepositoryMcpToolContext,
442
+ session: ExecutionSessionContext,
443
+ handle: WorkHandleState,
444
+ args: Record<string, unknown>,
445
+ ): void {
446
+ const workIdValue = handle.workContractId ?? handle.workId;
447
+ const controllerId = normalizedRequiredString(args, 'controller_id') ?? session.principalId;
448
+ if (controllerId !== session.principalId) {
449
+ throw new Error('WORK_CONTROLLER_IDENTITY_MISMATCH: controller_id must match the authenticated principal');
450
+ }
451
+ const options = { controllerHome: ctx.controllerHome, repoId: handle.repositoryId };
452
+ const current = getControllerSession(options, workIdValue);
453
+ const input = {
454
+ workId: workIdValue,
455
+ controllerId,
456
+ controllerType: current?.controllerType ?? 'chatgpt' as const,
457
+ sessionId: session.sessionId,
458
+ principalId: session.principalId,
459
+ controllerInstanceId: session.controllerInstanceId,
460
+ expectedClaimGeneration: current?.claimGeneration ?? 0,
461
+ leaseMs: 3_600_000,
462
+ };
463
+ const claimed = current
464
+ ? resumeControllerSession(options, input)
465
+ : claimControllerSession(options, input);
466
+ if (
467
+ claimed.controllerId !== controllerId
468
+ || claimed.sessionId !== session.sessionId
469
+ || claimed.principalId !== session.principalId
470
+ || claimed.controllerInstanceId !== session.controllerInstanceId
471
+ || (claimed.claimGeneration ?? 0) < 1
472
+ ) {
473
+ throw new Error('WORK_HEAD_ADOPTION_OWNERSHIP_FENCE_MISMATCH');
474
+ }
475
+ }
476
+
477
+ function adoptExistingWorkHead(
478
+ ctx: MultiRepositoryMcpToolContext,
479
+ session: ExecutionSessionContext,
480
+ repository: RepositoryRecord,
481
+ handle: WorkHandleState,
482
+ args: Record<string, unknown>,
483
+ ): Record<string, unknown> | undefined {
484
+ const previousInput = normalizedRequiredString(args, 'expected_previous_head');
485
+ const candidateInput = normalizedRequiredString(args, 'adopt_candidate_head');
486
+ if (!previousInput && !candidateInput) return undefined;
487
+ if (!previousInput || !candidateInput) {
488
+ throw new Error('WORK_HEAD_ADOPTION_ARGUMENTS_REQUIRED: expected_previous_head and adopt_candidate_head must be provided together');
489
+ }
490
+ const requestedCheckoutId = normalizedRequiredString(args, 'checkout_id');
491
+ if (!requestedCheckoutId) throw new Error('WORK_HEAD_ADOPTION_CHECKOUT_REQUIRED: checkout_id must be explicit');
492
+ if (repository.repoId !== handle.repositoryId) throw new Error('WORK_HEAD_ADOPTION_REPOSITORY_MISMATCH');
493
+ if (requestedCheckoutId !== handle.checkoutId || repository.activeCheckoutId !== handle.checkoutId) {
494
+ throw new Error(`WORK_HEAD_ADOPTION_CHECKOUT_MISMATCH: expected ${handle.checkoutId}, found ${requestedCheckoutId}`);
495
+ }
496
+ if (!handle.managedWorktree) throw new Error('WORK_HEAD_ADOPTION_MANAGED_WORKTREE_REQUIRED');
497
+ if (handle.principalId !== session.principalId) throw new Error('WORK_HANDLE_PRINCIPAL_MISMATCH: work handle belongs to another principal');
498
+ if (handle.state !== 'prepared' && handle.state !== 'editing') {
499
+ throw new Error(`WORK_HEAD_ADOPTION_STATE_INVALID: ${handle.state}`);
500
+ }
501
+ const finalizationStages = [
502
+ handle.finalization.validation,
503
+ handle.finalization.commit,
504
+ handle.finalization.merge,
505
+ handle.finalization.branchCleanup,
506
+ handle.finalization.worktreeCleanup,
507
+ ];
508
+ if (finalizationStages.some((stage) => stage !== 'pending')) {
509
+ throw new Error('WORK_HEAD_ADOPTION_FINALIZATION_ALREADY_STARTED');
510
+ }
511
+
512
+ const registered = getRepository(handle.repositoryId, ctx.controllerHome, { includeRemoved: true });
513
+ const registeredCheckout = registered.checkouts.find((entry) => entry.checkoutId === handle.checkoutId);
514
+ if (!registeredCheckout || registeredCheckout.lifecycle !== 'active' || registeredCheckout.worktree !== true) {
515
+ throw new Error('WORK_HEAD_ADOPTION_CHECKOUT_NOT_ACTIVE_MANAGED');
516
+ }
517
+ const worktreeRepository = selectRepositoryCheckout(registered, handle.checkoutId);
518
+ const guarded = assertExecutionIdentity({
519
+ controllerHome: ctx.controllerHome,
520
+ identity: executionIdentityFromCoordinates({
521
+ repositoryId: handle.repositoryId,
522
+ checkoutId: handle.checkoutId,
523
+ canonicalRoot: handle.worktreePath,
524
+ workId: handle.workId,
525
+ worktreePath: handle.worktreePath,
526
+ branch: handle.branch,
527
+ }),
528
+ cwd: handle.worktreePath,
529
+ requestedRepoId: repository.repoId,
530
+ requestedCheckoutId,
531
+ });
532
+ const status = repositoryGitStatus(worktreeRepository);
533
+ if (!status.clean) throw new Error('WORK_HEAD_ADOPTION_WORKTREE_DIRTY');
534
+
535
+ const previousHead = gitCommit(handle.worktreePath, previousInput, 'PREVIOUS_HEAD');
536
+ const candidateHead = gitCommit(handle.worktreePath, candidateInput, 'CANDIDATE_HEAD');
537
+ const authoritativePrevious = handle.expectedHead ? gitCommit(handle.worktreePath, handle.expectedHead, 'AUTHORITATIVE_PREVIOUS_HEAD') : undefined;
538
+ if (!authoritativePrevious || authoritativePrevious !== previousHead) {
539
+ throw new Error(`WORK_HEAD_ADOPTION_PREVIOUS_HEAD_MISMATCH: expected ${authoritativePrevious ?? 'missing'}, found ${previousHead}`);
540
+ }
541
+ if (guarded.currentHead !== candidateHead || status.head !== candidateHead) {
542
+ throw new Error(`WORK_HEAD_ADOPTION_CANDIDATE_NOT_CURRENT: expected current HEAD ${candidateHead}, found ${guarded.currentHead ?? status.head ?? 'missing'}`);
543
+ }
544
+ if (previousHead === candidateHead) throw new Error('WORK_HEAD_ADOPTION_SUCCESSOR_REQUIRED');
545
+ const ancestry = spawnSync('git', ['-C', handle.worktreePath, 'merge-base', '--is-ancestor', previousHead, candidateHead], {
546
+ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
547
+ });
548
+ if (ancestry.status !== 0 || ancestry.error) throw new Error('WORK_HEAD_ADOPTION_NOT_DESCENDANT');
549
+
550
+ const contract = contractFor(ctx, handle);
551
+ if (!contract || contract.repoId !== handle.repositoryId) throw new Error('WORK_HEAD_ADOPTION_CONTRACT_MISSING');
552
+ if (isTerminalWorkContractStatus(contract.status) || contract.completionReceipt) {
553
+ throw new Error('WORK_HEAD_ADOPTION_CONTRACT_TERMINAL');
554
+ }
555
+ const changedPaths = gitChangedPaths(handle.worktreePath, previousHead, candidateHead);
556
+ for (const path of changedPaths) {
557
+ if (contract.forbiddenPaths.some((pattern) => globMatches(pattern, path))) {
558
+ throw new Error(`WORK_HEAD_ADOPTION_FORBIDDEN_PATH: ${path}`);
559
+ }
560
+ if (contract.allowedPaths.length === 0 || !contract.allowedPaths.some((pattern) => globMatches(pattern, path))) {
561
+ throw new Error(`WORK_HEAD_ADOPTION_PATH_OUT_OF_SCOPE: ${path}`);
562
+ }
563
+ }
564
+
565
+ claimHeadAdoptionOwnership(ctx, session, handle, args);
566
+
567
+ const reviewedAt = new Date().toISOString();
568
+ const reconciliationId = `RECNC-${createHash('sha256').update([
569
+ handle.repositoryId, handle.workId, previousHead, candidateHead, handle.checkoutId, handle.branch,
570
+ ].join('\0')).digest('hex').slice(0, 16)}`;
571
+ const reconciliation: WorkReconciliationRecord = {
572
+ schemaVersion: 1,
573
+ reconciliationId,
574
+ originalExpectedRevision: previousHead,
575
+ observedTargetRevision: candidateHead,
576
+ baseRevision: gitCommit(handle.worktreePath, handle.baseCommit ?? previousHead, 'BASE_HEAD'),
577
+ targetBranch: handle.branch,
578
+ reachable: true,
579
+ method: 'exact_commit',
580
+ comparedPaths: changedPaths,
581
+ reviewer: session.principalId.slice(0, 200),
582
+ reviewedAt,
583
+ unrecoverableStages: [],
584
+ cleanupOwnershipProof: `No cleanup was performed; managed checkout ${handle.checkoutId} remains owned by Work finalizer.`,
585
+ rationale: 'Adopted an exact clean successor commit after repository, checkout, worktree, branch, controller ownership, ancestry, and WorkContract path-scope verification. This reconciliation is not completion evidence.',
586
+ outcome: 'accepted_equivalence',
587
+ };
588
+
589
+ const adopted = transitionWorkHandle(ctx.controllerHome, handle, 'editing', {
590
+ expectedHead: candidateHead,
591
+ failureReason: undefined,
592
+ finalization: initialStage(),
593
+ validationRun: undefined,
594
+ validatedInputFingerprint: undefined,
595
+ });
596
+ try {
597
+ updateWorkContract({ controllerHome: ctx.controllerHome, repoId: handle.repositoryId }, contract.workId, {
598
+ evidenceState: contract.checkRefs.length === 0
599
+ ? contract.evidenceState
600
+ : contract.evidenceState === 'valid' || contract.evidenceState === 'stale'
601
+ ? 'stale'
602
+ : 'partial',
603
+ reconciliations: [reconciliation, ...contract.reconciliations.filter((entry) => entry.reconciliationId !== reconciliationId)],
604
+ });
605
+ appendWorkEvidence({ controllerHome: ctx.controllerHome, repoId: handle.repositoryId }, contract.workId, {
606
+ title: 'audited WorkHandle successor HEAD adoption',
607
+ summary: `${previousHead} -> ${candidateHead}; ${changedPaths.length} changed path(s) remained within the WorkContract allow-list. Historical validation and completion receipts were not rewritten.`,
608
+ detailLevel: 'summary',
609
+ });
610
+ } catch (error) {
611
+ try {
612
+ writeWorkHandle(ctx.controllerHome, { ...handle, recordRevision: adopted.recordRevision });
613
+ } catch (rollbackError) {
614
+ throw new Error(`WORK_HEAD_ADOPTION_AUDIT_WRITE_FAILED_AND_ROLLBACK_FAILED: ${String(error)}; ${String(rollbackError)}`);
615
+ }
616
+ throw error;
617
+ }
618
+ const nextSession = updateExecutionSession(ctx.controllerHome, identityFor(ctx, args), {
619
+ activeRepositoryId: handle.repositoryId,
620
+ activeCheckoutId: handle.checkoutId,
621
+ activeWorkId: handle.workId,
622
+ permissionSnapshotVersion: handle.permissionSnapshotVersion,
623
+ lastValidatedAt: reviewedAt,
624
+ });
625
+ return {
626
+ session: nextSession,
627
+ work: compactHandle(adopted),
628
+ reused: true,
629
+ adopted: true,
630
+ adoption: { previousHead, candidateHead, changedPaths, reconciliationId },
631
+ controllerClaimed: true,
632
+ };
633
+ }
634
+
635
+ function releasePreparedWorkOwnership(
636
+ ctx: MultiRepositoryMcpToolContext,
637
+ handle: WorkHandleState,
638
+ ): 'released' | 'already_released' {
639
+ const workIdValue = handle.workContractId ?? handle.workId;
640
+ const current = getControllerSession(
641
+ { controllerHome: ctx.controllerHome, repoId: handle.repositoryId },
642
+ workIdValue,
643
+ );
644
+ if (!current) return 'already_released';
645
+ releaseControllerSession(
646
+ { controllerHome: ctx.controllerHome, repoId: handle.repositoryId },
647
+ workIdValue,
648
+ current.controllerId,
649
+ );
650
+ return 'released';
651
+ }
652
+
653
+ function terminalCleanupOutcome(
654
+ ctx: MultiRepositoryMcpToolContext,
655
+ handle: WorkHandleState,
656
+ ): WorkTerminalOutcome | undefined {
657
+ if (handle.cleanupReceipt) return handle.cleanupReceipt.terminalOutcome;
658
+ const contract = contractFor(ctx, handle);
659
+ if (contract?.status === 'cancelled') return 'cancelled';
660
+ const reason = `${handle.failureReason ?? ''} ${handle.finalization.lastError ?? ''}`.toLowerCase();
661
+ if (contract?.status === 'blocked' && reason.includes('terminal')) return 'blocked_terminal';
662
+ if (contract?.status === 'failed' || handle.state === 'failed' || handle.state === 'failed_terminal_cleanup') {
663
+ if (reason.includes('infrastructure') || reason.includes('timed out') || reason.includes('unavailable')) {
664
+ return 'infrastructure_failed';
665
+ }
666
+ if (handle.finalization.validation === 'failed') return 'validation_failed';
667
+ return 'failed';
668
+ }
669
+ return undefined;
670
+ }
671
+
672
+ async function reconcileTerminalCleanup(
673
+ ctx: MultiRepositoryMcpToolContext,
674
+ session: ExecutionSessionContext,
675
+ handle: WorkHandleState,
676
+ args: Record<string, unknown>,
677
+ outcome: WorkTerminalOutcome,
678
+ ): Promise<Record<string, unknown>> {
679
+ const wasComplete = handle.cleanupReceipt?.complete === true;
680
+ const cleaned = await cleanupTerminalWork({
681
+ controllerHome: ctx.controllerHome,
682
+ handle,
683
+ targetBranch: typeof args.target_branch === 'string' ? args.target_branch : undefined,
684
+ deleteBranch: args.delete_branch !== false,
685
+ terminalOutcome: outcome,
686
+ failureReason: handle.failureReason ?? handle.finalization.lastError,
687
+ });
688
+ const ownership = releasePreparedWorkOwnership(ctx, cleaned.handle);
689
+ cleaned.receipt.ownership.controllerLease = ownership;
690
+ const persisted = writeWorkHandle(ctx.controllerHome, {
691
+ ...cleaned.handle,
692
+ cleanupReceipt: cleaned.receipt,
693
+ });
694
+ updateExecutionSession(ctx.controllerHome, identityFor(ctx, args), {
695
+ activeWorkId: undefined,
696
+ activeCheckoutId: workReturnCheckoutId(ctx, persisted, session.activeCheckoutId),
697
+ });
698
+ if (cleaned.receipt.complete && !wasComplete) {
699
+ appendWorkEvidence(
700
+ { controllerHome: ctx.controllerHome, repoId: persisted.repositoryId },
701
+ persisted.workContractId ?? persisted.workId,
702
+ {
703
+ title: 'terminal cleanup receipt',
704
+ summary: `Cleanup ${cleaned.receipt.receiptId} completed for ${outcome}; Work outcome was preserved and no completion receipt was fabricated.`,
705
+ detailLevel: 'summary',
706
+ },
707
+ );
708
+ }
709
+ return {
710
+ work: compactHandle(persisted),
711
+ stages: persisted.finalization,
712
+ completed: false,
713
+ cleanupCompleted: cleaned.receipt.complete,
714
+ cleanupPartial: cleaned.receipt.partial,
715
+ failurePreserved: true,
716
+ cleanupReceipt: cleaned.receipt,
717
+ idempotent: wasComplete,
718
+ };
719
+ }
720
+
721
+ function invalidateActiveWork(ctx: MultiRepositoryMcpToolContext, session: ExecutionSessionContext, reason: string): void {
722
+ if (!session.activeRepositoryId || !session.activeWorkId) return;
723
+ const handle = readWorkHandle(ctx.controllerHome, session.activeRepositoryId, session.activeWorkId);
724
+ if (!handle || handle.state === 'cleaned') return;
725
+ const contract = contractFor(ctx, handle);
726
+ if (contract?.status === 'completed') return;
727
+ markWorkHandleFailed(ctx.controllerHome, handle, reason);
728
+ }
729
+
730
+ function bindSessionRepository(ctx: MultiRepositoryMcpToolContext, args: Record<string, unknown>): Record<string, unknown> {
731
+ const session = requireSession(ctx, args);
732
+ const repository = resolveRepositorySelection({ repoId: requireExplicitRepoId(args), checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined, controllerHome: ctx.controllerHome, allowSoleRepository: false });
733
+ const switching = session.activeRepositoryId !== undefined && (session.activeRepositoryId !== repository.repoId || session.activeCheckoutId !== repository.activeCheckoutId);
734
+ if (switching) invalidateActiveWork(ctx, session, 'explicit repository or checkout switch invalidated the previous active work handle');
735
+ const next = updateExecutionSession(ctx.controllerHome, identityFor(ctx, args), {
736
+ activeRepositoryId: repository.repoId,
737
+ activeCheckoutId: repository.activeCheckoutId,
738
+ activeWorkId: undefined,
739
+ goalDelegation: undefined,
740
+ permissionSnapshotVersion: currentPermissionSnapshotVersion(ctx.controllerHome, repository.repoId),
741
+ lastValidatedAt: new Date().toISOString(),
742
+ });
743
+ return { session: next, repository: { repoId: repository.repoId, checkoutId: repository.activeCheckoutId, canonicalRoot: repository.canonicalRoot, branch: repository.checkouts.find((entry) => entry.checkoutId === repository.activeCheckoutId)?.branch ?? null }, switched: switching };
744
+ }
745
+
746
+ function prepareWork(ctx: MultiRepositoryMcpToolContext, args: Record<string, unknown>): Record<string, unknown> {
747
+ const session = requireSession(ctx, args);
748
+ const repository = selectedRepository(ctx, session, args, true);
749
+ if (!session.activeRepositoryId) {
750
+ updateExecutionSession(ctx.controllerHome, identityFor(ctx, args), { activeRepositoryId: repository.repoId, activeCheckoutId: repository.activeCheckoutId, permissionSnapshotVersion: currentPermissionSnapshotVersion(ctx.controllerHome, repository.repoId) });
751
+ }
752
+ const existingId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
753
+ if (existingId) {
754
+ const existing = readWorkHandle(ctx.controllerHome, repository.repoId, existingId)
755
+ ?? findWorkHandle(ctx, session, { ...args, work_id: existingId, repo_id: repository.repoId });
756
+ if (existing.principalId !== session.principalId) throw new Error('WORK_HANDLE_ACCESS_DENIED');
757
+ const adopted = adoptExistingWorkHead(ctx, session, repository, existing, args);
758
+ if (adopted) return adopted;
759
+ validateWorkHandle(ctx.controllerHome, existing, identityFor(ctx, args), 'cheap', 'inspect');
760
+ updateExecutionSession(ctx.controllerHome, identityFor(ctx, args), { activeRepositoryId: existing.repositoryId, activeCheckoutId: existing.checkoutId, activeWorkId: existing.workId, permissionSnapshotVersion: existing.permissionSnapshotVersion });
761
+ claimPreparedWorkOwnership(ctx, session, existing, args);
762
+ return { session: requireSession(ctx, args), work: compactHandle(existing), reused: true, controllerClaimed: true };
763
+ }
764
+
765
+ const requestId = typeof args.request_id === 'string' ? args.request_id.trim() : '';
766
+ if (!requestId) throw new Error('WORK_PREPARE_REQUEST_ID_REQUIRED: new work preparation requires request_id');
767
+ const isolation = args.isolation === 'reuse' || args.isolation === 'new_worktree' || args.isolation === 'auto' ? args.isolation : 'auto';
768
+ const objective = String(args.objective ?? 'Controller-managed repository work').trim().slice(0, 2_000);
769
+ const goalId = typeof args.goal_id === 'string' && args.goal_id.trim() ? args.goal_id.trim() : undefined;
770
+ const acceptanceCriteria = boundedStringArray(args.acceptance_criteria, 20);
771
+ const allowedPaths = boundedStringArray(args.allowed_paths, 50);
772
+ const checks = boundedStringArray(args.checks, 30);
773
+ const baseRef = typeof args.base_ref === 'string' && args.base_ref.trim() ? args.base_ref.trim() : undefined;
774
+ const requestedCheckoutId = typeof args.checkout_id === 'string' && args.checkout_id.trim() ? args.checkout_id.trim() : undefined;
775
+ const baseCheckoutId = repository.activeCheckoutId;
776
+ const baseStatus = repositoryGitStatus(repository);
777
+ if (isolation === 'reuse' && !baseStatus.clean) throw new Error('WORKTREE_DIRTY: reuse was requested but the selected checkout is dirty; choose new_worktree or auto');
778
+ const useWorktree = isolation === 'new_worktree' || (isolation === 'auto' && !baseStatus.clean);
779
+ const policy = readRepositoryAccessPolicy(ctx.controllerHome, repository.repoId);
780
+ const fingerprint = workPrepareFingerprint({
781
+ repoId: repository.repoId,
782
+ requestedCheckoutId,
783
+ isolation,
784
+ objective,
785
+ goalId,
786
+ acceptanceCriteria,
787
+ allowedPaths,
788
+ checks,
789
+ baseRef,
790
+ });
791
+
792
+ return withWorkPrepareRequest({
793
+ controllerHome: ctx.controllerHome,
794
+ repoId: repository.repoId,
795
+ sessionId: session.sessionId,
796
+ principalId: session.principalId,
797
+ requestId,
798
+ fingerprint,
799
+ proposedWorkId: newWorkId(),
800
+ }, (request, requestReused) => {
801
+ const createdWorkId = request.workId;
802
+ const existingHandle = readWorkHandle(ctx.controllerHome, repository.repoId, createdWorkId);
803
+ if (existingHandle) {
804
+ if (existingHandle.principalId !== session.principalId || existingHandle.sessionId !== session.sessionId) {
805
+ throw new Error(`WORK_PREPARE_REQUEST_INDEX_CORRUPT: ${requestId}`);
806
+ }
807
+ const existingContract = getWorkContract({ controllerHome: ctx.controllerHome, repoId: repository.repoId }, createdWorkId);
808
+ if (!existingContract) throw new Error(`WORK_PREPARE_RESULT_LOST: ${requestId} has a Work handle without its WorkContract`);
809
+ const terminal = isTerminalWorkContractStatus(existingContract.status)
810
+ && !(existingContract.status === 'failed' && request.status === 'claimed');
811
+ if (terminal) {
812
+ return {
813
+ session: requireSession(ctx, args),
814
+ work: compactHandle(existingHandle),
815
+ reused: true,
816
+ terminal: true,
817
+ workContractStatus: existingContract.status,
818
+ controllerClaimed: false,
819
+ };
820
+ }
821
+ if (existingContract.status === 'open' || existingContract.status === 'failed') {
822
+ updateWorkContract({ controllerHome: ctx.controllerHome, repoId: repository.repoId }, createdWorkId, { status: 'running', worktreeRef: existingHandle.worktreePath });
823
+ }
824
+ const delegation = createGoalDelegation({
825
+ sessionId: session.sessionId,
826
+ repositoryId: repository.repoId,
827
+ workId: createdWorkId,
828
+ goalId,
829
+ allowedRiskClasses: ['readonly', 'local_repo_write', 'workspace_write', 'local_command', 'dependency_change', 'local_git'],
830
+ deniedRiskClasses: ['remote_write', 'destructive', 'secret_access', 'outside_repository'],
831
+ permissionSnapshotVersion: policy.revision,
832
+ source: 'gpt_risk_delegate',
833
+ });
834
+ const nextSession = updateExecutionSession(ctx.controllerHome, identityFor(ctx, args), { activeRepositoryId: repository.repoId, activeCheckoutId: existingHandle.checkoutId, activeWorkId: createdWorkId, permissionSnapshotVersion: policy.revision, goalDelegation: delegation, lastValidatedAt: new Date().toISOString() });
835
+ claimPreparedWorkOwnership(ctx, nextSession, existingHandle, args);
836
+ return { session: nextSession, work: compactHandle(existingHandle), reused: true, isolation: existingHandle.managedWorktree ? 'isolated' : 'current', controllerClaimed: true };
837
+ }
838
+
839
+ let contract = getWorkContract({ controllerHome: ctx.controllerHome, repoId: repository.repoId }, createdWorkId);
840
+ if (contract?.requestId && contract.requestId !== requestId) throw new Error(`WORK_PREPARE_REQUEST_INDEX_CORRUPT: ${requestId}`);
841
+ if (request.status === 'prepared') {
842
+ throw new Error(`WORK_PREPARE_RESULT_LOST: ${requestId} completed without a readable Work handle`);
843
+ }
844
+ if (contract && (contract.status === 'completed' || contract.status === 'cancelled')) {
845
+ throw new Error(`WORK_PREPARE_REQUEST_TERMINAL: ${requestId} belongs to ${contract.status} Work ${createdWorkId}`);
846
+ }
847
+ if (!contract) {
848
+ contract = createWorkContract({ controllerHome: ctx.controllerHome, repoId: repository.repoId }, {
849
+ workId: createdWorkId,
850
+ repoId: repository.repoId,
851
+ mode: useWorktree ? 'goal_workloop' : 'direct_control',
852
+ objective,
853
+ acceptanceCriteria,
854
+ allowedPaths,
855
+ forbiddenPaths: [],
856
+ checks,
857
+ constraints: { accessMode: policy.mode, workspaceMode: useWorktree ? 'isolated' : 'current', requireWorktree: useWorktree, allowCommit: true, allowMerge: true, allowCleanup: true },
858
+ worktreePolicy: { required: useWorktree, reason: useWorktree ? 'work_prepare selected isolated worktree execution' : 'explicitly reused a registered checkout' },
859
+ requestedBy: 'chatgpt',
860
+ requestId,
861
+ });
862
+ }
863
+ const delegation = createGoalDelegation({
864
+ sessionId: session.sessionId,
865
+ repositoryId: repository.repoId,
866
+ workId: createdWorkId,
867
+ goalId,
868
+ allowedRiskClasses: ['readonly', 'local_repo_write', 'workspace_write', 'local_command', 'dependency_change', 'local_git'],
869
+ deniedRiskClasses: ['remote_write', 'destructive', 'secret_access', 'outside_repository'],
870
+ permissionSnapshotVersion: policy.revision,
871
+ source: 'gpt_risk_delegate',
872
+ });
873
+ try {
874
+ const workspace = useWorktree
875
+ ? ensureManagedWorkspace(ctx.controllerHome, repository, { requestId: createdWorkId, title: objective, baseRef })
876
+ : { mode: 'current' as const, checkoutId: baseCheckoutId, root: repository.canonicalRoot, branch: baseStatus.branch ?? 'detached', baseRevision: baseStatus.head ?? undefined, managed: false };
877
+ const refreshed = getRepository(repository.repoId, ctx.controllerHome);
878
+ const checkout = selectRepositoryCheckout(refreshed, workspace.checkoutId);
879
+ const branch = workspace.branch || repositoryGitStatus(checkout).branch;
880
+ if (!branch) throw new Error('WORKTREE_DETACHED: selected worktree has no branch');
881
+ const head = gitHead(checkout.canonicalRoot);
882
+ const handle: WorkHandleState = {
883
+ schemaVersion: 1, workId: createdWorkId, sessionId: session.sessionId, principalId: session.principalId,
884
+ repositoryId: repository.repoId, checkoutId: checkout.activeCheckoutId, worktreePath: checkout.canonicalRoot, branch,
885
+ sourceCheckoutId: baseCheckoutId, managedWorktree: workspace.managed, workContractId: contract.workId, goalId, delegationVersion: delegation.version,
886
+ baseCommit: workspace.baseRevision ?? head, expectedHead: head, permissionSnapshotVersion: policy.revision,
887
+ state: 'prepared', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), finalization: initialStage(),
888
+ cleanupResponsibility: { owner: 'work_finalizer', registeredAt: new Date().toISOString() },
889
+ };
890
+ writeWorkHandle(ctx.controllerHome, handle);
891
+ updateWorkContract({ controllerHome: ctx.controllerHome, repoId: repository.repoId }, contract.workId, { status: 'running', worktreeRef: checkout.canonicalRoot });
892
+ const nextSession = updateExecutionSession(ctx.controllerHome, identityFor(ctx, args), { activeRepositoryId: repository.repoId, activeCheckoutId: checkout.activeCheckoutId, activeWorkId: createdWorkId, permissionSnapshotVersion: policy.revision, goalDelegation: delegation, lastValidatedAt: new Date().toISOString() });
893
+ claimPreparedWorkOwnership(ctx, nextSession, handle, args);
894
+ return { session: nextSession, work: compactHandle(handle), reused: requestReused, isolation: workspace.mode, controllerClaimed: true };
895
+ } catch (error) {
896
+ updateWorkContract({ controllerHome: ctx.controllerHome, repoId: repository.repoId }, contract.workId, { status: 'failed' });
897
+ throw error;
898
+ }
899
+ });
900
+ }
901
+
902
+ function inspectWork(ctx: MultiRepositoryMcpToolContext, args: Record<string, unknown>): Record<string, unknown> {
903
+ const session = requireSession(ctx, args);
904
+ // work_inspect is authorized read-only evidence; it must not require the write lease.
905
+ const handle = workForSession(ctx, session, args);
906
+ const started = performance.now();
907
+ const validationStarted = performance.now();
908
+ const validated = validateWorkHandle(ctx.controllerHome, handle, identityFor(ctx, args), 'cheap', 'inspect');
909
+ const validationMs = performance.now() - validationStarted;
910
+ const status = repositoryGitStatus(validated.worktreeRepository);
911
+ const diff = repositoryGitDiff(validated.worktreeRepository, { maxBytes: 64 * 1024 });
912
+ const contract = contractFor(ctx, handle);
913
+ const checks = contract?.checks ?? [];
914
+ const packageManifest = existsSync(`${validated.worktreeRepository.canonicalRoot}/package.json`)
915
+ ? JSON.parse(readFileSync(`${validated.worktreeRepository.canonicalRoot}/package.json`, 'utf-8')) as Record<string, unknown>
916
+ : undefined;
917
+ const value = {
918
+ session: { sessionId: session.sessionId, repoId: session.activeRepositoryId, checkoutId: session.activeCheckoutId },
919
+ work: compactHandle(handle),
920
+ readiness: { valid: true, warnings: validated.warnings, permissionSnapshotVersion: handle.permissionSnapshotVersion },
921
+ git: { status, diff: { nameOnly: diff.nameOnly, stat: diff.stat, patch: diff.patch, truncated: diff.truncated } },
922
+ workContract: contract ? {
923
+ workId: contract.workId,
924
+ status: contract.status,
925
+ objective: contract.objective,
926
+ checks: contract.checks,
927
+ acceptanceCriteria: contract.acceptanceCriteria,
928
+ allowedPaths: contract.allowedPaths,
929
+ semantics: buildWorkContinuationSnapshot(contract).semantics,
930
+ } : undefined,
931
+ continuation: contract ? buildWorkContinuationSnapshot(contract) : undefined,
932
+ paths: { allowed: handle.workContractId ? contract?.allowedPaths ?? [] : [], relevant: diff.nameOnly },
933
+ checks: checks.map((checkId) => ({ checkId, registered: listControllerChecks(validated.worktreeRepository.canonicalRoot).some((check) => check.id === checkId) })),
934
+ package: packageManifest ? { name: packageManifest.name, scripts: packageManifest.scripts } : undefined,
935
+ };
936
+ const response = makeBoundedResult(ctx, session, handle.repositoryId, handle.workId, 'inspection', value);
937
+ const trace: McpTimingTrace = { tool: 'work_inspect', sessionResolutionMs: 0, repositoryResolutionMs: 0, workHandleValidationMs: Math.round(validationMs * 100) / 100, resultSerializationMs: 0, totalToolDurationMs: Math.round((performance.now() - started) * 100) / 100, sessionId: session.sessionId, repoId: handle.repositoryId, workId: handle.workId };
938
+ recordMcpTiming(ctx.controllerHome, trace);
939
+ return response;
940
+ }
941
+
942
+ function commandInputs(args: Record<string, unknown>): Array<Record<string, unknown>> {
943
+ if (Array.isArray(args.commands)) return args.commands.filter((value): value is Record<string, unknown> => Boolean(value && typeof value === 'object' && !Array.isArray(value)));
944
+ if (args.command !== undefined) return [{ command: args.command, cwd: args.cwd, approval_token: args.approval_token, timeout_ms: args.timeout_ms, max_output_bytes: args.max_output_bytes }];
945
+ throw new Error('COMMAND_REQUIRED: provide command or commands');
946
+ }
947
+
948
+ function authorizationRisk(command: RepositoryCommandValue, classification: ReturnType<typeof classifyRepositoryCommand>): AuthorizationRiskClass {
949
+ if (classification.risk === 'readonly') return 'readonly';
950
+ if (classification.risk === 'remote_write') return 'remote_write';
951
+ if (classification.risk === 'destructive') return 'destructive';
952
+ const executable = typeof command === 'string' ? command : command[0] ?? '';
953
+ if (typeof command === 'string' && /\b(?:npm|bun|pnpm|yarn)\s+(?:install|add|remove|update)\b/i.test(command)) return 'dependency_change';
954
+ if (/^\s*(?:git|.*[\\/]git)(?:\s|$)/i.test(executable)) return 'local_git';
955
+ return 'workspace_write';
956
+ }
957
+
958
+ async function executeWork(ctx: MultiRepositoryMcpToolContext, args: Record<string, unknown>): Promise<Record<string, unknown>> {
959
+ const session = requireSession(ctx, args);
960
+ const handle = workForSession(ctx, session, args);
961
+ assertWorkControllerOwnership(ctx, session, handle, args);
962
+ const commands = commandInputs(args);
963
+ if (commands.length > 16) throw new Error('COMMAND_BATCH_TOO_LARGE: at most 16 commands per work_execute');
964
+ const cheap = validateWorkHandle(ctx.controllerHome, handle, identityFor(ctx, args), 'cheap', 'execute');
965
+ const inputs = commands.map((entry) => ({
966
+ command: commandValue(normalizeRepositoryCommand(entry.command)),
967
+ cwd: typeof entry.cwd === 'string' ? entry.cwd : undefined,
968
+ approvalToken: typeof entry.approval_token === 'string' ? entry.approval_token : undefined,
969
+ }));
970
+ const classifications = inputs.map((entry) => classifyRepositoryCommand(entry.command, cheap.repository.defaultBranch));
971
+ const requiresFull = classifications.some((classification) => classification.risk !== 'readonly');
972
+ if (requiresFull) validateWorkHandle(ctx.controllerHome, handle, identityFor(ctx, args), 'full', 'execute');
973
+ const decisions: AuthorizationDecision[] = [];
974
+ const approvalRequestId = typeof args.approval_request_id === 'string' ? args.approval_request_id.trim() : '';
975
+ const resolvedRequest = approvalRequestId
976
+ ? assertResolvedAuthorization({ controllerHome: ctx.controllerHome, repositoryId: handle.repositoryId, approvalRequestId, sessionId: session.sessionId, principalId: session.principalId, workId: handle.workId, permissionSnapshotVersion: handle.permissionSnapshotVersion, command: inputs[0]?.command })
977
+ : undefined;
978
+ for (const [index, entry] of inputs.entries()) {
979
+ const classification = classifications[index]!;
980
+ const outsideCwd = Boolean(entry.cwd && (isAbsolute(entry.cwd) || (() => {
981
+ const rel = relative(resolve(handle.worktreePath), resolve(handle.worktreePath, entry.cwd));
982
+ return rel === '..' || rel.startsWith('../') || rel.startsWith('..\\');
983
+ })()));
984
+ const risk = outsideCwd ? 'outside_repository' : authorizationRisk(entry.command, classification);
985
+ const decision = resolvedRequest
986
+ ? { decision: 'allow', source: 'user_confirmation', reason: 'The user resolved the exact approval request for this command.' } as const
987
+ : decideAuthorization({
988
+ controllerHome: ctx.controllerHome,
989
+ accessMode: readRepositoryAccessPolicy(ctx.controllerHome, handle.repositoryId).mode,
990
+ risk,
991
+ repositoryId: handle.repositoryId,
992
+ currentRepositoryId: handle.repositoryId,
993
+ workId: handle.workId,
994
+ boundWorkId: handle.workId,
995
+ goalId: handle.goalId,
996
+ boundGoalId: handle.goalId,
997
+ sessionId: session.sessionId,
998
+ principalId: session.principalId,
999
+ permissionSnapshotVersion: handle.permissionSnapshotVersion,
1000
+ delegation: session.goalDelegation,
1001
+ worktreePath: handle.worktreePath,
1002
+ cwd: entry.cwd,
1003
+ command: entry.command,
1004
+ approvedByUser: Boolean(resolvedRequest),
1005
+ });
1006
+ decisions.push(decision);
1007
+ if (decision.decision !== 'allow') return { authorization: decision, work: compactHandle(handle), command: entry.command };
1008
+ }
1009
+ const invocationId = typeof args.request_id === 'string' && args.request_id.trim()
1010
+ ? args.request_id.trim()
1011
+ : `invoke-${session.sessionId}-${handle.workId}-${Date.now()}-${randomUUID().slice(0, 8)}`;
1012
+ const invocationFingerprint = createHash('sha256')
1013
+ .update(JSON.stringify({
1014
+ tool: 'work_execute',
1015
+ workId: handle.workId,
1016
+ commands: inputs.map((entry) => ({ command: entry.command, cwd: entry.cwd ?? null })),
1017
+ timeoutMs: typeof args.timeout_ms === 'number' ? args.timeout_ms : null,
1018
+ maxOutputBytes: typeof args.max_output_bytes === 'number' ? args.max_output_bytes : null,
1019
+ }))
1020
+ .digest('hex');
1021
+ claimProcessInvocation({
1022
+ controllerHome: ctx.controllerHome,
1023
+ repoId: handle.repositoryId,
1024
+ checkoutId: handle.checkoutId,
1025
+ requestId: invocationId,
1026
+ invocationFingerprint,
1027
+ });
1028
+ const run = async (entry: typeof inputs[number], index: number) => {
1029
+ const commandId = `${invocationId}:command:${index + 1}`;
1030
+ const execution = await executeRepositoryCommandViaProcessRuntime({
1031
+ controllerHome: ctx.controllerHome,
1032
+ repository: cheap.worktreeRepository,
1033
+ executionIdentity: executionIdentityForWork(cheap.worktreeRepository, handle),
1034
+ command: entry.command,
1035
+ cwd: entry.cwd,
1036
+ workId: handle.workId,
1037
+ commandId,
1038
+ requestId: commandId,
1039
+ timeoutMs: typeof args.timeout_ms === 'number' ? args.timeout_ms : undefined,
1040
+ maxOutputBytes: typeof args.max_output_bytes === 'number' ? args.max_output_bytes : undefined,
1041
+ });
1042
+ const process = execution.process;
1043
+ const completed = process?.completed === true || execution.route === 'process_direct';
1044
+ const ok = process ? process.ok === true : execution.ok === true;
1045
+ const status = process
1046
+ ? (process.completed
1047
+ ? (process.cancelled ? 'cancelled' : process.timedOut ? 'timed_out' : ok ? 'executed' : 'failed')
1048
+ : 'running')
1049
+ : execution.route === 'durable'
1050
+ ? 'deferred_durable'
1051
+ : 'rejected';
1052
+ return {
1053
+ processId: process?.processId,
1054
+ commandId,
1055
+ requestId: commandId,
1056
+ status,
1057
+ ok,
1058
+ exitCode: process?.exitCode ?? execution.exitCode,
1059
+ timedOut: process?.timedOut === true,
1060
+ cancelled: process?.cancelled === true,
1061
+ startedAt: process?.startedAt,
1062
+ finishedAt: process?.completed ? process.startedAt : undefined,
1063
+ stdout: process?.stdout ?? execution.stdout,
1064
+ stderr: process?.stderr ?? execution.stderr,
1065
+ logArtifact: process?.processId ? { processId: process.processId, kind: 'process_logs' } : undefined,
1066
+ route: execution.route,
1067
+ reason: execution.reason,
1068
+ authorizationDecision: decisions[index],
1069
+ approvalRequestId: resolvedRequest?.approvalRequestId,
1070
+ authorization: decisions[index]?.decision === 'allow'
1071
+ ? (resolvedRequest ? 'confirmed_plan' : decisions[index]?.source)
1072
+ : 'explicit_user_request',
1073
+ durableSideEffects: execution.durableSideEffects,
1074
+ process,
1075
+ };
1076
+ };
1077
+ const started = performance.now();
1078
+ const executions = classifications.every((classification) => classification.risk === 'readonly')
1079
+ ? await Promise.all(inputs.map((entry, index) => run(entry, index)))
1080
+ : await (async () => {
1081
+ const ordered: Awaited<ReturnType<typeof run>>[] = [];
1082
+ for (const [index, entry] of inputs.entries()) {
1083
+ const execution = await run(entry, index);
1084
+ ordered.push(execution);
1085
+ // Do not launch another mutating command while this Work-owned Process
1086
+ // still owns workspace leases. The caller resumes through process_wait.
1087
+ if (execution.process && !execution.process.completed) break;
1088
+ }
1089
+ return ordered;
1090
+ })();
1091
+ const branch = repositoryGitStatus(cheap.worktreeRepository).branch;
1092
+ const head = gitHead(cheap.worktreeRepository.canonicalRoot);
1093
+ let nextHandle = handle;
1094
+ if (branch !== handle.branch) nextHandle = markWorkHandleFailed(ctx.controllerHome, handle, `command changed the bound branch to ${branch ?? 'detached'}`);
1095
+ else nextHandle = transitionWorkHandle(ctx.controllerHome, handle, 'editing', { expectedHead: head, failureReason: undefined });
1096
+ const value = {
1097
+ work: compactHandle(nextHandle),
1098
+ commands: executions,
1099
+ executedCount: executions.filter((entry) => entry.status === 'executed' && entry.ok === true).length,
1100
+ managedProcessCount: executions.filter((entry) => entry.process && !entry.process.completed).length,
1101
+ deferredCommandCount: Math.max(0, inputs.length - executions.length),
1102
+ authorization: decisions[0],
1103
+ requestId: invocationId,
1104
+ };
1105
+ const response = makeBoundedResult(ctx, session, handle.repositoryId, handle.workId, 'command', value);
1106
+ recordMcpTiming(ctx.controllerHome, { tool: 'work_execute', workHandleValidationMs: 0, commandExecutionMs: Math.round((performance.now() - started) * 100) / 100, totalToolDurationMs: Math.round((performance.now() - started) * 100) / 100, sessionId: session.sessionId, repoId: handle.repositoryId, workId: handle.workId });
1107
+ return response;
1108
+ }
1109
+
1110
+ export function selectDefaultWorkValidationChecks(
1111
+ contract: ReturnType<typeof contractFor>,
1112
+ changedPaths: string[],
1113
+ ): string[] {
1114
+ if (!contract || changedPaths.length === 0) return [];
1115
+ if (contract.risk === 'medium' || contract.risk === 'high' || contract.risk === 'destructive') {
1116
+ return [...contract.checks];
1117
+ }
1118
+ const sourceTypeScriptChanged = changedPaths.some((path) =>
1119
+ /\.(?:ts|tsx|mts|cts)$/.test(path)
1120
+ && !/(?:^|\/)(?:tests?|__tests__|fixtures)(?:\/|$)/.test(path));
1121
+ return contract.checks.filter((checkId) => {
1122
+ const normalized = checkId.toLowerCase();
1123
+ if (normalized.includes('package:test') || normalized.includes('full') || normalized.includes('architecture') || normalized.includes('release') || normalized.includes('runtime')) {
1124
+ return false;
1125
+ }
1126
+ if (normalized.includes('type')) return sourceTypeScriptChanged;
1127
+ return normalized.includes('focused') || normalized.includes('unit') || normalized.includes('changed') || normalized.includes('target');
1128
+ });
1129
+ }
1130
+
1131
+ async function validateWork(ctx: MultiRepositoryMcpToolContext, args: Record<string, unknown>): Promise<Record<string, unknown>> {
1132
+ const session = requireSession(ctx, args);
1133
+ const handle = workForSession(ctx, session, args, { reconcileValidation: false });
1134
+ const terminalOutcome = terminalCleanupOutcome(ctx, handle);
1135
+ if (terminalOutcome && args.cleanup !== false) {
1136
+ return await reconcileTerminalCleanup(ctx, session, handle, args, terminalOutcome);
1137
+ }
1138
+ const validated = validateWorkHandle(ctx.controllerHome, handle, identityFor(ctx, args), 'full', 'validate');
1139
+ const contract = contractFor(ctx, handle);
1140
+ const changed = repositoryGitDiff(validated.worktreeRepository, { maxBytes: 64 * 1024 });
1141
+ const changedPaths = Array.isArray(changed.nameOnly) ? changed.nameOnly.map(String) : [];
1142
+ const requestedChecks = Array.isArray(args.check_ids)
1143
+ ? args.check_ids.map(String).filter(Boolean)
1144
+ : selectDefaultWorkValidationChecks(contract, changedPaths);
1145
+ const validationInvocationId = typeof args.request_id === 'string' && args.request_id.trim()
1146
+ ? args.request_id.trim()
1147
+ : `validate-${session.sessionId}-${handle.workId}-${Date.now()}-${randomUUID().slice(0, 8)}`;
1148
+ const validationStatus = repositoryGitStatus(validated.worktreeRepository);
1149
+ const validationHead = validationStatus.head
1150
+ ?? handle.expectedHead
1151
+ ?? handle.baseCommit
1152
+ ?? 'unknown';
1153
+ const workspaceFingerprint = workspaceValidationFingerprint(
1154
+ validated.worktreeRepository.canonicalRoot,
1155
+ validationStatus,
1156
+ );
1157
+ const validationFingerprint = workValidationInputFingerprint(
1158
+ validationHead,
1159
+ workspaceFingerprint,
1160
+ requestedChecks,
1161
+ );
1162
+ const previousRun = handle.validationRun?.fingerprint === validationFingerprint
1163
+ ? handle.validationRun
1164
+ : undefined;
1165
+ let validationRun: NonNullable<WorkHandleState['validationRun']> = previousRun ?? {
1166
+ fingerprint: validationFingerprint,
1167
+ head: validationHead,
1168
+ workspaceFingerprint,
1169
+ requestedChecks,
1170
+ resumeState: handle.state === 'committed' || handle.state === 'merged' ? handle.state : 'editing',
1171
+ processes: {},
1172
+ };
1173
+ let current = transitionWorkHandle(ctx.controllerHome, handle, 'validating', {
1174
+ finalization: { ...handle.finalization, validation: 'pending', lastError: undefined },
1175
+ validationRun,
1176
+ });
1177
+ markWorkValidationPending(ctx.controllerHome, current);
1178
+ const available = new Set(listControllerChecks(validated.worktreeRepository.canonicalRoot).map((check) => check.id));
1179
+ const checks: Array<Record<string, unknown>> = [];
1180
+ for (const [index, checkId] of requestedChecks.entries()) {
1181
+ if (!available.has(checkId)) {
1182
+ checks.push({ checkId, ok: false, status: 'missing', summary: `Check not found: ${checkId}` });
1183
+ break;
1184
+ }
1185
+ const existingBinding = validationRun.processes[checkId];
1186
+ let process = existingBinding
1187
+ ? getCheckProcessHandle(ctx.controllerHome, handle.repositoryId, existingBinding.processId)
1188
+ : undefined;
1189
+ if (existingBinding && !process) {
1190
+ checks.push({
1191
+ checkId,
1192
+ ok: false,
1193
+ status: 'infrastructure_failure',
1194
+ summary: `Validation process record is unavailable: ${existingBinding.processId}`,
1195
+ });
1196
+ break;
1197
+ }
1198
+ if (!process) {
1199
+ const processRequestId = `${validationInvocationId}:check:${index + 1}`;
1200
+ const executed = await runPersistedCheckViaProcessRuntime({
1201
+ controllerHome: ctx.controllerHome,
1202
+ repoId: handle.repositoryId,
1203
+ checkoutId: handle.checkoutId,
1204
+ repoRoot: validated.worktreeRepository.canonicalRoot,
1205
+ executionIdentity: executionIdentityForWork(validated.worktreeRepository, handle),
1206
+ checkId,
1207
+ requestId: processRequestId,
1208
+ workId: handle.workId,
1209
+ commandId: processRequestId,
1210
+ verificationBinding: { executionSessionId: session.sessionId },
1211
+ });
1212
+ if (executed.mode === 'durable') {
1213
+ checks.push({ checkId, ok: undefined, status: 'deferred', summary: executed.durable?.reason, durable: executed.durable });
1214
+ break;
1215
+ }
1216
+ process = executed.process!;
1217
+ validationRun = {
1218
+ ...validationRun,
1219
+ processes: {
1220
+ ...validationRun.processes,
1221
+ [checkId]: { processId: process.processId, requestId: processRequestId },
1222
+ },
1223
+ };
1224
+ current = transitionWorkHandle(ctx.controllerHome, current, 'validating', { validationRun });
1225
+ }
1226
+ if (!process.completed) {
1227
+ checks.push({ checkId, ok: undefined, status: 'running', process });
1228
+ break;
1229
+ }
1230
+ const record = getProcessRecord(ctx.controllerHome, handle.repositoryId, process.processId);
1231
+ if (!record) {
1232
+ checks.push({ checkId, ok: false, status: 'infrastructure_failure', summary: `Validation process record is unavailable: ${process.processId}` });
1233
+ break;
1234
+ }
1235
+ const receipt = processCheckCompletionReceipt(record, {
1236
+ repoId: handle.repositoryId,
1237
+ checkoutId: handle.checkoutId,
1238
+ workId: handle.workId,
1239
+ executionSessionId: session.sessionId,
1240
+ checkId,
1241
+ processId: process.processId,
1242
+ });
1243
+ appendVerificationRecord({ controllerHome: ctx.controllerHome, repoId: handle.repositoryId }, handle.workId, {
1244
+ checkId,
1245
+ outcome: receipt.ok ? 'valid_pass' : receipt.status === 'timed_out' || receipt.status === 'cancelled' ? 'infrastructure_failure' : 'valid_fail',
1246
+ summary: receipt.summary,
1247
+ recordedAt: receipt.finishedAt,
1248
+ sourceRevision: validationHead,
1249
+ workspaceFingerprint,
1250
+ verificationInputFingerprint: verificationInputFingerprint({
1251
+ sourceRevision: validationHead,
1252
+ workspaceFingerprint,
1253
+ checkId,
1254
+ requestedChecks,
1255
+ commandId: receipt.commandId,
1256
+ }),
1257
+ commandFingerprint: commandFingerprint(checkId, receipt.commandId),
1258
+ resultArtifactId: receipt.receiptId,
1259
+ startedAt: receipt.startedAt,
1260
+ completedAt: receipt.finishedAt,
1261
+ evidenceRef: { title: checkId, summary: `${receipt.receiptId}; artifact=${receipt.artifactPath}`, detailLevel: 'summary' },
1262
+ receipt,
1263
+ });
1264
+ checks.push({
1265
+ checkId,
1266
+ ok: receipt.ok,
1267
+ status: receipt.ok ? 'passed' : receipt.status === 'timed_out' || receipt.status === 'cancelled' ? 'infrastructure_failure' : 'failed',
1268
+ process,
1269
+ receipt,
1270
+ });
1271
+ if (!receipt.ok) break;
1272
+ }
1273
+ const infrastructureFailure = checks.find((check) => (
1274
+ check.status === 'missing'
1275
+ || check.status === 'infrastructure_failure'
1276
+ || check.status === 'deferred'
1277
+ ));
1278
+ const acceptedFailure = checks.find((check) => check.status === 'failed');
1279
+ const allObserved = checks.length === requestedChecks.length && checks.every((check) => check.ok !== undefined);
1280
+ const completed = Boolean(infrastructureFailure || acceptedFailure || allObserved);
1281
+ const passed = completed
1282
+ && !infrastructureFailure
1283
+ && !acceptedFailure
1284
+ && checks.every((check) => check.ok === true);
1285
+ const failureSummary = infrastructureFailure
1286
+ ? String(infrastructureFailure.summary ?? 'validation infrastructure failure')
1287
+ : acceptedFailure
1288
+ ? String(acceptedFailure.summary ?? 'targeted validation failed')
1289
+ : undefined;
1290
+ const nextState = !completed
1291
+ ? 'validating'
1292
+ : passed
1293
+ ? validationRun.resumeState
1294
+ : 'failed';
1295
+ const validation = !completed ? 'pending' : passed ? 'done' : 'failed';
1296
+ const next = transitionWorkHandle(ctx.controllerHome, current, nextState, {
1297
+ finalization: {
1298
+ ...current.finalization,
1299
+ validation,
1300
+ lastError: failureSummary,
1301
+ },
1302
+ validationRun: completed ? undefined : validationRun,
1303
+ ...(passed ? { validatedInputFingerprint: validationFingerprint } : {}),
1304
+ ...(failureSummary ? { failureReason: failureSummary } : { failureReason: undefined }),
1305
+ });
1306
+ if (completed) {
1307
+ projectWorkValidationOutcome(
1308
+ ctx.controllerHome,
1309
+ next,
1310
+ infrastructureFailure ? 'infrastructure_failure' : passed ? 'passed' : 'failed',
1311
+ failureSummary,
1312
+ );
1313
+ }
1314
+ if (completed && !passed) {
1315
+ const cleanup = await reconcileTerminalCleanup(
1316
+ ctx,
1317
+ session,
1318
+ next,
1319
+ args,
1320
+ infrastructureFailure ? 'infrastructure_failed' : 'validation_failed',
1321
+ );
1322
+ const value = {
1323
+ ...cleanup,
1324
+ validation: { passed, completed, checks, targeted: true, changedPaths, cleanupTriggered: true },
1325
+ };
1326
+ return makeBoundedResult(ctx, session, handle.repositoryId, handle.workId, 'validation', value);
1327
+ }
1328
+ const value = { work: compactHandle(next), validation: { passed, completed, checks, targeted: true, changedPaths } };
1329
+ return makeBoundedResult(ctx, session, handle.repositoryId, handle.workId, 'validation', value);
1330
+ }
1331
+
1332
+ function runCleanup(targetRoot: string, worktreePath: string): { ok: boolean; message?: string } {
1333
+ if (targetRoot === worktreePath) return { ok: true };
1334
+ if (!existsSync(worktreePath)) return { ok: true, message: 'managed worktree already removed' };
1335
+ const status = repositoryGitStatus({ repoId: 'cleanup', activeCheckoutId: 'cleanup', canonicalRoot: worktreePath, localRoot: worktreePath, checkouts: [], schemaVersion: 1, displayName: basename(worktreePath), repositoryType: 'git', enabled: true, createdAt: '', updatedAt: '', lastSeenAt: '', configurationPath: '', stateStorageStrategy: 'controller-home' });
1336
+ if (!status.clean) return { ok: false, message: 'managed worktree is dirty; cleanup preserved it' };
1337
+ const process = spawnSync('git', ['-C', targetRoot, 'worktree', 'remove', worktreePath], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 30_000 });
1338
+ return process.status === 0 ? { ok: true } : { ok: false, message: String(process.stderr ?? 'git worktree remove failed').trim() };
1339
+ }
1340
+
1341
+ /**
1342
+ * Reconcile a stale Work Handle only for a cleanup-only request whose exact
1343
+ * branch HEAD is already reachable from the requested target branch.
1344
+ *
1345
+ * Two bounded recovery cases are supported:
1346
+ * 1. an older controller committed/merged successfully but failed before
1347
+ * recording the finalization stages; and
1348
+ * 2. a terminal cancelled WorkContract owns an unchanged, clean duplicate
1349
+ * worktree created by an idempotency defect.
1350
+ *
1351
+ * Missing worktrees are accepted only when this handle already recorded a
1352
+ * successful worktree cleanup. This lets a retry finish branch cleanup after a
1353
+ * crash without turning a missing checkout into proof of safety.
1354
+ */
1355
+ function cleanupOnlyMergedHead(
1356
+ ctx: MultiRepositoryMcpToolContext,
1357
+ current: WorkHandleState,
1358
+ args: Record<string, unknown>,
1359
+ ): { currentHead: string; cancelledContract: boolean; worktreeMissing: boolean } | undefined {
1360
+ if (args.cleanup !== true || args.commit === true || args.merge === true || !current.managedWorktree) return undefined;
1361
+ const contract = contractFor(ctx, current);
1362
+ const cancelledContract = contract?.status === 'cancelled';
1363
+
1364
+ const repository = getRepository(current.repositoryId, ctx.controllerHome, { includeRemoved: true });
1365
+ const target = selectWorkFinalizationTarget(repository, current);
1366
+ const targetBranch = typeof args.target_branch === 'string' && args.target_branch.trim()
1367
+ ? args.target_branch.trim()
1368
+ : repository.defaultBranch || 'main';
1369
+ const branchHeadResult = spawnSync('git', ['-C', target.canonicalRoot, 'rev-parse', '--verify', `refs/heads/${current.branch}`], {
1370
+ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
1371
+ });
1372
+ const currentHead = branchHeadResult.status === 0 ? String(branchHeadResult.stdout ?? '').trim() : '';
1373
+ if (!currentHead) return undefined;
1374
+ const merged = spawnSync('git', ['-C', target.canonicalRoot, 'merge-base', '--is-ancestor', currentHead, targetBranch], {
1375
+ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
1376
+ });
1377
+ if (merged.status !== 0) return undefined;
1378
+
1379
+ const worktreeMissing = !existsSync(current.worktreePath);
1380
+ if (worktreeMissing) {
1381
+ if (!cancelledContract || current.finalization.worktreeCleanup !== 'done') return undefined;
1382
+ return { currentHead, cancelledContract, worktreeMissing: true };
1383
+ }
1384
+
1385
+ const worktree = selectRepositoryCheckout(repository, current.checkoutId, { allowArchived: true });
1386
+ if (!repositoryGitStatus(worktree).clean) return undefined;
1387
+ const worktreeHead = gitHead(worktree.canonicalRoot);
1388
+ if (!worktreeHead || worktreeHead !== currentHead) return undefined;
1389
+ const currentBranch = spawnSync('git', ['-C', worktree.canonicalRoot, 'branch', '--show-current'], {
1390
+ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
1391
+ });
1392
+ if (currentBranch.status !== 0 || String(currentBranch.stdout ?? '').trim() !== current.branch) return undefined;
1393
+ if (current.expectedHead && currentHead === current.expectedHead && !cancelledContract) return undefined;
1394
+ if (current.expectedHead && currentHead !== current.expectedHead && cancelledContract) return undefined;
1395
+ return { currentHead, cancelledContract, worktreeMissing: false };
1396
+ }
1397
+
1398
+ interface FailedCleanupProof {
1399
+ currentHead: string;
1400
+ worktreeMissing: boolean;
1401
+ targetBranch: string;
1402
+ }
1403
+
1404
+ /**
1405
+ * Prove that a failed Work owns an unchanged, clean managed worktree whose
1406
+ * branch is already contained in the target branch. This authorizes resource
1407
+ * cleanup only; it never proves successful verification or delivery.
1408
+ */
1409
+ function failedCleanupOnlyHead(
1410
+ ctx: MultiRepositoryMcpToolContext,
1411
+ current: WorkHandleState,
1412
+ args: Record<string, unknown>,
1413
+ ): FailedCleanupProof | undefined {
1414
+ if (
1415
+ current.state !== 'failed'
1416
+ || args.cleanup !== true
1417
+ || args.commit === true
1418
+ || args.merge === true
1419
+ || !current.managedWorktree
1420
+ ) return undefined;
1421
+ const contract = contractFor(ctx, current);
1422
+ if (contract?.status !== 'failed') return undefined;
1423
+
1424
+ const expectedHead = current.expectedHead ?? current.baseCommit;
1425
+ if (!expectedHead) return undefined;
1426
+ const repository = getRepository(current.repositoryId, ctx.controllerHome, { includeRemoved: true });
1427
+ const target = selectWorkFinalizationTarget(repository, current);
1428
+ const targetBranch = typeof args.target_branch === 'string' && args.target_branch.trim()
1429
+ ? args.target_branch.trim()
1430
+ : repository.defaultBranch || 'main';
1431
+ const merged = spawnSync('git', ['-C', target.canonicalRoot, 'merge-base', '--is-ancestor', expectedHead, targetBranch], {
1432
+ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
1433
+ });
1434
+ if (merged.status !== 0) return undefined;
1435
+
1436
+ const worktreeMissing = !existsSync(current.worktreePath);
1437
+ if (worktreeMissing) {
1438
+ return current.finalization.worktreeCleanup === 'done'
1439
+ ? { currentHead: expectedHead, worktreeMissing: true, targetBranch }
1440
+ : undefined;
1441
+ }
1442
+
1443
+ const worktree = selectRepositoryCheckout(repository, current.checkoutId, { allowArchived: true });
1444
+ if (resolve(worktree.canonicalRoot) !== resolve(current.worktreePath)) return undefined;
1445
+ if (!repositoryGitStatus(worktree).clean) return undefined;
1446
+ if (gitHead(worktree.canonicalRoot) !== expectedHead) return undefined;
1447
+ const currentBranch = spawnSync('git', ['-C', worktree.canonicalRoot, 'branch', '--show-current'], {
1448
+ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
1449
+ });
1450
+ if (currentBranch.status !== 0 || String(currentBranch.stdout ?? '').trim() !== current.branch) return undefined;
1451
+ return { currentHead: expectedHead, worktreeMissing: false, targetBranch };
1452
+ }
1453
+
1454
+ function resetFailedFinalizationStages(stages: WorkFinalizationStages, wants: { commit: boolean; merge: boolean; cleanup: boolean }): WorkFinalizationStages {
1455
+ const next = { ...stages };
1456
+ let reset = false;
1457
+ if (next.validation === 'failed') {
1458
+ next.validation = 'pending';
1459
+ reset = true;
1460
+ }
1461
+ if (wants.commit && next.commit === 'failed') {
1462
+ next.commit = 'pending';
1463
+ reset = true;
1464
+ }
1465
+ if (wants.merge && next.merge === 'failed') {
1466
+ next.merge = 'pending';
1467
+ reset = true;
1468
+ }
1469
+ if (wants.cleanup && next.worktreeCleanup === 'failed') {
1470
+ next.worktreeCleanup = 'pending';
1471
+ reset = true;
1472
+ }
1473
+ if (wants.cleanup && next.branchCleanup === 'failed') {
1474
+ next.branchCleanup = 'pending';
1475
+ reset = true;
1476
+ }
1477
+ if (reset) delete next.lastError;
1478
+ return next;
1479
+ }
1480
+
1481
+ function finalizationComplete(stages: WorkFinalizationStages): boolean {
1482
+ return stages.validation === 'done'
1483
+ && stages.commit !== 'pending'
1484
+ && stages.merge !== 'pending'
1485
+ && stages.branchCleanup !== 'pending'
1486
+ && stages.worktreeCleanup !== 'pending'
1487
+ && stages.commit !== 'failed'
1488
+ && stages.merge !== 'failed'
1489
+ && stages.branchCleanup !== 'failed'
1490
+ && stages.worktreeCleanup !== 'failed'
1491
+ && !stages.lastError;
1492
+ }
1493
+
1494
+ function finalStateForStages(stages: WorkFinalizationStages, fallback: WorkHandleState['state']): WorkHandleState['state'] {
1495
+ if (stages.worktreeCleanup === 'done') return 'cleaned';
1496
+ if (stages.merge === 'done') return 'merged';
1497
+ if (stages.commit === 'done') return 'committed';
1498
+ return fallback === 'failed' ? 'editing' : fallback;
1499
+ }
1500
+
1501
+ function currentWorkValidationInput(
1502
+ repository: RepositoryRecord,
1503
+ handle: WorkHandleState,
1504
+ requestedChecks: string[],
1505
+ ): { head: string; workspaceFingerprint: string; fingerprint: string; clean: boolean } {
1506
+ const status = repositoryGitStatus(repository);
1507
+ const head = status.head ?? handle.expectedHead ?? handle.baseCommit ?? 'unknown';
1508
+ const workspaceFingerprint = workspaceValidationFingerprint(repository.canonicalRoot, status);
1509
+ return {
1510
+ head,
1511
+ workspaceFingerprint,
1512
+ fingerprint: workValidationInputFingerprint(head, workspaceFingerprint, requestedChecks),
1513
+ clean: status.clean,
1514
+ };
1515
+ }
1516
+
1517
+ async function finalizeWork(ctx: MultiRepositoryMcpToolContext, args: Record<string, unknown>): Promise<Record<string, unknown>> {
1518
+ const session = requireSession(ctx, args);
1519
+ let current = workForSession(ctx, session, args);
1520
+ const terminalOutcome = terminalCleanupOutcome(ctx, current);
1521
+ if (terminalOutcome && args.cleanup !== false) {
1522
+ return await reconcileTerminalCleanup(ctx, session, current, args, terminalOutcome);
1523
+ }
1524
+ assertWorkControllerOwnership(ctx, session, current, args);
1525
+ if (current.state === 'cleaned') {
1526
+ const terminalContract = contractFor(ctx, current);
1527
+ if (
1528
+ terminalContract?.status === 'failed'
1529
+ && current.finalization.validation === 'failed'
1530
+ && current.finalization.worktreeCleanup === 'done'
1531
+ ) {
1532
+ releasePreparedWorkOwnership(ctx, current);
1533
+ updateExecutionSession(ctx.controllerHome, identityFor(ctx, args), {
1534
+ activeWorkId: undefined,
1535
+ activeCheckoutId: workReturnCheckoutId(ctx, current, session.activeCheckoutId),
1536
+ });
1537
+ return {
1538
+ idempotent: true,
1539
+ work: compactHandle(current),
1540
+ stages: current.finalization,
1541
+ completed: false,
1542
+ cleanupCompleted: true,
1543
+ failurePreserved: true,
1544
+ };
1545
+ }
1546
+ validateWorkHandle(ctx.controllerHome, current, identityFor(ctx, args), 'none', 'finalize');
1547
+ return { idempotent: true, work: compactHandle(current) };
1548
+ }
1549
+ const identity = identityFor(ctx, args);
1550
+ const requestedOutcome = args.completion_outcome === 'completed_no_change' || args.completion_outcome === 'completed_changed'
1551
+ ? args.completion_outcome
1552
+ : undefined;
1553
+ const noChangeProof = typeof args.no_change_evidence === 'string' ? args.no_change_evidence.trim().slice(0, 1_000) : '';
1554
+ if (requestedOutcome === 'completed_no_change' && (!noChangeProof || args.commit === true || args.merge === true)) {
1555
+ throw new Error('WORK_NO_CHANGE_PROOF_REQUIRED: completed_no_change requires objective-specific evidence and forbids commit/merge');
1556
+ }
1557
+ const wants = { commit: args.commit === true, merge: args.merge === true, cleanup: args.cleanup === true };
1558
+
1559
+ const transact = (label: string, update: (fresh: WorkHandleState) => WorkHandleState): WorkHandleState =>
1560
+ withControllerLock(ctx.controllerHome, { scope: 'worktree', repoId: current.repositoryId, worktreeId: current.checkoutId }, `work-finalize:${current.workId}:${label}`, () => {
1561
+ const fresh = readWorkHandle(ctx.controllerHome, current.repositoryId, current.workId) ?? current;
1562
+ return update(fresh);
1563
+ }, 10_000);
1564
+
1565
+ const failStage = (stage: keyof WorkFinalizationStages, reason: string): Record<string, unknown> => {
1566
+ current = transact(`fail:${String(stage)}`, (fresh) => {
1567
+ const finalization = { ...fresh.finalization, [stage]: 'failed', lastError: reason } as WorkFinalizationStages;
1568
+ return markWorkHandleFailed(ctx.controllerHome, { ...fresh, finalization }, reason);
1569
+ });
1570
+ if (current.workContractId) updateWorkContract({ controllerHome: ctx.controllerHome, repoId: current.repositoryId }, current.workContractId, { status: 'failed' });
1571
+ markRepositoryProjectionDirty(ctx.controllerHome, current.repositoryId, `cleanup:${current.workId}:failed`);
1572
+ return { work: compactHandle(current), stages: current.finalization, completed: false };
1573
+ };
1574
+
1575
+ const failedCleanupRequested = current.state === 'failed'
1576
+ && wants.cleanup
1577
+ && !wants.commit
1578
+ && !wants.merge;
1579
+ const failedCleanupProof = failedCleanupRequested
1580
+ ? failedCleanupOnlyHead(ctx, current, args)
1581
+ : undefined;
1582
+ if (failedCleanupRequested && !failedCleanupProof) {
1583
+ throw new Error('WORK_FAILED_CLEANUP_UNSAFE: failed Work cleanup requires exact checkout/branch ownership and an unchanged clean controller-owned managed worktree whose exact HEAD is already contained in the target branch');
1584
+ }
1585
+
1586
+ if (!failedCleanupRequested) {
1587
+ current = transact('begin', (fresh) => writeWorkHandle(ctx.controllerHome, {
1588
+ ...fresh,
1589
+ state: fresh.state === 'failed' ? 'validating' : fresh.state,
1590
+ failureReason: undefined,
1591
+ finalization: resetFailedFinalizationStages(fresh.finalization, wants),
1592
+ }));
1593
+ }
1594
+
1595
+ const approvalRequestId = typeof args.approval_request_id === 'string' ? args.approval_request_id.trim() : '';
1596
+ const resolvedAuthorization = approvalRequestId
1597
+ ? assertResolvedAuthorization({ controllerHome: ctx.controllerHome, repositoryId: current.repositoryId, approvalRequestId, sessionId: session.sessionId, principalId: session.principalId, workId: current.workId, permissionSnapshotVersion: current.permissionSnapshotVersion, command: 'work_finalize' })
1598
+ : undefined;
1599
+ const gitAuthorization = resolvedAuthorization
1600
+ ? { decision: 'allow', source: 'user_confirmation', reason: 'The user resolved the exact finalization approval request.' } as const
1601
+ : decideAuthorization({
1602
+ controllerHome: ctx.controllerHome,
1603
+ accessMode: readRepositoryAccessPolicy(ctx.controllerHome, current.repositoryId).mode,
1604
+ risk: 'local_git',
1605
+ repositoryId: current.repositoryId,
1606
+ currentRepositoryId: current.repositoryId,
1607
+ workId: current.workId,
1608
+ boundWorkId: current.workId,
1609
+ goalId: current.goalId,
1610
+ boundGoalId: current.goalId,
1611
+ sessionId: session.sessionId,
1612
+ principalId: session.principalId,
1613
+ permissionSnapshotVersion: current.permissionSnapshotVersion,
1614
+ delegation: session.goalDelegation,
1615
+ command: 'work_finalize',
1616
+ });
1617
+ if (gitAuthorization.decision !== 'allow') return { authorization: gitAuthorization, work: compactHandle(current), stages: current.finalization };
1618
+
1619
+ if (failedCleanupProof) {
1620
+ const preservedFailure = current.failureReason ?? current.finalization.lastError ?? 'Work validation failed.';
1621
+ current = transact('failed-cleanup-begin', (fresh) => writeWorkHandle(ctx.controllerHome, {
1622
+ ...fresh,
1623
+ state: 'failed',
1624
+ failureReason: preservedFailure,
1625
+ finalization: {
1626
+ ...fresh.finalization,
1627
+ validation: 'failed',
1628
+ commit: fresh.finalization.commit === 'pending' ? 'skipped' : fresh.finalization.commit,
1629
+ merge: fresh.finalization.merge === 'pending' ? 'skipped' : fresh.finalization.merge,
1630
+ branchCleanup: args.delete_branch === false ? 'skipped' : fresh.finalization.branchCleanup,
1631
+ lastError: preservedFailure,
1632
+ },
1633
+ }));
1634
+
1635
+ if (!failedCleanupProof.worktreeMissing && current.finalization.worktreeCleanup !== 'done') {
1636
+ const target = selectWorkFinalizationTarget(
1637
+ getRepository(current.repositoryId, ctx.controllerHome, { includeRemoved: true }),
1638
+ current,
1639
+ );
1640
+ const cleanup = runCleanup(target.canonicalRoot, current.worktreePath);
1641
+ if (!cleanup.ok) {
1642
+ throw new Error(`WORK_FAILED_CLEANUP_UNSAFE: ${cleanup.message ?? 'managed worktree cleanup failed'}`);
1643
+ }
1644
+ current = transact('failed-worktree-cleanup-done', (fresh) => {
1645
+ setRepositoryCheckoutLifecycle({
1646
+ controllerHome: ctx.controllerHome,
1647
+ repoId: fresh.repositoryId,
1648
+ checkoutId: fresh.checkoutId,
1649
+ lifecycle: 'removed',
1650
+ reason: `Failed Work ${fresh.workId} cleanup completed without changing delivery state.`,
1651
+ });
1652
+ markRepositoryProjectionDirty(ctx.controllerHome, fresh.repositoryId, `cleanup:${fresh.workId}:failed-worktree`);
1653
+ return writeWorkHandle(ctx.controllerHome, {
1654
+ ...fresh,
1655
+ state: 'failed',
1656
+ failureReason: preservedFailure,
1657
+ finalization: { ...fresh.finalization, worktreeCleanup: 'done', lastError: preservedFailure },
1658
+ });
1659
+ });
1660
+ }
1661
+
1662
+ if (args.delete_branch !== false && current.finalization.branchCleanup !== 'done') {
1663
+ const target = selectWorkFinalizationTarget(
1664
+ getRepository(current.repositoryId, ctx.controllerHome, { includeRemoved: true }),
1665
+ current,
1666
+ );
1667
+ const deleted = repositoryGitDeleteBranch(ctx.controllerHome, target, {
1668
+ branch: current.branch,
1669
+ force: false,
1670
+ authorizationDecision: gitAuthorization,
1671
+ sessionId: session.sessionId,
1672
+ principalId: session.principalId,
1673
+ workId: current.workId,
1674
+ goalId: current.goalId,
1675
+ });
1676
+ if (deleted.execution.authorizationDecision?.decision === 'user_confirmation_required') {
1677
+ return { authorization: deleted.execution.authorizationDecision, work: compactHandle(current), stages: current.finalization };
1678
+ }
1679
+ if (deleted.execution.status !== 'executed' || deleted.execution.ok !== true) {
1680
+ current = transact('failed-branch-cleanup-failed', (fresh) => writeWorkHandle(ctx.controllerHome, {
1681
+ ...fresh,
1682
+ state: 'failed',
1683
+ failureReason: preservedFailure,
1684
+ finalization: {
1685
+ ...fresh.finalization,
1686
+ branchCleanup: 'failed',
1687
+ lastError: String(deleted.execution.stderr || 'feature branch cleanup failed').slice(0, 1_000),
1688
+ },
1689
+ }));
1690
+ return {
1691
+ work: compactHandle(current),
1692
+ stages: current.finalization,
1693
+ completed: false,
1694
+ cleanupCompleted: false,
1695
+ failurePreserved: true,
1696
+ };
1697
+ }
1698
+ current = transact('failed-branch-cleanup-done', (fresh) => {
1699
+ markRepositoryProjectionDirty(ctx.controllerHome, fresh.repositoryId, `cleanup:${fresh.workId}:failed-branch`);
1700
+ return writeWorkHandle(ctx.controllerHome, {
1701
+ ...fresh,
1702
+ state: 'failed',
1703
+ failureReason: preservedFailure,
1704
+ finalization: { ...fresh.finalization, branchCleanup: 'done', lastError: preservedFailure },
1705
+ });
1706
+ });
1707
+ }
1708
+
1709
+ current = transact('failed-cleanup-complete', (fresh) => transitionWorkHandle(ctx.controllerHome, fresh, 'cleaned', {
1710
+ failureReason: preservedFailure,
1711
+ finalization: {
1712
+ ...fresh.finalization,
1713
+ validation: 'failed',
1714
+ commit: fresh.finalization.commit === 'pending' ? 'skipped' : fresh.finalization.commit,
1715
+ merge: fresh.finalization.merge === 'pending' ? 'skipped' : fresh.finalization.merge,
1716
+ branchCleanup: args.delete_branch === false ? 'skipped' : fresh.finalization.branchCleanup,
1717
+ worktreeCleanup: 'done',
1718
+ lastError: preservedFailure,
1719
+ },
1720
+ }));
1721
+ appendWorkEvidence({ controllerHome: ctx.controllerHome, repoId: current.repositoryId }, current.workContractId ?? current.workId, {
1722
+ title: 'failed work cleanup completed',
1723
+ summary: `Controller preserved the failed Work outcome while removing its unchanged clean managed worktree and ${args.delete_branch === false ? 'retaining' : 'removing'} the local branch after proving ${failedCleanupProof.currentHead} is contained in ${failedCleanupProof.targetBranch}.`,
1724
+ detailLevel: 'summary',
1725
+ });
1726
+ releasePreparedWorkOwnership(ctx, current);
1727
+ updateExecutionSession(ctx.controllerHome, identity, {
1728
+ activeWorkId: undefined,
1729
+ activeCheckoutId: current.sourceCheckoutId ?? session.activeCheckoutId,
1730
+ });
1731
+ return {
1732
+ work: compactHandle(current),
1733
+ stages: current.finalization,
1734
+ completed: false,
1735
+ cleanupCompleted: true,
1736
+ failurePreserved: true,
1737
+ idempotent: false,
1738
+ };
1739
+ }
1740
+
1741
+ const contractAtStart = contractFor(ctx, current);
1742
+ const cancelledCleanupRequested = contractAtStart?.status === 'cancelled'
1743
+ && wants.cleanup
1744
+ && !wants.commit
1745
+ && !wants.merge;
1746
+ const cleanupReconciliation = cleanupOnlyMergedHead(ctx, current, args);
1747
+ if (cancelledCleanupRequested && !cleanupReconciliation) {
1748
+ throw new Error('WORK_CANCELLED_CLEANUP_UNSAFE: cancelled Work cleanup requires an unchanged clean managed worktree (or a previously recorded cleanup) and a branch HEAD already contained in the target branch');
1749
+ }
1750
+ if (cleanupReconciliation) {
1751
+ current = transact('cleanup-reconcile-merged-head', (fresh) => writeWorkHandle(ctx.controllerHome, {
1752
+ ...fresh,
1753
+ expectedHead: cleanupReconciliation.currentHead,
1754
+ state: 'merged',
1755
+ failureReason: undefined,
1756
+ finalization: {
1757
+ ...fresh.finalization,
1758
+ validation: cleanupReconciliation.worktreeMissing ? 'done' : fresh.finalization.validation,
1759
+ commit: fresh.finalization.commit === 'pending' ? 'skipped' : fresh.finalization.commit,
1760
+ merge: 'done',
1761
+ branchCleanup: args.delete_branch === false ? 'skipped' : 'pending',
1762
+ lastError: undefined,
1763
+ },
1764
+ }));
1765
+ }
1766
+
1767
+ const terminalCleanupOnly = Boolean(cleanupReconciliation && wants.cleanup && !wants.commit && !wants.merge);
1768
+ if (terminalCleanupOnly) {
1769
+ current = transact('terminal-cleanup-validation-skipped', (fresh) => writeWorkHandle(ctx.controllerHome, {
1770
+ ...fresh,
1771
+ finalization: { ...fresh.finalization, validation: 'done', lastError: undefined },
1772
+ }));
1773
+ } else {
1774
+ let validatedRepository: RepositoryRecord;
1775
+ try {
1776
+ validatedRepository = validateWorkHandle(ctx.controllerHome, current, identity, 'full', 'finalize').worktreeRepository;
1777
+ } catch (error) {
1778
+ return failStage('validation', error instanceof Error ? error.message : String(error));
1779
+ }
1780
+ const validationContract = contractFor(ctx, current);
1781
+ if (!validationContract) throw new Error(`WORK_VALIDATION_CONTRACT_MISSING: ${current.workContractId ?? current.workId}`);
1782
+ const validationInput = currentWorkValidationInput(validatedRepository, current, validationContract.checks);
1783
+ if (validationContract.checks.length === 0) {
1784
+ current = transact('validation-no-checks', (fresh) => writeWorkHandle(ctx.controllerHome, {
1785
+ ...fresh,
1786
+ validatedInputFingerprint: validationInput.fingerprint,
1787
+ finalization: { ...fresh.finalization, validation: 'done', lastError: undefined },
1788
+ }));
1789
+ projectWorkValidationOutcome(ctx.controllerHome, current, 'passed', 'No validation checks were required.');
1790
+ } else if (!hasCurrentWorkValidationAuthority({
1791
+ finalizationValidation: current.finalization.validation,
1792
+ validatedInputFingerprint: current.validatedInputFingerprint,
1793
+ evidenceState: validationContract.evidenceState,
1794
+ expectedFingerprint: validationInput.fingerprint,
1795
+ })) {
1796
+ current = transact('validation-required', (fresh) => writeWorkHandle(ctx.controllerHome, {
1797
+ ...fresh,
1798
+ validatedInputFingerprint: undefined,
1799
+ finalization: { ...fresh.finalization, validation: 'pending', lastError: undefined },
1800
+ }));
1801
+ markWorkValidationPending(ctx.controllerHome, current);
1802
+ throw new Error('WORK_VALIDATION_REQUIRED: run work_validate against the exact current workspace before finalization');
1803
+ }
1804
+ if (wants.merge && !wants.commit && current.finalization.commit !== 'done' && !validationInput.clean) {
1805
+ throw new Error('WORK_MERGE_UNCOMMITTED_CHANGES: commit the validated workspace before merging');
1806
+ }
1807
+ }
1808
+
1809
+ if (requestedOutcome === 'completed_no_change') {
1810
+ const inspected = validateWorkHandle(ctx.controllerHome, current, identity, 'full', 'finalize');
1811
+ if (!repositoryGitStatus(inspected.worktreeRepository).clean) {
1812
+ throw new Error('WORK_NO_CHANGE_DIRTY: completed_no_change cannot retain an owned dirty worktree');
1813
+ }
1814
+ }
1815
+
1816
+ if (wants.commit && current.finalization.commit === 'pending') {
1817
+ const validated = validateWorkHandle(ctx.controllerHome, current, identity, 'full', 'finalize');
1818
+ const contract = contractFor(ctx, current);
1819
+ if (contract?.constraints.allowCommit === false) throw new Error('WORK_COMMIT_NOT_ALLOWED: WorkContract disallows commit');
1820
+ const status = repositoryGitStatus(validated.worktreeRepository);
1821
+ const commitPaths = [...new Set([...status.staged, ...status.unstaged, ...status.untracked])];
1822
+ const committed = repositoryGitCommit(ctx.controllerHome, validated.worktreeRepository, { message: String(args.message ?? `Complete ${current.workId}`), paths: commitPaths, allowEmpty: false, authorizationDecision: gitAuthorization, sessionId: session.sessionId, principalId: session.principalId, workId: current.workId, goalId: current.goalId });
1823
+ const pendingAuthorization = [committed.stage, committed.commit].find((execution) => execution?.authorizationDecision?.decision === 'user_confirmation_required')?.authorizationDecision;
1824
+ if (pendingAuthorization) return { authorization: pendingAuthorization, work: compactHandle(current), stages: current.finalization };
1825
+ if (!committed.committed) return { ...failStage('commit', committed.error?.message ?? 'commit failed'), commit: committed };
1826
+ current = transact('commit-done', (fresh) => transitionWorkHandle(ctx.controllerHome, fresh, 'committed', {
1827
+ expectedHead: gitHead(validated.worktreeRepository.canonicalRoot),
1828
+ finalization: {
1829
+ ...fresh.finalization,
1830
+ validation: contract?.checks.length ? 'pending' : 'done',
1831
+ commit: 'done',
1832
+ lastError: undefined,
1833
+ },
1834
+ validatedInputFingerprint: contract?.checks.length ? undefined : fresh.validatedInputFingerprint,
1835
+ failureReason: undefined,
1836
+ }));
1837
+ if (contract?.checks.length) {
1838
+ markWorkValidationPending(ctx.controllerHome, current);
1839
+ return {
1840
+ work: compactHandle(current),
1841
+ stages: current.finalization,
1842
+ completed: false,
1843
+ continuation: 'WORK_COMMITTED_REVALIDATION_REQUIRED: run work_validate on the exact committed HEAD before merge or completion',
1844
+ };
1845
+ }
1846
+ const postCommitInput = currentWorkValidationInput(validated.worktreeRepository, current, []);
1847
+ current = transact('commit-no-checks-validation', (fresh) => writeWorkHandle(ctx.controllerHome, {
1848
+ ...fresh,
1849
+ expectedHead: postCommitInput.head,
1850
+ validatedInputFingerprint: postCommitInput.fingerprint,
1851
+ }));
1852
+ projectWorkValidationOutcome(ctx.controllerHome, current, 'passed', 'No validation checks were required after commit.');
1853
+ } else if (!wants.commit && current.finalization.commit === 'pending') {
1854
+ current = transact('commit-skipped', (fresh) => writeWorkHandle(ctx.controllerHome, { ...fresh, finalization: { ...fresh.finalization, commit: 'skipped' } }));
1855
+ }
1856
+
1857
+ if (wants.merge && current.finalization.merge === 'pending') {
1858
+ validateWorkHandle(ctx.controllerHome, current, identity, 'full', 'finalize');
1859
+ const contract = contractFor(ctx, current);
1860
+ if (contract?.constraints.allowMerge === false) throw new Error('WORK_MERGE_NOT_ALLOWED: WorkContract disallows merge');
1861
+ const target = selectWorkFinalizationTarget(getRepository(current.repositoryId, ctx.controllerHome), current);
1862
+ const deleteAfterWorktreeCleanup = current.managedWorktree && args.delete_branch !== false;
1863
+ const merged = repositoryGitFinishWorkflow(ctx.controllerHome, target, { featureBranch: current.branch, targetBranch: typeof args.target_branch === 'string' ? args.target_branch : undefined, deleteBranch: !deleteAfterWorktreeCleanup && args.delete_branch !== false, noFf: args.no_ff === true, authorizationDecision: gitAuthorization, sessionId: session.sessionId, principalId: session.principalId, workId: current.workId, goalId: current.goalId });
1864
+ const pendingAuthorization = merged.steps.find((step) => step.execution.authorizationDecision?.decision === 'user_confirmation_required')?.execution.authorizationDecision;
1865
+ if (pendingAuthorization) return { authorization: pendingAuthorization, work: compactHandle(current), stages: current.finalization };
1866
+ if (!merged.completed) return { ...failStage('merge', merged.error?.message ?? 'merge failed'), merge: merged };
1867
+ current = transact('merge-done', (fresh) => transitionWorkHandle(ctx.controllerHome, fresh, 'merged', {
1868
+ finalization: { ...fresh.finalization, merge: 'done', branchCleanup: args.delete_branch === false ? 'skipped' : deleteAfterWorktreeCleanup ? 'pending' : 'done', lastError: undefined },
1869
+ failureReason: undefined,
1870
+ }));
1871
+ } else if (!wants.merge && current.finalization.merge === 'pending') {
1872
+ current = transact('merge-skipped', (fresh) => writeWorkHandle(ctx.controllerHome, { ...fresh, finalization: { ...fresh.finalization, merge: 'skipped', branchCleanup: 'skipped' } }));
1873
+ }
1874
+
1875
+ if (wants.cleanup && current.finalization.worktreeCleanup === 'pending') {
1876
+ const contract = contractFor(ctx, current);
1877
+ if (contract?.constraints.allowCleanup === false) throw new Error('WORK_CLEANUP_NOT_ALLOWED: WorkContract disallows cleanup');
1878
+ if (!current.managedWorktree) {
1879
+ current = transact('worktree-cleanup-skipped', (fresh) => writeWorkHandle(ctx.controllerHome, { ...fresh, finalization: { ...fresh.finalization, worktreeCleanup: 'skipped' } }));
1880
+ } else {
1881
+ const target = selectWorkFinalizationTarget(getRepository(current.repositoryId, ctx.controllerHome), current);
1882
+ const cleanup = runCleanup(target.canonicalRoot, current.worktreePath);
1883
+ if (!cleanup.ok) return failStage('worktreeCleanup', cleanup.message ?? 'worktree cleanup failed');
1884
+ current = transact('worktree-cleanup-done', (fresh) => {
1885
+ setRepositoryCheckoutLifecycle({ controllerHome: ctx.controllerHome, repoId: fresh.repositoryId, checkoutId: fresh.checkoutId, lifecycle: 'removed', reason: `Work ${fresh.workId} cleanup completed.` });
1886
+ markRepositoryProjectionDirty(ctx.controllerHome, fresh.repositoryId, `cleanup:${fresh.workId}:worktree`);
1887
+ return writeWorkHandle(ctx.controllerHome, { ...fresh, finalization: { ...fresh.finalization, worktreeCleanup: 'done', lastError: undefined } });
1888
+ });
1889
+ }
1890
+ } else if (!wants.cleanup && current.finalization.worktreeCleanup === 'pending') {
1891
+ current = transact('worktree-cleanup-skipped', (fresh) => writeWorkHandle(ctx.controllerHome, { ...fresh, finalization: { ...fresh.finalization, worktreeCleanup: 'skipped' } }));
1892
+ }
1893
+
1894
+ if (wants.cleanup && current.finalization.branchCleanup === 'pending' && current.finalization.merge === 'done') {
1895
+ const target = selectWorkFinalizationTarget(getRepository(current.repositoryId, ctx.controllerHome), current);
1896
+ const deleted = repositoryGitDeleteBranch(ctx.controllerHome, target, { branch: current.branch, force: false, authorizationDecision: gitAuthorization, sessionId: session.sessionId, principalId: session.principalId, workId: current.workId, goalId: current.goalId });
1897
+ if (deleted.execution.authorizationDecision?.decision === 'user_confirmation_required') return { authorization: deleted.execution.authorizationDecision, work: compactHandle(current), stages: current.finalization };
1898
+ if (deleted.execution.status !== 'executed' || deleted.execution.ok !== true) return failStage('branchCleanup', deleted.execution.stderr || 'feature branch cleanup failed');
1899
+ current = transact('branch-cleanup-done', (fresh) => {
1900
+ markRepositoryProjectionDirty(ctx.controllerHome, fresh.repositoryId, `cleanup:${fresh.workId}:branch`);
1901
+ return writeWorkHandle(ctx.controllerHome, { ...fresh, finalization: { ...fresh.finalization, branchCleanup: 'done', lastError: undefined } });
1902
+ });
1903
+ }
1904
+
1905
+ const complete = finalizationComplete(current.finalization);
1906
+ if (complete) {
1907
+ const finalState = finalStateForStages(current.finalization, current.state);
1908
+ current = transact('complete', (fresh) => transitionWorkHandle(ctx.controllerHome, fresh, finalState, { finalization: current.finalization, failureReason: undefined }));
1909
+ const completedContract = contractFor(ctx, current);
1910
+ if (completedContract?.status === 'cancelled') {
1911
+ appendWorkEvidence(
1912
+ { controllerHome: ctx.controllerHome, repoId: current.repositoryId },
1913
+ completedContract.workId,
1914
+ {
1915
+ title: 'cancelled work cleanup completed',
1916
+ summary: 'Controller verified terminal ownership, a clean or previously removed managed worktree, and a branch HEAD already contained in the target branch before deleting retained workspace references.',
1917
+ detailLevel: 'summary',
1918
+ },
1919
+ );
1920
+ } else {
1921
+ const workId = current.workContractId ?? current.workId;
1922
+ const completionContract = contractFor(ctx, current);
1923
+ if (!completionContract) throw new Error(`WORK_COMPLETION_CONTRACT_MISSING: ${workId}`);
1924
+ if (requestedOutcome === 'completed_no_change') {
1925
+ appendWorkEvidence({ controllerHome: ctx.controllerHome, repoId: current.repositoryId }, workId, {
1926
+ title: 'objective-specific no-change proof',
1927
+ summary: noChangeProof,
1928
+ detailLevel: 'summary',
1929
+ });
1930
+ }
1931
+ const receipt = completionReceiptForFinalizedWork(ctx, current, completionContract, args);
1932
+ const recorded = recordWorkCompletionReceipt(
1933
+ { controllerHome: ctx.controllerHome, repoId: current.repositoryId },
1934
+ workId,
1935
+ receipt,
1936
+ requestedOutcome === 'completed_no_change' ? 'completed_no_change' : 'completed_changed',
1937
+ requestedOutcome === 'completed_no_change' ? 'completed_no_change' : 'repository_change',
1938
+ );
1939
+ if (recorded.requirementId) {
1940
+ completeRequirementFromWork(
1941
+ { controllerHome: ctx.controllerHome },
1942
+ { requirementId: recorded.requirementId, work: recorded },
1943
+ );
1944
+ }
1945
+ }
1946
+ // Successful WorkContract completion always ends controller ownership.
1947
+ // Physical branch/worktree retention is represented by finalization stages
1948
+ // and completion-receipt warnings; it must not keep a mutation owner live.
1949
+ releasePreparedWorkOwnership(ctx, current);
1950
+ updateExecutionSession(ctx.controllerHome, identity, {
1951
+ activeWorkId: undefined,
1952
+ activeCheckoutId: current.sourceCheckoutId ?? session.activeCheckoutId,
1953
+ });
1954
+ }
1955
+ return { work: compactHandle(current), stages: current.finalization, completed: complete, idempotent: !wants.commit && !wants.merge && !wants.cleanup && current.finalization.validation === 'done' };
1956
+ }
1957
+
1958
+ export async function callExecutionTool(ctx: MultiRepositoryMcpToolContext, name: string, args: Record<string, unknown>): Promise<CallToolResult | undefined> {
1959
+ if (!executionToolNames.has(name)) return undefined;
1960
+ try {
1961
+ switch (name) {
1962
+ case 'session_start': {
1963
+ const session = startOrResumeSession(ctx);
1964
+ return result({ session: { sessionId: session.sessionId, principalId: session.principalId, activeRepositoryId: session.activeRepositoryId, activeCheckoutId: session.activeCheckoutId, activeWorkId: session.activeWorkId, permissionSnapshotVersion: session.permissionSnapshotVersion, capabilitySnapshotVersion: session.capabilitySnapshotVersion, controllerInstanceId: session.controllerInstanceId, createdAt: session.createdAt, updatedAt: session.updatedAt } });
1965
+ }
1966
+ case 'session_bind_repository': return result(bindSessionRepository(ctx, args));
1967
+ case 'work_prepare': return result(prepareWork(ctx, args));
1968
+ case 'work_inspect': return result(inspectWork(ctx, args));
1969
+ case 'work_execute': return result(await executeWork(ctx, args));
1970
+ case 'work_validate': return result(await validateWork(ctx, args));
1971
+ case 'work_finalize': return result(await finalizeWork(ctx, args));
1972
+ case 'approval_resolve': {
1973
+ const session = requireSession(ctx, args);
1974
+ const repositoryId = typeof args.repo_id === 'string' && args.repo_id.trim() ? args.repo_id.trim() : session.activeRepositoryId;
1975
+ if (!repositoryId) throw new Error('SESSION_REPOSITORY_REQUIRED: bind a repository before resolving approval');
1976
+ const resolved = resolveAuthorizationRequest({
1977
+ controllerHome: ctx.controllerHome,
1978
+ repositoryId,
1979
+ approvalRequestId: String(args.approval_request_id ?? ''),
1980
+ sessionId: session.sessionId,
1981
+ principalId: session.principalId,
1982
+ workId: typeof args.work_id === 'string' ? args.work_id : session.activeWorkId,
1983
+ permissionSnapshotVersion: currentPermissionSnapshotVersion(ctx.controllerHome, repositoryId),
1984
+ confirm: args.confirm_authorization === true,
1985
+ });
1986
+ const resumed = resumeExecutionJobAfterApproval(ctx.controllerHome, repositoryId, resolved.approvalRequestId);
1987
+ return result({
1988
+ authorization: { decision: 'allow', source: 'user_confirmation', reason: 'User confirmation was recorded for the exact pending operation.' },
1989
+ approval: resolved,
1990
+ resumedJob: resumed ? { jobId: resumed.jobId, status: resumed.status, requestId: resumed.requestId } : undefined,
1991
+ continuation: resumed
1992
+ ? `The original durable Job ${resumed.jobId} was resumed with the resolved approval request.`
1993
+ : `Retry the original operation with approval_request_id=${resolved.approvalRequestId}.`,
1994
+ });
1995
+ }
1996
+ case 'result_read': {
1997
+ const session = requireSession(ctx, args);
1998
+ return result(readControllerResult({ controllerHome: ctx.controllerHome, resultRef: String(args.result_ref ?? ''), sessionId: session.sessionId, principalId: session.principalId, workId: typeof args.work_id === 'string' ? args.work_id : undefined, cursor: typeof args.cursor === 'number' ? args.cursor : undefined, limit: typeof args.limit === 'number' ? args.limit : undefined }));
1999
+ }
2000
+ case 'result_search': {
2001
+ const session = requireSession(ctx, args);
2002
+ return result(searchControllerResult({ controllerHome: ctx.controllerHome, resultRef: String(args.result_ref ?? ''), sessionId: session.sessionId, principalId: session.principalId, workId: typeof args.work_id === 'string' ? args.work_id : undefined, query: String(args.query ?? ''), limit: typeof args.limit === 'number' ? args.limit : undefined }));
2003
+ }
2004
+ default: return undefined;
2005
+ }
2006
+ } catch (error) {
2007
+ return failure(error);
2008
+ }
2009
+ }