@moretea-labs/forge 1.5.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +46 -0
  3. package/CLAUDE.md +2 -0
  4. package/DESIGN.md +7 -2
  5. package/README.md +4 -4
  6. package/README.zh-CN.md +2 -2
  7. package/SKILL.md +2 -2
  8. package/THIRD_PARTY_NOTICES.md +1 -1
  9. package/agents/openai.yaml +1 -1
  10. package/assets/hooks/hook-input.sh +11 -8
  11. package/assets/hooks/lib/session-state.sh +10 -7
  12. package/assets/hooks/lib/workflow-state.sh +13 -177
  13. package/assets/hooks/post-tool-observer.sh +8 -2
  14. package/assets/hooks/pre-edit-guard.sh +8 -39
  15. package/assets/hooks/prompt-guard.sh +7 -83
  16. package/assets/hooks/session-start-context.sh +56 -1
  17. package/assets/partials/05-workflow.partial.md +1 -1
  18. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  19. package/assets/plugin-registry.v1.json +54 -3
  20. package/assets/reference-configs/document-generation.md +1 -1
  21. package/assets/reference-configs/harness-overview.md +1 -1
  22. package/assets/reference-configs/hook-operations.md +1 -1
  23. package/assets/reference-configs/sprint-contracts.md +3 -3
  24. package/assets/skill-version.json +2 -2
  25. package/assets/templates/contract.template.md +2 -7
  26. package/assets/templates/helpers/capture-plan.sh +2 -2
  27. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  28. package/assets/templates/helpers/contract-worktree.sh +1 -21
  29. package/assets/templates/helpers/ensure-task-workflow.sh +13 -38
  30. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  31. package/assets/templates/helpers/new-plan.sh +1 -1
  32. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  33. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  34. package/assets/templates/helpers/sprint-backlog.sh +6 -6
  35. package/assets/templates/helpers/verify-contract.sh +1 -111
  36. package/assets/templates/helpers/verify-sprint.sh +3 -180
  37. package/assets/templates/plan.template.md +1 -1
  38. package/assets/templates/review.template.md +9 -34
  39. package/assets/workflow-contract.v1.json +10 -19
  40. package/docs/README.md +10 -10
  41. package/docs/architecture/CURRENT.md +170 -0
  42. package/docs/architecture/EVOLUTION.md +42 -0
  43. package/docs/architecture/current/README.md +4 -156
  44. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  45. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  46. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  47. package/docs/architecture/history/README.md +7 -0
  48. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  49. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  50. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  51. package/docs/architecture/history.md +3 -34
  52. package/docs/architecture/index.md +15 -98
  53. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  54. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  55. package/docs/architecture/versions/1.6.md +38 -0
  56. package/docs/architecture/versions/1.7.md +41 -0
  57. package/docs/forge-chatgpt-browser-engine.md +2 -2
  58. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  59. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  60. package/docs/forge-github-issue-launcher.md +1 -1
  61. package/docs/forge-runtime-self-healing-loop.md +1 -1
  62. package/docs/operations/controller-browser-plugin.md +38 -22
  63. package/docs/operations/controller-performance-and-502.md +124 -0
  64. package/docs/operations/controller-reliability-runbook.md +1 -1
  65. package/docs/operations/features.md +3 -3
  66. package/docs/operations/github-repository.md +1 -1
  67. package/docs/operations/homebrew.md +2 -2
  68. package/docs/operations/mcp-tool-exposure.md +5 -3
  69. package/docs/tutorials/01-install-and-start.md +1 -1
  70. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  71. package/docs/wiki/Architecture.md +6 -6
  72. package/docs/wiki/Implementation.md +2 -2
  73. package/docs/wiki/Installation.md +1 -1
  74. package/docs/wiki/Integrations.md +2 -2
  75. package/docs/wiki/Quick-Start.md +1 -1
  76. package/docs/wiki/Work-Lifecycle.md +1 -1
  77. package/package.json +29 -10
  78. package/scripts/activate-source-baseline.ts +17 -13
  79. package/scripts/benchmark-background-check-overlap.ts +228 -0
  80. package/scripts/benchmark-mcp-host.sh +117 -0
  81. package/scripts/benchmark-mcp-transport.ts +170 -0
  82. package/scripts/benchmark-quality-harness.ts +146 -0
  83. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  84. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  85. package/scripts/benchmark-typescript-navigation.ts +186 -0
  86. package/scripts/benchmark-work-efficiency.ts +334 -0
  87. package/scripts/capture-plan.sh +3 -3
  88. package/scripts/check-agent-tooling.sh +8 -35
  89. package/scripts/check-architecture-sync.sh +17 -86
  90. package/scripts/check-mcp-compatibility.ts +14 -0
  91. package/scripts/check-public-docs.sh +14 -1
  92. package/scripts/check-runtime-architecture.mjs +107 -64
  93. package/scripts/check-stable-baseline.ts +92 -0
  94. package/scripts/check-task-workflow.sh +4 -4
  95. package/scripts/contract-worktree.sh +1 -21
  96. package/scripts/ensure-task-workflow.sh +22 -55
  97. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  98. package/scripts/install-standalone-recovery.ts +24 -0
  99. package/scripts/lib/project-init-lib.sh +15 -44
  100. package/scripts/migrate-chatgpt-automation-schedules.ts +64 -0
  101. package/scripts/migrate-project-template.sh +23 -1179
  102. package/scripts/new-plan.sh +1 -1
  103. package/scripts/plan-to-todo.sh +11 -36
  104. package/scripts/run-governed-gate.ts +2 -1
  105. package/scripts/ship-worktrees.sh +2 -29
  106. package/scripts/smoke-ios-physical-device.ts +4 -2
  107. package/scripts/smoke-runtime-recovery.ts +1 -35
  108. package/scripts/smoke-schedule-engine.ts +1 -20
  109. package/scripts/sprint-backlog.sh +1 -1
  110. package/scripts/verify-contract.sh +1 -111
  111. package/scripts/verify-forge-runtime.sh +0 -1
  112. package/scripts/verify-sprint.sh +3 -180
  113. package/scripts/workflow-contract.ts +1 -5
  114. package/src/cli/commands/chatgpt.ts +31 -2
  115. package/src/cli/commands/init-hook.ts +3 -3
  116. package/src/cli/commands/mcp.ts +9 -5
  117. package/src/cli/commands/plugin.ts +37 -10
  118. package/src/cli/commands/recovery.ts +25 -3
  119. package/src/cli/commands/runtime.ts +55 -3
  120. package/src/cli/commands/security.ts +12 -1
  121. package/src/cli/commands/setup-profile.ts +31 -21
  122. package/src/cli/controller/check-runner.ts +106 -3
  123. package/src/cli/controller/composite-result.ts +1 -1
  124. package/src/cli/controller/context/focus.ts +51 -0
  125. package/src/cli/controller/context/instruction-context.ts +114 -0
  126. package/src/cli/controller/context/known-paths.ts +76 -0
  127. package/src/cli/controller/context/query-planning.ts +110 -0
  128. package/src/cli/controller/context/rule-resolution.ts +532 -0
  129. package/src/cli/controller/context/source-materializer.ts +278 -0
  130. package/src/cli/controller/context/structural.ts +16 -0
  131. package/src/cli/controller/context/types.ts +286 -0
  132. package/src/cli/controller/context-pack.ts +898 -430
  133. package/src/cli/controller/launch-agents.ts +77 -2
  134. package/src/cli/controller/operational-plan.ts +2 -2
  135. package/src/cli/controller/work-mode.ts +6 -4
  136. package/src/cli/editing/edit-session.ts +60 -7
  137. package/src/cli/github/github.ts +133 -0
  138. package/src/cli/hook/prompt-guard-decision.ts +17 -18
  139. package/src/cli/hook/prompt-intents.ts +1 -1
  140. package/src/cli/installer/managed-entries.ts +15 -1
  141. package/src/cli/local-bridge/console-automations.ts +225 -67
  142. package/src/cli/local-bridge/console-view-models.ts +3 -35
  143. package/src/cli/local-bridge/facade-api.ts +2 -217
  144. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  145. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +153 -3
  146. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  147. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +183 -3
  148. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +1 -1
  149. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +4 -3
  150. package/src/cli/local-bridge/frontend/src/lib/api.ts +18 -11
  151. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  152. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +1 -1
  153. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +4 -3
  154. package/src/cli/local-bridge/frontend/src/styles.css +27 -11
  155. package/src/cli/local-bridge/frontend/src/types.ts +8 -7
  156. package/src/cli/local-bridge/job-store.ts +1 -490
  157. package/src/cli/local-bridge/server.ts +29 -532
  158. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  159. package/src/cli/local-bridge/ui-dist/app.js +5101 -4967
  160. package/src/cli/mcp/auth.ts +5 -5
  161. package/src/cli/mcp/instructions.ts +3 -2
  162. package/src/cli/mcp/legacy-tool-service.ts +0 -189
  163. package/src/cli/mcp/multi-repository.ts +16 -5
  164. package/src/cli/mcp/oauth.ts +175 -26
  165. package/src/cli/mcp/paths.ts +8 -2
  166. package/src/cli/mcp/repository-tools.ts +356 -101
  167. package/src/cli/mcp/server.ts +625 -54
  168. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  169. package/src/cli/mcp/toolset-selection.ts +1 -1
  170. package/src/cli/mcp/toolset.ts +42 -5
  171. package/src/cli/mcp/transports/http.ts +125 -15
  172. package/src/cli/mcp/transports/session-registry.ts +67 -5
  173. package/src/cli/mcp/types.ts +3 -3
  174. package/src/cli/repositories/command-classifier.ts +209 -14
  175. package/src/cli/repositories/command-executor.ts +55 -521
  176. package/src/cli/repositories/command-process.ts +108 -0
  177. package/src/cli/repositories/command-scope.ts +99 -41
  178. package/src/cli/repositories/controller-home.ts +7 -8
  179. package/src/cli/repositories/git-command-executor.ts +2 -0
  180. package/src/cli/repositories/locks.ts +53 -16
  181. package/src/cli/repositories/repository-snapshot.ts +121 -0
  182. package/src/cli/repositories/safe-patch.ts +6 -4
  183. package/src/cli/repositories/selected-path-actions.ts +17 -9
  184. package/src/cli/repositories/structured-git.ts +110 -10
  185. package/src/cli/repository/inspector.ts +565 -62
  186. package/src/cli/repository/session-cache.ts +58 -17
  187. package/src/cli/v81-entry.ts +1 -4
  188. package/src/effects/fs-transaction.ts +85 -17
  189. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  190. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  191. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  192. package/src/runtime/context/lsp-client.ts +283 -0
  193. package/src/runtime/context/semantic-navigation.ts +275 -0
  194. package/src/runtime/context/swift-navigation.ts +565 -0
  195. package/src/runtime/context/typescript-navigation.ts +226 -0
  196. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  197. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +196 -2
  198. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  199. package/src/runtime/control-plane/execution/execution-identity.ts +65 -14
  200. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  201. package/src/runtime/control-plane/execution/validation.ts +15 -2
  202. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  203. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  204. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  205. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  206. package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
  207. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +175 -8
  208. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -21
  209. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +87 -9
  210. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  211. package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
  212. package/src/runtime/control-plane/facade/controller-session-store.ts +144 -14
  213. package/src/runtime/control-plane/facade/goal-workloop-access.ts +9 -2
  214. package/src/runtime/control-plane/facade/goal-workloop.ts +885 -105
  215. package/src/runtime/control-plane/facade/index.ts +1 -0
  216. package/src/runtime/control-plane/facade/plan-contract-store.ts +211 -26
  217. package/src/runtime/control-plane/facade/requirement-board.ts +24 -8
  218. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  219. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  220. package/src/runtime/control-plane/facade/types.ts +55 -11
  221. package/src/runtime/control-plane/facade/work-contract-store.ts +204 -73
  222. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  223. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  224. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  225. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  226. package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
  227. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  228. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  229. package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
  230. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  231. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  232. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  233. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  234. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  235. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  236. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  237. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  238. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  239. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  240. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
  241. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +642 -51
  242. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
  243. package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
  244. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  245. package/src/runtime/control-plane/persistence/requirement-store.ts +15 -11
  246. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  247. package/src/runtime/control-plane/release-retention.ts +62 -2
  248. package/src/runtime/control-plane/routing/route-policy.ts +7 -16
  249. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  250. package/src/runtime/diagnostics/mcp-timing.ts +19 -0
  251. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  252. package/src/runtime/diagnostics/performance.ts +24 -1
  253. package/src/runtime/evidence/event-ledger.ts +1 -1
  254. package/src/runtime/execution/jobs/store.ts +58 -4
  255. package/src/runtime/execution/jobs/types.ts +1 -1
  256. package/src/runtime/execution/managed-workspace.ts +203 -1
  257. package/src/runtime/execution/process-runtime/check-facade.ts +52 -14
  258. package/src/runtime/execution/process-runtime/check-result.ts +63 -2
  259. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  260. package/src/runtime/execution/process-runtime/command-facade.ts +346 -40
  261. package/src/runtime/execution/process-runtime/gc.ts +98 -12
  262. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
  263. package/src/runtime/execution/process-runtime/persisted-check.ts +57 -6
  264. package/src/runtime/execution/process-runtime/resource-claims.ts +97 -8
  265. package/src/runtime/execution/process-runtime/runtime.ts +59 -4
  266. package/src/runtime/execution/process-runtime/store.ts +7 -0
  267. package/src/runtime/execution/process-runtime/types.ts +2 -0
  268. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  269. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  270. package/src/runtime/execution/workers/executor.ts +0 -48
  271. package/src/runtime/gateway/mcp/execution-tools.ts +1219 -126
  272. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  273. package/src/runtime/gateway/mcp/router.ts +185 -83
  274. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
  275. package/src/runtime/gateway/mcp/runtime-tools.ts +2555 -1616
  276. package/src/runtime/model-clients/deepseek-adapter.ts +0 -32
  277. package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
  278. package/src/runtime/plugins/browser-adapter.ts +2925 -318
  279. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  280. package/src/runtime/plugins/browser-handoff-host.ts +6 -31
  281. package/src/runtime/plugins/browser-handoff.ts +92 -5
  282. package/src/runtime/plugins/browser-macos-bridge.ts +586 -45
  283. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  284. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  285. package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
  286. package/src/runtime/plugins/browser-runtime.ts +183 -0
  287. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  288. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  289. package/src/runtime/plugins/desktop-operator-registration.ts +15 -12
  290. package/src/runtime/plugins/external-adapter.ts +324 -3
  291. package/src/runtime/plugins/external-registration.ts +84 -5
  292. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  293. package/src/runtime/plugins/first-party-registry.ts +4 -2
  294. package/src/runtime/plugins/github-adapter.ts +125 -0
  295. package/src/runtime/plugins/gmail-adapter.ts +28 -2
  296. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  297. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  298. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  299. package/src/runtime/plugins/ios-adapter.ts +106 -16
  300. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  301. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  302. package/src/runtime/plugins/lightweight-action.ts +153 -0
  303. package/src/runtime/plugins/local-system-adapter.ts +125 -8
  304. package/src/runtime/plugins/macos-capability-broker.ts +55 -9
  305. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  306. package/src/runtime/plugins/plugin-management-adapter.ts +181 -0
  307. package/src/runtime/plugins/resend-adapter.ts +1 -1
  308. package/src/runtime/plugins/store.ts +366 -24
  309. package/src/runtime/plugins/types.ts +39 -2
  310. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  311. package/src/runtime/projections/git-status-sampler.ts +16 -16
  312. package/src/runtime/projections/materialized-view.ts +28 -10
  313. package/src/runtime/recovery/actions.ts +0 -10
  314. package/src/runtime/recovery/classifier.ts +0 -9
  315. package/src/runtime/recovery/maintenance-executor.ts +415 -18
  316. package/src/runtime/recovery/probe.ts +0 -1
  317. package/src/runtime/recovery/types.ts +0 -1
  318. package/src/runtime/root/mcp-transport.ts +180 -10
  319. package/src/runtime/root/package-connector-service.ts +186 -8
  320. package/src/runtime/root/package-runtime-release.ts +143 -17
  321. package/src/runtime/root/package-runtime-service.ts +430 -22
  322. package/src/runtime/root/release-manifest.ts +7 -2
  323. package/src/runtime/root/release-materialize.ts +162 -19
  324. package/src/runtime/root/release-store.ts +48 -11
  325. package/src/runtime/root/runtime.ts +23 -5
  326. package/src/runtime/root/service.ts +100 -27
  327. package/src/runtime/root/types.ts +2 -0
  328. package/src/runtime/safe-tooling/index.ts +0 -1
  329. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  330. package/src/runtime/safe-tooling/types.ts +0 -30
  331. package/src/runtime/shared/trusted-executable.ts +62 -0
  332. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  333. package/src/runtime/standalone-recovery/core.ts +702 -94
  334. package/src/runtime/standalone-recovery/entry.ts +99 -23
  335. package/src/runtime/standalone-recovery/installer.ts +19 -3
  336. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  337. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  338. package/src/runtime/workflow/schedules/browser-probe.ts +258 -0
  339. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +40 -0
  340. package/src/runtime/workflow/schedules/engine.ts +509 -80
  341. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  342. package/src/runtime/workflow/schedules/settlement.ts +48 -4
  343. package/src/runtime/workflow/schedules/store.ts +131 -10
  344. package/src/runtime/workflow/schedules/types.ts +6 -3
  345. package/src/runtime/workflow/schedules/work-continuation.ts +120 -34
  346. package/src/testing/test-governance.ts +8 -40
  347. package/assets/reference-configs/heartbeat-triage.md +0 -57
  348. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  349. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  350. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  351. package/bin/forge-desktop-helper.mjs +0 -204
  352. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  353. package/docs/architecture/current/architecture-invariants.md +0 -331
  354. package/docs/architecture/current/automation-and-schedule-engine.md +0 -453
  355. package/docs/architecture/current/codegraph-context-provider.md +0 -226
  356. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  357. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  358. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -354
  359. package/docs/architecture/current/entity-model.md +0 -592
  360. package/docs/architecture/current/external-plugin-governance.md +0 -396
  361. package/docs/architecture/current/failure-recovery.md +0 -447
  362. package/docs/architecture/current/governance.md +0 -213
  363. package/docs/architecture/current/human-interaction-plane.md +0 -87
  364. package/docs/architecture/current/implementation-status.md +0 -128
  365. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  366. package/docs/architecture/current/legacy-governance.md +0 -58
  367. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  368. package/docs/architecture/current/migration-roadmap.md +0 -105
  369. package/docs/architecture/current/multi-repository-execution.md +0 -371
  370. package/docs/architecture/current/operations-runbook.md +0 -45
  371. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  372. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  373. package/docs/architecture/current/repository-access-modes.md +0 -102
  374. package/docs/architecture/current/route-policy.md +0 -15
  375. package/docs/architecture/current/route-session-concurrency.md +0 -85
  376. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  377. package/docs/architecture/current/runtime-directory-map.md +0 -49
  378. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  379. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  380. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  381. package/docs/architecture/current/system-overview.md +0 -144
  382. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  383. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  384. package/docs/architecture/current/thin-harness-v1.md +0 -311
  385. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  386. package/docs/forge-autonomous-goal-loop.md +0 -9
  387. package/docs/forge-provider-configuration.md +0 -7
  388. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -83
  389. package/docs/operations/controller-desktop-plugin.md +0 -112
  390. package/docs/operations/gmail-assistant-routines.md +0 -186
  391. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  392. package/docs/operations/provider-configuration.md +0 -31
  393. package/scripts/harness-trace-grade.sh +0 -150
  394. package/scripts/heartbeat-triage.sh +0 -361
  395. package/scripts/maintenance-triage.sh +0 -167
  396. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  397. package/src/cli/repositories/goal-registry.ts +0 -239
  398. package/src/runtime/assistant/action-proposals.ts +0 -279
  399. package/src/runtime/assistant/intent.ts +0 -395
  400. package/src/runtime/assistant/model-provider.ts +0 -460
  401. package/src/runtime/assistant/openapi.ts +0 -223
  402. package/src/runtime/assistant/policy.ts +0 -89
  403. package/src/runtime/assistant/readiness.ts +0 -258
  404. package/src/runtime/assistant/routine-runtime.ts +0 -477
  405. package/src/runtime/assistant/schedule-binding.ts +0 -171
  406. package/src/runtime/assistant/standing-grants.ts +0 -375
  407. package/src/runtime/assistant/store.ts +0 -243
  408. package/src/runtime/assistant/types.ts +0 -135
  409. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  410. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  411. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  412. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  413. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  414. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  415. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  416. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  417. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  418. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  419. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  420. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  421. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  422. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  423. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  424. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  425. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  426. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  427. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  428. package/src/runtime/plugins/desktop-adapter.ts +0 -374
  429. package/src/runtime/safe-tooling/browser-safe.ts +0 -133
  430. package/src/runtime/workflow/findings/store.ts +0 -204
  431. package/src/runtime/workflow/findings/types.ts +0 -58
  432. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  433. package/src/runtime/workflow/portfolio/store.ts +0 -185
  434. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -52,13 +52,6 @@ const LOCAL_JOBS_LEGACY_ACTIVE_PATTERNS = [
52
52
  /local jobs?.*finish before runtime storage/i,
53
53
  ];
