@moretea-labs/forge 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (396) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +26 -0
  3. package/CLAUDE.md +2 -0
  4. package/README.md +2 -2
  5. package/README.zh-CN.md +1 -1
  6. package/SKILL.md +2 -2
  7. package/THIRD_PARTY_NOTICES.md +1 -1
  8. package/agents/openai.yaml +1 -1
  9. package/assets/hooks/hook-input.sh +11 -8
  10. package/assets/hooks/lib/session-state.sh +10 -7
  11. package/assets/hooks/lib/workflow-state.sh +13 -177
  12. package/assets/hooks/post-tool-observer.sh +8 -2
  13. package/assets/hooks/prompt-guard.sh +5 -81
  14. package/assets/hooks/session-start-context.sh +56 -1
  15. package/assets/partials/05-workflow.partial.md +1 -1
  16. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  17. package/assets/plugin-registry.v1.json +1 -1
  18. package/assets/reference-configs/document-generation.md +1 -1
  19. package/assets/reference-configs/harness-overview.md +1 -1
  20. package/assets/reference-configs/sprint-contracts.md +3 -3
  21. package/assets/skill-version.json +2 -2
  22. package/assets/templates/contract.template.md +2 -7
  23. package/assets/templates/helpers/capture-plan.sh +2 -2
  24. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  25. package/assets/templates/helpers/contract-worktree.sh +1 -21
  26. package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
  27. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  28. package/assets/templates/helpers/new-plan.sh +1 -1
  29. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  30. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  31. package/assets/templates/helpers/verify-contract.sh +1 -111
  32. package/assets/templates/helpers/verify-sprint.sh +3 -180
  33. package/assets/templates/plan.template.md +1 -1
  34. package/assets/templates/review.template.md +9 -34
  35. package/assets/workflow-contract.v1.json +10 -19
  36. package/docs/README.md +10 -10
  37. package/docs/architecture/CURRENT.md +170 -0
  38. package/docs/architecture/EVOLUTION.md +42 -0
  39. package/docs/architecture/current/README.md +4 -156
  40. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  41. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  42. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  43. package/docs/architecture/history/README.md +7 -0
  44. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  45. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  46. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  47. package/docs/architecture/history.md +3 -34
  48. package/docs/architecture/index.md +15 -98
  49. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  50. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  51. package/docs/architecture/versions/1.6.md +38 -0
  52. package/docs/architecture/versions/1.7.md +41 -0
  53. package/docs/forge-chatgpt-browser-engine.md +2 -2
  54. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  55. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  56. package/docs/forge-github-issue-launcher.md +1 -1
  57. package/docs/forge-runtime-self-healing-loop.md +1 -1
  58. package/docs/operations/controller-browser-plugin.md +26 -6
  59. package/docs/operations/controller-performance-and-502.md +124 -0
  60. package/docs/operations/controller-reliability-runbook.md +1 -1
  61. package/docs/operations/features.md +2 -2
  62. package/docs/operations/github-repository.md +1 -1
  63. package/docs/operations/homebrew.md +2 -2
  64. package/docs/operations/mcp-tool-exposure.md +5 -3
  65. package/docs/wiki/Architecture.md +6 -6
  66. package/docs/wiki/Implementation.md +2 -2
  67. package/docs/wiki/Integrations.md +2 -2
  68. package/docs/wiki/Work-Lifecycle.md +1 -1
  69. package/package.json +29 -10
  70. package/scripts/activate-source-baseline.ts +17 -13
  71. package/scripts/benchmark-background-check-overlap.ts +228 -0
  72. package/scripts/benchmark-mcp-host.sh +117 -0
  73. package/scripts/benchmark-mcp-transport.ts +170 -0
  74. package/scripts/benchmark-quality-harness.ts +146 -0
  75. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  76. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  77. package/scripts/benchmark-typescript-navigation.ts +186 -0
  78. package/scripts/benchmark-work-efficiency.ts +334 -0
  79. package/scripts/capture-plan.sh +3 -3
  80. package/scripts/check-agent-tooling.sh +8 -35
  81. package/scripts/check-architecture-sync.sh +17 -86
  82. package/scripts/check-mcp-compatibility.ts +14 -0
  83. package/scripts/check-runtime-architecture.mjs +107 -64
  84. package/scripts/check-stable-baseline.ts +92 -0
  85. package/scripts/check-task-workflow.sh +4 -4
  86. package/scripts/contract-worktree.sh +1 -21
  87. package/scripts/ensure-task-workflow.sh +22 -55
  88. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  89. package/scripts/install-standalone-recovery.ts +24 -0
  90. package/scripts/lib/project-init-lib.sh +15 -44
  91. package/scripts/migrate-project-template.sh +23 -1179
  92. package/scripts/new-plan.sh +1 -1
  93. package/scripts/plan-to-todo.sh +11 -36
  94. package/scripts/run-governed-gate.ts +2 -1
  95. package/scripts/ship-worktrees.sh +2 -29
  96. package/scripts/smoke-ios-physical-device.ts +4 -2
  97. package/scripts/smoke-runtime-recovery.ts +1 -35
  98. package/scripts/smoke-schedule-engine.ts +1 -20
  99. package/scripts/verify-contract.sh +1 -111
  100. package/scripts/verify-forge-runtime.sh +0 -1
  101. package/scripts/verify-sprint.sh +3 -180
  102. package/scripts/workflow-contract.ts +1 -5
  103. package/src/cli/commands/init-hook.ts +3 -3
  104. package/src/cli/commands/mcp.ts +9 -5
  105. package/src/cli/commands/plugin.ts +1 -1
  106. package/src/cli/commands/recovery.ts +25 -3
  107. package/src/cli/commands/runtime.ts +55 -3
  108. package/src/cli/commands/security.ts +12 -1
  109. package/src/cli/commands/setup-profile.ts +31 -21
  110. package/src/cli/controller/check-runner.ts +106 -3
  111. package/src/cli/controller/composite-result.ts +1 -1
  112. package/src/cli/controller/context/focus.ts +51 -0
  113. package/src/cli/controller/context/instruction-context.ts +114 -0
  114. package/src/cli/controller/context/known-paths.ts +76 -0
  115. package/src/cli/controller/context/query-planning.ts +110 -0
  116. package/src/cli/controller/context/rule-resolution.ts +532 -0
  117. package/src/cli/controller/context/source-materializer.ts +278 -0
  118. package/src/cli/controller/context/structural.ts +16 -0
  119. package/src/cli/controller/context/types.ts +286 -0
  120. package/src/cli/controller/context-pack.ts +785 -469
  121. package/src/cli/controller/launch-agents.ts +77 -2
  122. package/src/cli/controller/operational-plan.ts +2 -2
  123. package/src/cli/controller/work-mode.ts +6 -4
  124. package/src/cli/editing/edit-session.ts +60 -7
  125. package/src/cli/github/github.ts +133 -0
  126. package/src/cli/installer/managed-entries.ts +15 -1
  127. package/src/cli/local-bridge/console-automations.ts +54 -78
  128. package/src/cli/local-bridge/console-view-models.ts +1 -35
  129. package/src/cli/local-bridge/facade-api.ts +0 -217
  130. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  131. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
  132. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  133. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
  134. package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
  135. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  136. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
  137. package/src/cli/local-bridge/frontend/src/styles.css +1 -1
  138. package/src/cli/local-bridge/frontend/src/types.ts +5 -8
  139. package/src/cli/local-bridge/job-store.ts +1 -490
  140. package/src/cli/local-bridge/server.ts +0 -513
  141. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  142. package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
  143. package/src/cli/mcp/auth.ts +5 -5
  144. package/src/cli/mcp/instructions.ts +3 -2
  145. package/src/cli/mcp/legacy-tool-service.ts +0 -189
  146. package/src/cli/mcp/multi-repository.ts +16 -5
  147. package/src/cli/mcp/oauth.ts +175 -26
  148. package/src/cli/mcp/repository-tools.ts +247 -107
  149. package/src/cli/mcp/server.ts +537 -111
  150. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  151. package/src/cli/mcp/toolset-selection.ts +1 -1
  152. package/src/cli/mcp/toolset.ts +42 -5
  153. package/src/cli/mcp/transports/http.ts +101 -14
  154. package/src/cli/mcp/transports/session-registry.ts +43 -5
  155. package/src/cli/mcp/types.ts +3 -3
  156. package/src/cli/repositories/command-classifier.ts +101 -19
  157. package/src/cli/repositories/command-executor.ts +55 -521
  158. package/src/cli/repositories/command-process.ts +108 -0
  159. package/src/cli/repositories/command-scope.ts +99 -41
  160. package/src/cli/repositories/controller-home.ts +7 -8
  161. package/src/cli/repositories/git-command-executor.ts +2 -0
  162. package/src/cli/repositories/locks.ts +53 -16
  163. package/src/cli/repositories/repository-snapshot.ts +121 -0
  164. package/src/cli/repositories/selected-path-actions.ts +17 -9
  165. package/src/cli/repositories/structured-git.ts +61 -3
  166. package/src/cli/repository/inspector.ts +553 -63
  167. package/src/cli/repository/session-cache.ts +58 -17
  168. package/src/cli/v81-entry.ts +1 -4
  169. package/src/effects/fs-transaction.ts +85 -17
  170. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  171. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  172. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  173. package/src/runtime/context/lsp-client.ts +283 -0
  174. package/src/runtime/context/semantic-navigation.ts +275 -0
  175. package/src/runtime/context/swift-navigation.ts +565 -0
  176. package/src/runtime/context/typescript-navigation.ts +226 -0
  177. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  178. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +47 -8
  179. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  180. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  181. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  182. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  183. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  184. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  185. package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
  186. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +143 -8
  187. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
  188. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +85 -8
  189. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  190. package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
  191. package/src/runtime/control-plane/facade/controller-session-store.ts +132 -1
  192. package/src/runtime/control-plane/facade/goal-workloop-access.ts +2 -0
  193. package/src/runtime/control-plane/facade/goal-workloop.ts +686 -129
  194. package/src/runtime/control-plane/facade/index.ts +1 -0
  195. package/src/runtime/control-plane/facade/plan-contract-store.ts +185 -20
  196. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  197. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  198. package/src/runtime/control-plane/facade/types.ts +22 -5
  199. package/src/runtime/control-plane/facade/work-contract-store.ts +175 -73
  200. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  201. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  202. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  203. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  204. package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
  205. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  206. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  207. package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
  208. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  209. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  210. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  211. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  212. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  213. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  214. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  215. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  216. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  217. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  218. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
  219. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +355 -116
  220. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
  221. package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
  222. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  223. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  224. package/src/runtime/control-plane/release-retention.ts +62 -2
  225. package/src/runtime/control-plane/routing/route-policy.ts +7 -16
  226. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  227. package/src/runtime/diagnostics/mcp-timing.ts +13 -0
  228. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  229. package/src/runtime/diagnostics/performance.ts +24 -1
  230. package/src/runtime/evidence/event-ledger.ts +1 -1
  231. package/src/runtime/execution/jobs/store.ts +58 -4
  232. package/src/runtime/execution/jobs/types.ts +1 -1
  233. package/src/runtime/execution/managed-workspace.ts +203 -1
  234. package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
  235. package/src/runtime/execution/process-runtime/check-result.ts +53 -0
  236. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  237. package/src/runtime/execution/process-runtime/command-facade.ts +345 -40
  238. package/src/runtime/execution/process-runtime/gc.ts +38 -6
  239. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
  240. package/src/runtime/execution/process-runtime/persisted-check.ts +47 -4
  241. package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
  242. package/src/runtime/execution/process-runtime/runtime.ts +43 -3
  243. package/src/runtime/execution/process-runtime/store.ts +1 -0
  244. package/src/runtime/execution/process-runtime/types.ts +2 -0
  245. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  246. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  247. package/src/runtime/execution/workers/executor.ts +0 -48
  248. package/src/runtime/gateway/mcp/execution-tools.ts +1192 -114
  249. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  250. package/src/runtime/gateway/mcp/router.ts +184 -82
  251. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
  252. package/src/runtime/gateway/mcp/runtime-tools.ts +2224 -1589
  253. package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
  254. package/src/runtime/plugins/browser-adapter.ts +2901 -241
  255. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  256. package/src/runtime/plugins/browser-handoff.ts +92 -2
  257. package/src/runtime/plugins/browser-macos-bridge.ts +582 -49
  258. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  259. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  260. package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
  261. package/src/runtime/plugins/browser-runtime.ts +183 -0
  262. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  263. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  264. package/src/runtime/plugins/desktop-operator-registration.ts +8 -2
  265. package/src/runtime/plugins/external-adapter.ts +324 -3
  266. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  267. package/src/runtime/plugins/first-party-registry.ts +2 -0
  268. package/src/runtime/plugins/github-adapter.ts +125 -0
  269. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  270. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  271. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  272. package/src/runtime/plugins/ios-adapter.ts +106 -16
  273. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  274. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  275. package/src/runtime/plugins/lightweight-action.ts +153 -0
  276. package/src/runtime/plugins/local-system-adapter.ts +123 -6
  277. package/src/runtime/plugins/macos-capability-broker.ts +55 -9
  278. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  279. package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
  280. package/src/runtime/plugins/resend-adapter.ts +1 -1
  281. package/src/runtime/plugins/store.ts +269 -29
  282. package/src/runtime/plugins/types.ts +35 -2
  283. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  284. package/src/runtime/projections/git-status-sampler.ts +16 -16
  285. package/src/runtime/projections/materialized-view.ts +28 -10
  286. package/src/runtime/recovery/maintenance-executor.ts +393 -28
  287. package/src/runtime/root/mcp-transport.ts +180 -10
  288. package/src/runtime/root/package-connector-service.ts +186 -8
  289. package/src/runtime/root/package-runtime-release.ts +143 -17
  290. package/src/runtime/root/package-runtime-service.ts +430 -22
  291. package/src/runtime/root/release-manifest.ts +7 -2
  292. package/src/runtime/root/release-materialize.ts +19 -0
  293. package/src/runtime/root/release-store.ts +48 -11
  294. package/src/runtime/root/runtime.ts +23 -5
  295. package/src/runtime/root/service.ts +100 -29
  296. package/src/runtime/root/types.ts +2 -0
  297. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  298. package/src/runtime/shared/trusted-executable.ts +62 -0
  299. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  300. package/src/runtime/standalone-recovery/core.ts +700 -92
  301. package/src/runtime/standalone-recovery/entry.ts +99 -23
  302. package/src/runtime/standalone-recovery/installer.ts +19 -3
  303. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  304. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  305. package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
  306. package/src/runtime/workflow/schedules/engine.ts +287 -151
  307. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  308. package/src/runtime/workflow/schedules/settlement.ts +48 -4
  309. package/src/runtime/workflow/schedules/store.ts +131 -10
  310. package/src/runtime/workflow/schedules/types.ts +2 -4
  311. package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
  312. package/src/testing/test-governance.ts +8 -40
  313. package/assets/reference-configs/heartbeat-triage.md +0 -57
  314. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  315. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  316. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  317. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  318. package/docs/architecture/current/architecture-invariants.md +0 -331
  319. package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
  320. package/docs/architecture/current/codegraph-context-provider.md +0 -238
  321. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  322. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  323. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
  324. package/docs/architecture/current/entity-model.md +0 -612
  325. package/docs/architecture/current/external-plugin-governance.md +0 -396
  326. package/docs/architecture/current/failure-recovery.md +0 -447
  327. package/docs/architecture/current/governance.md +0 -213
  328. package/docs/architecture/current/human-interaction-plane.md +0 -87
  329. package/docs/architecture/current/implementation-status.md +0 -128
  330. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  331. package/docs/architecture/current/legacy-governance.md +0 -58
  332. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  333. package/docs/architecture/current/migration-roadmap.md +0 -105
  334. package/docs/architecture/current/multi-repository-execution.md +0 -371
  335. package/docs/architecture/current/operations-runbook.md +0 -45
  336. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  337. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  338. package/docs/architecture/current/repository-access-modes.md +0 -102
  339. package/docs/architecture/current/route-policy.md +0 -15
  340. package/docs/architecture/current/route-session-concurrency.md +0 -85
  341. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  342. package/docs/architecture/current/runtime-directory-map.md +0 -49
  343. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  344. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  345. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  346. package/docs/architecture/current/system-overview.md +0 -169
  347. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  348. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  349. package/docs/architecture/current/thin-harness-v1.md +0 -321
  350. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  351. package/docs/forge-autonomous-goal-loop.md +0 -9
  352. package/docs/forge-provider-configuration.md +0 -7
  353. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
  354. package/docs/operations/gmail-assistant-routines.md +0 -186
  355. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  356. package/docs/operations/provider-configuration.md +0 -31
  357. package/scripts/harness-trace-grade.sh +0 -150
  358. package/scripts/heartbeat-triage.sh +0 -361
  359. package/scripts/maintenance-triage.sh +0 -167
  360. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  361. package/src/cli/repositories/goal-registry.ts +0 -239
  362. package/src/runtime/assistant/action-proposals.ts +0 -279
  363. package/src/runtime/assistant/intent.ts +0 -395
  364. package/src/runtime/assistant/model-provider.ts +0 -460
  365. package/src/runtime/assistant/openapi.ts +0 -223
  366. package/src/runtime/assistant/policy.ts +0 -89
  367. package/src/runtime/assistant/readiness.ts +0 -258
  368. package/src/runtime/assistant/routine-runtime.ts +0 -477
  369. package/src/runtime/assistant/schedule-binding.ts +0 -171
  370. package/src/runtime/assistant/standing-grants.ts +0 -375
  371. package/src/runtime/assistant/store.ts +0 -243
  372. package/src/runtime/assistant/types.ts +0 -135
  373. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  374. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  375. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  376. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  377. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  378. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  379. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  380. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  381. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  382. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  383. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  384. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  385. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  386. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  387. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  388. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  389. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  390. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  391. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  392. package/src/runtime/workflow/findings/store.ts +0 -204
  393. package/src/runtime/workflow/findings/types.ts +0 -58
  394. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  395. package/src/runtime/workflow/portfolio/store.ts +0 -185
  396. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -18,10 +18,10 @@ import {
18
18
  type DispatchState,
19
19
  type EvidenceState,
20
20
  type PolicyDecision,
21
- type SubmittedWorkOperation,
22
21
  type SuggestedNextAction,
23
22
  type VerificationRecord,
24
23
  type WorkReconciliationRecord,
24
+ type SubmittedWorkOperation,
25
25
  type WorkContract,
26
26
  type WorkContractStatus,
27
27
  type WorkRisk,
@@ -370,11 +370,25 @@ const EVIDENCE_TRANSITIONS: Readonly<Record<EvidenceState, readonly EvidenceStat
370
370
  failed: ['failed', 'partial', 'valid', 'contradictory'],
371
371
  };
