@moretea-labs/forge 1.6.0 → 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 (396) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +26 -0
  3. package/CLAUDE.md +2 -0
  4. package/README.md +2 -2
  5. package/README.zh-CN.md +1 -1
  6. package/SKILL.md +2 -2
  7. package/THIRD_PARTY_NOTICES.md +1 -1
  8. package/agents/openai.yaml +1 -1
  9. package/assets/hooks/hook-input.sh +11 -8
  10. package/assets/hooks/lib/session-state.sh +10 -7
  11. package/assets/hooks/lib/workflow-state.sh +13 -177
  12. package/assets/hooks/post-tool-observer.sh +8 -2
  13. package/assets/hooks/prompt-guard.sh +5 -81
  14. package/assets/hooks/session-start-context.sh +56 -1
  15. package/assets/partials/05-workflow.partial.md +1 -1
  16. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  17. package/assets/plugin-registry.v1.json +1 -1
  18. package/assets/reference-configs/document-generation.md +1 -1
  19. package/assets/reference-configs/harness-overview.md +1 -1
  20. package/assets/reference-configs/sprint-contracts.md +3 -3
  21. package/assets/skill-version.json +2 -2
  22. package/assets/templates/contract.template.md +2 -7
  23. package/assets/templates/helpers/capture-plan.sh +2 -2
  24. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  25. package/assets/templates/helpers/contract-worktree.sh +1 -21
  26. package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
  27. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  28. package/assets/templates/helpers/new-plan.sh +1 -1
  29. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  30. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  31. package/assets/templates/helpers/verify-contract.sh +1 -111
  32. package/assets/templates/helpers/verify-sprint.sh +3 -180
  33. package/assets/templates/plan.template.md +1 -1
  34. package/assets/templates/review.template.md +9 -34
  35. package/assets/workflow-contract.v1.json +10 -19
  36. package/docs/README.md +10 -10
  37. package/docs/architecture/CURRENT.md +170 -0
  38. package/docs/architecture/EVOLUTION.md +42 -0
  39. package/docs/architecture/current/README.md +4 -156
  40. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  41. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  42. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  43. package/docs/architecture/history/README.md +7 -0
  44. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  45. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  46. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  47. package/docs/architecture/history.md +3 -34
  48. package/docs/architecture/index.md +15 -98
  49. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  50. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  51. package/docs/architecture/versions/1.6.md +38 -0
  52. package/docs/architecture/versions/1.7.md +41 -0
  53. package/docs/forge-chatgpt-browser-engine.md +2 -2
  54. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  55. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  56. package/docs/forge-github-issue-launcher.md +1 -1
  57. package/docs/forge-runtime-self-healing-loop.md +1 -1
  58. package/docs/operations/controller-browser-plugin.md +26 -6
  59. package/docs/operations/controller-performance-and-502.md +124 -0
  60. package/docs/operations/controller-reliability-runbook.md +1 -1
  61. package/docs/operations/features.md +2 -2
  62. package/docs/operations/github-repository.md +1 -1
  63. package/docs/operations/homebrew.md +2 -2
  64. package/docs/operations/mcp-tool-exposure.md +5 -3
  65. package/docs/wiki/Architecture.md +6 -6
  66. package/docs/wiki/Implementation.md +2 -2
  67. package/docs/wiki/Integrations.md +2 -2
  68. package/docs/wiki/Work-Lifecycle.md +1 -1
  69. package/package.json +29 -10
  70. package/scripts/activate-source-baseline.ts +17 -13
  71. package/scripts/benchmark-background-check-overlap.ts +228 -0
  72. package/scripts/benchmark-mcp-host.sh +117 -0
  73. package/scripts/benchmark-mcp-transport.ts +170 -0
  74. package/scripts/benchmark-quality-harness.ts +146 -0
  75. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  76. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  77. package/scripts/benchmark-typescript-navigation.ts +186 -0
  78. package/scripts/benchmark-work-efficiency.ts +334 -0
  79. package/scripts/capture-plan.sh +3 -3
  80. package/scripts/check-agent-tooling.sh +8 -35
  81. package/scripts/check-architecture-sync.sh +17 -86
  82. package/scripts/check-mcp-compatibility.ts +14 -0
  83. package/scripts/check-runtime-architecture.mjs +107 -64
  84. package/scripts/check-stable-baseline.ts +92 -0
  85. package/scripts/check-task-workflow.sh +4 -4
  86. package/scripts/contract-worktree.sh +1 -21
  87. package/scripts/ensure-task-workflow.sh +22 -55
  88. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  89. package/scripts/install-standalone-recovery.ts +24 -0
  90. package/scripts/lib/project-init-lib.sh +15 -44
  91. package/scripts/migrate-project-template.sh +23 -1179
  92. package/scripts/new-plan.sh +1 -1
  93. package/scripts/plan-to-todo.sh +11 -36
  94. package/scripts/run-governed-gate.ts +2 -1
  95. package/scripts/ship-worktrees.sh +2 -29
  96. package/scripts/smoke-ios-physical-device.ts +4 -2
  97. package/scripts/smoke-runtime-recovery.ts +1 -35
  98. package/scripts/smoke-schedule-engine.ts +1 -20
  99. package/scripts/verify-contract.sh +1 -111
  100. package/scripts/verify-forge-runtime.sh +0 -1
  101. package/scripts/verify-sprint.sh +3 -180
  102. package/scripts/workflow-contract.ts +1 -5
  103. package/src/cli/commands/init-hook.ts +3 -3
  104. package/src/cli/commands/mcp.ts +9 -5
  105. package/src/cli/commands/plugin.ts +1 -1
  106. package/src/cli/commands/recovery.ts +25 -3
  107. package/src/cli/commands/runtime.ts +55 -3
  108. package/src/cli/commands/security.ts +12 -1
  109. package/src/cli/commands/setup-profile.ts +31 -21
  110. package/src/cli/controller/check-runner.ts +106 -3
  111. package/src/cli/controller/composite-result.ts +1 -1
  112. package/src/cli/controller/context/focus.ts +51 -0
  113. package/src/cli/controller/context/instruction-context.ts +114 -0
  114. package/src/cli/controller/context/known-paths.ts +76 -0
  115. package/src/cli/controller/context/query-planning.ts +110 -0
  116. package/src/cli/controller/context/rule-resolution.ts +532 -0
  117. package/src/cli/controller/context/source-materializer.ts +278 -0
  118. package/src/cli/controller/context/structural.ts +16 -0
  119. package/src/cli/controller/context/types.ts +286 -0
  120. package/src/cli/controller/context-pack.ts +785 -469
  121. package/src/cli/controller/launch-agents.ts +77 -2
  122. package/src/cli/controller/operational-plan.ts +2 -2
  123. package/src/cli/controller/work-mode.ts +6 -4
  124. package/src/cli/editing/edit-session.ts +60 -7
  125. package/src/cli/github/github.ts +133 -0
  126. package/src/cli/installer/managed-entries.ts +15 -1
  127. package/src/cli/local-bridge/console-automations.ts +54 -78
  128. package/src/cli/local-bridge/console-view-models.ts +1 -35
  129. package/src/cli/local-bridge/facade-api.ts +0 -217
  130. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  131. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
  132. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  133. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
  134. package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
  135. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  136. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
  137. package/src/cli/local-bridge/frontend/src/styles.css +1 -1
  138. package/src/cli/local-bridge/frontend/src/types.ts +5 -8
  139. package/src/cli/local-bridge/job-store.ts +1 -490
  140. package/src/cli/local-bridge/server.ts +0 -513
  141. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  142. package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
  143. package/src/cli/mcp/auth.ts +5 -5
  144. package/src/cli/mcp/instructions.ts +3 -2
  145. package/src/cli/mcp/legacy-tool-service.ts +0 -189
  146. package/src/cli/mcp/multi-repository.ts +16 -5
  147. package/src/cli/mcp/oauth.ts +175 -26
  148. package/src/cli/mcp/repository-tools.ts +247 -107
  149. package/src/cli/mcp/server.ts +537 -111
  150. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  151. package/src/cli/mcp/toolset-selection.ts +1 -1
  152. package/src/cli/mcp/toolset.ts +42 -5
  153. package/src/cli/mcp/transports/http.ts +101 -14
  154. package/src/cli/mcp/transports/session-registry.ts +43 -5
  155. package/src/cli/mcp/types.ts +3 -3
  156. package/src/cli/repositories/command-classifier.ts +101 -19
  157. package/src/cli/repositories/command-executor.ts +55 -521
  158. package/src/cli/repositories/command-process.ts +108 -0
  159. package/src/cli/repositories/command-scope.ts +99 -41
  160. package/src/cli/repositories/controller-home.ts +7 -8
  161. package/src/cli/repositories/git-command-executor.ts +2 -0
  162. package/src/cli/repositories/locks.ts +53 -16
  163. package/src/cli/repositories/repository-snapshot.ts +121 -0
  164. package/src/cli/repositories/selected-path-actions.ts +17 -9
  165. package/src/cli/repositories/structured-git.ts +61 -3
  166. package/src/cli/repository/inspector.ts +553 -63
  167. package/src/cli/repository/session-cache.ts +58 -17
  168. package/src/cli/v81-entry.ts +1 -4
  169. package/src/effects/fs-transaction.ts +85 -17
  170. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  171. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  172. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  173. package/src/runtime/context/lsp-client.ts +283 -0
  174. package/src/runtime/context/semantic-navigation.ts +275 -0
  175. package/src/runtime/context/swift-navigation.ts +565 -0
  176. package/src/runtime/context/typescript-navigation.ts +226 -0
  177. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  178. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +47 -8
  179. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  180. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  181. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  182. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  183. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  184. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  185. package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
  186. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +143 -8
  187. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
  188. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +85 -8
  189. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  190. package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
  191. package/src/runtime/control-plane/facade/controller-session-store.ts +132 -1
  192. package/src/runtime/control-plane/facade/goal-workloop-access.ts +2 -0
  193. package/src/runtime/control-plane/facade/goal-workloop.ts +686 -129
  194. package/src/runtime/control-plane/facade/index.ts +1 -0
  195. package/src/runtime/control-plane/facade/plan-contract-store.ts +185 -20
  196. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  197. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  198. package/src/runtime/control-plane/facade/types.ts +22 -5
  199. package/src/runtime/control-plane/facade/work-contract-store.ts +175 -73
  200. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  201. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  202. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  203. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  204. package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
  205. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  206. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  207. package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
  208. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  209. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  210. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  211. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  212. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  213. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  214. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  215. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  216. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  217. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  218. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
  219. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +355 -116
  220. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
  221. package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
  222. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  223. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  224. package/src/runtime/control-plane/release-retention.ts +62 -2
  225. package/src/runtime/control-plane/routing/route-policy.ts +7 -16
  226. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  227. package/src/runtime/diagnostics/mcp-timing.ts +13 -0
  228. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  229. package/src/runtime/diagnostics/performance.ts +24 -1
  230. package/src/runtime/evidence/event-ledger.ts +1 -1
  231. package/src/runtime/execution/jobs/store.ts +58 -4
  232. package/src/runtime/execution/jobs/types.ts +1 -1
  233. package/src/runtime/execution/managed-workspace.ts +203 -1
  234. package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
  235. package/src/runtime/execution/process-runtime/check-result.ts +53 -0
  236. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  237. package/src/runtime/execution/process-runtime/command-facade.ts +345 -40
  238. package/src/runtime/execution/process-runtime/gc.ts +38 -6
  239. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
  240. package/src/runtime/execution/process-runtime/persisted-check.ts +47 -4
  241. package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
  242. package/src/runtime/execution/process-runtime/runtime.ts +43 -3
  243. package/src/runtime/execution/process-runtime/store.ts +1 -0
  244. package/src/runtime/execution/process-runtime/types.ts +2 -0
  245. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  246. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  247. package/src/runtime/execution/workers/executor.ts +0 -48
  248. package/src/runtime/gateway/mcp/execution-tools.ts +1192 -114
  249. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  250. package/src/runtime/gateway/mcp/router.ts +184 -82
  251. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
  252. package/src/runtime/gateway/mcp/runtime-tools.ts +2224 -1589
  253. package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
  254. package/src/runtime/plugins/browser-adapter.ts +2901 -241
  255. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  256. package/src/runtime/plugins/browser-handoff.ts +92 -2
  257. package/src/runtime/plugins/browser-macos-bridge.ts +582 -49
  258. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  259. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  260. package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
  261. package/src/runtime/plugins/browser-runtime.ts +183 -0
  262. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  263. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  264. package/src/runtime/plugins/desktop-operator-registration.ts +8 -2
  265. package/src/runtime/plugins/external-adapter.ts +324 -3
  266. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  267. package/src/runtime/plugins/first-party-registry.ts +2 -0
  268. package/src/runtime/plugins/github-adapter.ts +125 -0
  269. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  270. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  271. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  272. package/src/runtime/plugins/ios-adapter.ts +106 -16
  273. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  274. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  275. package/src/runtime/plugins/lightweight-action.ts +153 -0
  276. package/src/runtime/plugins/local-system-adapter.ts +123 -6
  277. package/src/runtime/plugins/macos-capability-broker.ts +55 -9
  278. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  279. package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
  280. package/src/runtime/plugins/resend-adapter.ts +1 -1
  281. package/src/runtime/plugins/store.ts +269 -29
  282. package/src/runtime/plugins/types.ts +35 -2
  283. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  284. package/src/runtime/projections/git-status-sampler.ts +16 -16
  285. package/src/runtime/projections/materialized-view.ts +28 -10
  286. package/src/runtime/recovery/maintenance-executor.ts +393 -28
  287. package/src/runtime/root/mcp-transport.ts +180 -10
  288. package/src/runtime/root/package-connector-service.ts +186 -8
  289. package/src/runtime/root/package-runtime-release.ts +143 -17
  290. package/src/runtime/root/package-runtime-service.ts +430 -22
  291. package/src/runtime/root/release-manifest.ts +7 -2
  292. package/src/runtime/root/release-materialize.ts +19 -0
  293. package/src/runtime/root/release-store.ts +48 -11
  294. package/src/runtime/root/runtime.ts +23 -5
  295. package/src/runtime/root/service.ts +100 -29
  296. package/src/runtime/root/types.ts +2 -0
  297. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  298. package/src/runtime/shared/trusted-executable.ts +62 -0
  299. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  300. package/src/runtime/standalone-recovery/core.ts +700 -92
  301. package/src/runtime/standalone-recovery/entry.ts +99 -23
  302. package/src/runtime/standalone-recovery/installer.ts +19 -3
  303. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  304. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  305. package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
  306. package/src/runtime/workflow/schedules/engine.ts +287 -151
  307. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  308. package/src/runtime/workflow/schedules/settlement.ts +48 -4
  309. package/src/runtime/workflow/schedules/store.ts +131 -10
  310. package/src/runtime/workflow/schedules/types.ts +2 -4
  311. package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
  312. package/src/testing/test-governance.ts +8 -40
  313. package/assets/reference-configs/heartbeat-triage.md +0 -57
  314. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  315. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  316. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  317. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  318. package/docs/architecture/current/architecture-invariants.md +0 -331
  319. package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
  320. package/docs/architecture/current/codegraph-context-provider.md +0 -238
  321. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  322. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  323. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
  324. package/docs/architecture/current/entity-model.md +0 -612
  325. package/docs/architecture/current/external-plugin-governance.md +0 -396
  326. package/docs/architecture/current/failure-recovery.md +0 -447
  327. package/docs/architecture/current/governance.md +0 -213
  328. package/docs/architecture/current/human-interaction-plane.md +0 -87
  329. package/docs/architecture/current/implementation-status.md +0 -128
  330. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  331. package/docs/architecture/current/legacy-governance.md +0 -58
  332. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  333. package/docs/architecture/current/migration-roadmap.md +0 -105
  334. package/docs/architecture/current/multi-repository-execution.md +0 -371
  335. package/docs/architecture/current/operations-runbook.md +0 -45
  336. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  337. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  338. package/docs/architecture/current/repository-access-modes.md +0 -102
  339. package/docs/architecture/current/route-policy.md +0 -15
  340. package/docs/architecture/current/route-session-concurrency.md +0 -85
  341. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  342. package/docs/architecture/current/runtime-directory-map.md +0 -49
  343. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  344. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  345. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  346. package/docs/architecture/current/system-overview.md +0 -169
  347. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  348. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  349. package/docs/architecture/current/thin-harness-v1.md +0 -321
  350. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  351. package/docs/forge-autonomous-goal-loop.md +0 -9
  352. package/docs/forge-provider-configuration.md +0 -7
  353. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
  354. package/docs/operations/gmail-assistant-routines.md +0 -186
  355. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  356. package/docs/operations/provider-configuration.md +0 -31
  357. package/scripts/harness-trace-grade.sh +0 -150
  358. package/scripts/heartbeat-triage.sh +0 -361
  359. package/scripts/maintenance-triage.sh +0 -167
  360. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  361. package/src/cli/repositories/goal-registry.ts +0 -239
  362. package/src/runtime/assistant/action-proposals.ts +0 -279
  363. package/src/runtime/assistant/intent.ts +0 -395
  364. package/src/runtime/assistant/model-provider.ts +0 -460
  365. package/src/runtime/assistant/openapi.ts +0 -223
  366. package/src/runtime/assistant/policy.ts +0 -89
  367. package/src/runtime/assistant/readiness.ts +0 -258
  368. package/src/runtime/assistant/routine-runtime.ts +0 -477
  369. package/src/runtime/assistant/schedule-binding.ts +0 -171
  370. package/src/runtime/assistant/standing-grants.ts +0 -375
  371. package/src/runtime/assistant/store.ts +0 -243
  372. package/src/runtime/assistant/types.ts +0 -135
  373. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  374. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  375. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  376. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  377. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  378. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  379. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  380. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  381. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  382. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  383. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  384. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  385. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  386. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  387. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  388. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  389. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  390. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  391. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  392. package/src/runtime/workflow/findings/store.ts +0 -204
  393. package/src/runtime/workflow/findings/types.ts +0 -58
  394. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  395. package/src/runtime/workflow/portfolio/store.ts +0 -185
  396. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -1,14 +1,16 @@
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 { getControllerSession } from '../control-plane/facade/controller-session-store';
10
+ import { getControllerSession, withControllerSessionTerminalizationFence } from '../control-plane/facade/controller-session-store';
10
11
  import { listPlanContracts } from '../control-plane/facade/plan-contract-store';