54
54
 
55
- const BROWSER_GRANT_PATTERNS = [
56
- /web_target_not_allowed/i,
57
- /browser.*domain.*not allowed/i,
58
- /allowed_domains/i,
59
- /domain access.*required/i,
60
- /browser domain grant/i,
61
- ];
62
55
 
63
56
  const EXTERNAL_FILESYSTEM_GRANT_PATTERNS = [
64
57
  /external filesystem/i,
@@ -125,7 +118,6 @@ export function classifyFailure(message: string | undefined): RecoveryClass {
125
118
  if (LOCAL_JOBS_UNREADABLE_PATTERNS.some((pattern) => pattern.test(text))) return 'local_jobs_unreadable';
126
119
  if (RUNTIME_STORAGE_PATTERNS.some((pattern) => pattern.test(text))) return 'runtime_storage_not_ready';
127
120
  if (AUTH_PATTERNS.some((pattern) => pattern.test(text))) return 'auth_required';
128
- if (BROWSER_GRANT_PATTERNS.some((pattern) => pattern.test(text))) return 'browser_domain_grant_required';
129
121
  if (EXTERNAL_FILESYSTEM_GRANT_PATTERNS.some((pattern) => pattern.test(text))) return 'external_filesystem_grant_required';
130
122
  if (DIRTY_WORKTREE_CONFLICT_PATTERNS.some((pattern) => pattern.test(text))) return 'dirty_worktree_conflict';
131
123
  if (USER_ACTION_PATTERNS.some((pattern) => pattern.test(text))) return 'user_action_required';
@@ -144,7 +136,6 @@ export function dominantRecoveryClass(classes: readonly RecoveryClass[]): Recove
144
136
  'local_jobs_legacy_active',
145
137
  'local_jobs_unreadable',
146
138
  'runtime_storage_not_ready',
147
- 'browser_domain_grant_required',
148
139
  'external_filesystem_grant_required',
149
140
  'local_jobs_reconciliation_required',
150
141
  'maintenance_executor_required',
@@ -1,12 +1,18 @@
1
1
  import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'fs';
2
2
  import { basename, dirname, join, relative, resolve } from 'path';
3
3
  import { tmpdir } from 'os';
4
+ import { runProcess } from '../../effects/process-runner';
4
5
  import { cleanupEditSession, getEditSession, listEditSessions, reconcileEditSession } from '../../cli/editing/edit-session';
5
6
  import { ensureRepositoryRuntimeStorage, type RepositoryRuntimeStorageReport } from '../../cli/repositories/runtime-storage';
6
7
  import type { RepositoryRecord } from '../../cli/repositories/types';
7
8
  import { rebuildRepositoryProjection } from '../projections/materialized-view';
8
9
  import { getWorkContract, readWorkContractStore, transitionWorkContractPhase } from '../control-plane/facade/work-contract-store';
9
- import { isTerminalWorkContractStatus } from '../control-plane/facade/types';
10
+ import { getControllerSession, withControllerSessionTerminalizationFence } from '../control-plane/facade/controller-session-store';
11
+ import { listPlanContracts } from '../control-plane/facade/plan-contract-store';
12
+ import { readRequirement } from '../control-plane/persistence/requirement-store';
13
+ import { listControlPlaneRecords, type ControlPlaneRecord } from '../control-plane/persistence/sqlite-store';
14
+ import { isTerminalWorkContractStatus, type WorkContract } from '../control-plane/facade/types';
15
+ import { listSchedules } from '../workflow/schedules/store';
10
16
  import {
11
17
  collectRuntimeProcesses,
12
18
  removeRuntimeTempEntry,
@@ -36,11 +42,14 @@ export type RuntimeMaintenanceCandidateKind =
36
42
  | 'runtime_storage_warning'
37
43
  | 'stale_runtime_temp_entry'
38
44
  | 'stale_work_contract'
39
- | 'stale_edit_session';
45
+ | 'stale_edit_session'
46
+ | 'retained_migrated_work'
47
+ | 'unowned_managed_worktree';
40
48
 
41
49
  export interface RuntimeMaintenanceRepository {
42
50
  repoId: string;
43
51
  canonicalRoot: string;
52
+ defaultBranch?: string;
44
53
  /** Test-only or embedded-runtime override. Production defaults to system temp roots. */
45
54
  runtimeTempRoots?: string[];
46
55
  }
@@ -79,6 +88,11 @@ export interface RuntimeMaintenanceCandidate {
79
88
  deadlineAt?: string;
80
89
  suggestedAction: RuntimeMaintenanceActionId;
81
90
  ownershipStatus?: 'explicit' | 'unknown';
91
+ sourceControllerHome?: string;
92
+ objective?: string;
93
+ continuationPrompt?: string;
94
+ disposition?: 'resume_or_supersede_review' | 'completion_reconciliation' | 'ownership_reconciliation' | 'source_preservation_required';
95
+ sourceState?: 'no_managed_source' | 'clean_integrated' | 'dirty_worktree' | 'unintegrated_commits' | 'source_state_unknown';
82
96
  }
83
97
 
84
98
  export interface RuntimeMaintenanceSummary {
@@ -93,6 +107,8 @@ export interface RuntimeMaintenanceSummary {
93
107
  staleRuntimeTempEntries: number;
94
108
  staleWorkContracts: number;
95
109
  staleEditSessions: number;
110
+ retainedMigratedWork: number;
111
+ unownedManagedWorktrees: number;
96
112
  }
97
113
 
98
114
  export interface RuntimeMaintenanceStatus {
@@ -223,6 +239,8 @@ function summarize(candidates: RuntimeMaintenanceCandidate[]): RuntimeMaintenanc
223
239
  staleRuntimeTempEntries: candidates.filter((candidate) => candidate.kind === 'stale_runtime_temp_entry').length,
224
240
  staleWorkContracts: candidates.filter((candidate) => candidate.kind === 'stale_work_contract').length,
225
241
  staleEditSessions: candidates.filter((candidate) => candidate.kind === 'stale_edit_session').length,
242
+ retainedMigratedWork: candidates.filter((candidate) => candidate.kind === 'retained_migrated_work').length,
243
+ unownedManagedWorktrees: candidates.filter((candidate) => candidate.kind === 'unowned_managed_worktree').length,
226
244
  };
227
245
  }
228
246
 
@@ -389,6 +407,282 @@ function scanRuntimeTempCandidates(
389
407
  }));
390
408
  }
