@moretea-labs/forge 1.6.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) hide show
  1. package/AGENTS.md +4 -0
  2. package/CHANGELOG.md +39 -0
  3. package/CLAUDE.md +4 -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/02-operating-mode.partial.md +2 -0
  17. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  18. package/assets/plugin-registry.v1.json +3 -3
  19. package/assets/reference-configs/document-generation.md +1 -1
  20. package/assets/reference-configs/harness-overview.md +1 -1
  21. package/assets/reference-configs/sprint-contracts.md +3 -3
  22. package/assets/skill-version.json +2 -2
  23. package/assets/templates/contract.template.md +2 -7
  24. package/assets/templates/helpers/capture-plan.sh +2 -2
  25. package/assets/templates/helpers/check-architecture-sync.sh +17 -0
  26. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  27. package/assets/templates/helpers/contract-worktree.sh +1 -21
  28. package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
  29. package/assets/templates/helpers/inspect-project-state.ts +8 -6
  30. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  31. package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
  32. package/assets/templates/helpers/new-plan.sh +1 -1
  33. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  34. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  35. package/assets/templates/helpers/verify-contract.sh +1 -111
  36. package/assets/templates/helpers/verify-sprint.sh +3 -180
  37. package/assets/templates/helpers/workflow-contract.ts +5 -1
  38. package/assets/templates/plan.template.md +1 -1
  39. package/assets/templates/review.template.md +9 -34
  40. package/assets/workflow-contract.v1.json +10 -19
  41. package/docs/README.md +10 -10
  42. package/docs/architecture/CURRENT.md +178 -0
  43. package/docs/architecture/EVOLUTION.md +42 -0
  44. package/docs/architecture/current/README.md +4 -156
  45. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  46. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  47. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  48. package/docs/architecture/history/README.md +7 -0
  49. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  50. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  51. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  52. package/docs/architecture/history.md +3 -34
  53. package/docs/architecture/index.md +15 -98
  54. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  55. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  56. package/docs/architecture/versions/1.6.md +38 -0
  57. package/docs/architecture/versions/1.7.md +41 -0
  58. package/docs/forge-chatgpt-browser-engine.md +2 -2
  59. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  60. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  61. package/docs/forge-github-issue-launcher.md +1 -1
  62. package/docs/forge-plugin-management.md +1 -1
  63. package/docs/forge-runtime-self-healing-loop.md +1 -1
  64. package/docs/operations/controller-browser-plugin.md +26 -6
  65. package/docs/operations/controller-performance-and-502.md +143 -0
  66. package/docs/operations/controller-reliability-runbook.md +1 -1
  67. package/docs/operations/features.md +2 -2
  68. package/docs/operations/github-repository.md +1 -1
  69. package/docs/operations/homebrew.md +2 -2
  70. package/docs/operations/mcp-tool-exposure.md +5 -3
  71. package/docs/wiki/Architecture.md +6 -6
  72. package/docs/wiki/Implementation.md +2 -2
  73. package/docs/wiki/Integrations.md +2 -2
  74. package/docs/wiki/Work-Lifecycle.md +1 -1
  75. package/package.json +33 -11
  76. package/scripts/activate-source-baseline.ts +17 -13
  77. package/scripts/architecture-event.ts +2 -820
  78. package/scripts/architecture-queue.sh +2 -645
  79. package/scripts/archive-architecture-request.sh +2 -242
  80. package/scripts/archive-workflow.sh +1 -291
  81. package/scripts/benchmark-background-check-overlap.ts +228 -0
  82. package/scripts/benchmark-mcp-host.sh +117 -0
  83. package/scripts/benchmark-mcp-transport.ts +170 -0
  84. package/scripts/benchmark-quality-harness.ts +146 -0
  85. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  86. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  87. package/scripts/benchmark-typescript-navigation.ts +186 -0
  88. package/scripts/benchmark-work-efficiency.ts +334 -0
  89. package/scripts/capability-config.ts +2 -392
  90. package/scripts/capability-resolver.ts +2 -503
  91. package/scripts/capture-plan.sh +1 -332
  92. package/scripts/check-agent-tooling.sh +2 -1558
  93. package/scripts/check-architecture-sync.sh +2 -371
  94. package/scripts/check-brain-manifest.sh +2 -253
  95. package/scripts/check-context-files.sh +2 -47
  96. package/scripts/check-deploy-sql-order.sh +2 -115
  97. package/scripts/check-mcp-compatibility.ts +14 -0
  98. package/scripts/check-runtime-architecture.mjs +349 -79
  99. package/scripts/check-skill-version.ts +3 -191
  100. package/scripts/check-source-duplication.mjs +61 -0
  101. package/scripts/check-stable-baseline.ts +92 -0
  102. package/scripts/check-task-sync.sh +2 -63
  103. package/scripts/check-task-workflow.sh +2 -1000
  104. package/scripts/codex-handoff-resume.sh +2 -267
  105. package/scripts/context-contract-sync.sh +2 -514
  106. package/scripts/contract-run.ts +2 -463
  107. package/scripts/contract-worktree.sh +1 -722
  108. package/scripts/ensure-task-workflow.sh +1 -1225
  109. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  110. package/scripts/inspect-project-state.ts +4 -279
  111. package/scripts/install-standalone-recovery.ts +24 -0
  112. package/scripts/lib/project-init-lib.sh +22 -46
  113. package/scripts/lib/run-forge-helper.sh +19 -0
  114. package/scripts/lib/run-forge-helper.ts +26 -0
  115. package/scripts/migrate-project-template.sh +1 -1189
  116. package/scripts/migrate-workflow-docs.ts +4 -412
  117. package/scripts/new-plan.sh +1 -180
  118. package/scripts/new-spec.sh +1 -47
  119. package/scripts/new-sprint.sh +1 -46
  120. package/scripts/plan-to-todo.sh +1 -796
  121. package/scripts/prepare-codex-handoff.sh +2 -201
  122. package/scripts/prepare-handoff.sh +1 -78
  123. package/scripts/refresh-current-status.sh +2 -467
  124. package/scripts/run-governed-gate.ts +5 -1
  125. package/scripts/select-agent-context-blocks.sh +2 -64
  126. package/scripts/ship-worktrees.sh +1 -558
  127. package/scripts/smoke-ios-physical-device.ts +4 -2
  128. package/scripts/smoke-runtime-recovery.ts +1 -35
  129. package/scripts/smoke-schedule-engine.ts +1 -20
  130. package/scripts/sprint-backlog.sh +1 -834
  131. package/scripts/stage-runtime-release.ts +2 -1
  132. package/scripts/summarize-failures.sh +2 -123
  133. package/scripts/switch-plan.sh +1 -150
  134. package/scripts/sync-brain-docs.sh +2 -379
  135. package/scripts/sync-generated-authority.mjs +175 -0
  136. package/scripts/verify-contract.sh +2 -703
  137. package/scripts/verify-forge-runtime.sh +0 -1
  138. package/scripts/verify-sprint.sh +1 -677
  139. package/scripts/workflow-contract.ts +4 -205
  140. package/scripts/workstream-sync.sh +2 -261
  141. package/src/cli/agent-jobs/executor-health.ts +1 -1
  142. package/src/cli/commands/init-hook.ts +3 -3
  143. package/src/cli/commands/mcp.ts +9 -5
  144. package/src/cli/commands/plugin.ts +12 -3
  145. package/src/cli/commands/recovery.ts +25 -3
  146. package/src/cli/commands/runtime.ts +55 -3
  147. package/src/cli/commands/security.ts +12 -1
  148. package/src/cli/commands/setup-profile.ts +31 -21
  149. package/src/cli/controller/check-runner.ts +113 -5
  150. package/src/cli/controller/composite-result.ts +1 -1
  151. package/src/cli/controller/context/focus.ts +51 -0
  152. package/src/cli/controller/context/instruction-context.ts +114 -0
  153. package/src/cli/controller/context/known-paths.ts +76 -0
  154. package/src/cli/controller/context/query-planning.ts +110 -0
  155. package/src/cli/controller/context/rule-resolution.ts +532 -0
  156. package/src/cli/controller/context/source-materializer.ts +278 -0
  157. package/src/cli/controller/context/structural.ts +16 -0
  158. package/src/cli/controller/context/types.ts +286 -0
  159. package/src/cli/controller/context-pack.ts +828 -487
  160. package/src/cli/controller/launch-agents.ts +77 -2
  161. package/src/cli/controller/operational-plan.ts +2 -2
  162. package/src/cli/controller/work-mode.ts +6 -4
  163. package/src/cli/editing/edit-session.ts +60 -7
  164. package/src/cli/github/contracts.ts +16 -0
  165. package/src/cli/github/github.ts +135 -16
  166. package/src/cli/github/session.ts +2 -1
  167. package/src/cli/installer/managed-entries.ts +15 -1
  168. package/src/cli/local-bridge/console-automations.ts +54 -78
  169. package/src/cli/local-bridge/console-view-models.ts +1 -35
  170. package/src/cli/local-bridge/facade-api.ts +37 -264
  171. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  172. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
  173. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  174. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
  175. package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
  176. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  177. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
  178. package/src/cli/local-bridge/frontend/src/styles.css +1 -1
  179. package/src/cli/local-bridge/frontend/src/types.ts +5 -8
  180. package/src/cli/local-bridge/job-store.ts +1 -490
  181. package/src/cli/local-bridge/server.ts +2 -515
  182. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  183. package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
  184. package/src/cli/mcp/auth.ts +5 -5
  185. package/src/cli/mcp/instructions.ts +3 -2
  186. package/src/cli/mcp/legacy-tool-service.ts +10 -222
  187. package/src/cli/mcp/multi-repository.ts +16 -5
  188. package/src/cli/mcp/oauth.ts +175 -26
  189. package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
  190. package/src/cli/mcp/repository-tools.ts +282 -146
  191. package/src/cli/mcp/server.ts +586 -116
  192. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  193. package/src/cli/mcp/tool-contract.ts +14 -0
  194. package/src/cli/mcp/toolset-selection.ts +1 -1
  195. package/src/cli/mcp/toolset.ts +44 -7
  196. package/src/cli/mcp/transports/http.ts +124 -14
  197. package/src/cli/mcp/transports/session-registry.ts +43 -5
  198. package/src/cli/mcp/types.ts +3 -3
  199. package/src/cli/repositories/command-classifier.ts +103 -21
  200. package/src/cli/repositories/command-executor.ts +78 -526
  201. package/src/cli/repositories/command-process.ts +108 -0
  202. package/src/cli/repositories/command-scope.ts +99 -41
  203. package/src/cli/repositories/controller-home.ts +7 -8
  204. package/src/cli/repositories/git-command-executor.ts +2 -0
  205. package/src/cli/repositories/locks.ts +53 -16
  206. package/src/cli/repositories/repository-snapshot.ts +124 -0
  207. package/src/cli/repositories/selected-path-actions.ts +17 -9
  208. package/src/cli/repositories/structured-git.ts +118 -6
  209. package/src/cli/repository/inspector.ts +563 -65
  210. package/src/cli/repository/session-cache.ts +58 -17
  211. package/src/cli/v81-entry.ts +1 -4
  212. package/src/effects/fs-transaction.ts +85 -17
  213. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  214. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  215. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  216. package/src/runtime/context/lsp-client.ts +283 -0
  217. package/src/runtime/context/semantic-navigation-contract.ts +61 -0
  218. package/src/runtime/context/semantic-navigation.ts +223 -0
  219. package/src/runtime/context/swift-navigation.ts +565 -0
  220. package/src/runtime/context/typescript-navigation.ts +226 -0
  221. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  222. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +48 -16
  223. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  224. package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
  225. package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
  226. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  227. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  228. package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
  229. package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
  230. package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
  231. package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
  232. package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
  233. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  234. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  235. package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
  236. package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
  237. package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
  238. package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
  239. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  240. package/src/runtime/control-plane/execution/work-task-receipt.ts +22 -18
  241. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +156 -11
  242. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
  243. package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
  244. package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
  245. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +90 -9
  246. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  247. package/src/runtime/control-plane/facade/controller-round-relay.ts +1118 -0
  248. package/src/runtime/control-plane/facade/controller-session-store.ts +302 -12
  249. package/src/runtime/control-plane/facade/goal-workloop-access.ts +17 -1
  250. package/src/runtime/control-plane/facade/goal-workloop.ts +928 -190
  251. package/src/runtime/control-plane/facade/index.ts +3 -0
  252. package/src/runtime/control-plane/facade/plan-contract-store.ts +275 -42
  253. package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
  254. package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
  255. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  256. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  257. package/src/runtime/control-plane/facade/types.ts +90 -10
  258. package/src/runtime/control-plane/facade/work-contract-store.ts +157 -261
  259. package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
  260. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  261. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  262. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  263. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  264. package/src/runtime/control-plane/global-scheduler/maintenance.ts +152 -0
  265. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  266. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  267. package/src/runtime/control-plane/global-scheduler/scheduler.ts +278 -543
  268. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  269. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  270. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  271. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  272. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  273. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  274. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  275. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  276. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  277. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  278. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +63 -0
  279. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +365 -117
  280. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +89 -0
  281. package/src/runtime/control-plane/launcher/thin-launcher.ts +244 -11
  282. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  283. package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
  284. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  285. package/src/runtime/control-plane/release-retention.ts +62 -2
  286. package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
  287. package/src/runtime/control-plane/routing/route-policy.ts +24 -21
  288. package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
  289. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  290. package/src/runtime/diagnostics/mcp-timing.ts +13 -0
  291. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  292. package/src/runtime/diagnostics/performance.ts +24 -1
  293. package/src/runtime/evidence/event-ledger.ts +1 -1
  294. package/src/runtime/evidence/process-check-execution.ts +18 -0
  295. package/src/runtime/execution/jobs/child-reference.ts +6 -2
  296. package/src/runtime/execution/jobs/store.ts +58 -4
  297. package/src/runtime/execution/jobs/types.ts +1 -1
  298. package/src/runtime/execution/managed-workspace.ts +203 -1
  299. package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
  300. package/src/runtime/execution/process-runtime/check-result.ts +88 -0
  301. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
  302. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  303. package/src/runtime/execution/process-runtime/command-facade.ts +367 -40
  304. package/src/runtime/execution/process-runtime/gc.ts +38 -6
  305. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1201 -0
  306. package/src/runtime/execution/process-runtime/persisted-check.ts +51 -5
  307. package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
  308. package/src/runtime/execution/process-runtime/runtime.ts +53 -3
  309. package/src/runtime/execution/process-runtime/store.ts +1 -0
  310. package/src/runtime/execution/process-runtime/types.ts +4 -12
  311. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  312. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  313. package/src/runtime/execution/work-activity.ts +21 -0
  314. package/src/runtime/execution/workers/executor.ts +18 -61
  315. package/src/runtime/gateway/mcp/execution-tools.ts +35 -1852
  316. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
  317. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  318. package/src/runtime/gateway/mcp/router.ts +186 -84
  319. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +565 -0
  320. package/src/runtime/gateway/mcp/runtime-tools.ts +2378 -1947
  321. package/src/runtime/plugins/app-store-connect-adapter.ts +216 -47
  322. package/src/runtime/plugins/browser-adapter.ts +2662 -829
  323. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  324. package/src/runtime/plugins/browser-handoff-host.ts +2 -25
  325. package/src/runtime/plugins/browser-handoff.ts +110 -5
  326. package/src/runtime/plugins/browser-macos-bridge.ts +701 -63
  327. package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
  328. package/src/runtime/plugins/browser-native-session-service.ts +222 -0
  329. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  330. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  331. package/src/runtime/plugins/browser-runtime-contract.ts +154 -0
  332. package/src/runtime/plugins/browser-runtime.ts +193 -0
  333. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  334. package/src/runtime/plugins/browser-session-store.ts +121 -0
  335. package/src/runtime/plugins/browser-session-types.ts +98 -0
  336. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  337. package/src/runtime/plugins/desktop-operator-registration.ts +10 -4
  338. package/src/runtime/plugins/external-adapter.ts +358 -3
  339. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  340. package/src/runtime/plugins/first-party-registry.ts +2 -0
  341. package/src/runtime/plugins/github-adapter.ts +125 -0
  342. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  343. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  344. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  345. package/src/runtime/plugins/ios-adapter.ts +106 -16
  346. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  347. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  348. package/src/runtime/plugins/lightweight-action.ts +153 -0
  349. package/src/runtime/plugins/local-system-adapter.ts +123 -6
  350. package/src/runtime/plugins/macos-capability-broker.ts +47 -9
  351. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  352. package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
  353. package/src/runtime/plugins/resend-adapter.ts +1 -1
  354. package/src/runtime/plugins/store.ts +330 -33
  355. package/src/runtime/plugins/types.ts +38 -3
  356. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  357. package/src/runtime/projections/git-status-sampler.ts +16 -16
  358. package/src/runtime/projections/materialized-view.ts +28 -10
  359. package/src/runtime/recovery/maintenance-executor.ts +426 -25
  360. package/src/runtime/root/mcp-transport.ts +180 -10
  361. package/src/runtime/root/package-connector-service.ts +230 -10
  362. package/src/runtime/root/package-runtime-release.ts +143 -17
  363. package/src/runtime/root/package-runtime-service.ts +461 -22
  364. package/src/runtime/root/release-manifest.ts +144 -18
  365. package/src/runtime/root/release-materialize.ts +125 -32
  366. package/src/runtime/root/release-store.ts +48 -11
  367. package/src/runtime/root/runtime.ts +23 -5
  368. package/src/runtime/root/service.ts +100 -29
  369. package/src/runtime/root/types.ts +12 -0
  370. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  371. package/src/runtime/shared/trusted-executable.ts +62 -0
  372. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  373. package/src/runtime/standalone-recovery/core.ts +1079 -240
  374. package/src/runtime/standalone-recovery/entry.ts +100 -24
  375. package/src/runtime/standalone-recovery/installer.ts +20 -4
  376. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  377. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  378. package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
  379. package/src/runtime/workflow/schedules/engine.ts +364 -202
  380. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  381. package/src/runtime/workflow/schedules/settlement.ts +64 -17
  382. package/src/runtime/workflow/schedules/store.ts +209 -19
  383. package/src/runtime/workflow/schedules/types.ts +2 -4
  384. package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
  385. package/src/testing/test-governance.ts +8 -40
  386. package/assets/reference-configs/heartbeat-triage.md +0 -57
  387. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  388. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  389. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  390. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  391. package/docs/architecture/current/architecture-invariants.md +0 -331
  392. package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
  393. package/docs/architecture/current/codegraph-context-provider.md +0 -238
  394. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  395. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  396. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
  397. package/docs/architecture/current/entity-model.md +0 -612
  398. package/docs/architecture/current/external-plugin-governance.md +0 -396
  399. package/docs/architecture/current/failure-recovery.md +0 -447
  400. package/docs/architecture/current/governance.md +0 -213
  401. package/docs/architecture/current/human-interaction-plane.md +0 -87
  402. package/docs/architecture/current/implementation-status.md +0 -128
  403. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  404. package/docs/architecture/current/legacy-governance.md +0 -58
  405. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  406. package/docs/architecture/current/migration-roadmap.md +0 -105
  407. package/docs/architecture/current/multi-repository-execution.md +0 -371
  408. package/docs/architecture/current/operations-runbook.md +0 -45
  409. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  410. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  411. package/docs/architecture/current/repository-access-modes.md +0 -102
  412. package/docs/architecture/current/route-policy.md +0 -15
  413. package/docs/architecture/current/route-session-concurrency.md +0 -85
  414. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  415. package/docs/architecture/current/runtime-directory-map.md +0 -49
  416. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  417. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  418. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  419. package/docs/architecture/current/system-overview.md +0 -169
  420. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  421. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  422. package/docs/architecture/current/thin-harness-v1.md +0 -321
  423. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  424. package/docs/forge-autonomous-goal-loop.md +0 -9
  425. package/docs/forge-provider-configuration.md +0 -7
  426. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
  427. package/docs/operations/gmail-assistant-routines.md +0 -186
  428. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  429. package/docs/operations/provider-configuration.md +0 -31
  430. package/scripts/harness-trace-grade.sh +0 -150
  431. package/scripts/heartbeat-triage.sh +0 -361
  432. package/scripts/maintenance-triage.sh +0 -167
  433. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  434. package/src/cli/repositories/goal-registry.ts +0 -239
  435. package/src/runtime/assistant/action-proposals.ts +0 -279
  436. package/src/runtime/assistant/intent.ts +0 -395
  437. package/src/runtime/assistant/model-provider.ts +0 -460
  438. package/src/runtime/assistant/openapi.ts +0 -223
  439. package/src/runtime/assistant/policy.ts +0 -89
  440. package/src/runtime/assistant/readiness.ts +0 -258
  441. package/src/runtime/assistant/routine-runtime.ts +0 -477
  442. package/src/runtime/assistant/schedule-binding.ts +0 -171
  443. package/src/runtime/assistant/standing-grants.ts +0 -375
  444. package/src/runtime/assistant/store.ts +0 -243
  445. package/src/runtime/assistant/types.ts +0 -135
  446. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  447. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  448. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  449. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  450. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  451. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  452. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  453. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  454. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  455. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  456. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  457. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  458. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  459. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  460. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  461. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  462. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  463. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  464. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  465. package/src/runtime/workflow/findings/store.ts +0 -204
  466. package/src/runtime/workflow/findings/types.ts +0 -58
  467. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  468. package/src/runtime/workflow/portfolio/store.ts +0 -185
  469. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -5,6 +5,7 @@ export * from './work-task-projection';