11
12
  import { readRequirement } from '../control-plane/persistence/requirement-store';
13
+ import { listControlPlaneRecords, type ControlPlaneRecord } from '../control-plane/persistence/sqlite-store';
12
14
  import { isTerminalWorkContractStatus, type WorkContract } from '../control-plane/facade/types';
13
15
  import { listSchedules } from '../workflow/schedules/store';
14
16
  import {
@@ -40,11 +42,14 @@ export type RuntimeMaintenanceCandidateKind =
40
42
  | 'runtime_storage_warning'
41
43
  | 'stale_runtime_temp_entry'
42
44
  | 'stale_work_contract'
43
- | 'stale_edit_session';
45
+ | 'stale_edit_session'
46
+ | 'retained_migrated_work'
47
+ | 'unowned_managed_worktree';
44
48
 
45
49
  export interface RuntimeMaintenanceRepository {
46
50
  repoId: string;
47
51
  canonicalRoot: string;
52
+ defaultBranch?: string;
48
53
  /** Test-only or embedded-runtime override. Production defaults to system temp roots. */
49
54
  runtimeTempRoots?: string[];
50
55
  }
@@ -83,6 +88,11 @@ export interface RuntimeMaintenanceCandidate {
83
88
  deadlineAt?: string;
84
89
  suggestedAction: RuntimeMaintenanceActionId;
85
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';
86
96
  }
87
97
 
88
98
  export interface RuntimeMaintenanceSummary {
@@ -97,6 +107,8 @@ export interface RuntimeMaintenanceSummary {
97
107
  staleRuntimeTempEntries: number;
98
108
  staleWorkContracts: number;
99
109
  staleEditSessions: number;
110
+ retainedMigratedWork: number;
111
+ unownedManagedWorktrees: number;
100
112
  }
101
113
 
102
114
  export interface RuntimeMaintenanceStatus {
@@ -227,6 +239,8 @@ function summarize(candidates: RuntimeMaintenanceCandidate[]): RuntimeMaintenanc
227
239
  staleRuntimeTempEntries: candidates.filter((candidate) => candidate.kind === 'stale_runtime_temp_entry').length,
228
240
  staleWorkContracts: candidates.filter((candidate) => candidate.kind === 'stale_work_contract').length,
229
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,
230
244
  };
231
245
  }
232
246
 
@@ -393,6 +407,282 @@ function scanRuntimeTempCandidates(
393
407
  }));
