@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
@@ -0,0 +1,250 @@
1
+ import {
2
+ isDirectEditWorkCompletionReceipt,
3
+ isReadOnlyReviewCompletionReceipt,
4
+ isRemoteEffectCompletionReceipt,
5
+ isRepositoryCompletionReceipt,
6
+ type DispatchState,
7
+ type EvidenceRef,
8
+ type EvidenceState,
9
+ type SuggestedNextAction,
10
+ type WorkContract,
11
+ type WorkContractStatus,
12
+ type WorkPhase,
13
+ type WorkPhaseEvidence,
14
+ type WorkPhaseEvidenceMap,
15
+ } from './types';
16
+
17
+ export function phaseIndex(phase: WorkPhase): number {
18
+ return ['implementation', 'verification', 'delivery', 'cleanup'].indexOf(phase);
19
+ }
20
+
21
+ export function transitionPhaseEvidence(
22
+ current: Pick<WorkContract, 'phaseEvidence' | 'evidenceRefs'>,
23
+ targetPhase: WorkPhase,
24
+ input: {
25
+ status: WorkContractStatus;
26
+ summary: string;
27
+ evidenceRefs?: EvidenceRef[];
28
+ recordedAt: string;
29
+ source?: WorkPhaseEvidence['source'];
30
+ },
31
+ ): WorkPhaseEvidenceMap {
32
+ const targetIndex = phaseIndex(targetPhase);
33
+ const evidenceRefs = (input.evidenceRefs ?? current.evidenceRefs).slice(0, 20);
34
+ const source = input.source ?? 'recorded';
35
+ const phaseEvidence = { ...current.phaseEvidence };
36
+ for (const phase of ['implementation', 'verification', 'delivery', 'cleanup'] as WorkPhase[]) {
37
+ const index = phaseIndex(phase);
38
+ if (index < targetIndex) {
39
+ phaseEvidence[phase] = {
40
+ state: ['satisfied', 'skipped'].includes(phaseEvidence[phase].state) ? phaseEvidence[phase].state : 'satisfied',
41
+ source,
42
+ summary: `Advanced to ${targetPhase}: ${input.summary}`.slice(0, 1_000),
43
+ evidenceRefs,
44
+ recordedAt: input.recordedAt,
45
+ };
46
+ } else if (index > targetIndex) {
47
+ phaseEvidence[phase] = {
48
+ state: 'pending',
49
+ source,
50
+ summary: `Waiting for Work phase ${phase}.`,
51
+ evidenceRefs: [],
52
+ recordedAt: input.recordedAt,
53
+ };
54
+ }
55
+ }
56
+ phaseEvidence[targetPhase] = {
57
+ state: input.status === 'failed'
58
+ ? 'failed'
59
+ : input.status === 'cancelled'
60
+ ? 'skipped'
61
+ : input.status === 'blocked' || input.status === 'ready'
62
+ ? 'blocked'
63
+ : 'active',
64
+ source,
65
+ summary: input.summary.trim().slice(0, 1_000) || `Work entered ${targetPhase}.`,
66
+ evidenceRefs,
67
+ recordedAt: input.recordedAt,
68
+ };
69
+ return phaseEvidence;
70
+ }
71
+
72
+ export function validateWorkSemantics(contract: WorkContract): WorkContract {
73
+ if (!contract.objective.trim()) throw new Error('WORK_OBJECTIVE_REQUIRED');
74
+ if (contract.status === 'completed' && !contract.completionReceipt) {
75
+ throw new Error('WORK_COMPLETION_RECEIPT_REQUIRED');
76
+ }
77
+ const currentPhaseIndex = phaseIndex(contract.phase);
78
+ for (const phase of ['implementation', 'verification', 'delivery', 'cleanup'] as WorkPhase[]) {
79
+ const checkpoint = contract.phaseEvidence?.[phase];
80
+ if (!checkpoint) throw new Error(`WORK_PHASE_EVIDENCE_REQUIRED: ${phase}`);
81
+ const index = phaseIndex(phase);
82
+ if (index < currentPhaseIndex && !['satisfied', 'skipped'].includes(checkpoint.state)) {
83
+ throw new Error(`WORK_PHASE_EVIDENCE_PREVIOUS_NOT_SATISFIED: ${phase}`);
84
+ }
85
+ if (index === currentPhaseIndex && checkpoint.state === 'pending') {
86
+ throw new Error(`WORK_PHASE_EVIDENCE_REQUIRED: ${phase}`);
87
+ }
88
+ if (index > currentPhaseIndex && checkpoint.state !== 'pending') {
89
+ throw new Error(`WORK_PHASE_EVIDENCE_FUTURE_NOT_PENDING: ${phase}`);
90
+ }
91
+ }
92
+ if (contract.completionReceipt) {
93
+ const receipt = contract.completionReceipt;
94
+ if (receipt.workId !== contract.workId) throw new Error('WORK_COMPLETION_RECEIPT_IDENTITY_MISMATCH');
95
+ if (isRepositoryCompletionReceipt(receipt)) {
96
+ if (!receipt.targetBranch.trim() || !receipt.targetRevision.trim()) throw new Error('WORK_COMPLETION_RECEIPT_TARGET_REQUIRED');
97
+ if (receipt.delivery.status !== 'integrated' || !receipt.delivery.reachable) throw new Error('WORK_COMPLETION_RECEIPT_DELIVERY_NOT_PROVEN');
98
+ if (!['complete', 'maintenance_warning'].includes(receipt.cleanup.status) || receipt.cleanup.blockers.length > 0) throw new Error('WORK_COMPLETION_RECEIPT_CLEANUP_NOT_PROVEN');
99
+ } else if (isDirectEditWorkCompletionReceipt(receipt)) {
100
+ const directEditAuthorityCount = Number(Boolean(receipt.editSessionId?.trim())) + Number(Boolean(receipt.reconciliationId?.trim()));
101
+ if (directEditAuthorityCount !== 1) throw new Error('WORK_COMPLETION_RECEIPT_DIRECT_EDIT_AUTHORITY_REQUIRED');
102
+ if (!receipt.targetBranch.trim() || !receipt.targetRevision.trim()) throw new Error('WORK_COMPLETION_RECEIPT_TARGET_REQUIRED');
103
+ if (receipt.delivery.status !== 'integrated' || !receipt.delivery.reachable) throw new Error('WORK_COMPLETION_RECEIPT_DELIVERY_NOT_PROVEN');
104
+ if (!['complete', 'maintenance_warning'].includes(receipt.cleanup.status) || receipt.cleanup.blockers.length > 0) throw new Error('WORK_COMPLETION_RECEIPT_CLEANUP_NOT_PROVEN');
105
+ if (!['repository_change', 'completed_no_change'].includes(contract.workKind)) throw new Error('WORK_COMPLETION_RECEIPT_DIRECT_EDIT_KIND_REQUIRED');
106
+ const expectedOutcome = contract.workKind === 'completed_no_change' ? 'completed_no_change' : 'completed_changed';
107
+ if (contract.completionOutcome !== expectedOutcome) throw new Error('WORK_COMPLETION_RECEIPT_DIRECT_EDIT_OUTCOME_REQUIRED');
108
+ } else if (isReadOnlyReviewCompletionReceipt(receipt)) {
109
+ if (contract.workKind !== 'read_only_review') throw new Error('WORK_COMPLETION_RECEIPT_READ_ONLY_REVIEW_KIND_REQUIRED');
110
+ if (contract.completionOutcome !== 'completed_no_change') throw new Error('WORK_COMPLETION_RECEIPT_READ_ONLY_REVIEW_OUTCOME_REQUIRED');
111
+ if (!receipt.baseRevision.trim() || receipt.baseRevision !== receipt.sourceRevision) throw new Error('WORK_COMPLETION_RECEIPT_READ_ONLY_REVIEW_SOURCE_REQUIRED');
112
+ if (contract.baseRevision && receipt.baseRevision !== contract.baseRevision) throw new Error('WORK_COMPLETION_RECEIPT_READ_ONLY_REVIEW_BASE_MISMATCH');
113
+ if (receipt.workspaceChangedPaths.length !== 0) throw new Error('WORK_COMPLETION_RECEIPT_READ_ONLY_REVIEW_WORKSPACE_CHANGED');
114
+ const reviewEvidence = contract.readOnlyReviewEvidence;
115
+ if (!reviewEvidence || reviewEvidence.sourceRevision !== receipt.sourceRevision) throw new Error('WORK_COMPLETION_RECEIPT_READ_ONLY_REVIEW_EVIDENCE_REQUIRED');
116
+ if (reviewEvidence.findings.length !== 0 || receipt.findingCount !== 0 || receipt.inspectedPaths.length === 0) throw new Error('WORK_COMPLETION_RECEIPT_READ_ONLY_REVIEW_CLEAN_SCOPE_REQUIRED');
117
+ const evidencePaths = [...new Set(reviewEvidence.inspectedPaths)].sort();
118
+ const receiptPaths = [...new Set(receipt.inspectedPaths)].sort();
119
+ if (evidencePaths.length !== receiptPaths.length || evidencePaths.some((path, index) => path !== receiptPaths[index])) {
120
+ throw new Error('WORK_COMPLETION_RECEIPT_READ_ONLY_REVIEW_SCOPE_MISMATCH');
121
+ }
122
+ if (reviewEvidence.workspaceFingerprint && receipt.workspaceFingerprint && reviewEvidence.workspaceFingerprint !== receipt.workspaceFingerprint) {
123
+ throw new Error('WORK_COMPLETION_RECEIPT_READ_ONLY_REVIEW_WORKSPACE_MISMATCH');
124
+ }
125
+ } else if (isRemoteEffectCompletionReceipt(receipt)) {
126
+ if (contract.workKind !== 'remote_effect') throw new Error('WORK_COMPLETION_RECEIPT_REMOTE_EFFECT_KIND_REQUIRED');
127
+ if (contract.completionOutcome !== 'completed_remote') throw new Error('WORK_COMPLETION_RECEIPT_REMOTE_EFFECT_OUTCOME_REQUIRED');
128
+ const authority = receipt.authority ?? 'plugin_action';
129
+ if (!receipt.actionId.trim() || !receipt.requestId.trim() || !receipt.semanticKey.trim() || !receipt.resultDigest.trim()) {
130
+ throw new Error('WORK_COMPLETION_RECEIPT_REMOTE_EFFECT_IDENTITY_REQUIRED');
131
+ }
132
+ if (authority === 'plugin_action' && !receipt.pluginId?.trim()) {
133
+ throw new Error('WORK_COMPLETION_RECEIPT_REMOTE_EFFECT_PLUGIN_IDENTITY_REQUIRED');
134
+ }
135
+ if (authority === 'repository_process' && !receipt.processId?.trim()) {
136
+ throw new Error('WORK_COMPLETION_RECEIPT_REMOTE_EFFECT_PROCESS_IDENTITY_REQUIRED');
137
+ }
138
+ } else {
139
+ if (contract.workKind !== 'local_effect') throw new Error('WORK_COMPLETION_RECEIPT_LOCAL_EFFECT_KIND_REQUIRED');
140
+ if (contract.completionOutcome !== 'completed_local') throw new Error('WORK_COMPLETION_RECEIPT_LOCAL_EFFECT_OUTCOME_REQUIRED');
141
+ if (!receipt.operation.trim() || !receipt.target.id.trim()) throw new Error('WORK_COMPLETION_RECEIPT_LOCAL_EFFECT_TARGET_REQUIRED');
142
+ }
143
+ if (contract.status !== 'completed') throw new Error('WORK_COMPLETION_RECEIPT_REQUIRES_COMPLETED_WORK');
144
+ for (const phase of ['implementation', 'verification', 'delivery', 'cleanup'] as WorkPhase[]) {
145
+ if (!['satisfied', 'skipped'].includes(contract.phaseEvidence[phase].state)) {
146
+ throw new Error(`WORK_COMPLETION_PHASE_NOT_SATISFIED: ${phase}`);
147
+ }
148
+ }
149
+ for (const phase of ['delivery', 'cleanup'] as WorkPhase[]) {
150
+ if (contract.phaseEvidence[phase].receiptId !== receipt.receiptId) {
151
+ throw new Error(`WORK_COMPLETION_PHASE_RECEIPT_MISMATCH: ${phase}`);
152
+ }
153
+ }
154
+ }
155
+ const outcome = contract.completionOutcome;
156
+ if (!outcome) return contract;
157
+ if (contract.status !== 'completed' || !contract.completionReceipt) {
158
+ throw new Error(`WORK_SEMANTICS_INVALID: ${outcome} requires a completed Work receipt`);
159
+ }
160
+ if (contract.dispatchState !== 'terminal') {
161
+ throw new Error(`WORK_SEMANTICS_INVALID: ${outcome} requires terminal dispatch`);
162
+ }
163
+ if (contract.evidenceState !== 'valid') {
164
+ throw new Error(`WORK_SEMANTICS_INVALID: ${outcome} requires valid evidence`);
165
+ }
166
+ if (outcome === 'completed_changed' && contract.workKind !== 'repository_change') {
167
+ throw new Error('WORK_SEMANTICS_INVALID: completed_changed requires repository_change WorkKind');
168
+ }
169
+ if (outcome === 'completed_no_change' && !['completed_no_change', 'read_only_review'].includes(contract.workKind)) {
170
+ throw new Error('WORK_SEMANTICS_INVALID: completed_no_change requires completed_no_change or read_only_review WorkKind');
171
+ }
172
+ if (outcome === 'completed_no_change' && contract.workKind === 'read_only_review' && contract.completionReceipt?.source !== 'read_only_review') {
173
+ throw new Error('WORK_SEMANTICS_INVALID: read_only_review completed_no_change requires a read_only_review completion receipt');
174
+ }
175
+ if (outcome === 'completed_local' && contract.workKind !== 'local_effect') {
176
+ throw new Error('WORK_SEMANTICS_INVALID: completed_local requires local_effect WorkKind');
177
+ }
178
+ if (outcome === 'completed_local' && contract.completionReceipt?.source !== 'local_effect') {
179
+ throw new Error('WORK_SEMANTICS_INVALID: completed_local requires a local_effect completion receipt');
180
+ }
181
+ if (outcome === 'completed_remote' && contract.workKind !== 'remote_effect') {
182
+ throw new Error('WORK_SEMANTICS_INVALID: completed_remote requires remote_effect WorkKind');
183
+ }
184
+ if (outcome === 'completed_remote' && contract.completionReceipt?.source !== 'remote_effect') {
185
+ throw new Error('WORK_SEMANTICS_INVALID: completed_remote requires a remote_effect completion receipt');
186
+ }
187
+ if (outcome === 'superseded' && contract.workKind !== 'superseded') {
188
+ throw new Error('WORK_SEMANTICS_INVALID: superseded requires superseded WorkKind');
189
+ }
190
+ return contract;
191
+ }
192
+
193
+ const DISPATCH_TRANSITIONS: Readonly<Record<DispatchState, readonly DispatchState[]>> = {
194
+ not_dispatched: ['not_dispatched', 'claimed', 'launching', 'running', 'blocked', 'terminal'],
195
+ claimed: ['claimed', 'launching', 'running', 'blocked', 'terminal'],
196
+ launching: ['launching', 'running', 'blocked', 'terminal'],
197
+ running: ['running', 'blocked', 'terminal'],
198
+ blocked: ['blocked', 'claimed', 'launching', 'running', 'terminal'],
199
+ terminal: ['terminal'],
200
+ };
201
+
202
+ const EVIDENCE_TRANSITIONS: Readonly<Record<EvidenceState, readonly EvidenceState[]>> = {
203
+ none: ['none', 'partial', 'valid', 'failed'],
204
+ partial: ['partial', 'valid', 'stale', 'contradictory', 'failed'],
205
+ valid: ['valid', 'stale', 'contradictory', 'failed'],
206
+ stale: ['stale', 'partial', 'valid', 'contradictory', 'failed'],
207
+ contradictory: ['contradictory', 'partial', 'valid', 'failed'],
208
+ failed: ['failed', 'partial', 'valid', 'contradictory'],
209
+ };
210
+
211
+ export function validateWorkSemanticTransition(
212
+ current: WorkContract,
213
+ next: WorkContract,
214
+ options: { allowRetainedCancelledResume?: boolean } = {},
215
+ ): WorkContract {
216
+ const retryingFailedWork = current.status === 'failed'
217
+ && !current.completionOutcome
218
+ && ['claimed', 'launching', 'running', 'blocked'].includes(next.dispatchState);
219
+ const resumingRetainedCancelledWork = options.allowRetainedCancelledResume === true
220
+ && current.status === 'cancelled'
221
+ && current.dispatchState === 'terminal'
222
+ && current.phase === 'cleanup'
223
+ && current.phaseEvidence.cleanup.state === 'skipped'
224
+ && !current.completionReceipt
225
+ && !current.completionOutcome
226
+ && next.status === 'running'
227
+ && next.dispatchState === 'running'
228
+ && next.phase === 'implementation';
229
+ if (!retryingFailedWork && !resumingRetainedCancelledWork && !DISPATCH_TRANSITIONS[current.dispatchState].includes(next.dispatchState)) {
230
+ throw new Error(`WORK_SEMANTICS_TRANSITION_INVALID: dispatch ${current.dispatchState} -> ${next.dispatchState}`);
231
+ }
232
+ if (!EVIDENCE_TRANSITIONS[current.evidenceState].includes(next.evidenceState)) {
233
+ throw new Error(`WORK_SEMANTICS_TRANSITION_INVALID: evidence ${current.evidenceState} -> ${next.evidenceState}`);
234
+ }
235
+ if (current.completionOutcome && current.completionOutcome !== next.completionOutcome) {
236
+ throw new Error(`WORK_SEMANTICS_TRANSITION_INVALID: completion outcome ${current.completionOutcome} is immutable`);
237
+ }
238
+ if ((current.lifecycleRole ?? 'primary') !== (next.lifecycleRole ?? 'primary')) {
239
+ throw new Error('WORK_SEMANTICS_TRANSITION_INVALID: lifecycleRole is immutable');
240
+ }
241
+ if (current.parentWorkId !== next.parentWorkId) {
242
+ throw new Error('WORK_SEMANTICS_TRANSITION_INVALID: parentWorkId is immutable');
243
+ }
244
+ return next;
245
+ }
246
+
247
+ export function suggestedActionsForStatus(status: WorkContractStatus, actions: readonly SuggestedNextAction[]): SuggestedNextAction[] {
248
+ if (status === 'completed' || status === 'cancelled') return [];
249
+ return actions.slice(0, 8);
250
+ }
@@ -0,0 +1,271 @@
1
+ import { execFileSync } from 'child_process';
2
+ import {
3
+ existsSync,
4
+ lstatSync,
5
+ readdirSync,
6
+ realpathSync,
7
+ rmSync,
8
+ } from 'fs';
9
+ import { homedir } from 'os';
10
+ import { basename, join, relative, resolve, sep } from 'path';
11
+
12
+ const DEFAULT_GENERATED_CACHE_GRACE_MS = 6 * 60 * 60_000;
13
+ const DEFAULT_SCAN_BUDGET = 1_000;
14
+ const DEFAULT_REMOVAL_BUDGET = 8;
15
+
16
+ export interface GeneratedCacheRetentionOptions {
17
+ nowMs?: number;
18
+ graceMs?: number;
19
+ maxEntries?: number;
20
+ maxRemovals?: number;
21
+ processCommands?: string[];
22
+ }
23
+
24
+ export interface GeneratedCacheRetentionReport {
25
+ inspected: number;
26
+ eligible: number;
27
+ removedPaths: string[];
28
+ retainedPaths: string[];
29
+ skippedByReason: Record<string, number>;
30
+ errors: string[];
31
+ budgetExhausted: boolean;
32
+ }
33
+
34
+ export interface XCTestDeviceCleanupReport {
35
+ attempted: boolean;
36
+ removedDevices: number;
37
+ skippedReason?: string;
38
+ error?: string;
39
+ }
40
+
41
+ interface Candidate {
42
+ path: string;
43
+ kind: 'forge_ios_derived_data' | 'harness_derived_data' | 'repo_harness_derived_data' | 'tmp_derived_data';
44
+ }
45
+
46
+ function increment(counts: Record<string, number>, reason: string): void {
47
+ counts[reason] = (counts[reason] ?? 0) + 1;
48
+ }
49
+
50
+ function canonical(path: string): string {
51
+ try { return realpathSync(path); } catch { return resolve(path); }
52
+ }
53
+
54
+ function pathInside(root: string, path: string): boolean {
55
+ const base = canonical(root);
56
+ const target = canonical(path);
57
+ const rel = relative(base, target);
58
+ return rel !== '' && rel !== '..' && !rel.startsWith(`..${sep}`);
59
+ }
60
+
61
+ function collectProcessCommands(): string[] {
62
+ try {
63
+ return execFileSync('ps', ['-axo', 'command='], {
64
+ encoding: 'utf8',
65
+ timeout: 2_000,
66
+ maxBuffer: 2 * 1024 * 1024,
67
+ }).split('\n').map((line) => line.trim()).filter(Boolean);
68
+ } catch {
69
+ return [];
70
+ }
71
+ }
72
+
73
+ function commandReferencesPath(commands: readonly string[], repoRoot: string, path: string): boolean {
74
+ const absolute = canonical(path);
75
+ const rel = relative(resolve(repoRoot), absolute).replace(/\\/g, '/');
76
+ return commands.some((command) => command.includes(absolute) || (rel && command.includes(rel)));
77
+ }
78
+
79
+ function containsTrackedFiles(repoRoot: string, path: string): boolean {
80
+ const rel = relative(resolve(repoRoot), resolve(path));
81
+ if (!rel || rel === '..' || rel.startsWith(`..${sep}`)) return true;
82
+ try {
83
+ const output = execFileSync('git', ['-C', repoRoot, 'ls-files', '--', rel], {
84
+ encoding: 'utf8',
85
+ timeout: 2_000,
86
+ maxBuffer: 256 * 1024,
87
+ });
88
+ return output.trim().length > 0;
89
+ } catch {
90
+ // If repository identity cannot be proven, fail closed.
91
+ return true;
92
+ }
93
+ }
94
+
95
+ function directChildDirectories(root: string, predicate: (name: string) => boolean, budget: { remaining: number }): string[] {
96
+ if (!existsSync(root) || budget.remaining <= 0) return [];
97
+ const found: string[] = [];
98
+ let entries;
99
+ try { entries = readdirSync(root, { withFileTypes: true }); } catch { return found; }
100
+ for (const entry of entries) {
101
+ if (budget.remaining <= 0) break;
102
+ budget.remaining -= 1;
103
+ const name = String(entry.name);
104
+ if (entry.isDirectory() && predicate(name)) found.push(join(root, name));
105
+ }
106
+ return found;
107
+ }
108
+
109
+ function nestedDerivedDataDirectories(root: string, maxDepth: number, budget: { remaining: number }): string[] {
110
+ if (!existsSync(root) || budget.remaining <= 0) return [];
111
+ const found: string[] = [];
112
+ const visit = (dir: string, depth: number): void => {
113
+ if (depth > maxDepth || budget.remaining <= 0) return;
114
+ let entries;
115
+ try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
116
+ for (const entry of entries) {
117
+ if (budget.remaining <= 0) return;
118
+ budget.remaining -= 1;
119
+ if (!entry.isDirectory()) continue;
120
+ const path = join(dir, String(entry.name));
121
+ if (String(entry.name) === 'DerivedData') {
122
+ found.push(path);
123
+ continue;
124
+ }
125
+ visit(path, depth + 1);
126
+ }
127
+ };
128
+ visit(root, 0);
129
+ return found;
130
+ }
131
+
132
+ function discoverCandidates(repoRoot: string, maxEntries: number): { candidates: Candidate[]; inspected: number; truncated: boolean } {
133
+ const root = resolve(repoRoot);
134
+ const budget = { remaining: Math.max(1, maxEntries) };
135
+ const initial = budget.remaining;
136
+ const candidates: Candidate[] = [];
137
+
138
+ for (const path of directChildDirectories(join(root, '.forge', 'ios', 'DerivedData'), () => true, budget)) {
139
+ candidates.push({ path, kind: 'forge_ios_derived_data' });
140
+ }
141
+ for (const path of directChildDirectories(join(root, '.ai', 'harness'), (name) => name.startsWith('deriveddata-'), budget)) {
142
+ candidates.push({ path, kind: 'harness_derived_data' });
143
+ }
144
+ for (const path of nestedDerivedDataDirectories(join(root, '.repo-harness'), 5, budget)) {
145
+ candidates.push({ path, kind: 'repo_harness_derived_data' });
146
+ }
147
+ for (const path of nestedDerivedDataDirectories(join(root, '.tmp'), 6, budget)) {
148
+ candidates.push({ path, kind: 'tmp_derived_data' });
149
+ }
150
+
151
+ return {
152
+ candidates: candidates.filter((candidate, index, values) => values.findIndex((item) => resolve(item.path) === resolve(candidate.path)) === index),
153
+ inspected: initial - budget.remaining,
154
+ truncated: budget.remaining <= 0,
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Reclaim only rebuildable Xcode DerivedData under Forge/repo-harness-owned
160
+ * namespaces. Result bundles, logs, edit-session metadata, and all tracked files
161
+ * are deliberately outside this cleanup surface.
162
+ */
163
+ export function cleanupGeneratedRepositoryCaches(
164
+ repoRoot: string,
165
+ options: GeneratedCacheRetentionOptions = {},
166
+ ): GeneratedCacheRetentionReport {
167
+ const nowMs = options.nowMs ?? Date.now();
168
+ const graceMs = Math.max(60_000, Math.floor(options.graceMs ?? DEFAULT_GENERATED_CACHE_GRACE_MS));
169
+ const maxEntries = Math.max(1, Math.floor(options.maxEntries ?? DEFAULT_SCAN_BUDGET));
170
+ let remainingRemovals = Math.max(1, Math.floor(options.maxRemovals ?? DEFAULT_REMOVAL_BUDGET));
171
+ const commands = options.processCommands ?? collectProcessCommands();
172
+ const discovered = discoverCandidates(repoRoot, maxEntries);
173
+ const report: GeneratedCacheRetentionReport = {
174
+ inspected: discovered.inspected,
175
+ eligible: 0,
176
+ removedPaths: [],
177
+ retainedPaths: [],
178
+ skippedByReason: {},
179
+ errors: [],
180
+ budgetExhausted: discovered.truncated,
181
+ };
182
+
183
+ for (const candidate of discovered.candidates) {
184
+ const ownedRoot = candidate.kind === 'forge_ios_derived_data'
185
+ ? join(repoRoot, '.forge', 'ios', 'DerivedData')
186
+ : candidate.kind === 'harness_derived_data'
187
+ ? join(repoRoot, '.ai', 'harness')
188
+ : candidate.kind === 'repo_harness_derived_data'
189
+ ? join(repoRoot, '.repo-harness')
190
+ : join(repoRoot, '.tmp');
191
+ try {
192
+ const stat = lstatSync(candidate.path);
193
+ if (!stat.isDirectory() || stat.isSymbolicLink() || !pathInside(ownedRoot, candidate.path)) {
194
+ increment(report.skippedByReason, 'ownership_unproven');
195
+ report.retainedPaths.push(relative(repoRoot, candidate.path));
196
+ continue;
197
+ }
198
+ if (nowMs - stat.mtimeMs < graceMs) {
199
+ increment(report.skippedByReason, 'retention_grace');
200
+ continue;
201
+ }
202
+ if (commandReferencesPath(commands, repoRoot, candidate.path)) {
203
+ increment(report.skippedByReason, 'active_process');
204
+ report.retainedPaths.push(relative(repoRoot, candidate.path));
205
+ continue;
206
+ }
207
+ if (containsTrackedFiles(repoRoot, candidate.path)) {
208
+ increment(report.skippedByReason, 'tracked_content');
209
+ report.retainedPaths.push(relative(repoRoot, candidate.path));
210
+ continue;
211
+ }
212
+ report.eligible += 1;
213
+ if (remainingRemovals <= 0) {
214
+ report.budgetExhausted = true;
215
+ increment(report.skippedByReason, 'cleanup_budget_exhausted');
216
+ continue;
217
+ }
218
+ remainingRemovals -= 1;
219
+ rmSync(candidate.path, { recursive: true, force: true });
220
+ report.removedPaths.push(relative(repoRoot, candidate.path).replace(/\\/g, '/'));
221
+ } catch (error) {
222
+ report.errors.push(`${candidate.kind}:${relative(repoRoot, candidate.path)}: ${error instanceof Error ? error.message : String(error)}`);
223
+ }
224
+ }
225
+
226
+ return report;
227
+ }
228
+
229
+ /**
230
+ * XCTest's private device set is separate from the user's normal CoreSimulator
231
+ * devices. When Forge is idle, delete only that dedicated set through simctl so
232
+ * XCTest clones cannot accumulate indefinitely. Never rm the device set.
233
+ */
234
+ export function cleanupIdleXCTestDevices(controllerHome: string): XCTestDeviceCleanupReport {
235
+ if (process.platform !== 'darwin') return { attempted: false, removedDevices: 0, skippedReason: 'not_darwin' };
236
+ const normalizedControllerHome = resolve(controllerHome).replace(/\\/g, '/');
237
+ if (!normalizedControllerHome.includes('/.forge/controller')) {
238
+ return { attempted: false, removedDevices: 0, skippedReason: 'nonstandard_controller_home' };
239
+ }
240
+ const deviceSet = join(homedir(), 'Library', 'Developer', 'XCTestDevices');
241
+ if (!existsSync(deviceSet)) return { attempted: false, removedDevices: 0, skippedReason: 'device_set_missing' };
242
+ let deviceDirectories = 0;
243
+ try {
244
+ deviceDirectories = readdirSync(deviceSet, { withFileTypes: true }).filter((entry) => entry.isDirectory()).length;
245
+ } catch (error) {
246
+ return { attempted: false, removedDevices: 0, error: error instanceof Error ? error.message : String(error) };
247
+ }
248
+ if (deviceDirectories <= 4) return { attempted: false, removedDevices: 0, skippedReason: 'below_cleanup_threshold' };
249
+
250
+ const commands = collectProcessCommands();
251
+ if (commands.some((command) => /(?:^|\/)(?:xcodebuild|xctest|swiftc)(?:\s|$)/.test(command))) {
252
+ return { attempted: false, removedDevices: 0, skippedReason: 'active_xcode_process' };
253
+ }
254
+ try {
255
+ const listed = execFileSync('xcrun', ['simctl', '--set', deviceSet, 'list', 'devices', '--json'], {
256
+ encoding: 'utf8',
257
+ timeout: 10_000,
258
+ maxBuffer: 2 * 1024 * 1024,
259
+ });
260
+ const parsed = JSON.parse(listed) as { devices?: Record<string, Array<{ state?: string }>> };
261
+ const devices = Object.values(parsed.devices ?? {}).flat();
262
+ if (devices.some((device) => device.state === 'Booted')) {
263
+ return { attempted: false, removedDevices: 0, skippedReason: 'booted_xctest_device' };
264
+ }
265
+ execFileSync('xcrun', ['simctl', '--set', deviceSet, 'shutdown', 'all'], { timeout: 15_000, stdio: 'ignore' });
266
+ execFileSync('xcrun', ['simctl', '--set', deviceSet, 'delete', 'all'], { timeout: 30_000, stdio: 'ignore' });
267
+ return { attempted: true, removedDevices: devices.length };
268
+ } catch (error) {
269
+ return { attempted: true, removedDevices: 0, error: error instanceof Error ? error.message : String(error) };
270
+ }
271
+ }
@@ -0,0 +1,44 @@
1
+ export interface SchedulerConfig {
2
+ maxWorkers: number;
3
+ maxConcurrentRepositories: number;
4
+ pollIntervalMs: number;
5
+ idleBackoffMaxMs: number;
6
+ heartbeatIntervalMs: number;
7
+ heartbeatTimeoutMs: number;
8
+ maxHeavyChecks: number;
9
+ maxAgentProcesses: number;
10
+ maxCodexProcesses: number;
11
+ maxClaudeProcesses: number;
12
+ maxGitHubProcesses: number;
13
+ minFreeMemoryMb: number;
14
+ maxLoadPerCpu: number;
15
+ }
16
+
17
+ export function normalizeSchedulerConfig(
18
+ config: Partial<SchedulerConfig> = {},
19
+ env: NodeJS.ProcessEnv = process.env,
20
+ ): SchedulerConfig {
21
+ const pollIntervalMs = Math.max(50, config.pollIntervalMs ?? 250);
22
+ const idleBackoffMaxMs = Math.max(250, config.idleBackoffMaxMs ?? Number(env.FORGE_IDLE_BACKOFF_MAX_MS ?? 2_000));
23
+ const heartbeatIntervalMs = Math.max(25, config.heartbeatIntervalMs ?? Number(env.FORGE_SCHEDULER_HEARTBEAT_INTERVAL_MS ?? 1_000));
24
+ const heartbeatTimeoutMs = Math.max(
25
+ heartbeatIntervalMs * 4,
26
+ idleBackoffMaxMs * 4,
27
+ config.heartbeatTimeoutMs ?? Number(env.FORGE_SCHEDULER_HEARTBEAT_TIMEOUT_MS ?? 60_000),
28
+ );
29
+ return {
30
+ maxWorkers: Math.max(1, config.maxWorkers ?? Number(env.FORGE_MAX_WORKERS ?? 4)),
31
+ maxConcurrentRepositories: Math.max(1, config.maxConcurrentRepositories ?? Number(env.FORGE_MAX_ACTIVE_REPOS ?? 4)),
32
+ pollIntervalMs,
33
+ idleBackoffMaxMs,
34
+ heartbeatIntervalMs,
35
+ heartbeatTimeoutMs,
36
+ maxHeavyChecks: Math.max(1, config.maxHeavyChecks ?? Number(env.FORGE_MAX_HEAVY_CHECKS ?? 2)),
37
+ maxAgentProcesses: Math.max(1, config.maxAgentProcesses ?? Number(env.FORGE_MAX_AGENT_PROCESSES ?? 4)),
38
+ maxCodexProcesses: Math.max(1, config.maxCodexProcesses ?? Number(env.FORGE_MAX_CODEX_PROCESSES ?? 3)),
39
+ maxClaudeProcesses: Math.max(1, config.maxClaudeProcesses ?? Number(env.FORGE_MAX_CLAUDE_PROCESSES ?? 2)),
40
+ maxGitHubProcesses: Math.max(1, config.maxGitHubProcesses ?? Number(env.FORGE_MAX_GITHUB_PROCESSES ?? 2)),
41
+ minFreeMemoryMb: Math.max(64, config.minFreeMemoryMb ?? Number(env.FORGE_MIN_FREE_MEMORY_MB ?? 512)),
42
+ maxLoadPerCpu: Math.max(0.25, config.maxLoadPerCpu ?? Number(env.FORGE_MAX_LOAD_PER_CPU ?? 1.5)),
43
+ };
44
+ }
@@ -0,0 +1,80 @@
1
+ import type { ExecutionJob } from '../../execution/jobs/types';
2
+
3
+ export type SchedulerAgentProvider = 'codex' | 'claude' | 'github-copilot';
4
+
5
+ export interface SchedulerDispatchLimits {
6
+ maxWorkers: number;
7
+ maxHeavyChecks: number;
8
+ maxAgentProcesses: number;
9
+ maxCodexProcesses: number;
10
+ maxClaudeProcesses: number;
11
+ maxGitHubProcesses: number;
12
+ }
13
+
14
+ export interface SchedulerDispatchCapacity {
15
+ reservedJobs: ExecutionJob[];
16
+ workers: number;
17
+ heavyChecks: number;
18
+ agents: number;
19
+ providers: Map<SchedulerAgentProvider, number>;
20
+ }
21
+
22
+ export function schedulerAgentProvider(
23
+ job: Pick<ExecutionJob, 'payload'>,
24
+ ): SchedulerAgentProvider {
25
+ const agent = job.payload.arguments?.agent;
26
+ if (agent === 'claude' || agent === 'github-copilot') return agent;
27
+ return 'codex';
28
+ }
29
+
30
+ export function createSchedulerDispatchCapacity(
31
+ activeJobs: readonly ExecutionJob[],
32
+ limits: SchedulerDispatchLimits,
33
+ pressured: boolean,
34
+ ): SchedulerDispatchCapacity {
35
+ const reservedJobs = activeJobs.filter((job) => job.status === 'running' || job.status === 'dispatched');
36
+ const reservedAgents = reservedJobs.filter((job) => job.type === 'agent-run' || job.type === 'dispatch-task');
37
+ let workers = limits.maxWorkers - reservedJobs.length;
38
+ let heavyChecks = limits.maxHeavyChecks
39
+ - reservedJobs.filter((job) => job.type === 'check' || job.type === 'verify-edit').length;
40
+ let agents = limits.maxAgentProcesses - reservedAgents.length;
41
+ const providers = new Map<SchedulerAgentProvider, number>([
42
+ ['codex', limits.maxCodexProcesses - reservedAgents.filter((job) => schedulerAgentProvider(job) === 'codex').length],
43
+ ['claude', limits.maxClaudeProcesses - reservedAgents.filter((job) => schedulerAgentProvider(job) === 'claude').length],
44
+ ['github-copilot', limits.maxGitHubProcesses - reservedAgents.filter((job) => schedulerAgentProvider(job) === 'github-copilot').length],
45
+ ]);
46
+ if (pressured) {
47
+ workers = Math.min(workers, 1);
48
+ heavyChecks = Math.min(heavyChecks, 1);
49
+ agents = 0;
50
+ providers.set('codex', 0);
51
+ providers.set('claude', 0);
52
+ providers.set('github-copilot', 0);
53
+ }
54
+ return { reservedJobs, workers, heavyChecks, agents, providers };
55
+ }
56
+
57
+ export function schedulerDispatchCapacityAllows(
58
+ capacity: SchedulerDispatchCapacity,
59
+ job: Pick<ExecutionJob, 'type' | 'payload'>,
60
+ ): boolean {
61
+ if ((job.type === 'check' || job.type === 'verify-edit') && capacity.heavyChecks <= 0) return false;
62
+ if (job.type === 'agent-run' || job.type === 'dispatch-task') {
63
+ if (capacity.agents <= 0) return false;
64
+ if ((capacity.providers.get(schedulerAgentProvider(job)) ?? 0) <= 0) return false;
65
+ }
66
+ return true;
67
+ }
68
+
69
+ export function consumeSchedulerDispatchCapacity(
70
+ capacity: SchedulerDispatchCapacity,
71
+ job: Pick<ExecutionJob, 'type' | 'payload'>,
72
+ ): void {
73
+ capacity.workers -= 1;
74
+ if (job.type === 'check' || job.type === 'verify-edit') capacity.heavyChecks -= 1;
75
+ if (job.type === 'agent-run' || job.type === 'dispatch-task') {
76
+ capacity.agents -= 1;
77
+ const provider = schedulerAgentProvider(job);
78
+ capacity.providers.set(provider, (capacity.providers.get(provider) ?? 0) - 1);
79
+ }
80
+ }