391
409
 
410
+ interface RetainedMigratedWork {
411
+ migrationId: string;
412
+ sourceHome: string;
413
+ work: WorkContract;
414
+ }
415
+
416
+ interface AppliedControllerHomeMigrationRecord {
417
+ migrationId: string;
418
+ status: 'applied' | 'rolled_back';
419
+ sourceHome: string;
420
+ }
421
+
422
+ function listRetainedMigratedWork(destinationHomeInput: string, repoId: string): RetainedMigratedWork[] {
423
+ const destinationHome = resolve(destinationHomeInput);
424
+ const destinationWorkIds = new Set(
425
+ listControlPlaneRecords<WorkContract>(destinationHome, { namespace: 'work_contract', scope: repoId, limit: 5_000 })
426
+ .map((record) => record.key),
427
+ );
428
+ const migrations = listControlPlaneRecords<AppliedControllerHomeMigrationRecord>(destinationHome, {
429
+ namespace: 'controller_home_migration',
430
+ scope: 'controller',
431
+ limit: 100,
432
+ });
433
+ const retained: RetainedMigratedWork[] = [];
434
+ for (const record of migrations) {
435
+ const migration = record.value;
436
+ if (migration.status !== 'applied') continue;
437
+ const sourceHome = resolve(migration.sourceHome);
438
+ if (!existsSync(join(sourceHome, 'control-plane.sqlite'))) continue;
439
+ let sourceWork: ControlPlaneRecord<WorkContract>[];
440
+ try {
441
+ sourceWork = listControlPlaneRecords<WorkContract>(sourceHome, { namespace: 'work_contract', limit: 5_000 });
442
+ } catch {
443
+ continue;
444
+ }
445
+ for (const source of sourceWork) {
446
+ const work = source.value;
447
+ if (work.repoId !== repoId || !isTerminalWorkContractStatus(work.status)) continue;
448
+ if (!work.worktreeRef?.trim() || !existsSync(work.worktreeRef)) continue;
449
+ if (destinationWorkIds.has(work.workId)) continue;
450
+ retained.push({ migrationId: migration.migrationId, sourceHome, work });
451
+ }
452
+ }
453
+ return retained.sort((left, right) => left.work.updatedAt.localeCompare(right.work.updatedAt));
454
+ }
455
+
456
+ function gitWorktreePaths(repoRoot: string): string[] {
457
+ const result = runProcess('git', ['worktree', 'list', '--porcelain'], {
458
+ cwd: repoRoot,
459
+ timeoutMs: 10_000,
460
+ maxOutputBytes: 500_000,
461
+ });
462
+ if (!result.ok) return [];
463
+ return result.stdout
464
+ .split(/\r?\n/)
465
+ .filter((line) => line.startsWith('worktree '))
466
+ .map((line) => resolve(line.slice('worktree '.length).trim()))
467
+ .filter(Boolean);
468
+ }
469
+
470
+ function looksLikeForgeManagedWorktree(path: string): boolean {
471
+ const normalized = resolve(path).replace(/\\/g, '/');
472
+ return normalized.includes('/.forge/managed-worktrees/')
473
+ || normalized.includes('/.forge/controller/managed-worktrees/')
474
+ || normalized.includes('/.repo-harness/managed-worktrees/')
475
+ || normalized.includes('/managed-worktrees/repo_');
476
+ }
477
+
478
+ type StaleWorkSourceState = NonNullable<RuntimeMaintenanceCandidate['sourceState']>;
479
+
480
+ interface StaleWorkSourceInspection {
481
+ safeToCancel: boolean;
482
+ state: StaleWorkSourceState;
483
+ path?: string;
484
+ detail: string;
485
+ }
486
+
487
+ function inspectStaleWorkRepositorySource(
488
+ repository: RuntimeMaintenanceRepository,
489
+ contract: WorkContract,
490
+ ): StaleWorkSourceInspection {
491
+ const recordedPath = contract.worktreeRef?.trim();
492
+ if (!recordedPath) {
493
+ return {
494
+ safeToCancel: true,
495
+ state: 'no_managed_source',
496
+ detail: 'Work has no recorded managed worktree source.',
497
+ };
498
+ }
499
+ const path = resolve(recordedPath);
500
+ if (!looksLikeForgeManagedWorktree(path)) {
501
+ return {
502
+ safeToCancel: true,
503
+ state: 'no_managed_source',
504
+ path,
505
+ detail: 'Recorded Work path is not a Forge-managed worktree and is not disposable maintenance source.',
506
+ };
507
+ }
508
+ if (!existsSync(path)) {
509
+ return {
510
+ safeToCancel: false,
511
+ state: 'source_state_unknown',
512
+ path,
513
+ detail: 'Recorded managed worktree path is absent, so maintenance cannot prove that no unique branch/source remains recoverable through Git or checkout metadata; destructive stale-Work cancellation is fenced.',
514
+ };
515
+ }
516
+
517
+ const status = runProcess('git', ['status', '--porcelain', '--untracked-files=all'], {
518
+ cwd: path,
519
+ timeoutMs: 10_000,
520
+ maxOutputBytes: 500_000,
521
+ });
522
+ if (!status.ok) {
523
+ return {
524
+ safeToCancel: false,
525
+ state: 'source_state_unknown',
526
+ path,
527
+ detail: `Managed worktree source state could not be read (${status.stderr || 'git status failed'}); destructive stale-Work cancellation is fenced.`,
528
+ };
529
+ }
530
+ if (status.stdout.trim()) {
531
+ return {
532
+ safeToCancel: false,
533
+ state: 'dirty_worktree',
534
+ path,
535
+ detail: 'Managed worktree contains uncommitted or untracked repository source; durable metadata/process evidence is not a substitute for preserving the live source checkout.',
536
+ };
537
+ }
538
+
539
+ const head = runProcess('git', ['rev-parse', 'HEAD'], {
540
+ cwd: path,
541
+ timeoutMs: 10_000,
542
+ maxOutputBytes: 100_000,
543
+ });
544
+ const targetBranch = repository.defaultBranch?.trim() || 'main';
545
+ const target = runProcess('git', ['rev-parse', '--verify', `refs/heads/${targetBranch}`], {
546
+ cwd: repository.canonicalRoot,
547
+ timeoutMs: 10_000,
548
+ maxOutputBytes: 100_000,
549
+ });
550
+ if (!head.ok || !head.stdout.trim() || !target.ok || !target.stdout.trim()) {
551
+ return {
552
+ safeToCancel: false,
553
+ state: 'source_state_unknown',
554
+ path,
555
+ detail: `Managed worktree HEAD or target branch ${targetBranch} could not be resolved; destructive stale-Work cancellation is fenced.`,
556
+ };
557
+ }
558
+ const unique = runProcess('git', ['rev-list', '--count', `${target.stdout.trim()}..${head.stdout.trim()}`], {
559
+ cwd: repository.canonicalRoot,
560
+ timeoutMs: 10_000,
561
+ maxOutputBytes: 100_000,
562
+ });
563
+ if (!unique.ok || !/^\d+$/.test(unique.stdout.trim())) {
564
+ return {
565
+ safeToCancel: false,
566
+ state: 'source_state_unknown',
567
+ path,
568
+ detail: 'Managed worktree unique-commit state could not be proven; destructive stale-Work cancellation is fenced.',
569
+ };
570
+ }
571
+ if (Number(unique.stdout.trim()) > 0) {
572
+ return {
573
+ safeToCancel: false,
574
+ state: 'unintegrated_commits',
575
+ path,
576
+ detail: `Managed worktree HEAD has ${unique.stdout.trim()} commit(s) not reachable from ${targetBranch}; explicit semantic adoption or cleanup authority is required.`,
577
+ };
578
+ }
579
+ return {
580
+ safeToCancel: true,
581
+ state: 'clean_integrated',
582
+ path,
583
+ detail: `Managed worktree is clean and its HEAD is already reachable from ${targetBranch}.`,
584
+ };
585
+ }
586
+
587
+ function staleWorkCandidateReason(source: StaleWorkSourceInspection): string {
588
+ return source.safeToCancel
589
+ ? `Nonterminal WorkContract exceeded the explicit maintenance age threshold and has no active Plan, Requirement, Schedule, or Controller authority. ${source.detail} Explicit full maintenance may cancel Work metadata while retaining durable evidence.`
590
+ : `Protected stale Work: no active lifecycle authority remains, but repository source preservation is required. ${source.detail} The Work must remain nonterminal until source is explicitly adopted, delivered, or cleanup is semantically authorized.`;
591
+ }
592
+
593
+ function scanRetainedWorktreeCandidates(
594
+ repository: RuntimeMaintenanceRepository,
595
+ controllerHome: string,
596
+ maxCandidates: number,
597
+ ): RuntimeMaintenanceCandidate[] {
598
+ const worktreePaths = gitWorktreePaths(repository.canonicalRoot);
599
+ if (worktreePaths.length === 0) return [];
600
+ const normalizedInventory = new Set(worktreePaths.map((path) => resolve(path)));
601
+ const currentContracts = readWorkContractStore({ controllerHome, repoId: repository.repoId }).contracts;
602
+ const currentOwnedPaths = new Set(
603
+ currentContracts
604
+ .map((contract) => contract.worktreeRef?.trim())
605
+ .filter((path): path is string => Boolean(path))
606
+ .map((path) => resolve(path)),
607
+ );
608
+ const candidates: RuntimeMaintenanceCandidate[] = [];
609
+ const retained = listRetainedMigratedWork(controllerHome, repository.repoId);
610
+ const retainedByPath = new Map(retained.map((entry) => [resolve(entry.work.worktreeRef!), entry]));
611
+
612
+ for (const path of worktreePaths) {
613
+ const normalized = resolve(path);
614
+ if (normalized === resolve(repository.canonicalRoot) || currentOwnedPaths.has(normalized)) continue;
615
+ const migrated = retainedByPath.get(normalized);
616
+ if (migrated) {
617
+ const completed = migrated.work.status === 'completed';
618
+ candidates.push({
619
+ kind: 'retained_migrated_work',
620
+ id: migrated.work.workId,
621
+ path: normalized,
622
+ status: migrated.work.status,
623
+ safe: false,
624
+ reason: completed
625
+ ? 'A terminal Work from a retired Controller Home still owns a Git worktree and requires completion/cleanup reconciliation under the current Controller before lifecycle health can be reported.'
626
+ : 'A terminal Work from a retired Controller Home still owns preserved implementation state in a Git worktree. Review and explicitly resume or supersede it under current authority; automatic maintenance must not delete it.',
627
+ suggestedAction: 'full_maintenance_pass',
628
+ ownershipStatus: 'explicit',
629
+ sourceControllerHome: migrated.sourceHome,
630
+ objective: migrated.work.objective,
631
+ continuationPrompt: migrated.work.continuationPrompt,
632
+ disposition: completed ? 'completion_reconciliation' : 'resume_or_supersede_review',
633
+ });
634
+ continue;
635
+ }
636
+ if (!looksLikeForgeManagedWorktree(normalized)) continue;
637
+ candidates.push({
638
+ kind: 'unowned_managed_worktree',
639
+ id: `managed-worktree:${basename(normalized)}`,
640
+ path: normalized,
641
+ safe: false,
642
+ reason: 'Git reports a Forge-managed worktree for this repository, but the current Controller has no WorkContract ownership and no retained migration identity for it. Ownership must be reconciled before lifecycle health can be reported.',
643
+ suggestedAction: 'full_maintenance_pass',
644
+ ownershipStatus: 'unknown',
645
+ disposition: 'ownership_reconciliation',
646
+ });
647
+ }
648
+
649
+ // A retained migrated Work whose referenced path still exists but is no longer
650
+ // in Git inventory remains preservation debt rather than disappearing silently.
651
+ for (const migrated of retained) {
652
+ const normalized = resolve(migrated.work.worktreeRef!);
653
+ if (normalizedInventory.has(normalized) || currentOwnedPaths.has(normalized)) continue;
654
+ // Legacy WorkContract records sometimes stored the repository checkout/root
655
+ // in worktreeRef even though it was not a Controller-managed worktree. Such
656
+ // historical references remain audit evidence, but must not turn every old
657
+ // repository root into current lifecycle debt. Only known Forge/RepoHarness
658
+ // managed-worktree storage can represent preserved implementation here.
659
+ if (!looksLikeForgeManagedWorktree(normalized)) continue;
660
+ // A removed legacy campaign may leave an empty managed-storage directory
661
+ // behind after its Git metadata and contents are already gone. That is not
662
+ // preserved implementation and must not block lifecycle health. A retained
663
+ // path outside current Git inventory only remains lifecycle debt when its
664
+ // worktree .git marker still exists for manual reconciliation.
665
+ if (!existsSync(join(normalized, '.git'))) continue;
666
+ const completed = migrated.work.status === 'completed';
667
+ candidates.push({
668
+ kind: 'retained_migrated_work',
669
+ id: migrated.work.workId,
670
+ path: normalized,
671
+ status: migrated.work.status,
672
+ safe: false,
673
+ reason: 'A terminal Work retained by a migrated Controller Home still has preserved filesystem state, but Git no longer reports it as a registered worktree. Manual lifecycle reconciliation is required; automatic cleanup is forbidden.',
674
+ suggestedAction: 'full_maintenance_pass',
675
+ ownershipStatus: 'explicit',
676
+ sourceControllerHome: migrated.sourceHome,
677
+ objective: migrated.work.objective,
678
+ continuationPrompt: migrated.work.continuationPrompt,
679
+ disposition: completed ? 'completion_reconciliation' : 'resume_or_supersede_review',
680
+ });
681
+ }
682
+
683
+ return candidates.slice(0, maxCandidates);
684
+ }
685
+
392
686
  function normalizedOptions(options: RuntimeMaintenanceOptions = {}): Required<RuntimeMaintenanceOptions> {
393
687
  return {
394
688
  minAgeMinutes: clampNumber(options.minAgeMinutes, DEFAULT_MIN_AGE_MINUTES, 0, 7 * 24 * 60),
@@ -397,6 +691,29 @@ function normalizedOptions(options: RuntimeMaintenanceOptions = {}): Required<Ru
397
691
  };
398
692
  }
