@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
@@ -0,0 +1,226 @@
1
+ import { statSync } from 'fs';
2
+ import { dirname, relative, resolve } from 'path';
3
+ import * as ts from 'typescript';
4
+
5
+ export type TypeScriptNavigationKind = 'definition' | 'references' | 'implementations';
6
+
7
+ export interface TypeScriptNavigationRequest {
8
+ navigation: TypeScriptNavigationKind;
9
+ path: string;
10
+ line: number;
11
+ column: number;
12
+ tsconfigPath?: string;
13
+ }
14
+
15
+ export interface TypeScriptNavigationLocation {
16
+ path: string;
17
+ line: number;
18
+ column: number;
19
+ name?: string;
20
+ kind?: string;
21
+ }
22
+
23
+ export interface TypeScriptNavigationResult {
24
+ navigation: TypeScriptNavigationKind;
25
+ target: { path: string; line: number; column: number };
26
+ locations: TypeScriptNavigationLocation[];
27
+ }
28
+
29
+ export interface TypeScriptNavigationAccess {
30
+ /** Stable identity for one read-policy scope. Restricted and unrestricted projects must never share a Language Service. */
31
+ cacheScope: string;
32
+ /** Source identity observed by the caller. A different identity must not reuse project membership. */
33
+ sourceIdentity?: string;
34
+ /** Return true only for repository-relative paths that this navigation call may read. */
35
+ allowRepositoryPath(relativePath: string): boolean;
36
+ }
37
+
38
+ interface CachedProject {
39
+ repoRoot: string;
40
+ configPath: string;
41
+ service: ts.LanguageService;
42
+ }
43
+
44
+ const projects = new Map<string, CachedProject>();
45
+ const MAX_CACHED_TYPESCRIPT_PROJECTS = 32;
46
+
47
+ function normalizePath(path: string): string {
48
+ return path.replace(/\\/g, '/');
49
+ }
50
+
51
+ function scriptVersion(path: string): string {
52
+ try {
53
+ const stat = statSync(path);
54
+ return `${stat.mtimeMs}:${stat.size}`;
55
+ } catch {
56
+ return 'missing';
57
+ }
58
+ }
59
+
60
+ function repositoryRelativePath(repoRoot: string, fileName: string): string | undefined {
61
+ const normalized = normalizePath(relative(repoRoot, resolve(fileName)));
62
+ if (!normalized || normalized === '.' || normalized === '..' || normalized.startsWith('../')) return undefined;
63
+ return normalized;
64
+ }
65
+
66
+ function loadProject(repoRoot: string, tsconfigPath = 'tsconfig.json', access?: TypeScriptNavigationAccess): CachedProject {
67
+ const root = resolve(repoRoot);
68
+ const configPath = resolve(root, tsconfigPath);
69
+ const configRelative = repositoryRelativePath(root, configPath);
70
+ if (!configRelative) throw new Error('TypeScript navigation tsconfig must be inside the repository.');
71
+ if (access && !access.allowRepositoryPath(configRelative)) {
72
+ throw new Error(`TypeScript navigation tsconfig is denied by read policy: ${configRelative}.`);
73
+ }
74
+ const cacheKey = `${root}\0${configPath}\0${scriptVersion(configPath)}\0${access?.cacheScope ?? 'unrestricted'}\0${access?.sourceIdentity ?? 'unbound-source'}`;
75
+ const cached = projects.get(cacheKey);
76
+ if (cached) {
77
+ projects.delete(cacheKey);
78
+ projects.set(cacheKey, cached);
79
+ return cached;
80
+ }
81
+
82
+ const canReadAbsolute = (fileName: string): boolean => {
83
+ if (!access) return true;
84
+ const repoRelative = repositoryRelativePath(root, fileName);
85
+ return repoRelative === undefined || access.allowRepositoryPath(repoRelative);
86
+ };
87
+ const readAllowedFile = (fileName: string): string | undefined => canReadAbsolute(fileName) ? ts.sys.readFile(fileName) : undefined;
88
+ const fileExistsAllowed = (fileName: string): boolean => canReadAbsolute(fileName) && ts.sys.fileExists(fileName);
89
+ const readDirectoryAllowed: typeof ts.sys.readDirectory = (path, extensions, exclude, include, depth) =>
90
+ ts.sys.readDirectory(path, extensions, exclude, include, depth).filter(canReadAbsolute);
91
+
92
+ const config = ts.readConfigFile(configPath, readAllowedFile);
93
+ if (config.error) {
94
+ throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, '\n'));
95
+ }
96
+ const parseHost: ts.ParseConfigHost = {
97
+ useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
98
+ readDirectory: readDirectoryAllowed,
99
+ fileExists: fileExistsAllowed,
100
+ readFile: readAllowedFile,
101
+ };
102
+ const parsed = ts.parseJsonConfigFileContent(config.config, parseHost, dirname(configPath), undefined, configPath);
103
+ if (parsed.errors.length > 0) {
104
+ throw new Error(parsed.errors.map((entry) => ts.flattenDiagnosticMessageText(entry.messageText, '\n')).join('\n'));
105
+ }
106
+ const scriptFileNames = parsed.fileNames.filter(canReadAbsolute);
107
+
108
+ const host: ts.LanguageServiceHost = {
109
+ getCompilationSettings: () => parsed.options,
110
+ getScriptFileNames: () => scriptFileNames,
111
+ getScriptVersion: scriptVersion,
112
+ getScriptSnapshot: (fileName) => {
113
+ if (!fileExistsAllowed(fileName)) return undefined;
114
+ const content = readAllowedFile(fileName);
115
+ return content === undefined ? undefined : ts.ScriptSnapshot.fromString(content);
116
+ },
117
+ getCurrentDirectory: () => root,
118
+ getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
119
+ fileExists: fileExistsAllowed,
120
+ readFile: readAllowedFile,
121
+ readDirectory: readDirectoryAllowed,
122
+ directoryExists: ts.sys.directoryExists,
123
+ getDirectories: ts.sys.getDirectories,
124
+ realpath: ts.sys.realpath,
125
+ useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
126
+ getNewLine: () => ts.sys.newLine,
127
+ };
128
+
129
+ const project = {
130
+ repoRoot: root,
131
+ configPath,
132
+ service: ts.createLanguageService(host, ts.createDocumentRegistry()),
133
+ };
134
+ projects.set(cacheKey, project);
135
+ while (projects.size > MAX_CACHED_TYPESCRIPT_PROJECTS) {
136
+ const oldestKey = projects.keys().next().value as string | undefined;
137
+ if (!oldestKey) break;
138
+ const oldest = projects.get(oldestKey);
139
+ projects.delete(oldestKey);
140
+ oldest?.service.dispose();
141
+ }
142
+ return project;
143
+ }
144
+
145
+ function sourcePosition(project: CachedProject, program: ts.Program, fileName: string, line: number, column: number): number {
146
+ if (!Number.isInteger(line) || line < 1 || !Number.isInteger(column) || column < 1) {
147
+ throw new Error('TypeScript navigation line and column must be positive 1-based integers.');
148
+ }
149
+ const source = program.getSourceFile(fileName);
150
+ if (!source) throw new Error(`TypeScript project does not include ${normalizePath(relative(project.repoRoot, fileName))}.`);
151
+ if (line > source.getLineAndCharacterOfPosition(source.getEnd()).line + 1) {
152
+ throw new Error(`TypeScript navigation line ${line} is outside ${normalizePath(relative(project.repoRoot, fileName))}.`);
153
+ }
154
+ return source.getPositionOfLineAndCharacter(line - 1, column - 1);
155
+ }
156
+
157
+ function location(
158
+ project: CachedProject,
159
+ program: ts.Program,
160
+ fileName: string,
161
+ textSpan: ts.TextSpan,
162
+ name?: string,
163
+ kind?: ts.ScriptElementKind,
164
+ ): TypeScriptNavigationLocation {
165
+ const source = program.getSourceFile(fileName);
166
+ if (!source) {
167
+ return { path: normalizePath(relative(project.repoRoot, fileName)), line: 1, column: 1, ...(name ? { name } : {}), ...(kind ? { kind } : {}) };
168
+ }
169
+ const point = source.getLineAndCharacterOfPosition(textSpan.start);
170
+ return {
171
+ path: normalizePath(relative(project.repoRoot, fileName)),
172
+ line: point.line + 1,
173
+ column: point.character + 1,
174
+ ...(name ? { name } : {}),
175
+ ...(kind ? { kind } : {}),
176
+ };
177
+ }
178
+
179
+ function dedupe(locations: TypeScriptNavigationLocation[]): TypeScriptNavigationLocation[] {
180
+ const seen = new Set<string>();
181
+ return locations.filter((entry) => {
182
+ const key = `${entry.path}:${entry.line}:${entry.column}:${entry.name ?? ''}:${entry.kind ?? ''}`;
183
+ if (seen.has(key)) return false;
184
+ seen.add(key);
185
+ return true;
186
+ });
187
+ }
188
+
189
+ export function navigateTypeScriptSymbol(
190
+ repoRoot: string,
191
+ request: TypeScriptNavigationRequest,
192
+ access?: TypeScriptNavigationAccess,
193
+ ): TypeScriptNavigationResult {
194
+ const project = loadProject(repoRoot, request.tsconfigPath, access);
195
+ const fileName = resolve(project.repoRoot, request.path);
196
+ // A references response may materialize hundreds of locations. Acquire the
197
+ // Language Service Program once per navigation so that location formatting
198
+ // does not repeatedly re-enter the service while the result is built.
199
+ const program = project.service.getProgram();
200
+ if (!program) throw new Error('TypeScript Language Service did not produce a Program.');
201
+ const position = sourcePosition(project, program, fileName, request.line, request.column);
202
+ let locations: TypeScriptNavigationLocation[] = [];
203
+
204
+ if (request.navigation === 'definition') {
205
+ locations = (project.service.getDefinitionAtPosition(fileName, position) ?? [])
206
+ .map((entry) => location(project, program, entry.fileName, entry.textSpan, entry.name, entry.kind));
207
+ } else if (request.navigation === 'implementations') {
208
+ locations = (project.service.getImplementationAtPosition(fileName, position) ?? [])
209
+ .map((entry) => location(project, program, entry.fileName, entry.textSpan, undefined, entry.kind));
210
+ } else {
211
+ locations = (project.service.findReferences(fileName, position) ?? []).flatMap((group) =>
212
+ group.references.map((entry) => location(project, program, entry.fileName, entry.textSpan, group.definition.name, group.definition.kind)),
213
+ );
214
+ }
215
+
216
+ return {
217
+ navigation: request.navigation,
218
+ target: { path: normalizePath(request.path), line: request.line, column: request.column },
219
+ locations: dedupe(locations),
220
+ };
221
+ }
222
+
223
+ export function clearTypeScriptNavigationCache(): void {
224
+ for (const project of projects.values()) project.service.dispose();
225
+ projects.clear();
226
+ }
@@ -65,3 +65,31 @@ export function compareExecutionJobDispatchRanks(
65
65
  || left.queuedAtMs - right.queuedAtMs
66
66
  || left.jobId.localeCompare(right.jobId);
67
67
  }