5
5
  export * from './work-continuation';
6
6
  export * from './controller-session-store';
7
7
  export * from './plan-contract-store';
8
+ export * from './semantic-admission';
8
9
  export * from './facade-result';
9
10
  export * from './suggested-actions';
10
11
  export * from './capability-registry';
@@ -14,3 +15,5 @@ export * from './goal-workloop-access';
14
15
  export * from './codex-delegation';
15
16
  export * from './self-healing-loop';
16
17
  export * from './operation-digest';
18
+ export * from './requirement-authority';
19
+ export * from './repository-work-admission';
@@ -3,12 +3,21 @@ import { join } from 'path';
3
3
  import { repositoryControllerRoot } from '../../../cli/repositories/controller-home';
4
4
  import { withControllerLock } from '../../../cli/repositories/locks';
5
5
  import { readJsonFile, sanitizeFileComponent, writeJsonAtomic } from '../../shared/json-files';
6
+ import {
7
+ normalizePlanScopeKey,
8
+ resolvePlanAdmission,
9
+ withPlanAdmissionLock,
10
+ withPlanAdmissionLockAsync,
11
+ type PlanAdmissionRelation,
12
+ type PlanAdmissionResolution,
13
+ } from './semantic-admission';
6
14
  import {
7
15
  listControlPlaneRecords,
8
16
  readControlPlaneRecordWithinTransaction,
9
17
  withControlPlaneTransaction,
10
18
  writeControlPlaneRecordWithinTransaction,
11
19
  } from '../persistence/sqlite-store';