399
693
 
694
+ function activeWorkAuthorityRefs(
695
+ repository: RuntimeMaintenanceRepository,
696
+ controllerHome: string,
697
+ contract: WorkContract,
698
+ ): string[] {
699
+ const refs: string[] = [];
700
+ if (getControllerSession({ controllerHome, repoId: repository.repoId }, contract.workId)) refs.push('controller_session');
701
+ const activePlans = listPlanContracts({ controllerHome, repoId: repository.repoId, status: 'active', limit: 100 });
702
+ const plan = activePlans.find((candidate) => candidate.planId === contract.planId || candidate.steps.some((step) => step.workId === contract.workId));
703
+ if (plan) refs.push(`plan:${plan.planId}`);
704
+ if (contract.requirementId) {
705
+ const requirement = readRequirement({ controllerHome }, contract.requirementId)?.value;
706
+ if (requirement && !['done', 'cancelled'].includes(requirement.state)) refs.push(`requirement:${requirement.requirementId}:${requirement.state}`);
707
+ }
708
+ const boundSchedule = listSchedules(controllerHome, repository.repoId).find((schedule) => {
709
+ if (!schedule.enabled) return false;
710
+ const args = schedule.action.arguments as Record<string, unknown> | undefined;
711
+ return args?.work_id === contract.workId;
712
+ });
713
+ if (boundSchedule) refs.push(`schedule:${boundSchedule.scheduleId}`);
714
+ return refs;
715
+ }
716
+
400
717
  function scanStaleWorkContractCandidates(
401
718
  repository: RuntimeMaintenanceRepository,
402
719
  controllerHome: string,
@@ -411,20 +728,109 @@ function scanStaleWorkContractCandidates(
411
728
  return { contract, ageMinutes };
412
729
  })