68
+
69
+ export function selectExecutionJobDispatchRepositories(
70
+ activeJobs: readonly ExecutionJob[],
71
+ scheduleNow: number,
72
+ lastRepoDispatch: ReadonlyMap<string, number>,
73
+ ): string[] {
74
+ const waiting = activeJobs.filter(isExecutionJobDispatchCandidate);
75
+ const rankByJobId = new Map(
76
+ waiting.map((job) => [job.jobId, rankExecutionJobForDispatch(job, scheduleNow)] as const),
77
+ );
78
+ const compareWaiting = (left: ExecutionJob, right: ExecutionJob): number =>
79
+ compareExecutionJobDispatchRanks(rankByJobId.get(left.jobId)!, rankByJobId.get(right.jobId)!);
80
+ const topByRepo = new Map<string, ExecutionJob>();
81
+ for (const job of waiting.slice().sort(compareWaiting)) {
82
+ if (!topByRepo.has(job.repoId)) topByRepo.set(job.repoId, job);
83
+ }
84
+ return [...topByRepo.keys()].sort((left, right) => {
85
+ const leftRank = rankByJobId.get(topByRepo.get(left)!.jobId)!;
86
+ const rightRank = rankByJobId.get(topByRepo.get(right)!.jobId)!;
87
+ const priority = leftRank.effectivePriority - rightRank.effectivePriority;
88
+ if (priority !== 0) return priority;
89
+ const fairness = (lastRepoDispatch.get(left) ?? 0) - (lastRepoDispatch.get(right) ?? 0);
90
+ return fairness
91
+ || leftRank.queuedAtMs - rightRank.queuedAtMs
92
+ || leftRank.jobId.localeCompare(rightRank.jobId)
93
+ || left.localeCompare(right);
94
+ });
95
+ }
@@ -1,8 +1,12 @@
1
1
  import { createHash } from 'crypto';