20
+ import { readRequirement } from '../persistence/requirement-store';
12
21
  import {
13
22
  isTerminalPlanContractStatus,
14
23
  type EvidenceRef,
@@ -46,6 +55,18 @@ export interface CreatePlanContractInput {
46
55
  evidenceRefs?: EvidenceRef[];
47
56
  }
48
57
 
58
+ export interface AdmitPlanContractInput extends CreatePlanContractInput {
59
+ planRelation?: PlanAdmissionRelation;
60
+ relatedPlanId?: string;
61
+ }
62
+
63
+ export interface RepairDraftPlanContractInput extends Omit<CreatePlanContractInput, 'planId' | 'repoId' | 'requirementId' | 'evidenceRefs'> {
64
+ /** Source revision observed before entering the admission lock; rejects stale draft writers. */
65
+ expectedSourceRevision?: string;
66
+ }
67
+
68
+ export type AdmitPlanContractResult = PlanAdmissionResolution;
69
+
49
70
  export interface PlanContractSummary {
50
71
  planId: string;
51
72
  repoId: string;
@@ -68,7 +89,7 @@ function bounded(values: readonly string[] | undefined, limit: number, maxLength
68
89
  }
69
90
 
70
91
  function normalizeScopeKey(value: string): string {
71
- return sanitizeFileComponent(value).toLowerCase().slice(0, 160);
92
+ return normalizePlanScopeKey(value) || 'unknown';
72
93
  }
73
94
 
74
95
  function normalizeStep(step: CreatePlanContractInput['steps'][number]): PlanStep {
@@ -115,6 +136,14 @@ function updatePlanContract(
115
136
  );
116
137
  }
117
138
 
139
+ function assertRequirementReference(options: PlanContractStoreOptions, requirementId: string | undefined): void {
140
+ const normalized = requirementId?.trim();
141
+ if (!normalized || !options.controllerHome) return;
142
+ if (!readRequirement({ controllerHome: options.controllerHome, now: options.now }, normalized)) {
143
+ throw new Error(`PLAN_REQUIREMENT_NOT_FOUND: ${normalized}`);
144
+ }
145
+ }
146
+
118
147
  export function planContractRoot(location: PlanContractStoreLocation): string {
119
148
  if (location.root) {
120
149
  mkdirSync(location.root, { recursive: true });
@@ -205,11 +234,10 @@ function writePlanContractStore(options: PlanContractStoreOptions, store: PlanCo
205
234
  return store;
206
235
  }
207
236
 
208
- export function createPlanContract(options: PlanContractStoreOptions, input: CreatePlanContractInput): PlanContract {
209
- const at = nowIso(options);
237
+ function buildPlanContract(input: CreatePlanContractInput, at: string): PlanContract {
210
238
  const planId = sanitizeFileComponent(input.planId);
211
239
  if (!String(input.planId ?? '').trim() || planId === 'unknown') throw new Error('plan_id is required');
212
- const plan: PlanContract = {
240
+ return {
213
241
  schemaVersion: 1,
214
242
  planId,
215
243
  repoId: input.repoId,
@@ -229,6 +257,38 @@ export function createPlanContract(options: PlanContractStoreOptions, input: Cre
229
257
  createdAt: at,
230
258
  updatedAt: at,
231
259
  };
260
+ }
261
+
262
+ function draftContentErrors(plan: PlanContract): string[] {
263
+ const errors: string[] = [];
264
+ if (!plan.sourceRevision) errors.push('source_revision is required');
265
+ if (!plan.scopeKey || plan.scopeKey === 'unknown') errors.push('scope_key is required');
266
+ if (!plan.goal) errors.push('goal is required');
267
+ if (plan.steps.length === 0) errors.push('at least one plan step is required');
268
+ const ids = new Set<string>();
269
+ for (const step of plan.steps) {
270
+ if (!step.id || step.id === 'unknown') errors.push('every plan step needs an id');
271
+ else if (ids.has(step.id)) errors.push(`duplicate plan step id: ${step.id}`);
272
+ else ids.add(step.id);
273
+ if (!step.objective) errors.push(`step ${step.id || 'unknown'} needs an objective`);
274
+ if (step.checks.length === 0) errors.push(`step ${step.id || 'unknown'} needs at least one machine-checkable check`);
275
+ if (step.acceptanceCriteria.length === 0) errors.push(`step ${step.id || 'unknown'} needs acceptance criteria`);
276
+ if (step.status !== 'pending') errors.push(`step ${step.id || 'unknown'} must be pending before approval`);
277
+ }
278
+ for (const step of plan.steps) {
279
+ for (const dependency of step.dependencies) {
280
+ const dependencyStep = plan.steps.find((candidate) => candidate.id === dependency);
281
+ if (!dependencyStep) errors.push(`step ${step.id} references unknown dependency ${dependency}`);
282
+ else if (dependencyStep.status !== 'pending') errors.push(`step ${step.id} dependency ${dependency} must be pending before approval`);
283
+ }
284
+ }
285
+ return [...new Set(errors)];
286
+ }
287
+
288
+ function createPlanContractUnlocked(options: PlanContractStoreOptions, input: CreatePlanContractInput): PlanContract {
289
+ assertRequirementReference(options, input.requirementId);
290
+ const at = nowIso(options);
291
+ const plan = buildPlanContract(input, at);
232
292
  const store = readPlanContractStore(options);
233
293
  if (store.contracts.some((existing) => existing.planId === plan.planId)) {
234
294
  throw new Error(`plan contract already exists: ${plan.planId}`);
@@ -237,6 +297,135 @@ export function createPlanContract(options: PlanContractStoreOptions, input: Cre
237
297
  return plan;
238
298
  }
239
299
 
300
+ function replacePlanContractUnlocked(
301
+ options: PlanContractStoreOptions,
302
+ predecessorId: string,
303
+ input: CreatePlanContractInput,
304
+ ): PlanContract {
305
+ assertRequirementReference(options, input.requirementId);
306
+ const at = nowIso(options);
307
+ const store = readPlanContractStore(options);
308
+ const predecessorKey = sanitizeFileComponent(predecessorId);
309
+ const predecessorIndex = store.contracts.findIndex((contract) => contract.planId === predecessorKey);
310
+ if (predecessorIndex < 0) throw new Error(`plan contract not found: ${predecessorKey}`);
311
+ const predecessor = store.contracts[predecessorIndex]!;
312
+ if (isTerminalPlanContractStatus(predecessor.status)) throw new Error(`plan contract ${predecessor.planId} is terminal (${predecessor.status})`);
313
+ const successor = buildPlanContract(input, at);
314
+ if (successor.planId === predecessor.planId) throw new Error('PLAN_SUCCESSOR_ID_MUST_CHANGE');
315
+ if (successor.repoId !== predecessor.repoId) throw new Error('PLAN_SUCCESSOR_REPOSITORY_MISMATCH');
316
+ if (store.contracts.some((existing) => existing.planId === successor.planId)) {
317
+ throw new Error(`plan contract already exists: ${successor.planId}`);
318
+ }
319
+ const conflictingScope = store.contracts.find((existing, index) => index !== predecessorIndex
320
+ && !isTerminalPlanContractStatus(existing.status)
321
+ && existing.scopeKey === successor.scopeKey);
322
+ if (conflictingScope) throw new Error(`PLAN_SCOPE_ALREADY_OWNED: ${successor.scopeKey}:${conflictingScope.planId}`);
323
+ const contracts = [...store.contracts];
324
+ contracts[predecessorIndex] = { ...predecessor, status: 'superseded', supersededBy: successor.planId, updatedAt: at };
325
+ writePlanContractStore(options, { schemaVersion: 1, updatedAt: at, contracts: [successor, ...contracts] });
326
+ return successor;
327
+ }
328
+
329
+ function repairDraftPlanContractUnlocked(
330
+ options: PlanContractStoreOptions,
331
+ planId: string,
332
+ input: RepairDraftPlanContractInput,
333
+ ): PlanContract {
334
+ const store = readPlanContractStore(options);
335
+ const key = sanitizeFileComponent(planId);
336
+ const index = store.contracts.findIndex((contract) => contract.planId === key);
337
+ if (index < 0) throw new Error(`plan contract not found: ${key}`);
338
+ const current = store.contracts[index]!;
339
+ if (current.status !== 'draft') throw new Error(`PLAN_DRAFT_REPAIR_STATUS_INVALID: ${current.planId}:${current.status}`);
340
+ if (input.expectedSourceRevision !== undefined && current.sourceRevision !== input.expectedSourceRevision) {
341
+ throw new Error(`PLAN_DRAFT_REPAIR_STALE_SOURCE: ${current.planId}:expected=${input.expectedSourceRevision}:actual=${current.sourceRevision}`);
342
+ }
343
+ assertRequirementReference(options, current.requirementId);
344
+ const at = nowIso(options);
345
+ const candidate = buildPlanContract({
346
+ ...input,
347
+ planId: current.planId,
348
+ repoId: current.repoId,
349
+ requirementId: current.requirementId,
350
+ evidenceRefs: current.evidenceRefs,
351
+ }, at);
352
+ const contentErrors = draftContentErrors(candidate);
353
+ if (contentErrors.length > 0) {
354
+ throw new Error(`PLAN_DRAFT_REPAIR_INVALID: ${contentErrors.join('; ')}`);
355
+ }
356
+ const conflictingScope = store.contracts.find((existing, candidateIndex) => candidateIndex !== index
357
+ && !isTerminalPlanContractStatus(existing.status)
358
+ && existing.scopeKey === candidate.scopeKey);
359
+ if (conflictingScope) throw new Error(`PLAN_SCOPE_ALREADY_OWNED: ${candidate.scopeKey}:${conflictingScope.planId}`);
360
+ const repaired: PlanContract = {
361
+ ...candidate,
362
+ createdAt: current.createdAt,
363
+ updatedAt: at,
364
+ };
365
+ const contracts = [...store.contracts];
366
+ contracts[index] = repaired;
367
+ writePlanContractStore(options, { schemaVersion: 1, updatedAt: at, contracts });
368
+ return repaired;
369
+ }
370
+
371
+ export function repairDraftPlanContract(
372
+ options: PlanContractStoreOptions,
373
+ planId: string,
374
+ input: RepairDraftPlanContractInput,
375
+ ): PlanContract {
376
+ return withPlanAdmissionLock(options, () => repairDraftPlanContractUnlocked(options, planId, input));
377
+ }
378
+
379
+ export async function repairDraftPlanContractAsync(
380
+ options: PlanContractStoreOptions,
381
+ planId: string,
382
+ input: RepairDraftPlanContractInput,
383
+ ): Promise<PlanContract> {
384
+ return await withPlanAdmissionLockAsync(options, () => repairDraftPlanContractUnlocked(options, planId, input));
385
+ }
386
+
387
+ export function createPlanContract(options: PlanContractStoreOptions, input: CreatePlanContractInput): PlanContract {
388
+ return withPlanAdmissionLock(options, () => {
389
+ const store = readPlanContractStore(options);
390
+ const planId = sanitizeFileComponent(input.planId);
391
+ if (store.contracts.some((existing) => existing.planId === planId)) {
392
+ throw new Error(`plan contract already exists: ${planId}`);
393
+ }
394
+ const scopeKey = normalizeScopeKey(input.scopeKey);
395
+ const existingScopeAuthority = scopeKey === 'unknown' ? undefined : store.contracts.find((existing) =>
396
+ !isTerminalPlanContractStatus(existing.status) && existing.scopeKey === scopeKey);
397
+ if (existingScopeAuthority) {
398
+ throw new Error(`PLAN_SCOPE_ALREADY_OWNED: ${scopeKey}:${existingScopeAuthority.planId}`);
399
+ }
400
+ return createPlanContractUnlocked(options, input);
401
+ });
402
+ }
403
+
404
+ function admitPlanContractUnlocked(options: PlanContractStoreOptions, input: AdmitPlanContractInput): AdmitPlanContractResult {
405
+ assertRequirementReference(options, input.requirementId);
406
+ const activePlans = listPlanContracts({ ...options, status: 'active', limit: 100 });
407
+ const resolution = resolvePlanAdmission(activePlans, {
408
+ requirementId: input.requirementId,
409
+ scopeKey: input.scopeKey,
410
+ planRelation: input.planRelation,
411
+ relatedPlanId: input.relatedPlanId,
412
+ });
413
+ if (resolution.admissionDecision === 'extend_existing' && resolution.plan) {
414
+ const successor = replacePlanContractUnlocked(options, resolution.plan.planId, input);
415
+ return { ...resolution, admissionDecision: 'create_new', plan: successor };
416
+ }
417
+ if (resolution.admissionDecision !== 'create_new') return resolution;
418
+ return { ...resolution, plan: createPlanContractUnlocked(options, input) };
419
+ }
420
+
421
+ export function admitPlanContract(options: PlanContractStoreOptions, input: AdmitPlanContractInput): AdmitPlanContractResult {
422
+ return withPlanAdmissionLock(options, () => admitPlanContractUnlocked(options, input));
423
+ }
424
+
425
+ export async function admitPlanContractAsync(options: PlanContractStoreOptions, input: AdmitPlanContractInput): Promise<AdmitPlanContractResult> {
426
+ return await withPlanAdmissionLockAsync(options, () => admitPlanContractUnlocked(options, input));
427
+ }
428
+
240
429
  export function getPlanContract(options: PlanContractStoreOptions, planId: string): PlanContract | undefined {
241
430
  return readPlanContractStore(options).contracts.find((contract) => contract.planId === sanitizeFileComponent(planId));
242
431
  }
@@ -266,28 +455,7 @@ export function summarizePlanContract(plan: PlanContract): PlanContractSummary {
266
455
  }
267
456
 
268
457
  function approvalErrors(plan: PlanContract, allPlans: readonly PlanContract[]): string[] {
269
- const errors: string[] = [];
270
- if (!plan.sourceRevision) errors.push('source_revision is required before approval');
271
- if (!plan.scopeKey || plan.scopeKey === 'unknown') errors.push('scope_key is required before approval');
272
- if (!plan.goal) errors.push('goal is required before approval');
273
- if (plan.steps.length === 0) errors.push('at least one plan step is required before approval');
274
- const ids = new Set<string>();
275
- for (const step of plan.steps) {
276
- if (!step.id || step.id === 'unknown') errors.push('every plan step needs an id');
277
- else if (ids.has(step.id)) errors.push(`duplicate plan step id: ${step.id}`);
278
- else ids.add(step.id);
279
- if (!step.objective) errors.push(`step ${step.id || 'unknown'} needs an objective`);
280
- if (step.checks.length === 0) errors.push(`step ${step.id || 'unknown'} needs at least one machine-checkable check`);
281
- if (step.acceptanceCriteria.length === 0) errors.push(`step ${step.id || 'unknown'} needs acceptance criteria`);
282
- if (step.status !== 'pending') errors.push(`step ${step.id || 'unknown'} must be pending before approval`);
283
- }
284
- for (const step of plan.steps) {
285
- for (const dependency of step.dependencies) {
286
- const dependencyStep = plan.steps.find((candidate) => candidate.id === dependency);
287
- if (!dependencyStep) errors.push(`step ${step.id} references unknown dependency ${dependency}`);
288
- else if (dependencyStep.status !== 'pending') errors.push(`step ${step.id} dependency ${dependency} must be pending before approval`);
289
- }
290
- }
458
+ const errors = draftContentErrors(plan).map((error) => `${error} before approval`);
291
459
  const scopeIsCommitted = (status: PlanContractStatus): boolean => [
292
460
  'approved',
293
461
  'executing',
@@ -301,11 +469,12 @@ function approvalErrors(plan: PlanContract, allPlans: readonly PlanContract[]):
301
469
  return [...new Set(errors)];
302
470
  }
303
471
 
304
- export function approvePlanContract(options: PlanContractStoreOptions, planId: string): PlanContract {
472
+ function approvePlanContractUnlocked(options: PlanContractStoreOptions, planId: string): PlanContract {
305
473
  const store = readPlanContractStore(options);
306
474
  const index = store.contracts.findIndex((contract) => contract.planId === sanitizeFileComponent(planId));
307
475
  if (index < 0) throw new Error(`plan contract not found: ${sanitizeFileComponent(planId)}`);
308
476
  const current = store.contracts[index];
477
+ assertRequirementReference(options, current.requirementId);
309
478
  if (current.status !== 'draft' && current.status !== 'reviewing') throw new Error(`plan contract ${current.planId} cannot be approved from ${current.status}`);
310
479
  const errors = approvalErrors(current, store.contracts);
311
480
  if (errors.length > 0) throw new Error(`plan contract ${current.planId} cannot be approved: ${errors.join('; ')}`);
@@ -317,20 +486,33 @@ export function approvePlanContract(options: PlanContractStoreOptions, planId: s
317
486
  return next;
318
487
  }
319
488
 
489
+ export function approvePlanContract(options: PlanContractStoreOptions, planId: string): PlanContract {
490
+ return withPlanAdmissionLock(options, () => approvePlanContractUnlocked(options, planId));
491
+ }
492
+
493
+ export async function approvePlanContractAsync(options: PlanContractStoreOptions, planId: string): Promise<PlanContract> {
494
+ return await withPlanAdmissionLockAsync(options, () => approvePlanContractUnlocked(options, planId));
495
+ }
496
+
320
497
  export function supersedePlanContract(options: PlanContractStoreOptions, planId: string, supersededBy: string): PlanContract {
321
- const store = readPlanContractStore(options);
322
- const index = store.contracts.findIndex((contract) => contract.planId === sanitizeFileComponent(planId));
323
- if (index < 0) throw new Error(`plan contract not found: ${sanitizeFileComponent(planId)}`);
324
- const current = store.contracts[index];
325
- if (isTerminalPlanContractStatus(current.status)) throw new Error(`plan contract ${current.planId} is terminal (${current.status})`);
326
- const replacement = sanitizeFileComponent(supersededBy);
327
- if (!replacement || replacement === 'unknown') throw new Error('superseded_by is required');
328
- const at = nowIso(options);
329
- const next = { ...current, status: 'superseded' as const, supersededBy: replacement, updatedAt: at };
330
- const contracts = [...store.contracts];
331
- contracts[index] = next;
332
- writePlanContractStore(options, { schemaVersion: 1, updatedAt: at, contracts });
333
- return next;
498
+ return withPlanAdmissionLock(options, () => {
499
+ const store = readPlanContractStore(options);
500
+ const index = store.contracts.findIndex((contract) => contract.planId === sanitizeFileComponent(planId));
501
+ if (index < 0) throw new Error(`plan contract not found: ${sanitizeFileComponent(planId)}`);
502
+ const current = store.contracts[index]!;
503
+ if (isTerminalPlanContractStatus(current.status)) throw new Error(`plan contract ${current.planId} is terminal (${current.status})`);
504
+ const replacement = sanitizeFileComponent(supersededBy);
505
+ if (!replacement || replacement === 'unknown') throw new Error('superseded_by is required');
506
+ if (replacement === current.planId) throw new Error('PLAN_SUCCESSOR_ID_MUST_CHANGE');
507
+ const successor = store.contracts.find((contract) => contract.planId === replacement);
508
+ if (!successor) throw new Error(`PLAN_SUCCESSOR_NOT_FOUND: ${replacement}`);
509
+ const at = nowIso(options);
510
+ const next = { ...current, status: 'superseded' as const, supersededBy: successor.planId, updatedAt: at };
511
+ const contracts = [...store.contracts];
512
+ contracts[index] = next;
513
+ writePlanContractStore(options, { schemaVersion: 1, updatedAt: at, contracts });
514
+ return next;
515
+ });
334
516
  }
335
517
 
336
518
  /**
@@ -343,6 +525,7 @@ export function claimPlanStepForWork(
343
525
  input: { planId: string; stepId: string; workId: string; sourceRevision: string },
344
526
  ): PlanContract {
345
527
  return updatePlanContract(options, input.planId, (current) => {
528
+ assertRequirementReference(options, current.requirementId);
346
529
  if (current.status !== 'approved' && current.status !== 'executing') {
347
530
  throw new Error(`PLAN_NOT_EXECUTABLE: ${current.planId} is ${current.status}`);
348
531
  }
@@ -363,6 +546,45 @@ export function claimPlanStepForWork(
363
546
  });
364
547
  }
365
548
 
549
+ export function repairDanglingPlanStepWorkBinding(
550
+ options: PlanContractStoreOptions,
551
+ input: { planId: string; stepId: string; expectedWorkId: string; reason: string },
552
+ ): PlanContract {
553
+ const expectedWorkId = input.expectedWorkId.trim();
554
+ const reason = input.reason.trim();
555
+ if (!expectedWorkId) throw new Error('PLAN_STEP_REPAIR_EXPECTED_WORK_REQUIRED');
556
+ if (!reason) throw new Error('PLAN_STEP_REPAIR_REASON_REQUIRED');
557
+ return updatePlanContract(options, input.planId, (current) => {
558
+ if (isTerminalPlanContractStatus(current.status)) throw new Error(`PLAN_STEP_REPAIR_PLAN_TERMINAL: ${current.planId} is ${current.status}`);
559
+ const stepIndex = current.steps.findIndex((step) => step.id === sanitizeFileComponent(input.stepId));
560
+ if (stepIndex < 0) throw new Error(`PLAN_STEP_NOT_FOUND: ${input.stepId}`);
561
+ const step = current.steps[stepIndex];
562
+ if (step.workId !== expectedWorkId) {
563
+ throw new Error(`PLAN_STEP_REPAIR_BINDING_CHANGED: ${step.id} is bound to ${step.workId ?? 'no Work'}, expected ${expectedWorkId}`);
564
+ }
565
+ if (step.status !== 'executing') {
566
+ throw new Error(`PLAN_STEP_REPAIR_STATUS_INVALID: ${step.id} is ${step.status}`);
567
+ }
568
+ const at = nowIso(options);
569
+ const steps = [...current.steps];
570
+ steps[stepIndex] = {
571
+ ...step,
572
+ status: 'ready',
573
+ workId: undefined,
574
+ evidenceRefs: [{
575
+ title: 'dangling Work binding repaired',
576
+ summary: `${expectedWorkId}: ${reason}`.slice(0, 2_000),
577
+ detailLevel: 'summary' as const,
578
+ }, ...step.evidenceRefs].slice(0, 20),
579
+ };
580
+ // This is admission-state repair, not semantic replanning: the Plan and its
581
+ // acceptance criteria remain authoritative. Return the exact step to ready
582
+ // so a later start can atomically admit one replacement for the missing
583
+ // record without forcing a second Plan authority.
584
+ return { ...current, status: 'executing', steps, updatedAt: at };
585
+ });
586
+ }
587
+
366
588
  export function completePlanStepForWork(
367
589
  options: PlanContractStoreOptions,
368
590
  input: {
@@ -392,6 +614,10 @@ export function completePlanStepForWork(
392
614
  // semantic validation until an explicit Controller acceptance records it.
393
615
  // Failed/cancelled Work returns the slice to ready for replanning.
394
616
  status: delivered ? 'validating' : 'ready',
617
+ // Successful delivery keeps the exact Work identity through semantic
618
+ // validation. Failed/cancelled Work releases the step authority so the
619
+ // replanning path cannot retain a ghost binding to a terminal Work.
620
+ workId: delivered ? step.workId : undefined,
395
621
  evidenceRefs: input.work.evidenceRefs.length > 0 ? input.work.evidenceRefs.slice(0, 20) : step.evidenceRefs,
396
622
  };
397
623
  return { ...current, status: delivered ? 'verifying' : 'replanning', steps, updatedAt: at };
@@ -400,7 +626,7 @@ export function completePlanStepForWork(
400
626
 
401
627
  export function acceptPlanStepEvidence(
402
628
  options: PlanContractStoreOptions,
403
- input: { planId: string; stepId: string; reviewer: string; rationale: string },
629
+ input: { planId: string; stepId: string; reviewer: string; rationale: string; acceptedSourceRevision?: string },
404
630
  ): PlanContract {
405
631
  const reviewer = input.reviewer.trim();
406
632
  const rationale = input.rationale.trim();
@@ -418,6 +644,13 @@ export function acceptPlanStepEvidence(
418
644
  evidenceRefs: [{ title: 'semantic acceptance', summary: `${reviewer}: ${rationale}`, detailLevel: 'summary' as const }, ...step.evidenceRefs].slice(0, 20),
419
645
  };
420
646
  const allCompleted = steps.every((candidate) => candidate.status === 'completed');
421
- return { ...current, status: allCompleted ? 'finalized' : 'executing', steps, updatedAt: nowIso(options) };
647
+ const acceptedSourceRevision = input.acceptedSourceRevision?.trim();
648
+ return {
649
+ ...current,
650
+ ...(acceptedSourceRevision ? { sourceRevision: acceptedSourceRevision } : {}),
651
+ status: allCompleted ? 'finalized' : 'executing',
652
+ steps,
653
+ updatedAt: nowIso(options),
654
+ };
422
655
  });
423
656
  }
@@ -0,0 +1,147 @@
1
+ import type { AccessMode } from '../governance/access-policy';
2
+ import type { RouteDecision } from '../routing/route-policy';
3
+ import { createWorkContract, getWorkContract, updateWorkContract, type WorkContractStoreOptions } from './work-contract-store';
4
+ import type { WorkContract } from './types';
5
+
6
+ export interface PreparedRepositoryWorkAdmissionInput {
7
+ workId: string;
8
+ repoId: string;
9
+ objective: string;
10
+ acceptanceCriteria: string[];
11
+ allowedPaths: string[];
12
+ checks: string[];
13
+ accessMode: AccessMode;
14
+ isolated: boolean;
15
+ requestedBy: WorkContract['requestedBy'];
16
+ requestId: string;
17
+ }
18
+
19
+ /**
20
+ * Canonical WorkContract admission for the compatibility work_prepare surface.
21
+ * Workspace materialization remains an execution concern; this function owns
22
+ * the semantic contract shape so transports cannot invent a parallel policy.
23
+ */
24
+ export function admitPreparedRepositoryWorkContract(
25
+ store: WorkContractStoreOptions,
26
+ input: PreparedRepositoryWorkAdmissionInput,
27
+ ): WorkContract {
28
+ return createWorkContract(store, {
29
+ workId: input.workId,
30
+ repoId: input.repoId,
31
+ mode: input.isolated ? 'goal_workloop' : 'direct_control',
32
+ objective: input.objective,
33
+ acceptanceCriteria: input.acceptanceCriteria,
34
+ allowedPaths: input.allowedPaths,
35
+ forbiddenPaths: [],
36
+ checks: input.checks,
37
+ constraints: {
38
+ accessMode: input.accessMode,
39
+ workspaceMode: input.isolated ? 'isolated' : 'current',
40
+ requireWorktree: input.isolated,
41
+ directMainProhibited: input.isolated,
42
+ allowCommit: true,
43
+ allowMerge: true,
44
+ allowCleanup: true,
45
+ },
46
+ worktreePolicy: {
47
+ required: input.isolated,
48
+ reason: input.isolated ? 'work_prepare selected isolated worktree execution' : 'explicitly reused a registered checkout',
49
+ },
50
+ requestedBy: input.requestedBy,
51
+ requestId: input.requestId,
52
+ });
53
+ }
54
+
55
+ export interface DirectEditWorkAdmissionInput {
56
+ workId: string;
57
+ repoId: string;
58
+ checkoutId?: string;
59
+ principalId?: string;
60
+ controllerInstanceId?: string;
61
+ baseRevision?: string;
62
+ workspaceFingerprint: string;
63
+ routeDecision: RouteDecision;
64
+ objective: string;
65
+ issueId?: string;
66
+ taskId?: string;
67
+ allowedPaths: string[];
68
+ checks: string[];
69
+ requestedBy: WorkContract['requestedBy'];
70
+ }
71
+
72
+ /**
73
+ * Canonical compatibility admission for an Edit Session bound to Direct Work.
74
+ * The caller supplies the canonical Route Policy decision as evidence; this
75
+ * boundary rejects any decision that requires isolation or a non-direct lane.
76
+ */
77
+ export function admitDirectEditWorkContract(
78
+ store: WorkContractStoreOptions,
79
+ input: DirectEditWorkAdmissionInput,
80
+ ): WorkContract {
81
+ if (input.routeDecision.executionMode !== 'direct_control' || input.routeDecision.requiresIsolation) {
82
+ throw new Error('DIRECT_EDIT_WORK_ROUTE_CONFLICT: canonical Route Policy does not permit Direct Control');
83
+ }
84
+ return createWorkContract(store, {
85
+ workId: input.workId,
86
+ repoId: input.repoId,
87
+ checkoutId: input.checkoutId,
88
+ principalId: input.principalId,
89
+ controllerInstanceId: input.controllerInstanceId,
90
+ baseRevision: input.baseRevision,
91
+ workspaceFingerprint: input.workspaceFingerprint,
92
+ routeDecisionFingerprint: input.routeDecision.inputFingerprint,
93
+ routeDecision: input.routeDecision,
94
+ mode: 'direct_control',
95
+ objective: input.objective,
96
+ acceptanceCriteria: [],
97
+ constraints: { workspaceMode: 'current', requireWorktree: false, requireHandoffOnAmbiguity: true },
98
+ risk: 'low',
99
+ status: 'running',
100
+ phase: 'implementation',
101
+ issueId: input.issueId,
102
+ taskId: input.taskId,
103
+ scopeSummary: input.objective,
104
+ allowedPaths: input.allowedPaths,
105
+ forbiddenPaths: [],
106
+ checks: input.checks,
107
+ requestedBy: input.requestedBy,
108
+ evidenceRefs: [],
109
+ handoffRefs: [],
110
+ suggestedNextActions: [],
111
+ policyDecisions: [],
112
+ checkRefs: [],
113
+ reconciliations: [],
114
+ });
115
+ }
116
+
117
+
118
+ export interface MaterializedRepositoryWorkspace {
119
+ managed: boolean;
120
+ checkoutId?: string;
121
+ root?: string;
122
+ baseRevision?: string | null;
123
+ }
124
+
125
+ /**
126
+ * Canonical placement materialization transition for an admitted isolated
127
+ * WorkContract. The concrete worktree creator is injected by the execution
128
+ * boundary; only this authority decides how its evidence updates the Work.
129
+ */
130
+ export function materializeRepositoryWorkPlacement(
131
+ store: WorkContractStoreOptions,
132
+ workId: string,
133
+ materialize: (contract: WorkContract) => MaterializedRepositoryWorkspace,
134
+ ): WorkContract | undefined {
135
+ const contract = getWorkContract(store, workId);
136
+ if (!contract || contract.worktreePolicy.required !== true || contract.worktreeRef) return contract;
137
+ const workspace = materialize(contract);
138
+ if (!workspace.managed || !workspace.checkoutId || !workspace.root) {
139
+ throw new Error('MANAGED_WORKSPACE_NOT_MATERIALIZED');
140
+ }
141
+ return updateWorkContract(store, workId, {
142
+ checkoutId: workspace.checkoutId,
143
+ baseRevision: workspace.baseRevision ?? contract.baseRevision,
144
+ worktreeRef: workspace.root,
145
+ driver: { ...contract.driver, preferred: 'isolated_worktree', allowDirectEdit: false },
146
+ });
147
+ }
@@ -0,0 +1,83 @@
1
+ import {
2
+ createRequirement,
3
+ readRequirement,
4
+ type CreateRequirementInput,
5
+ type Requirement,
6
+ type RequirementStoreOptions,
7
+ } from '../persistence/requirement-store';
8
+
9
+ export type RequirementAdmissionDecision = 'created' | 'reuse_existing' | 'existing_conflict';
10
+
11
+ export interface RequirementAdmissionResult {
12
+ decision: RequirementAdmissionDecision;
13
+ requirement: Requirement;
14
+ created: boolean;
15
+ }
16
+
17
+ function bounded(values: readonly string[] | undefined, limit: number, maxLength = 500): string[] {
18
+ return (values ?? [])
19
+ .map((value) => String(value).trim())
20
+ .filter(Boolean)
21
+ .slice(0, limit)
22
+ .map((value) => value.slice(0, maxLength));
23
+ }
24
+
25
+ /**
26
+ * Canonical Requirement bootstrap request. Persistence performs the same
27
+ * defensive bounds before writing, but create/reuse/conflict admission lives
28
+ * only here so MCP/CLI adapters cannot grow their own Requirement lifecycle.
29
+ */
30
+ export function normalizeRequirementAdmissionInput(input: CreateRequirementInput): CreateRequirementInput {
31
+ const requirementId = String(input.requirementId ?? '').trim();
32
+ if (!requirementId || requirementId.includes('/') || requirementId.includes('\\')) throw new Error('REQUIREMENT_ID_INVALID');
33
+ const normalized: CreateRequirementInput = {
34
+ requirementId: requirementId.slice(0, 160),
35
+ title: String(input.title ?? '').trim().slice(0, 500),
36
+ outcomeStatement: String(input.outcomeStatement ?? '').trim().slice(0, 2_000),
37
+ acceptanceCriteria: bounded(input.acceptanceCriteria, 50),
38
+ requiredDeliveryReferences: bounded(input.requiredDeliveryReferences, 50),
39
+ legacyAliases: bounded(input.legacyAliases, 20, 160),
40
+ };
41
+ if (!normalized.title || !normalized.outcomeStatement) throw new Error('REQUIREMENT_CONTENT_REQUIRED');
42
+ return normalized;
43
+ }
44
+
45
+ function sameBootstrapIdentity(existing: Requirement, requested: CreateRequirementInput): boolean {
46
+ return existing.requirementId === requested.requirementId
47
+ && existing.title === requested.title
48
+ && existing.outcomeStatement === requested.outcomeStatement
49
+ && JSON.stringify(existing.acceptanceCriteria) === JSON.stringify(requested.acceptanceCriteria ?? [])
50
+ && JSON.stringify(existing.requiredDeliveryReferences) === JSON.stringify(requested.requiredDeliveryReferences ?? [])
51
+ && JSON.stringify(existing.legacyAliases) === JSON.stringify(requested.legacyAliases ?? []);
52
+ }
53
+
54
+ function existingDecision(existing: Requirement, requested: CreateRequirementInput): RequirementAdmissionResult {
55
+ return {
56
+ decision: sameBootstrapIdentity(existing, requested) ? 'reuse_existing' : 'existing_conflict',
57
+ requirement: existing,
58
+ created: false,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Sole bootstrap admission authority for Requirement identity. A create race is
64
+ * closed by rereading the winner and applying the same identity comparison;
65
+ * adapters therefore never need read/compare/create policy of their own.
66
+ */
67
+ export function admitRequirement(
68
+ options: RequirementStoreOptions,
69
+ input: CreateRequirementInput,
70
+ ): RequirementAdmissionResult {
71
+ const requested = normalizeRequirementAdmissionInput(input);
72
+ const existing = readRequirement(options, requested.requirementId)?.value;
73
+ if (existing) return existingDecision(existing, requested);
74
+
75
+ try {
76
+ return { decision: 'created', requirement: createRequirement(options, requested), created: true };
77
+ } catch (error) {
78
+ if (!(error instanceof Error) || !error.message.startsWith('REQUIREMENT_ALREADY_EXISTS:')) throw error;
79
+ const raced = readRequirement(options, requested.requirementId)?.value;
80
+ if (!raced) throw error;
81
+ return existingDecision(raced, requested);
82
+ }
83
+ }