394
408
  }
395
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
+
396
686
  function normalizedOptions(options: RuntimeMaintenanceOptions = {}): Required<RuntimeMaintenanceOptions> {
397
687
  return {
398
688
  minAgeMinutes: clampNumber(options.minAgeMinutes, DEFAULT_MIN_AGE_MINUTES, 0, 7 * 24 * 60),
@@ -438,23 +728,107 @@ function scanStaleWorkContractCandidates(
438
728
  return { contract, ageMinutes };
439
729
  })
440
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
+ })
441
739
  .sort((left, right) => right.ageMinutes - left.ageMinutes)
442
740
  .slice(0, options.maxCandidates)
443
- .map(({ contract, ageMinutes }) => {
444
- const authorityRefs = activeWorkAuthorityRefs(repository, controllerHome, contract);
741
+ .map(({ contract, ageMinutes, source }) => ({
742
+ kind: 'stale_work_contract' as const,
743
+ id: contract.workId,
744
+ path: source.path,
745
+ status: contract.status,
746
+ safe: source.safeToCancel,
747
+ reason: staleWorkCandidateReason(source),
748
+ ageMinutes,
749
+ suggestedAction: 'full_maintenance_pass' as const,
750
+ ownershipStatus: 'explicit' as const,
751
+ sourceState: source.state,
752
+ disposition: source.safeToCancel ? undefined : ('source_preservation_required' as const),
753
+ }));
754
+ }
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
+ });
445
811
  return {
446
- kind: 'stale_work_contract' as const,
447
- id: contract.workId,
448
- status: contract.status,
449
- safe: authorityRefs.length === 0,
450
- reason: authorityRefs.length > 0
451
- ? `Nonterminal WorkContract is old but still has active lifecycle authority (${authorityRefs.join(', ')}); maintenance must not cancel it from age alone.`
452
- : 'Nonterminal WorkContract exceeded the explicit maintenance age threshold and has no active Plan, Requirement, Schedule, or Controller authority; explicit full maintenance may cancel it while retaining evidence.',
453
- ageMinutes,
454
- suggestedAction: 'full_maintenance_pass' as const,
455
- ownershipStatus: 'explicit' as const,
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',
456
820
  };