2
2
  import { getEditSession, listEditSessions, type EditSession } from '../../../cli/editing/edit-session';
3
+ import { globMatches } from '../../../cli/mcp/paths';
3
4
  import { runProcess } from '../../../effects/process-runner';
4
- import { getWorkContract, recordWorkCompletionReceipt } from '../facade/work-contract-store';
5
- import { isTerminalWorkContractStatus, type DirectEditWorkCompletionReceipt } from '../facade/types';
5
+ import { completeRequirementFromWork } from '../persistence/requirement-store';
6
+ import { appendWorkEvidence, getWorkContract, recordWorkCompletionReceipt, updateWorkContract } from '../facade/work-contract-store';
7
+ import { isDirectEditWorkCompletionReceipt, isTerminalWorkContractStatus, type DirectEditWorkCompletionReceipt, type WorkReconciliationRecord } from '../facade/types';
8
+ import { historicalVerificationEvidenceAtRevision } from './verification-evidence';
9
+ import { readWorkHandle } from './work-handle-store';
6
10
 
7
11
  export interface DirectEditWorkCompletionReconciliation {
8
12
  completedWorkIds: string[];
@@ -149,3 +153,193 @@ export function reconcileFinalizedDirectEditWorksAfterCommit(input: {
149
153
 
150
154
  return { completedWorkIds, examinedSessionIds, skipped, targetBranch, targetRevision };
151
155
  }
156
+
157
+ export interface ReviewedDirectEditWorkReconciliationInput {
158
+ controllerHome: string;
159
+ repoId: string;
160
+ checkoutId: string;
161
+ repoRoot: string;
162
+ workId: string;
163
+ targetBranch: string;
164
+ targetRevision: string;
165
+ comparedPaths: string[];
166
+ reviewer: string;
167
+ rationale: string;
168
+ cleanupOwnershipProof: string;
169
+ reviewedAt?: string;
170
+ }
171
+
172
+ function exactCommit(repoRoot: string, revision: string, label: string): string {
173
+ const result = git(repoRoot, ['rev-parse', '--verify', `${revision}^{commit}`]);
174
+ if (!result.ok || !result.stdout.trim()) throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_${label}_INVALID`);
175
+ return result.stdout.trim();
176
+ }
177
+
178
+ function comparedRevisionPaths(repoRoot: string, baseRevision: string, targetRevision: string): string[] {
179
+ const result = git(repoRoot, ['diff', '--name-only', '-z', baseRevision, targetRevision]);
180
+ if (!result.ok) throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_CHANGED_PATHS_UNAVAILABLE');
181
+ return [...new Set(result.stdout.split('\0').filter(Boolean))].sort((left, right) => left.localeCompare(right));
182
+ }
183
+
184
+ function normalizedComparedPaths(paths: string[]): string[] {
185
+ const normalized = [...new Set(paths.map((path) => path.trim()))].sort((left, right) => left.localeCompare(right));
186
+ if (normalized.length === 0 || normalized.length > 100 || normalized.some((path) => !path || path.startsWith('/') || path.includes('\0') || path.split('/').some((part) => part === '' || part === '.' || part === '..'))) {
187
+ throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_PATHS_INVALID');
188
+ }
189
+ return normalized;
190
+ }
191
+
192
+ /**
193
+ * Explicitly close a historically delivered Direct Edit Work whose original
194
+ * edit-session binding was missed. This is never inferred: the caller must
195
+ * supply the exact accepted revision and the complete reviewed path set.
196
+ */
197
+ export function acceptReviewedDirectEditWorkReconciliation(input: ReviewedDirectEditWorkReconciliationInput): {
198
+ workId: string;
199
+ reconciliation: WorkReconciliationRecord;
200
+ receipt: DirectEditWorkCompletionReceipt;
201
+ } {
202
+ const work = getWorkContract({ controllerHome: input.controllerHome, repoId: input.repoId }, input.workId);
203
+ if (!work || work.repoId !== input.repoId) throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_WORK_NOT_FOUND: ${input.workId}`);
204
+ if (work.checkoutId && work.checkoutId !== input.checkoutId) throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_CHECKOUT_MISMATCH: ${input.workId}`);
205
+ if (work.completionReceipt) {
206
+ const completionReceipt = work.completionReceipt;
207
+ if (isDirectEditWorkCompletionReceipt(completionReceipt) && completionReceipt.reconciliationId) {
208
+ const existing = work.reconciliations.find((entry) => entry.reconciliationId === completionReceipt.reconciliationId);
209
+ if (existing) return { workId: work.workId, reconciliation: existing, receipt: completionReceipt };
210
+ }
211
+ throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_WORK_TERMINAL: ${input.workId}`);
212
+ }
213
+ const failedHandle = work.status === 'failed'
214
+ ? readWorkHandle(input.controllerHome, input.repoId, input.workId)
215
+ : undefined;
216
+ const failedReviewedRecovery = work.workKind === 'repository_change'
217
+ && work.status === 'failed'
218
+ && failedHandle?.managedWorktree === false
219
+ && failedHandle.state === 'failed'
220
+ && failedHandle.finalization.validation === 'failed'
221
+ && String(failedHandle.finalization.lastError ?? failedHandle.failureReason ?? '').includes('WORK_HANDLE_HEAD_CHANGED');
222
+ if ((isTerminalWorkContractStatus(work.status) && !failedReviewedRecovery) || work.workKind !== 'repository_change') {
223
+ throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_WORK_NOT_ELIGIBLE: ${input.workId}`);
224
+ }
225
+ if (!work.baseRevision?.trim()) throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_BASE_REVISION_MISSING');
226
+
227
+ const baseRevision = exactCommit(input.repoRoot, work.baseRevision, 'BASE_REVISION');
228
+ const targetRevision = exactCommit(input.repoRoot, input.targetRevision.trim(), 'TARGET_REVISION');
229
+ const targetBranch = input.targetBranch.trim();
230
+ if (!targetBranch) throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_TARGET_BRANCH_REQUIRED');
231
+ if (!git(input.repoRoot, ['merge-base', '--is-ancestor', baseRevision, targetRevision]).ok) {
232
+ throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_TARGET_NOT_DESCENDANT');
233
+ }
234
+ if (!git(input.repoRoot, ['merge-base', '--is-ancestor', targetRevision, targetBranch]).ok) {
235
+ throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_TARGET_UNREACHABLE');
236
+ }
237
+
238
+ const comparedPaths = normalizedComparedPaths(input.comparedPaths);
239
+ let comparisonBaseRevision = baseRevision;
240
+ if (failedReviewedRecovery) {
241
+ const targetParent = exactCommit(input.repoRoot, `${targetRevision}^`, 'TARGET_PARENT_REVISION');
242
+ if (!git(input.repoRoot, ['merge-base', '--is-ancestor', baseRevision, targetParent]).ok) {
243
+ throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_TARGET_PARENT_NOT_DESCENDANT');
244
+ }
245
+ comparisonBaseRevision = targetParent;
246
+ }
247
+ const actualPaths = comparedRevisionPaths(input.repoRoot, comparisonBaseRevision, targetRevision);
248
+ if (comparedPaths.length !== actualPaths.length || comparedPaths.some((path, index) => path !== actualPaths[index])) {
249
+ throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_PATH_COMPARISON_MISMATCH');
250
+ }
251
+ for (const path of comparedPaths) {
252
+ if (work.forbiddenPaths.some((pattern) => globMatches(pattern, path))) throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_FORBIDDEN_PATH: ${path}`);
253
+ if (work.allowedPaths.length > 0 && !work.allowedPaths.some((pattern) => globMatches(pattern, path))) throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_PATH_OUT_OF_SCOPE: ${path}`);
254
+ }
255
+ const dirty = git(input.repoRoot, ['status', '--porcelain=v1', '--', ...comparedPaths]);
256
+ if (!dirty.ok || dirty.stdout.trim()) throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_OWNED_PATHS_DIRTY');
257
+
258
+ let verifiedAt = new Date().toISOString();
259
+ for (const checkId of work.checks) {
260
+ const candidates = historicalVerificationEvidenceAtRevision(work.checkRefs, {
261
+ sourceRevision: targetRevision,
262
+ repoId: input.repoId,
263
+ workId: work.workId,
264
+ checkoutId: work.checkoutId,
265
+ checkId,
266
+ requestedChecks: work.checks,
267
+ }).filter((entry) => entry.current && entry.record.outcome === 'valid_pass');
268
+ const distinctReceipts = new Map(candidates.map((entry) => [entry.record.receipt!.receiptId, entry]));
269
+ if (distinctReceipts.size === 0) throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_CHECK_EVIDENCE_STALE: ${checkId}`);
270
+ if (distinctReceipts.size > 1) throw new Error(`DIRECT_EDIT_WORK_RECONCILIATION_CHECK_EVIDENCE_AMBIGUOUS: ${checkId}`);
271
+ const current = [...distinctReceipts.values()][0]!;
272
+ verifiedAt = current.record.completedAt ?? current.record.recordedAt ?? verifiedAt;
273
+ }
274
+
275
+ const reviewer = input.reviewer.trim();
276
+ const rationale = input.rationale.trim();
277
+ const cleanupOwnershipProof = input.cleanupOwnershipProof.trim();
278
+ const reviewedAt = input.reviewedAt ?? new Date().toISOString();
279
+ if (!reviewer || !rationale || !cleanupOwnershipProof || Number.isNaN(Date.parse(reviewedAt))) {
280
+ throw new Error('DIRECT_EDIT_WORK_RECONCILIATION_REVIEW_METADATA_INVALID');
281
+ }
282
+ const reconciliationId = `RECNC-${createHash('sha256').update([input.repoId, input.workId, baseRevision, targetRevision, comparedPaths.join('\0'), reviewer, reviewedAt].join('\0')).digest('hex').slice(0, 16)}`;
283
+ const reconciliation: WorkReconciliationRecord = {
284
+ schemaVersion: 1,
285
+ reconciliationId,
286
+ originalExpectedRevision: targetRevision,
287
+ observedTargetRevision: targetRevision,
288
+ baseRevision,
289
+ targetBranch,
290
+ reachable: true,
291
+ method: 'owned_path_tree',
292
+ comparedPaths,
293
+ reviewer: reviewer.slice(0, 200),
294
+ reviewedAt,
295
+ unrecoverableStages: ['receipt'],
296
+ cleanupOwnershipProof: cleanupOwnershipProof.slice(0, 2_000),
297
+ rationale: rationale.slice(0, 2_000),
298
+ outcome: 'accepted_equivalence',
299
+ };
300
+ updateWorkContract({ controllerHome: input.controllerHome, repoId: input.repoId }, input.workId, {
301
+ reconciliations: [reconciliation, ...work.reconciliations.filter((entry) => entry.reconciliationId !== reconciliationId)],
302
+ });
303
+
304
+ const recordedAt = new Date().toISOString();
305
+ const receipt: DirectEditWorkCompletionReceipt = {
306
+ schemaVersion: 1,
307
+ receiptId: `REC-direct-edit-reconciled-${createHash('sha256').update(`${input.repoId}\0${input.workId}\0${reconciliationId}\0${targetRevision}`).digest('hex').slice(0, 20)}`,
308
+ source: 'direct_edit_work',
309
+ workId: input.workId,
310
+ reconciliationId,
311
+ targetBranch,
312
+ targetRevision,
313
+ sourceRevision: targetRevision,
314
+ baseRevision,
315
+ changedPaths: comparedPaths,
316
+ delivery: { kind: 'commit', status: 'integrated', strategy: 'already_integrated', reachable: true, recordedAt },
317
+ cleanup: { status: 'complete', warnings: [], blockers: [], recordedAt },
318
+ verifiedAt,
319
+ recordedAt,
320
+ };
321
+ const recorded = recordWorkCompletionReceipt(
322
+ { controllerHome: input.controllerHome, repoId: input.repoId },
323
+ input.workId,
324
+ receipt,
325
+ 'completed_changed',
326
+ 'repository_change',
327
+ );
328
+ if (recorded.requirementId) {
329
+ try {
330
+ completeRequirementFromWork({ controllerHome: input.controllerHome }, { requirementId: recorded.requirementId, work: recorded });
331
+ } catch (error) {
332
+ try {
333
+ appendWorkEvidence({ controllerHome: input.controllerHome, repoId: input.repoId }, input.workId, {
334
+ title: 'requirement completion projection pending',
335
+ summary: `Work completion remains authoritative; Requirement projection could not be applied: ${error instanceof Error ? error.message : String(error)}`.slice(0, 2_000),
336
+ detailLevel: 'summary',
337
+ });
338
+ } catch {
339
+ // Reconciliation already recorded the terminal receipt. A diagnostic
340
+ // write failure cannot make that completed Work non-terminal again.
341
+ }
342
+ }
343
+ }
344
+ return { workId: input.workId, reconciliation, receipt };
345
+ }
@@ -9,6 +9,7 @@ import {
9
9
  listControllerChecks,
10
10
  } from '../../../cli/controller/check-runner';