372
372
 
373
- function validateWorkSemanticTransition(current: WorkContract, next: WorkContract): WorkContract {
373
+ function validateWorkSemanticTransition(
374
+ current: WorkContract,
375
+ next: WorkContract,
376
+ options: { allowRetainedCancelledResume?: boolean } = {},
377
+ ): WorkContract {
374
378
  const retryingFailedWork = current.status === 'failed'
375
379
  && !current.completionOutcome
376
380
  && ['claimed', 'launching', 'running', 'blocked'].includes(next.dispatchState);
377
- if (!retryingFailedWork && !DISPATCH_TRANSITIONS[current.dispatchState].includes(next.dispatchState)) {
381
+ const resumingRetainedCancelledWork = options.allowRetainedCancelledResume === true
382
+ && current.status === 'cancelled'
383
+ && current.dispatchState === 'terminal'
384
+ && current.phase === 'cleanup'
385
+ && current.phaseEvidence.cleanup.state === 'skipped'
386
+ && !current.completionReceipt
387
+ && !current.completionOutcome
388
+ && next.status === 'running'
389
+ && next.dispatchState === 'running'
390
+ && next.phase === 'implementation';
391
+ if (!retryingFailedWork && !resumingRetainedCancelledWork && !DISPATCH_TRANSITIONS[current.dispatchState].includes(next.dispatchState)) {
378
392
  throw new Error(`WORK_SEMANTICS_TRANSITION_INVALID: dispatch ${current.dispatchState} -> ${next.dispatchState}`);
379
393
  }
380
394
  if (!EVIDENCE_TRANSITIONS[current.evidenceState].includes(next.evidenceState)) {
@@ -392,6 +406,11 @@ function validateWorkSemanticTransition(current: WorkContract, next: WorkContrac
392
406
  return next;
393
407
  }
394
408
 
409
+ function suggestedActionsForStatus(status: WorkContractStatus, actions: readonly SuggestedNextAction[]): SuggestedNextAction[] {
410
+ if (status === 'completed' || status === 'cancelled') return [];
411
+ return actions.slice(0, 8);
412
+ }
413
+
395
414
  function sqliteBacked(options: WorkContractStoreOptions): options is WorkContractStoreOptions & { controllerHome: string; repoId: string } {
396
415
  // A caller-provided root is a test/portable compatibility store. Runtime
397
416
  // controller state always carries controllerHome + repoId and is SQLite.
@@ -429,6 +448,7 @@ function normalizeWorkContractStore(store: WorkContractStore): WorkContractStore
429
448
  workKind: legacy.workKind ?? 'repository_change',
430
449
  dispatchState: legacy.dispatchState ?? inferredDispatchState(status),
431
450
  evidenceState: legacy.evidenceState ?? inferredEvidenceState(status),
451
+ suggestedNextActions: suggestedActionsForStatus(status, legacy.suggestedNextActions ?? []),
432
452
  reconciliations: legacy.reconciliations ?? [],
433
453
  driver: (legacy.driver as unknown as { preferred?: string } | undefined)?.preferred === 'codex_worker'
434
454
  ? { ...legacy.driver, preferred: 'external_controller', allowWorker: false }
@@ -541,7 +561,7 @@ export function createWorkContract(options: WorkContractStoreOptions, input: Cre
541
561
  if (input.status === 'completed' || input.completionReceipt || input.completionOutcome) {
542
562
  throw new Error('WORK_COMPLETION_REQUIRES_RECORD_API');
543
563
  }
544
- return withWorkContractStoreWrite(options, () => {
564
+ const create = (): WorkContract => {
545
565
  const at = input.createdAt ?? input.updatedAt ?? nowIso(options);
546
566
  const workId = sanitizeFileComponent(input.workId);
547
567
  const contract: WorkContract = validateWorkSemantics({
@@ -585,6 +605,12 @@ export function createWorkContract(options: WorkContractStoreOptions, input: Cre
585
605
  planStepId: input.planStepId,
586
606
  planSourceRevision: input.planSourceRevision,
587
607
  scopeSummary: input.scopeSummary?.slice(0, 1_000),
608
+ scopeEvidence: input.scopeEvidence ? {
609
+ initialLikelyPaths: [...new Set(input.scopeEvidence.initialLikelyPaths)].slice(0, 100),
610
+ inspectedPaths: [...new Set(input.scopeEvidence.inspectedPaths)].slice(0, 500),
611
+ actualChangedPaths: [...new Set(input.scopeEvidence.actualChangedPaths)].slice(0, 500),
612
+ recordedAt: input.scopeEvidence.recordedAt,
613
+ } : undefined,
588
614
  allowedPaths: (input.allowedPaths ?? []).slice(0, 50),
589
615
  forbiddenPaths: (input.forbiddenPaths ?? []).slice(0, 50),
590
616
  checks: (input.checks ?? []).slice(0, 30),
@@ -618,6 +644,23 @@ export function createWorkContract(options: WorkContractStoreOptions, input: Cre
618
644
  submittedOperation: input.submittedOperation,
619
645
  });
620
646
 
647
+ if (sqliteBacked(options)) {
648
+ withControlPlaneTransaction(options.controllerHome, (database) => {
649
+ if (readControlPlaneRecordWithinTransaction<WorkContract>(database, 'work_contract', options.repoId, contract.workId)) {
650
+ throw new Error(`work contract already exists: ${contract.workId}`);
651
+ }
652
+ writeControlPlaneRecordWithinTransaction(database, {
653
+ namespace: 'work_contract',
654
+ scope: options.repoId,
655
+ key: contract.workId,
656
+ schemaVersion: 2,
657
+ value: contract,
658
+ action: 'work_contract_created',
659
+ expectedRevision: null,
660
+ });
661
+ });
662
+ return contract;
663
+ }
621
664
  const store = readWorkContractStore(options);
622
665
  if (store.contracts.some((existing) => existing.workId === contract.workId)) {
623
666
  throw new Error(`work contract already exists: ${contract.workId}`);
@@ -629,7 +672,8 @@ export function createWorkContract(options: WorkContractStoreOptions, input: Cre
629
672
  };
630
673
  writeWorkContractStore(options, nextStore);
631
674
  return contract;
632
- });
675
+ };
676
+ return create();
633
677
  }
634
678
 
635
679
  interface WorkRequestIndexRecord {
@@ -647,26 +691,6 @@ function workRequestIndexPath(controllerHome: string, requestId: string): string
647
691
  return join(root, `${hash}.json`);
648
692
  }
649
693
 
650
- export interface AcceptSubmittedWorkInput {
651
- requestId: string;
652
- repoId: string;
653
- parentWorkId?: string;
654
- semanticKey: string;
655
- operation: SubmittedWorkOperation;
656
- objective?: string;
657
- mode?: WorkContract['mode'];
658
- requestedBy?: WorkContract['requestedBy'];
659
- principalId?: string;
660
- controllerInstanceId?: string;
661
- workKind?: WorkKind;
662
- risk?: WorkRisk;
663
- allowedPaths?: string[];
664
- forbiddenPaths?: string[];
665
- checks?: string[];
666
- acceptanceCriteria?: string[];
667
- constraints?: WorkContract['constraints'];
668
- }
669
-
670
694
  export function getWorkContractByRequestId(
671
695
  controllerHome: string,
672
696
  requestId: string,
@@ -687,8 +711,8 @@ export function getWorkContractByRequestId(
687
711
  }
688
712
 
689
713
  /**
690
- * Accept one work_submit operation as a WorkContract without creating ExecutionJobs.
691
- * Same controllerHome + requestId + semanticKey returns the original contract.
714
+ * Create or reuse the execution-child Work used by typed callers. This is not
715
+ * a generic MCP submission authority and never creates an ExecutionJob.
692
716
  */
693
717
  export function acceptSubmittedWorkContract(
694
718
  controllerHome: string,
@@ -696,20 +720,12 @@ export function acceptSubmittedWorkContract(
696
720
  options: WorkContractStoreOptions = {},
697
721
  ): { contract: WorkContract; deduplicated: boolean } {
698
722
  const home = ensureControllerHome(controllerHome);
699
- const normalizedRequestId = input.requestId.trim();
700
- if (!normalizedRequestId) {
701
- throw new Error('INVALID_ARGUMENT: work_submit is missing required argument(s): request_id');
702
- }
703
- const normalizedSemanticKey = input.semanticKey.trim();
704
- if (!normalizedSemanticKey) {
705
- throw new Error('INVALID_ARGUMENT: work_submit requires a semantic operation key');
706
- }
707
- if (!input.repoId.trim()) {
708
- throw new Error('INVALID_ARGUMENT: work_submit is missing required argument(s): repo_id');
709
- }
710
- if (!input.operation?.name?.trim()) {
711
- throw new Error('INVALID_ARGUMENT: work_submit is missing required argument(s): operation');
712
- }
723
+ const requestId = input.requestId.trim();
724
+ const semanticKey = input.semanticKey.trim();
725
+ if (!requestId) throw new Error('INVALID_ARGUMENT: typed execution is missing request_id');
726
+ if (!semanticKey) throw new Error('INVALID_ARGUMENT: typed execution requires a semantic operation key');
727
+ if (!input.repoId.trim()) throw new Error('INVALID_ARGUMENT: typed execution is missing repo_id');
728
+ if (!input.operation?.name?.trim()) throw new Error('INVALID_ARGUMENT: typed execution is missing operation');
713
729
  const parentWorkId = input.parentWorkId?.trim() || undefined;
714
730
  if (parentWorkId) {
715
731
  const parent = getWorkContract({ controllerHome: home, repoId: input.repoId, now: options.now }, parentWorkId);
@@ -717,25 +733,17 @@ export function acceptSubmittedWorkContract(
717
733
  if (isTerminalWorkContractStatus(parent.status)) throw new Error(`PARENT_WORK_TERMINAL: ${parentWorkId}:${parent.status}`);
718
734
  if ((parent.lifecycleRole ?? 'primary') !== 'primary') throw new Error(`PARENT_WORK_NOT_PRIMARY: ${parentWorkId}`);
719
735
  }
720
-
721
- const requestLockId = createHash('sha256').update(normalizedRequestId).digest('hex').slice(0, 24);
722
- return withControllerLock(home, { scope: 'global', resource: `work-request-${requestLockId}` }, `accept-work:${normalizedRequestId}`, () => {
723
- const recordPath = workRequestIndexPath(home, normalizedRequestId);
736
+ const lockId = createHash('sha256').update(requestId).digest('hex').slice(0, 24);
737
+ return withControllerLock(home, { scope: 'global', resource: `work-request-${lockId}` }, `accept-work:${requestId}`, () => {
738
+ const recordPath = workRequestIndexPath(home, requestId);
724
739
  if (existsSync(recordPath)) {
725
740
  const record = readJsonFile<WorkRequestIndexRecord>(recordPath);
726
- if (record.repoId !== input.repoId) {
727
- throw new Error(`REQUEST_ID_REPO_CONFLICT: ${normalizedRequestId} already belongs to repository ${record.repoId}`);
728
- }
729
- if (record.semanticKey !== normalizedSemanticKey) {
730
- throw new Error(`REQUEST_ID_CONFLICT: ${normalizedRequestId} already belongs to ${record.semanticKey}`);
731
- }
741
+ if (record.repoId !== input.repoId) throw new Error(`REQUEST_ID_REPO_CONFLICT: ${requestId} already belongs to repository ${record.repoId}`);
742
+ if (record.semanticKey !== semanticKey) throw new Error(`REQUEST_ID_CONFLICT: ${requestId} already belongs to ${record.semanticKey}`);
732
743
  const existing = getWorkContract({ controllerHome: home, repoId: record.repoId, now: options.now }, record.workId);
733
- if (!existing) {
734
- throw new Error(`WORK_ACCEPTANCE_LOST: request ${normalizedRequestId} is indexed without a readable WorkContract`);
735
- }
744
+ if (!existing) throw new Error(`WORK_ACCEPTANCE_LOST: request ${requestId} is indexed without a readable WorkContract`);
736
745
  return { contract: existing, deduplicated: true };
737
746
  }
738
-
739
747
  const contract = createWorkContract({ controllerHome: home, repoId: input.repoId, now: options.now }, {
740
748
  workId: `WORK-${Date.now()}-${randomUUID().slice(0, 8)}`,
741
749
  repoId: input.repoId,
@@ -744,7 +752,7 @@ export function acceptSubmittedWorkContract(
744
752
  mode: input.mode ?? 'direct_control',
745
753
  lifecycleRole: 'execution_child',
746
754
  parentWorkId,
747
- objective: (input.objective ?? `Accepted operation ${input.operation.name}`).slice(0, 2_000),
755
+ objective: (input.objective ?? `Typed operation ${input.operation.name}`).slice(0, 2_000),
748
756
  acceptanceCriteria: input.acceptanceCriteria ?? [],
749
757
  workKind: input.workKind,
750
758
  risk: input.risk,
@@ -754,13 +762,9 @@ export function acceptSubmittedWorkContract(
754
762
  checks: input.checks ?? [],
755
763
  requestedBy: input.requestedBy ?? 'chatgpt',
756
764
  status: 'open',
757
- requestId: normalizedRequestId,
765
+ requestId,
758
766
  submittedOperation: input.operation,
759
- driver: {
760
- preferred: 'external_controller',
761
- allowWorker: false,
762
- allowDirectEdit: input.operation.mode === 'readonly' || input.operation.mode === 'mutating',
763
- },
767
+ driver: { preferred: 'external_controller', allowWorker: false, allowDirectEdit: input.operation.mode === 'readonly' || input.operation.mode === 'mutating' },
764
768
  suggestedNextActions: [{
765
769
  label: 'Claim controller ownership',
766
770
  tool: 'rh_work',
@@ -770,15 +774,7 @@ export function acceptSubmittedWorkContract(
770
774
  reason: 'Claim this Work before launching an external SuperController or Process Runtime command.',
771
775
  }],
772
776
  });
773
-
774
- writeJsonAtomic(recordPath, {
775
- requestId: normalizedRequestId,
776
- repoId: input.repoId,
777
- workId: contract.workId,
778
- semanticKey: normalizedSemanticKey,
779
- createdAt: contract.createdAt,
780
- } satisfies WorkRequestIndexRecord);
781
-
777
+ writeJsonAtomic(recordPath, { requestId, repoId: input.repoId, workId: contract.workId, semanticKey, createdAt: contract.createdAt } satisfies WorkRequestIndexRecord);
782
778
  return { contract, deduplicated: false };
783
779
  });
784
780
  }
@@ -965,6 +961,7 @@ function updateWorkContractInternal(
965
961
  patch: Partial<Omit<WorkContract, 'schemaVersion' | 'workId' | 'repoId' | 'createdAt'>>,
966
962
  allowCompletionWrite: boolean,
967
963
  allowPhaseWrite = false,
964
+ allowRetainedCancelledResume = false,
968
965
  ): WorkContract {
969
966
  return withWorkContractStoreWrite(options, () => {
970
967
  const sanitizedId = sanitizeFileComponent(workId);
@@ -1022,13 +1019,13 @@ function updateWorkContractInternal(
1022
1019
  completionOutcome: patch.completionOutcome ?? current.completionOutcome,
1023
1020
  evidenceRefs: (patch.evidenceRefs ?? current.evidenceRefs).slice(0, current.evidencePolicy.maxEvidenceRefs),
1024
1021
  handoffRefs: (patch.handoffRefs ?? current.handoffRefs).slice(0, 20),
1025
- suggestedNextActions: (patch.suggestedNextActions ?? current.suggestedNextActions).slice(0, 8),
1022
+ suggestedNextActions: suggestedActionsForStatus(patch.status ?? current.status, patch.suggestedNextActions ?? current.suggestedNextActions),
1026
1023
  policyDecisions: (patch.policyDecisions ?? current.policyDecisions).slice(0, 20),
1027
1024
  checkRefs: (patch.checkRefs ?? current.checkRefs).slice(0, 50),
1028
1025
  reconciliations: (patch.reconciliations ?? current.reconciliations ?? []).slice(0, 20),
1029
1026
  objective: (patch.objective ?? current.objective).slice(0, 2_000),
1030
1027
  continuationPrompt: (patch.continuationPrompt ?? current.continuationPrompt)?.slice(0, 2_000),
1031
- }));
1028
+ }), { allowRetainedCancelledResume });
1032
1029
  const contracts = [...store.contracts];
1033
1030
  contracts[index] = next;
1034
1031
  writeWorkContractStore(options, { schemaVersion: 2, updatedAt: at, contracts });
@@ -1044,6 +1041,83 @@ export function updateWorkContract(
1044
1041
  return updateWorkContractInternal(options, workId, patch, false);
1045
1042
  }
1046
1043
 
1044
+ /**
1045
+ * Reopen one explicitly retained cancelled repository Work after the facade has
1046
+ * reauthenticated the same principal and revalidated physical Work ownership.
1047
+ * Generic mutation APIs intentionally cannot perform terminal -> running.
1048
+ */
1049
+ export function resumeRetainedCancelledWorkContract(
1050
+ options: WorkContractStoreOptions,
1051
+ workId: string,
1052
+ input: {
1053
+ principalId: string;
1054
+ controllerInstanceId: string;
1055
+ summary: string;
1056
+ checkoutId?: string;
1057
+ worktreeRef?: string;
1058
+ },
1059
+ ): WorkContract {
1060
+ const current = getWorkContract(options, workId);
1061
+ if (!current) throw new Error(`work contract not found: ${workId}`);
1062
+ if (current.status !== 'cancelled' || current.dispatchState !== 'terminal' || current.phase !== 'cleanup') {
1063
+ throw new Error(`WORK_CANCELLED_RESUME_STATUS_INVALID: ${workId}`);
1064
+ }
1065
+ if (current.workKind !== 'repository_change') throw new Error(`WORK_CANCELLED_RESUME_KIND_INVALID: ${workId}`);
1066
+ if (current.completionReceipt || current.completionOutcome) throw new Error(`WORK_CANCELLED_RESUME_COMPLETION_CONFLICT: ${workId}`);
1067
+ if (current.phaseEvidence.cleanup.state !== 'skipped' || current.phaseEvidence.cleanup.source !== 'recorded') {
1068
+ throw new Error(`WORK_CANCELLED_RESUME_HISTORY_AMBIGUOUS: ${workId}`);
1069
+ }
1070
+ const originalPrincipal = current.principalId?.trim();
1071
+ if (!originalPrincipal || originalPrincipal !== input.principalId.trim()) {
1072
+ throw new Error(`WORK_CANCELLED_RESUME_PRINCIPAL_MISMATCH: ${workId}`);
1073
+ }
1074
+ const at = nowIso(options);
1075
+ const evidenceRefs = [...current.evidenceRefs, {
1076
+ title: 'explicit current-user Work reauthorization',
1077
+ summary: input.summary.trim().slice(0, 1_000),
1078
+ detailLevel: 'summary' as const,
1079
+ }].slice(-current.evidencePolicy.maxEvidenceRefs);
1080
+ const phaseEvidence = transitionPhaseEvidence({ ...current, evidenceRefs }, 'implementation', {
1081
+ status: 'running',
1082
+ summary: input.summary,
1083
+ evidenceRefs,
1084
+ recordedAt: at,
1085
+ source: 'recorded',
1086
+ });
1087
+ return updateWorkContractInternal(options, workId, {
1088
+ status: 'running',
1089
+ phase: 'implementation',
1090
+ phaseEvidence,
1091
+ dispatchState: 'running',
1092
+ evidenceRefs,
1093
+ checkoutId: input.checkoutId ?? current.checkoutId,
1094
+ worktreeRef: input.worktreeRef ?? current.worktreeRef,
1095
+ controllerInstanceId: input.controllerInstanceId,
1096
+ continuationPrompt: input.summary,
1097
+ }, false, true, true);
1098
+ }
1099
+
1100
+ /** Merge non-authoritative discovery/change evidence without changing policy fences. */
1101
+ export function recordWorkScopeEvidence(
1102
+ options: WorkContractStoreOptions,
1103
+ workId: string,
1104
+ input: { initialLikelyPaths?: string[]; inspectedPaths?: string[]; actualChangedPaths?: string[] },
1105
+ ): WorkContract {
1106
+ const current = getWorkContract(options, workId);
1107
+ if (!current) throw new Error(`work contract not found: ${workId}`);
1108
+ const previous = current.scopeEvidence ?? {
1109
+ initialLikelyPaths: [], inspectedPaths: [], actualChangedPaths: [], recordedAt: current.createdAt,
1110
+ };
1111
+ return updateWorkContract(options, workId, {
1112
+ scopeEvidence: {
1113
+ initialLikelyPaths: [...new Set([...previous.initialLikelyPaths, ...(input.initialLikelyPaths ?? [])])].slice(0, 100),
1114
+ inspectedPaths: [...new Set([...previous.inspectedPaths, ...(input.inspectedPaths ?? [])])].slice(0, 500),
1115
+ actualChangedPaths: [...new Set([...previous.actualChangedPaths, ...(input.actualChangedPaths ?? [])])].slice(0, 500),
1116
+ recordedAt: nowIso(options),
1117
+ },
1118
+ });
1119
+ }
1120
+
1047
1121
  export function transitionWorkContractPhase(
1048
1122
  options: WorkContractStoreOptions,
1049
1123
  workId: string,
@@ -1126,6 +1200,9 @@ export function recordWorkCompletionReceipt(
1126
1200
  return current;
1127
1201
  }
1128
1202
  const recordedAt = receipt.recordedAt;
1203
+ const receiptChangedPaths = isRepositoryCompletionReceipt(receipt) || isDirectEditWorkCompletionReceipt(receipt)
1204
+ ? receipt.changedPaths
1205
+ : [];
1129
1206
  const phaseEvidence = Object.fromEntries((['implementation', 'verification', 'delivery', 'cleanup'] as WorkPhase[]).map((phase) => [phase, {
1130
1207
  state: 'satisfied',
1131
1208
  source: 'recorded',
@@ -1143,5 +1220,30 @@ export function recordWorkCompletionReceipt(
1143
1220
  completionOutcome,
1144
1221
  ...(completionWorkKind ? { workKind: completionWorkKind } : {}),
1145
1222
  completionReceipt: receipt,
1223
+ scopeEvidence: {
1224
+ initialLikelyPaths: current.scopeEvidence?.initialLikelyPaths ?? current.allowedPaths,
1225
+ inspectedPaths: current.scopeEvidence?.inspectedPaths ?? [],
1226
+ actualChangedPaths: [...new Set(receiptChangedPaths)].slice(0, 500),
1227
+ recordedAt,
1228
+ },
1146
1229
  }, true, true);
1147
1230
  }
1231
+ export interface AcceptSubmittedWorkInput {
1232
+ requestId: string;
1233
+ repoId: string;
1234
+ parentWorkId?: string;
1235
+ semanticKey: string;
1236
+ operation: SubmittedWorkOperation;
1237
+ objective?: string;
1238
+ mode?: WorkContract['mode'];
1239
+ requestedBy?: WorkContract['requestedBy'];
1240
+ principalId?: string;
1241
+ controllerInstanceId?: string;
1242
+ workKind?: WorkKind;
1243
+ risk?: WorkRisk;
1244
+ allowedPaths?: string[];
1245
+ forbiddenPaths?: string[];
1246
+ checks?: string[];
1247
+ acceptanceCriteria?: string[];
1248
+ constraints?: WorkContract['constraints'];
1249
+ }
@@ -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
+ }