413
730
  .filter(({ ageMinutes }) => ageMinutes >= options.minAgeMinutes)
731
+ .flatMap(({ contract, ageMinutes }) => {
732
+ const authorityRefs = activeWorkAuthorityRefs(repository, controllerHome, contract);
733
+ // A live Plan/Requirement/Schedule/Controller reference is lifecycle authority,
734
+ // not maintenance debt. Keep the Work fenced until that authority disappears;
735
+ // if it later becomes unowned, the next scan will surface it as stale.
736
+ if (authorityRefs.length > 0) return [];
737
+ return [{ contract, ageMinutes, source: inspectStaleWorkRepositorySource(repository, contract) }];
738
+ })
414
739
  .sort((left, right) => right.ageMinutes - left.ageMinutes)
415
740
  .slice(0, options.maxCandidates)
416
- .map(({ contract, ageMinutes }) => ({
741
+ .map(({ contract, ageMinutes, source }) => ({
417
742
  kind: 'stale_work_contract' as const,
418
743
  id: contract.workId,
744
+ path: source.path,
419
745
  status: contract.status,
420
- safe: true,
421
- reason: 'Nonterminal WorkContract exceeded the explicit maintenance age threshold; cancellation preserves durable evidence and does not delete source or remote state.',
746
+ safe: source.safeToCancel,
747
+ reason: staleWorkCandidateReason(source),
422
748
  ageMinutes,
423
749
  suggestedAction: 'full_maintenance_pass' as const,
424
750
  ownershipStatus: 'explicit' as const,
751
+ sourceState: source.state,
752
+ disposition: source.safeToCancel ? undefined : ('source_preservation_required' as const),
425
753
  }));
426
754
  }
427
755
 
756
+ export function applyStaleWorkContractMaintenanceCandidate(
757
+ repository: RuntimeMaintenanceRepository,
758
+ controllerHome: string,
759
+ candidate: RuntimeMaintenanceCandidate,
760
+ ): RuntimeMaintenanceCandidate & { applied: boolean; result: string } {
761
+ const work = getWorkContract({ controllerHome, repoId: repository.repoId }, candidate.id);
762
+ if (!work || isTerminalWorkContractStatus(work.status)) {
763
+ return { ...candidate, applied: false, result: 'already_terminal' };
764
+ }
765
+
766
+ // The stale scan is discovery only. Re-evaluate every non-Controller authority
767
+ // while holding the canonical ControllerSession lock, then perform the Work
768
+ // transition before releasing it. A newer controller_claim therefore either
769
+ // wins the lock first and fences this mutation, or starts only after an already
770
+ // completed maintenance terminalization; there is no check-then-cancel gap.
771
+ const fenced = withControllerSessionTerminalizationFence(
772
+ { controllerHome, repoId: repository.repoId },
773
+ { workId: work.workId, actor: `runtime-maintenance-terminalize:${work.workId}` },
774
+ () => {
775
+ const current = getWorkContract({ controllerHome, repoId: repository.repoId }, work.workId);
776
+ if (!current || isTerminalWorkContractStatus(current.status)) {
777
+ return { ...candidate, applied: false, result: 'already_terminal' };
778
+ }
779
+ const authorityRefs = activeWorkAuthorityRefs(repository, controllerHome, current);
780
+ if (authorityRefs.length > 0) {
781
+ return {
782
+ ...candidate,
783
+ applied: false,
784
+ result: `work_authority_became_active:${authorityRefs.join(',')}`,
785
+ };
786
+ }
787
+ // Discovery and mutation are separated by an arbitrary Controller/MCP delay.
788
+ // Re-probe the live worktree while the canonical ControllerSession fence is
789
+ // held so neither a newer Controller claim nor a late source write can be
790
+ // raced by stale maintenance terminalization.
791
+ const source = inspectStaleWorkRepositorySource(repository, current);
792
+ if (!source.safeToCancel) {
793
+ return {
794
+ ...candidate,
795
+ path: source.path ?? candidate.path,
796
+ safe: false,
797
+ reason: staleWorkCandidateReason(source),
798
+ sourceState: source.state,
799
+ disposition: 'source_preservation_required' as const,
800
+ applied: false,
801
+ result: `work_source_preserved:${source.state}`,
802
+ };
803
+ }
804
+ transitionWorkContractPhase({ controllerHome, repoId: repository.repoId }, current.workId, {
805
+ phase: 'cleanup',
806
+ status: 'cancelled',
807
+ state: 'skipped',
808
+ summary: 'Cancelled by explicit full maintenance stale Work cleanup after proving no unique live repository source; durable evidence retained.',
809
+ evidenceRefs: current.evidenceRefs,
810
+ });
811
+ return {
812
+ ...candidate,
813
+ path: source.path ?? candidate.path,
814
+ safe: true,
815
+ reason: staleWorkCandidateReason(source),
816
+ sourceState: source.state,
817
+ disposition: undefined,
818
+ applied: true,
819
+ result: 'work_contract_cancelled_evidence_retained',
820
+ };
821
+ },
822
+ );
823
+ if (!fenced.allowed) {
824
+ const generation = fenced.owner?.claimGeneration;
825
+ return {
826
+ ...candidate,
827
+ applied: false,
828
+ result: `work_terminalization_fenced:${fenced.reason}${typeof generation === 'number' ? `:claim_generation=${generation}` : ''}`,
829
+ };
830
+ }
831
+ return fenced.value;
832
+ }
833
+
428
834
  function scanStaleEditSessionCandidates(
429
835
  repository: RuntimeMaintenanceRepository,
430
836
  controllerHome: string,
@@ -520,7 +926,8 @@ export function buildRuntimeMaintenanceStatus(
520
926
  const tempCandidates = scanRuntimeTempCandidates(repository, normalized.maxCandidates);
521
927
  const staleWorkCandidates = scanStaleWorkContractCandidates(repository, controllerHome, normalized);
522
928
  const staleEditCandidates = scanStaleEditSessionCandidates(repository, controllerHome, normalized);
523
- const candidates = [...localJobCandidates, ...storageCandidates, ...staleWorkCandidates, ...staleEditCandidates, ...tempCandidates].slice(0, normalized.maxCandidates);
929
+ const retainedWorktreeCandidates = scanRetainedWorktreeCandidates(repository, controllerHome, normalized.maxCandidates);
930
+ const candidates = [...localJobCandidates, ...storageCandidates, ...staleWorkCandidates, ...staleEditCandidates, ...retainedWorktreeCandidates, ...tempCandidates].slice(0, normalized.maxCandidates);
524
931
  const summary = summarize(candidates);
525
932
  const blockingCandidates = candidates.filter((candidate) => candidate.kind !== 'stale_runtime_temp_entry');
526
933
  const readyForExecution = storage.report?.readyForExecution === true
@@ -556,6 +963,7 @@ export function buildRuntimeMaintenanceStatus(
556
963
  'Pending approvals are not cancelled unless cancel_pending_approvals is explicitly enabled.',
557
964
  'System temp cleanup only removes direct forge-prefixed children of approved temp roots after a 24-hour retention period and a fresh process-occupancy check.',
558
965
  'Runtime lifecycle changes and source repair are outside the runtime maintenance executor.',
966
+ 'Retained or unowned managed worktrees are review-only lifecycle blockers. Runtime maintenance never deletes or modifies their preserved implementation state.',
559
967
  ],
560
968
  };
561
969
  }
@@ -774,18 +1182,7 @@ export function applyRuntimeMaintenance(
774
1182
  };
775
1183
  }
776
1184
  if (candidate.kind === 'stale_work_contract') {
777
- const work = getWorkContract({ controllerHome, repoId: repository.repoId }, candidate.id);
778
- if (!work || isTerminalWorkContractStatus(work.status)) {
779
- return { ...candidate, applied: false, result: 'already_terminal' };
780
- }
781
- transitionWorkContractPhase({ controllerHome, repoId: repository.repoId }, work.workId, {
782
- phase: 'cleanup',
783
- status: 'cancelled',
784
- state: 'skipped',
785
- summary: 'Cancelled by explicit full maintenance stale Work cleanup; durable evidence retained.',
786
- evidenceRefs: work.evidenceRefs,
787
- });
788
- return { ...candidate, applied: true, result: 'work_contract_cancelled_evidence_retained' };
1185
+ return applyStaleWorkContractMaintenanceCandidate(repository, controllerHome, candidate);
789
1186
  }
790
1187
  if (candidate.kind === 'stale_edit_session') {
791
1188
  const cleaned = cleanupEditSession(repository.canonicalRoot, candidate.id, {
@@ -61,7 +61,6 @@ function failingJobClass(input: CapabilityRecoveryInput): RecoveryClass {
61
61
  function suggestedActionsForClass(recoveryClass: RecoveryClass): RecoveryActionDescriptor[] {
62
62
  if (recoveryClass === 'platform_blocked' || recoveryClass === 'dirty_worktree_conflict') return [RECOVERY_ACTIONS.createPatchHandoff];
63
63
  if (recoveryClass === 'auth_required') return [RECOVERY_ACTIONS.workspaceAuthLoginPrepare];
64
- if (recoveryClass === 'browser_domain_grant_required') return [RECOVERY_ACTIONS.browserDomainAccessPreview];
65
64
  if (recoveryClass === 'external_filesystem_grant_required') return [RECOVERY_ACTIONS.externalFilesystemGrantPreview];
66
65
  if (recoveryClass === 'agent_runtime_failure') return [RECOVERY_ACTIONS.reconcileJobs];
67
66
  if (recoveryClass === 'source_defect_suspected' || recoveryClass === 'external_lifecycle_required') return [];
@@ -9,7 +9,6 @@ export type RecoveryClass =
9
9
  | 'stale_runtime_state'
10
10
  | 'external_lifecycle_required'
11
11
  | 'runtime_storage_not_ready'
12
- | 'browser_domain_grant_required'
13
12
  | 'external_filesystem_grant_required'
14
13
  | 'local_jobs_legacy_active'
15
14
  | 'local_jobs_unreadable'