11
11
  import type { RepositoryRecord } from '../../../cli/repositories/types';
12
+ import { selectRepositoryCheckout } from '../../../cli/repositories/registry';
12
13
  import { executionIdentityForRepository } from './execution-identity';
13
14
  import {
14
15
  claimsForCheck,
@@ -266,6 +267,57 @@ function failedRun(
266
267
  return coordinatorResult(controllerHome, failed);
267
268
  }
268
269
 
270
+ export function resolveEditValidationRepository(
271
+ repository: RepositoryRecord,
272
+ editSessionId: string,
273
+ ): { repository: RepositoryRecord; session: EditSession } {
274
+ const checkoutIds = [...new Set([
275
+ repository.activeCheckoutId,
276
+ ...repository.checkouts.map((checkout) => checkout.checkoutId),
277
+ ].filter(Boolean))];
278
+ for (const checkoutId of checkoutIds) {
279
+ let candidate: RepositoryRecord;
280
+ try {
281
+ candidate = selectRepositoryCheckout(repository, checkoutId);
282
+ } catch {
283
+ continue;
284
+ }
285
+ let session: EditSession;
286
+ try {
287
+ session = getEditSession(candidate.canonicalRoot, editSessionId);
288
+ } catch (error) {
289
+ if (error instanceof Error && error.message === `edit session not found: ${editSessionId}`) continue;
290
+ throw error;
291
+ }
292
+ if (session.repoId && session.repoId !== candidate.repoId) {
293
+ throw new Error(`EDIT_VALIDATION_SESSION_IDENTITY_MISMATCH: repoId ${session.repoId} != ${candidate.repoId}`);
294
+ }
295
+ if (session.checkoutId && session.checkoutId !== candidate.activeCheckoutId) {
296
+ throw new Error(`EDIT_VALIDATION_SESSION_IDENTITY_MISMATCH: checkoutId ${session.checkoutId} != ${candidate.activeCheckoutId}`);
297
+ }
298
+ return { repository: candidate, session };
299
+ }
300
+ throw new Error(`edit session not found: ${editSessionId}`);
301
+ }
302
+
303
+ function validationRunRepository(
304
+ repository: RepositoryRecord,
305
+ run: EditValidationRunState,
306
+ ): RepositoryRecord {
307
+ if (run.repositoryId !== repository.repoId) {
308
+ throw new Error(`EDIT_VALIDATION_REPOSITORY_MISMATCH: ${run.repositoryId} != ${repository.repoId}`);
309
+ }
310
+ const candidate = selectRepositoryCheckout(repository, run.checkoutId);
311
+ const session = getEditSession(candidate.canonicalRoot, run.editSessionId);
312
+ if (session.repoId && session.repoId !== run.repositoryId) {
313
+ throw new Error(`EDIT_VALIDATION_SESSION_IDENTITY_MISMATCH: repoId ${session.repoId} != ${run.repositoryId}`);
314
+ }
315
+ if (session.checkoutId && session.checkoutId !== run.checkoutId) {
316
+ throw new Error(`EDIT_VALIDATION_SESSION_IDENTITY_MISMATCH: checkoutId ${session.checkoutId} != ${run.checkoutId}`);
317
+ }
318
+ return candidate;
319
+ }
320
+
269
321
  function receiptFor(
270
322
  controllerHome: string,
271
323
  repository: RepositoryRecord,
@@ -312,10 +364,18 @@ export async function reconcileEditValidationRun(
312
364
  ): Promise<EditValidationCoordinatorResult> {
313
365
  let run = readRun(controllerHome, repository.repoId, runInput.validationId) ?? runInput;
314
366
  if (run.status !== 'running') return coordinatorResult(controllerHome, run);
315
- if (run.checkoutId !== repository.activeCheckoutId) {
316
- return failedRun(controllerHome, run, 'EDIT_VALIDATION_CHECKOUT_CHANGED', `Validation checkout ${run.checkoutId} no longer matches ${repository.activeCheckoutId}.`);
367
+ let validationRepository: RepositoryRecord;
368
+ try {
369
+ validationRepository = validationRunRepository(repository, run);
370
+ } catch (error) {
371
+ return failedRun(
372
+ controllerHome,
373
+ run,
374
+ 'EDIT_VALIDATION_CHECKOUT_UNAVAILABLE',
375
+ error instanceof Error ? error.message : String(error),
376
+ );
317
377
  }
318
- const session = getEditSession(repository.canonicalRoot, run.editSessionId);
378
+ const session = getEditSession(validationRepository.canonicalRoot, run.editSessionId);
319
379
  if (session.currentRevision !== run.editRevision) {
320
380
  return failedRun(controllerHome, run, 'EDIT_VALIDATION_STALE_REVISION', `Validation targets revision ${run.editRevision}, current edit revision is ${session.currentRevision}.`);
321
381
  }
@@ -334,10 +394,10 @@ export async function reconcileEditValidationRun(
334
394
  const checkRequestId = `${run.requestId}:check:${index + 1}:${createHash('sha256').update(checkId).digest('hex').slice(0, 12)}`;
335
395
  const facade = await runPersistedCheckViaProcessRuntime({
336
396
  controllerHome,
337
- repoId: repository.repoId,
338
- checkoutId: repository.activeCheckoutId,
339
- repoRoot: repository.canonicalRoot,
340
- executionIdentity: executionIdentityForRepository(repository),
397
+ repoId: validationRepository.repoId,
398
+ checkoutId: validationRepository.activeCheckoutId,
399
+ repoRoot: validationRepository.canonicalRoot,
400
+ executionIdentity: executionIdentityForRepository(validationRepository),
341
401
  checkId,
342
402
  timeoutMs: run.timeoutMs,
343
403
  interactiveWaitMs: 0,
@@ -384,10 +444,10 @@ export async function reconcileEditValidationRun(
384
444
  }
385
445
 
386
446
  try {
387
- const receipts = run.checkIds.map((checkId) => receiptFor(controllerHome, repository, run, checkId));
388
- const checked = recordEditSessionProcessCheckReceipts(repository.canonicalRoot, run.editSessionId, {
389
- repoId: repository.repoId,
390
- checkoutId: repository.activeCheckoutId,
447
+ const receipts = run.checkIds.map((checkId) => receiptFor(controllerHome, validationRepository, run, checkId));
448
+ const checked = recordEditSessionProcessCheckReceipts(validationRepository.canonicalRoot, run.editSessionId, {
449
+ repoId: validationRepository.repoId,
450
+ checkoutId: validationRepository.activeCheckoutId,
391
451
  receipts,
392
452
  reviewer: run.reviewer,
393
453
  note: run.note,
@@ -415,7 +475,9 @@ export async function startOrJoinEditValidation(
415
475
  repository: RepositoryRecord,
416
476
  input: EditValidationStartInput,
417
477
  ): Promise<EditValidationCoordinatorResult> {
418
- const session = getEditSession(repository.canonicalRoot, input.editSessionId);
478
+ const resolved = resolveEditValidationRepository(repository, input.editSessionId);
479
+ const validationRepository = resolved.repository;
480
+ const session = resolved.session;
419
481
  const checkIds = normalized(input.checkIds ?? session.requestedChecks);
420
482
  const requestId = input.validationRequestId?.trim()
421
483
  || input.requestId?.trim()
@@ -426,14 +488,14 @@ export async function startOrJoinEditValidation(
426
488
  if (existing.checkIds.join('\n') !== checkIds.join('\n')) {
427
489
  return failedRun(controllerHome, existing, 'EDIT_VALIDATION_REQUEST_CONFLICT', 'The validation request id is already bound to a different check set.');
428
490
  }
429
- return reconcileEditValidationRun(controllerHome, repository, existing);
491
+ return reconcileEditValidationRun(controllerHome, validationRepository, existing);
430
492
  }
431
493
 
432
494
  if (checkIds.length === 0) {
433
495
  try {
434
- const checked = recordEditSessionProcessCheckReceipts(repository.canonicalRoot, session.sessionId, {
435
- repoId: repository.repoId,
436
- checkoutId: repository.activeCheckoutId,
496
+ const checked = recordEditSessionProcessCheckReceipts(validationRepository.canonicalRoot, session.sessionId, {
497
+ repoId: validationRepository.repoId,
498
+ checkoutId: validationRepository.activeCheckoutId,
437
499
  receipts: [],
438
500
  reviewer: input.reviewer,
439
501
  note: input.note,
@@ -441,8 +503,8 @@ export async function startOrJoinEditValidation(
441
503
  const completed = saveRun(controllerHome, {
442
504
  schemaVersion: 1,
443
505
  validationId: id,
444
- repositoryId: repository.repoId,
445
- checkoutId: repository.activeCheckoutId,
506
+ repositoryId: validationRepository.repoId,
507
+ checkoutId: validationRepository.activeCheckoutId,
446
508
  editSessionId: session.sessionId,
447
509
  editRevision: session.currentRevision,
448
510
  requestId,
@@ -464,8 +526,8 @@ export async function startOrJoinEditValidation(
464
526
  const run: EditValidationRunState = {
465
527
  schemaVersion: 1,
466
528
  validationId: id,
467
- repositoryId: repository.repoId,
468
- checkoutId: repository.activeCheckoutId,
529
+ repositoryId: validationRepository.repoId,
530
+ checkoutId: validationRepository.activeCheckoutId,
469
531
  editSessionId: session.sessionId,
470
532
  editRevision: session.currentRevision,
471
533
  requestId,
@@ -487,13 +549,13 @@ export async function startOrJoinEditValidation(
487
549
  const run = saveRun(controllerHome, {
488
550
  schemaVersion: 1,
489
551
  validationId: id,
490
- repositoryId: repository.repoId,
491
- checkoutId: repository.activeCheckoutId,
552
+ repositoryId: validationRepository.repoId,
553
+ checkoutId: validationRepository.activeCheckoutId,
492
554
  editSessionId: session.sessionId,
493
555
  editRevision: session.currentRevision,
494
556
  requestId,
495
557
  checkIds,
496
- lanes: checkLanes(repository, checkIds),
558
+ lanes: checkLanes(validationRepository, checkIds),
497
559
  processes: {},
498
560
  reviewer: input.reviewer,
499
561
  note: input.note,
@@ -505,7 +567,7 @@ export async function startOrJoinEditValidation(
505
567
  createdAt: now(),
506
568
  updatedAt: now(),
507
569
  });
508
- return reconcileEditValidationRun(controllerHome, repository, run);
570
+ return reconcileEditValidationRun(controllerHome, validationRepository, run);
509
571
  }
510
572
 
511
573
  export interface PendingEditValidationSummary {