457
- });
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;
458
832
  }
459
833
 
460
834
  function scanStaleEditSessionCandidates(
@@ -552,7 +926,8 @@ export function buildRuntimeMaintenanceStatus(
552
926
  const tempCandidates = scanRuntimeTempCandidates(repository, normalized.maxCandidates);
553
927
  const staleWorkCandidates = scanStaleWorkContractCandidates(repository, controllerHome, normalized);
554
928
  const staleEditCandidates = scanStaleEditSessionCandidates(repository, controllerHome, normalized);
555
- 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);
556
931
  const summary = summarize(candidates);
557
932
  const blockingCandidates = candidates.filter((candidate) => candidate.kind !== 'stale_runtime_temp_entry');
558
933
  const readyForExecution = storage.report?.readyForExecution === true
@@ -588,6 +963,7 @@ export function buildRuntimeMaintenanceStatus(
588
963
  'Pending approvals are not cancelled unless cancel_pending_approvals is explicitly enabled.',
589
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.',
590
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.',
591
967
  ],
592
968
  };
593
969
  }
@@ -806,18 +1182,7 @@ export function applyRuntimeMaintenance(
806
1182
  };
807
1183
  }
808
1184
  if (candidate.kind === 'stale_work_contract') {
809
- const work = getWorkContract({ controllerHome, repoId: repository.repoId }, candidate.id);
810
- if (!work || isTerminalWorkContractStatus(work.status)) {
811
- return { ...candidate, applied: false, result: 'already_terminal' };
812
- }
813
- transitionWorkContractPhase({ controllerHome, repoId: repository.repoId }, work.workId, {
814
- phase: 'cleanup',
815
- status: 'cancelled',
816
- state: 'skipped',
817
- summary: 'Cancelled by explicit full maintenance stale Work cleanup; durable evidence retained.',
818
- evidenceRefs: work.evidenceRefs,
819
- });
820
- return { ...candidate, applied: true, result: 'work_contract_cancelled_evidence_retained' };
1185
+ return applyStaleWorkContractMaintenanceCandidate(repository, controllerHome, candidate);
821
1186
  }
822
1187
  if (candidate.kind === 'stale_edit_session') {
823
1188
  const cleaned = cleanupEditSession(repository.canonicalRoot, candidate.id, {