@moretea-labs/forge 1.5.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +46 -0
  3. package/CLAUDE.md +2 -0
  4. package/DESIGN.md +7 -2
  5. package/README.md +4 -4
  6. package/README.zh-CN.md +2 -2
  7. package/SKILL.md +2 -2
  8. package/THIRD_PARTY_NOTICES.md +1 -1
  9. package/agents/openai.yaml +1 -1
  10. package/assets/hooks/hook-input.sh +11 -8
  11. package/assets/hooks/lib/session-state.sh +10 -7
  12. package/assets/hooks/lib/workflow-state.sh +13 -177
  13. package/assets/hooks/post-tool-observer.sh +8 -2
  14. package/assets/hooks/pre-edit-guard.sh +8 -39
  15. package/assets/hooks/prompt-guard.sh +7 -83
  16. package/assets/hooks/session-start-context.sh +56 -1
  17. package/assets/partials/05-workflow.partial.md +1 -1
  18. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  19. package/assets/plugin-registry.v1.json +54 -3
  20. package/assets/reference-configs/document-generation.md +1 -1
  21. package/assets/reference-configs/harness-overview.md +1 -1
  22. package/assets/reference-configs/hook-operations.md +1 -1
  23. package/assets/reference-configs/sprint-contracts.md +3 -3
  24. package/assets/skill-version.json +2 -2
  25. package/assets/templates/contract.template.md +2 -7
  26. package/assets/templates/helpers/capture-plan.sh +2 -2
  27. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  28. package/assets/templates/helpers/contract-worktree.sh +1 -21
  29. package/assets/templates/helpers/ensure-task-workflow.sh +13 -38
  30. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  31. package/assets/templates/helpers/new-plan.sh +1 -1
  32. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  33. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  34. package/assets/templates/helpers/sprint-backlog.sh +6 -6
  35. package/assets/templates/helpers/verify-contract.sh +1 -111
  36. package/assets/templates/helpers/verify-sprint.sh +3 -180
  37. package/assets/templates/plan.template.md +1 -1
  38. package/assets/templates/review.template.md +9 -34
  39. package/assets/workflow-contract.v1.json +10 -19
  40. package/docs/README.md +10 -10
  41. package/docs/architecture/CURRENT.md +170 -0
  42. package/docs/architecture/EVOLUTION.md +42 -0
  43. package/docs/architecture/current/README.md +4 -156
  44. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  45. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  46. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  47. package/docs/architecture/history/README.md +7 -0
  48. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  49. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  50. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  51. package/docs/architecture/history.md +3 -34
  52. package/docs/architecture/index.md +15 -98
  53. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  54. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  55. package/docs/architecture/versions/1.6.md +38 -0
  56. package/docs/architecture/versions/1.7.md +41 -0
  57. package/docs/forge-chatgpt-browser-engine.md +2 -2
  58. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  59. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  60. package/docs/forge-github-issue-launcher.md +1 -1
  61. package/docs/forge-runtime-self-healing-loop.md +1 -1
  62. package/docs/operations/controller-browser-plugin.md +38 -22
  63. package/docs/operations/controller-performance-and-502.md +124 -0
  64. package/docs/operations/controller-reliability-runbook.md +1 -1
  65. package/docs/operations/features.md +3 -3
  66. package/docs/operations/github-repository.md +1 -1
  67. package/docs/operations/homebrew.md +2 -2
  68. package/docs/operations/mcp-tool-exposure.md +5 -3
  69. package/docs/tutorials/01-install-and-start.md +1 -1
  70. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  71. package/docs/wiki/Architecture.md +6 -6
  72. package/docs/wiki/Implementation.md +2 -2
  73. package/docs/wiki/Installation.md +1 -1
  74. package/docs/wiki/Integrations.md +2 -2
  75. package/docs/wiki/Quick-Start.md +1 -1
  76. package/docs/wiki/Work-Lifecycle.md +1 -1
  77. package/package.json +29 -10
  78. package/scripts/activate-source-baseline.ts +17 -13
  79. package/scripts/benchmark-background-check-overlap.ts +228 -0
  80. package/scripts/benchmark-mcp-host.sh +117 -0
  81. package/scripts/benchmark-mcp-transport.ts +170 -0
  82. package/scripts/benchmark-quality-harness.ts +146 -0
  83. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  84. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  85. package/scripts/benchmark-typescript-navigation.ts +186 -0
  86. package/scripts/benchmark-work-efficiency.ts +334 -0
  87. package/scripts/capture-plan.sh +3 -3
  88. package/scripts/check-agent-tooling.sh +8 -35
  89. package/scripts/check-architecture-sync.sh +17 -86
  90. package/scripts/check-mcp-compatibility.ts +14 -0
  91. package/scripts/check-public-docs.sh +14 -1
  92. package/scripts/check-runtime-architecture.mjs +107 -64
  93. package/scripts/check-stable-baseline.ts +92 -0
  94. package/scripts/check-task-workflow.sh +4 -4
  95. package/scripts/contract-worktree.sh +1 -21
  96. package/scripts/ensure-task-workflow.sh +22 -55
  97. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  98. package/scripts/install-standalone-recovery.ts +24 -0
  99. package/scripts/lib/project-init-lib.sh +15 -44
  100. package/scripts/migrate-chatgpt-automation-schedules.ts +64 -0
  101. package/scripts/migrate-project-template.sh +23 -1179
  102. package/scripts/new-plan.sh +1 -1
  103. package/scripts/plan-to-todo.sh +11 -36
  104. package/scripts/run-governed-gate.ts +2 -1
  105. package/scripts/ship-worktrees.sh +2 -29
  106. package/scripts/smoke-ios-physical-device.ts +4 -2
  107. package/scripts/smoke-runtime-recovery.ts +1 -35
  108. package/scripts/smoke-schedule-engine.ts +1 -20
  109. package/scripts/sprint-backlog.sh +1 -1
  110. package/scripts/verify-contract.sh +1 -111
  111. package/scripts/verify-forge-runtime.sh +0 -1
  112. package/scripts/verify-sprint.sh +3 -180
  113. package/scripts/workflow-contract.ts +1 -5
  114. package/src/cli/commands/chatgpt.ts +31 -2
  115. package/src/cli/commands/init-hook.ts +3 -3
  116. package/src/cli/commands/mcp.ts +9 -5
  117. package/src/cli/commands/plugin.ts +37 -10
  118. package/src/cli/commands/recovery.ts +25 -3
  119. package/src/cli/commands/runtime.ts +55 -3
  120. package/src/cli/commands/security.ts +12 -1
  121. package/src/cli/commands/setup-profile.ts +31 -21
  122. package/src/cli/controller/check-runner.ts +106 -3
  123. package/src/cli/controller/composite-result.ts +1 -1
  124. package/src/cli/controller/context/focus.ts +51 -0
  125. package/src/cli/controller/context/instruction-context.ts +114 -0
  126. package/src/cli/controller/context/known-paths.ts +76 -0
  127. package/src/cli/controller/context/query-planning.ts +110 -0
  128. package/src/cli/controller/context/rule-resolution.ts +532 -0
  129. package/src/cli/controller/context/source-materializer.ts +278 -0
  130. package/src/cli/controller/context/structural.ts +16 -0
  131. package/src/cli/controller/context/types.ts +286 -0
  132. package/src/cli/controller/context-pack.ts +898 -430
  133. package/src/cli/controller/launch-agents.ts +77 -2
  134. package/src/cli/controller/operational-plan.ts +2 -2
  135. package/src/cli/controller/work-mode.ts +6 -4
  136. package/src/cli/editing/edit-session.ts +60 -7
  137. package/src/cli/github/github.ts +133 -0
  138. package/src/cli/hook/prompt-guard-decision.ts +17 -18
  139. package/src/cli/hook/prompt-intents.ts +1 -1
  140. package/src/cli/installer/managed-entries.ts +15 -1
  141. package/src/cli/local-bridge/console-automations.ts +225 -67
  142. package/src/cli/local-bridge/console-view-models.ts +3 -35
  143. package/src/cli/local-bridge/facade-api.ts +2 -217
  144. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  145. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +153 -3
  146. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  147. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +183 -3
  148. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +1 -1
  149. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +4 -3
  150. package/src/cli/local-bridge/frontend/src/lib/api.ts +18 -11
  151. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  152. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +1 -1
  153. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +4 -3
  154. package/src/cli/local-bridge/frontend/src/styles.css +27 -11
  155. package/src/cli/local-bridge/frontend/src/types.ts +8 -7
  156. package/src/cli/local-bridge/job-store.ts +1 -490
  157. package/src/cli/local-bridge/server.ts +29 -532
  158. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  159. package/src/cli/local-bridge/ui-dist/app.js +5101 -4967
  160. package/src/cli/mcp/auth.ts +5 -5
  161. package/src/cli/mcp/instructions.ts +3 -2
  162. package/src/cli/mcp/legacy-tool-service.ts +0 -189
  163. package/src/cli/mcp/multi-repository.ts +16 -5
  164. package/src/cli/mcp/oauth.ts +175 -26
  165. package/src/cli/mcp/paths.ts +8 -2
  166. package/src/cli/mcp/repository-tools.ts +356 -101
  167. package/src/cli/mcp/server.ts +625 -54
  168. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  169. package/src/cli/mcp/toolset-selection.ts +1 -1
  170. package/src/cli/mcp/toolset.ts +42 -5
  171. package/src/cli/mcp/transports/http.ts +125 -15
  172. package/src/cli/mcp/transports/session-registry.ts +67 -5
  173. package/src/cli/mcp/types.ts +3 -3
  174. package/src/cli/repositories/command-classifier.ts +209 -14
  175. package/src/cli/repositories/command-executor.ts +55 -521
  176. package/src/cli/repositories/command-process.ts +108 -0
  177. package/src/cli/repositories/command-scope.ts +99 -41
  178. package/src/cli/repositories/controller-home.ts +7 -8
  179. package/src/cli/repositories/git-command-executor.ts +2 -0
  180. package/src/cli/repositories/locks.ts +53 -16
  181. package/src/cli/repositories/repository-snapshot.ts +121 -0
  182. package/src/cli/repositories/safe-patch.ts +6 -4
  183. package/src/cli/repositories/selected-path-actions.ts +17 -9
  184. package/src/cli/repositories/structured-git.ts +110 -10
  185. package/src/cli/repository/inspector.ts +565 -62
  186. package/src/cli/repository/session-cache.ts +58 -17
  187. package/src/cli/v81-entry.ts +1 -4
  188. package/src/effects/fs-transaction.ts +85 -17
  189. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  190. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  191. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  192. package/src/runtime/context/lsp-client.ts +283 -0
  193. package/src/runtime/context/semantic-navigation.ts +275 -0
  194. package/src/runtime/context/swift-navigation.ts +565 -0
  195. package/src/runtime/context/typescript-navigation.ts +226 -0
  196. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  197. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +196 -2
  198. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  199. package/src/runtime/control-plane/execution/execution-identity.ts +65 -14
  200. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  201. package/src/runtime/control-plane/execution/validation.ts +15 -2
  202. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  203. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  204. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  205. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  206. package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
  207. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +175 -8
  208. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -21
  209. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +87 -9
  210. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  211. package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
  212. package/src/runtime/control-plane/facade/controller-session-store.ts +144 -14
  213. package/src/runtime/control-plane/facade/goal-workloop-access.ts +9 -2
  214. package/src/runtime/control-plane/facade/goal-workloop.ts +885 -105
  215. package/src/runtime/control-plane/facade/index.ts +1 -0
  216. package/src/runtime/control-plane/facade/plan-contract-store.ts +211 -26
  217. package/src/runtime/control-plane/facade/requirement-board.ts +24 -8
  218. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  219. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  220. package/src/runtime/control-plane/facade/types.ts +55 -11
  221. package/src/runtime/control-plane/facade/work-contract-store.ts +204 -73
  222. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  223. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  224. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  225. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  226. package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
  227. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  228. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  229. package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
  230. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  231. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  232. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  233. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  234. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  235. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  236. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  237. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  238. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  239. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  240. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
  241. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +642 -51
  242. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
  243. package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
  244. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  245. package/src/runtime/control-plane/persistence/requirement-store.ts +15 -11
  246. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  247. package/src/runtime/control-plane/release-retention.ts +62 -2
  248. package/src/runtime/control-plane/routing/route-policy.ts +7 -16
  249. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  250. package/src/runtime/diagnostics/mcp-timing.ts +19 -0
  251. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  252. package/src/runtime/diagnostics/performance.ts +24 -1
  253. package/src/runtime/evidence/event-ledger.ts +1 -1
  254. package/src/runtime/execution/jobs/store.ts +58 -4
  255. package/src/runtime/execution/jobs/types.ts +1 -1
  256. package/src/runtime/execution/managed-workspace.ts +203 -1
  257. package/src/runtime/execution/process-runtime/check-facade.ts +52 -14
  258. package/src/runtime/execution/process-runtime/check-result.ts +63 -2
  259. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  260. package/src/runtime/execution/process-runtime/command-facade.ts +346 -40
  261. package/src/runtime/execution/process-runtime/gc.ts +98 -12
  262. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
  263. package/src/runtime/execution/process-runtime/persisted-check.ts +57 -6
  264. package/src/runtime/execution/process-runtime/resource-claims.ts +97 -8
  265. package/src/runtime/execution/process-runtime/runtime.ts +59 -4
  266. package/src/runtime/execution/process-runtime/store.ts +7 -0
  267. package/src/runtime/execution/process-runtime/types.ts +2 -0
  268. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  269. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  270. package/src/runtime/execution/workers/executor.ts +0 -48
  271. package/src/runtime/gateway/mcp/execution-tools.ts +1219 -126
  272. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  273. package/src/runtime/gateway/mcp/router.ts +185 -83
  274. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
  275. package/src/runtime/gateway/mcp/runtime-tools.ts +2555 -1616
  276. package/src/runtime/model-clients/deepseek-adapter.ts +0 -32
  277. package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
  278. package/src/runtime/plugins/browser-adapter.ts +2925 -318
  279. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  280. package/src/runtime/plugins/browser-handoff-host.ts +6 -31
  281. package/src/runtime/plugins/browser-handoff.ts +92 -5
  282. package/src/runtime/plugins/browser-macos-bridge.ts +586 -45
  283. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  284. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  285. package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
  286. package/src/runtime/plugins/browser-runtime.ts +183 -0
  287. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  288. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  289. package/src/runtime/plugins/desktop-operator-registration.ts +15 -12
  290. package/src/runtime/plugins/external-adapter.ts +324 -3
  291. package/src/runtime/plugins/external-registration.ts +84 -5
  292. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  293. package/src/runtime/plugins/first-party-registry.ts +4 -2
  294. package/src/runtime/plugins/github-adapter.ts +125 -0
  295. package/src/runtime/plugins/gmail-adapter.ts +28 -2
  296. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  297. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  298. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  299. package/src/runtime/plugins/ios-adapter.ts +106 -16
  300. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  301. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  302. package/src/runtime/plugins/lightweight-action.ts +153 -0
  303. package/src/runtime/plugins/local-system-adapter.ts +125 -8
  304. package/src/runtime/plugins/macos-capability-broker.ts +55 -9
  305. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  306. package/src/runtime/plugins/plugin-management-adapter.ts +181 -0
  307. package/src/runtime/plugins/resend-adapter.ts +1 -1
  308. package/src/runtime/plugins/store.ts +366 -24
  309. package/src/runtime/plugins/types.ts +39 -2
  310. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  311. package/src/runtime/projections/git-status-sampler.ts +16 -16
  312. package/src/runtime/projections/materialized-view.ts +28 -10
  313. package/src/runtime/recovery/actions.ts +0 -10
  314. package/src/runtime/recovery/classifier.ts +0 -9
  315. package/src/runtime/recovery/maintenance-executor.ts +415 -18
  316. package/src/runtime/recovery/probe.ts +0 -1
  317. package/src/runtime/recovery/types.ts +0 -1
  318. package/src/runtime/root/mcp-transport.ts +180 -10
  319. package/src/runtime/root/package-connector-service.ts +186 -8
  320. package/src/runtime/root/package-runtime-release.ts +143 -17
  321. package/src/runtime/root/package-runtime-service.ts +430 -22
  322. package/src/runtime/root/release-manifest.ts +7 -2
  323. package/src/runtime/root/release-materialize.ts +162 -19
  324. package/src/runtime/root/release-store.ts +48 -11
  325. package/src/runtime/root/runtime.ts +23 -5
  326. package/src/runtime/root/service.ts +100 -27
  327. package/src/runtime/root/types.ts +2 -0
  328. package/src/runtime/safe-tooling/index.ts +0 -1
  329. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  330. package/src/runtime/safe-tooling/types.ts +0 -30
  331. package/src/runtime/shared/trusted-executable.ts +62 -0
  332. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  333. package/src/runtime/standalone-recovery/core.ts +702 -94
  334. package/src/runtime/standalone-recovery/entry.ts +99 -23
  335. package/src/runtime/standalone-recovery/installer.ts +19 -3
  336. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  337. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  338. package/src/runtime/workflow/schedules/browser-probe.ts +258 -0
  339. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +40 -0
  340. package/src/runtime/workflow/schedules/engine.ts +509 -80
  341. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  342. package/src/runtime/workflow/schedules/settlement.ts +48 -4
  343. package/src/runtime/workflow/schedules/store.ts +131 -10
  344. package/src/runtime/workflow/schedules/types.ts +6 -3
  345. package/src/runtime/workflow/schedules/work-continuation.ts +120 -34
  346. package/src/testing/test-governance.ts +8 -40
  347. package/assets/reference-configs/heartbeat-triage.md +0 -57
  348. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  349. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  350. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  351. package/bin/forge-desktop-helper.mjs +0 -204
  352. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  353. package/docs/architecture/current/architecture-invariants.md +0 -331
  354. package/docs/architecture/current/automation-and-schedule-engine.md +0 -453
  355. package/docs/architecture/current/codegraph-context-provider.md +0 -226
  356. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  357. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  358. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -354
  359. package/docs/architecture/current/entity-model.md +0 -592
  360. package/docs/architecture/current/external-plugin-governance.md +0 -396
  361. package/docs/architecture/current/failure-recovery.md +0 -447
  362. package/docs/architecture/current/governance.md +0 -213
  363. package/docs/architecture/current/human-interaction-plane.md +0 -87
  364. package/docs/architecture/current/implementation-status.md +0 -128
  365. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  366. package/docs/architecture/current/legacy-governance.md +0 -58
  367. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  368. package/docs/architecture/current/migration-roadmap.md +0 -105
  369. package/docs/architecture/current/multi-repository-execution.md +0 -371
  370. package/docs/architecture/current/operations-runbook.md +0 -45
  371. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  372. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  373. package/docs/architecture/current/repository-access-modes.md +0 -102
  374. package/docs/architecture/current/route-policy.md +0 -15
  375. package/docs/architecture/current/route-session-concurrency.md +0 -85
  376. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  377. package/docs/architecture/current/runtime-directory-map.md +0 -49
  378. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  379. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  380. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  381. package/docs/architecture/current/system-overview.md +0 -144
  382. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  383. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  384. package/docs/architecture/current/thin-harness-v1.md +0 -311
  385. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  386. package/docs/forge-autonomous-goal-loop.md +0 -9
  387. package/docs/forge-provider-configuration.md +0 -7
  388. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -83
  389. package/docs/operations/controller-desktop-plugin.md +0 -112
  390. package/docs/operations/gmail-assistant-routines.md +0 -186
  391. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  392. package/docs/operations/provider-configuration.md +0 -31
  393. package/scripts/harness-trace-grade.sh +0 -150
  394. package/scripts/heartbeat-triage.sh +0 -361
  395. package/scripts/maintenance-triage.sh +0 -167
  396. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  397. package/src/cli/repositories/goal-registry.ts +0 -239
  398. package/src/runtime/assistant/action-proposals.ts +0 -279
  399. package/src/runtime/assistant/intent.ts +0 -395
  400. package/src/runtime/assistant/model-provider.ts +0 -460
  401. package/src/runtime/assistant/openapi.ts +0 -223
  402. package/src/runtime/assistant/policy.ts +0 -89
  403. package/src/runtime/assistant/readiness.ts +0 -258
  404. package/src/runtime/assistant/routine-runtime.ts +0 -477
  405. package/src/runtime/assistant/schedule-binding.ts +0 -171
  406. package/src/runtime/assistant/standing-grants.ts +0 -375
  407. package/src/runtime/assistant/store.ts +0 -243
  408. package/src/runtime/assistant/types.ts +0 -135
  409. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  410. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  411. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  412. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  413. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  414. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  415. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  416. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  417. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  418. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  419. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  420. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  421. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  422. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  423. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  424. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  425. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  426. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  427. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  428. package/src/runtime/plugins/desktop-adapter.ts +0 -374
  429. package/src/runtime/safe-tooling/browser-safe.ts +0 -133
  430. package/src/runtime/workflow/findings/store.ts +0 -204
  431. package/src/runtime/workflow/findings/types.ts +0 -58
  432. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  433. package/src/runtime/workflow/portfolio/store.ts +0 -185
  434. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -13,8 +13,14 @@ import { observeRuntimeStatus } from '../../runtime/root/status';
13
13
  import { readMcpServiceBearerToken } from '../mcp/auth';
14
14
  import { publishRuntimeRelease } from '../../runtime/root/release-store';
15
15
  import { installForgeRuntimeService } from '../../runtime/root/service';
16
- import { installPackageRuntimeService } from '../../runtime/root/package-runtime-service';
16
+ import { activateScheduledPackageRuntimeServiceFromPath, installPackageRuntimeService, readPackageRuntimeActivationReceipt } from '../../runtime/root/package-runtime-service';
17
17
  import { assertRuntimeReleaseFiles, stageRuntimeReleaseFromCandidateSource } from '../../runtime/root/release-materialize';
18
+ import {
19
+ applyControllerHomeMigration,
20
+ previewControllerHomeMigration,
21
+ rollbackControllerHomeMigration,
22
+ archiveUnroutableMigratedWork,
23
+ } from '../../runtime/control-plane/persistence/controller-home-migration';
18
24
 
19
25
  function output(value: unknown, json = true): void {
20
26
  console.log(json ? JSON.stringify(value, null, 2) : String(value));
@@ -30,6 +36,22 @@ export function buildRuntimeCommand(): Command {
30
36
  const service = command.command('service')
31
37
  .description('Install or inspect the user-level Forge Runtime owner');
32
38
 
39
+ service.command('activate-package', { hidden: true })
40
+ .description('Internal one-shot activation helper for a staged package Runtime release')
41
+ .requiredOption('--request <path>', 'Activation request path')
42
+ .action(async (opts: { request: string }) => {
43
+ output(await activateScheduledPackageRuntimeServiceFromPath(opts.request));
44
+ });
45
+
46
+ service.command('activation-status')
47
+ .description('Read a durable package Runtime activation receipt')
48
+ .requiredOption('--receipt <path>', 'Activation receipt path returned by install-package')
49
+ .action((opts: { receipt: string }) => {
50
+ const receipt = readPackageRuntimeActivationReceipt(resolve(opts.receipt));
51
+ if (!receipt) throw new Error('RUNTIME_PACKAGE_ACTIVATION_RECEIPT_NOT_FOUND');
52
+ output(receipt);
53
+ });
54
+
33
55
  service.command('install-package')
34
56
  .description('Install the current packaged Forge Runtime without requiring a Git checkout, Bun compilation, CodeGraph, or Standalone Recovery')
35
57
  .option('--controller-home <path>', 'Controller Home; defaults to the user-level Forge Controller Home')
@@ -37,7 +59,8 @@ export function buildRuntimeCommand(): Command {
37
59
  .option('--port <port>', 'MCP listener port', '8765')
38
60
  .option('--auth-token-file <path>', 'Raw bearer token file (created from user-level MCP config when missing)')
39
61
  .option('--portable', 'Force a detached session process instead of launchd/systemd user persistence')
40
- .action(async (opts: { controllerHome?: string; host: string; port: string; authTokenFile?: string; portable?: boolean }) => {
62
+ .option('--refresh-connector', 'Explicitly replace/restart the persistent public MCP Gateway instead of reusing a healthy existing Gateway')
63
+ .action(async (opts: { controllerHome?: string; host: string; port: string; authTokenFile?: string; portable?: boolean; refreshConnector?: boolean }) => {
41
64
  const home = resolveControllerHome(opts.controllerHome);
42
65
  const port = Number(opts.port);
43
66
  if (!Number.isInteger(port) || port < 1 || port > 65_535) throw new Error('RUNTIME_SERVICE_PORT_INVALID');
@@ -56,6 +79,7 @@ export function buildRuntimeCommand(): Command {
56
79
  port,
57
80
  authTokenFile: tokenPath,
58
81
  forcePortable: opts.portable === true,
82
+ refreshConnector: opts.refreshConnector === true,
59
83
  });
60
84
  output({
61
85
  status: result.status,
@@ -73,8 +97,11 @@ export function buildRuntimeCommand(): Command {
73
97
  servicePath: result.servicePath,
74
98
  pid: result.pid,
75
99
  connector: result.connector,
100
+ activation: result.activation,
76
101
  warnings: result.warnings,
77
- next: `forge runtime status --controller-home ${home}`,
102
+ next: result.activation
103
+ ? `forge runtime service activation-status --receipt ${result.activation.receiptPath}`
104
+ : `forge runtime status --controller-home ${home}`,
78
105
  });
79
106
  });
80
107
 
@@ -163,6 +190,31 @@ export function buildRuntimeCommand(): Command {
163
190
  });
164
191
  });
165
192
 
193
+ command.command('migrate-controller-home')
194
+ .description('Preview or transactionally migrate non-terminal business state from an explicitly selected retired Controller Home')
195
+ .requiredOption('--source <path>', 'Retired Controller Home to inspect; repository presence never selects this implicitly')
196
+ .option('--destination <path>', 'Canonical Controller Home; defaults to the installed user-level authority')
197
+ .option('--apply', 'Apply the reviewed migration and freeze the source SQLite as a read-only archive')
198
+ .option('--rollback <migration-id>', 'Rollback one migration only when none of its imported records changed')
199
+ .option('--archive-unroutable <migration-id>', 'Archive imported nonterminal Work whose repository is not registered in the canonical Home')
200
+ .action(async (opts: { source: string; destination?: string; apply?: boolean; rollback?: string; archiveUnroutable?: string }) => {
201
+ const destination = resolveControllerHome(opts.destination);
202
+ if (opts.archiveUnroutable?.trim()) {
203
+ output(archiveUnroutableMigratedWork({ destinationHome: destination, migrationId: opts.archiveUnroutable.trim() }));
204
+ return;
205
+ }
206
+ if (opts.rollback?.trim()) {
207
+ output(rollbackControllerHomeMigration({ destinationHome: destination, migrationId: opts.rollback.trim() }));
208
+ return;
209
+ }
210
+ const input = { sourceHome: resolve(opts.source), destinationHome: destination };
211
+ if (opts.apply === true) {
212
+ output(await applyControllerHomeMigration(input));
213
+ return;
214
+ }
215
+ output(await previewControllerHomeMigration(input));
216
+ });
217
+
166
218
  command.command('status')
167
219
  .description('Read the Runtime Root status projection, active durable Jobs, and materialized repository projections')
168
220
  .requiredOption('--controller-home <path>', 'Explicit Controller Home')
@@ -8,7 +8,7 @@ import * as fs from 'fs';
8
8
  import * as os from 'os';
9
9
  import * as path from 'path';
10
10
  import { execFileSync } from 'child_process';
11
- import { MANAGED_TAG, type HookEntry } from '../installer/managed-entries';
11
+ import { MANAGED_TAG, isLegacyRepoHarnessCommand, type HookEntry } from '../installer/managed-entries';
12
12
 
13
13
  export type SecurityStatus = 'ok' | 'warn' | 'fail';
14
14
  export type SecuritySeverity = 'warn' | 'high' | 'fail';
@@ -273,6 +273,17 @@ function scanHookConfig(
273
273
  if (!command) continue;
274
274
  const suspicious = suspiciousMatch(command);
275
275
  if (command.includes(MANAGED_TAG) && suspicious === null) continue;
276
+ if (isLegacyRepoHarnessCommand(command)) {
277
+ findings.push({
278
+ filePath,
279
+ ruleId: 'legacy-repo-harness-hook-adapter',
280
+ severity: 'warn',
281
+ summary: `${hostLabel} ${event} hook uses the retired repo-harness adapter`,
282
+ recommendation: 'Run forge install --target codex --location global to replace retired adapters with forge-hook.',
283
+ command,
284
+ });
285
+ continue;
286
+ }
276
287
  findings.push({
277
288
  filePath,
278
289
  ruleId: suspicious?.ruleId ?? 'unmanaged-hook-command',
@@ -206,7 +206,7 @@ export function detectSetupPlatform(options: { platform?: NodeJS.Platform; arch?
206
206
  }
207
207
 
208
208
 
209
- function isLoopbackMcpEndpoint(value: string | undefined): boolean {
209
+ function isLoopbackMcpEndpoint(value: string | undefined): value is string {
210
210
  if (!value) return false;
211
211
  try {
212
212
  const parsed = new URL(value);
@@ -220,6 +220,11 @@ function isLoopbackMcpEndpoint(value: string | undefined): boolean {
220
220
  }
221
221
  }
222
222
 
223
+ function tunnelRuntimeProfileTargetsEndpoint(profilePath: string | undefined, endpoint: string): boolean {
224
+ if (!profilePath) return false;
225
+ try { return readFileSync(profilePath, 'utf8').includes(endpoint); } catch { return false; }
226
+ }
227
+
223
228
  export interface ControllerGuidance {
224
229
  controller: SetupControllerKind;
225
230
  ready: boolean;
@@ -266,8 +271,8 @@ export function resolveControllerGuidance(
266
271
  controller,
267
272
  ready: false,
268
273
  title: controller === 'chatgpt' ? 'Configure the ChatGPT controller endpoint' : 'Configure the remote MCP controller endpoint',
269
- detail: 'Create the user-level Forge OAuth/MCP configuration. This does not install Codex or Claude and does not require a repository.',
270
- command: `forge mcp setup chatgpt --user-level${endpoint}`,
274
+ detail: 'Create or refresh the Forge OAuth/MCP configuration in the selected Controller Home. This does not install Codex or Claude and does not require a repository.',
275
+ command: `forge mcp setup chatgpt --user-level --controller-home ${JSON.stringify(controllerHome)}${endpoint}`,
271
276
  };
272
277
  }
273
278
  continue;
@@ -327,7 +332,7 @@ export function resolveRuntimeGuidance(
327
332
  ready: false,
328
333
  title: 'Install the user-level Forge Runtime',
329
334
  detail: 'Use the packaged Runtime path. It does not require a Git checkout, Bun compilation, CodeGraph, or Standalone Recovery.',
330
- command: 'forge runtime service install-package',
335
+ command: `forge runtime service install-package --controller-home ${JSON.stringify(controllerHome)}`,
331
336
  };
332
337
  }
333
338
 
@@ -364,48 +369,53 @@ export function resolveTunnelGuidance(
364
369
  detail: 'Install the supported tunnel-client binary from OpenAI Platform Tunnels or the official openai/tunnel-client release. The runtime API key remains an environment/file reference owned by tunnel-client; Forge does not store it.',
365
370
  };
366
371
  }
372
+ const localConfig = loadMcpServiceLocalConfig(resolveControllerHome(options.controllerHome));
373
+ const localEndpoint = localConfig?.chatgpt?.localEndpoint;
374
+ if (!isLoopbackMcpEndpoint(localEndpoint)) {
375
+ return {
376
+ provider, ready: false, title: 'Prepare the local ChatGPT OAuth endpoint',
377
+ detail: 'Secure Tunnel must terminate at Forge’s loopback OAuth Gateway, not the bearer-only Canonical Runtime. Refresh the user-level ChatGPT MCP configuration first.',
378
+ command: `forge mcp setup chatgpt --user-level --controller-home ${JSON.stringify(resolveControllerHome(options.controllerHome))}`,
379
+ };
380
+ }
367
381
  const preferredAlias = 'forge';
368
- const aliases = [preferredAlias];
382
+ const configuredAliases: string[] = [];
369
383
  const listed = spawnSync('tunnel-client', ['runtimes', 'list', '--json'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 15_000, env: options.env ?? process.env });
370
384
  if (listed.status === 0) {
371
385
  try {
372
386
  const value = JSON.parse(listed.stdout || '{}') as { aliases?: Array<{ alias?: string; tunnel_id?: string }> };
373
387
  for (const entry of value.aliases ?? []) {
374
- if (entry.tunnel_id === profile.tunnel.tunnelId && entry.alias) aliases.push(entry.alias);
388
+ if (entry.tunnel_id === profile.tunnel.tunnelId && entry.alias) configuredAliases.push(entry.alias);
375
389
  }
376
390
  } catch {
377
391
  // Older clients may not expose local aliases as JSON. Keep the preferred alias fallback.
378
392
  }
379
393
  }
380
- for (const alias of Array.from(new Set(aliases))) {
394
+ const aliases = Array.from(new Set([...configuredAliases, preferredAlias]));
395
+ for (const alias of aliases) {
381
396
  const status = spawnSync('tunnel-client', ['runtimes', 'status', alias, '--json'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 15_000, env: options.env ?? process.env });
382
397
  if (status.status !== 0) continue;
383
398
  try {
384
- const value = JSON.parse(status.stdout || '{}') as { process_running?: boolean; healthy?: boolean; ready?: boolean; tunnel_id?: string };
399
+ const value = JSON.parse(status.stdout || '{}') as { process_running?: boolean; healthy?: boolean; ready?: boolean; tunnel_id?: string; profile_path?: string };
385
400
  if (value.process_running === true && value.healthy === true && value.ready === true
386
- && (!value.tunnel_id || value.tunnel_id === profile.tunnel.tunnelId)) {
401
+ && value.tunnel_id === profile.tunnel.tunnelId
402
+ && tunnelRuntimeProfileTargetsEndpoint(value.profile_path, localEndpoint)) {
387
403
  return {
388
404
  provider, ready: true, title: 'OpenAI Secure MCP Tunnel',
389
- detail: `Managed runtime ${alias} is running, healthy, and ready for ${profile.tunnel.tunnelId}.`,
405
+ detail: `Managed runtime ${alias} is running, healthy, and bound to ${profile.tunnel.tunnelId} -> ${localEndpoint}.`,
390
406
  };
391
407
  }
392
408
  } catch {
393
409
  // Treat malformed or older status output as not-ready instead of claiming success.
394
410
  }
395
411
  }
396
- const alias = preferredAlias;
397
- const localConfig = loadMcpServiceLocalConfig(resolveControllerHome(options.controllerHome));
398
- const localEndpoint = localConfig?.chatgpt?.localEndpoint;
399
- if (!isLoopbackMcpEndpoint(localEndpoint)) {
400
- return {
401
- provider, ready: false, title: 'Prepare the local ChatGPT OAuth endpoint',
402
- detail: 'Secure Tunnel must terminate at Forge’s loopback OAuth Gateway, not the bearer-only Canonical Runtime. Refresh the user-level ChatGPT MCP configuration first.',
403
- command: 'forge mcp setup chatgpt --user-level',
404
- };
405
- }
412
+ // A stopped alias still owns its recorded tunnel identity. Reconnect that
413
+ // alias instead of repointing the conventional `forge` alias, which may
414
+ // legitimately refer to a different tunnel from an earlier setup.
415
+ const alias = configuredAliases[0] ?? preferredAlias;
406
416
  return {
407
417
  provider, ready: false, title: 'Connect OpenAI Secure MCP Tunnel',
408
- detail: 'Create a supervised tunnel-client runtime and verify it with runtimes status. Keep the runtime API key in an environment or file reference owned by tunnel-client; Forge never reads or stores the key.',
418
+ detail: 'Create a supervised tunnel-client runtime and verify its tunnel id and MCP target with runtimes status. Keep the runtime API key in an environment or file reference owned by tunnel-client; Forge never reads or stores the key.',
409
419
  command: `tunnel-client runtimes connect --alias ${alias} --tunnel-id ${profile.tunnel.tunnelId} --runtime-api-key env:CONTROL_PLANE_API_KEY --mcp-server-url ${localEndpoint}`,
410
420
  };
411
421
  }
@@ -11,6 +11,7 @@ import { atomicWriteFileSync } from '../installer/shared';
11
11
  import { runBoundedChild } from '../../runtime/shared/bounded-child-supervisor';
12
12
  import { signalProcessTree } from '../../runtime/shared/process-tree';
13
13
  import { repositoryChildProcessEnvironment, resolveBunExecutable } from '../../runtime/shared/process-environment';
14
+ import { materializeManagedWorkspaceCheckDependencies } from '../../runtime/execution/managed-workspace';
14
15
 
15
16
  export interface ControllerCheckEffects {
16
17
  /** Repository-relative read scopes. Use [\".\"] for the whole checkout. */
@@ -25,6 +26,18 @@ export interface ControllerCheckEffects {
25
26
  hostServices?: string[];
26
27
  }
27
28
 
29
+ export type ControllerCheckCostClass = 'L0' | 'L1' | 'L2' | 'L3' | 'L4';
30
+ export type ControllerCheckRiskFloor = 'readonly' | 'low' | 'medium' | 'high' | 'destructive';
31
+ export type ControllerCheckPhase = 'pre_edit' | 'post_edit' | 'pre_finalize' | 'release';
32
+
33
+ export interface ControllerCheckSelection {
34
+ /** Execution cost only; it does not lower severity or automatically run the check. */
35
+ costClass: ControllerCheckCostClass;
36
+ /** Lowest change-risk band where this check is normally useful when selected mechanically. */
37
+ riskFloor: ControllerCheckRiskFloor;
38
+ phases: ControllerCheckPhase[];
39
+ }
40
+
28
41
  export interface ControllerCheck {
29
42
  id: string;
30
43
  description: string;
@@ -33,6 +46,8 @@ export interface ControllerCheck {
33
46
  timeoutMs: number;
34
47
  source: 'repo-config' | 'package-script';
35
48
  effects?: ControllerCheckEffects;
49
+ /** Selection/preflight metadata; intentionally excluded from physical execution identity. */
50
+ selection?: ControllerCheckSelection;
36
51
  }
37
52
 
38
53
  export interface ControllerCheckSnapshot extends ControllerCheck {
@@ -68,10 +83,12 @@ interface CheckConfig {
68
83
  cwd?: string;
69
84
  timeoutMs?: number;
70
85
  effects?: unknown;
86
+ selection?: unknown;
71
87
  }>;
72
88
  }
73
89
 
74
90
  const CHECK_CONFIG = '.forge/checks.json';
91
+ const LEGACY_TRACKED_CHECK_CONFIG = '.repo-harness/checks.json';
75
92
  const CHECK_EVIDENCE_ROOT = '.ai/harness/checks/controller';
76
93
  const HEAVY_CHECK_LOCK = '.ai/harness/controller/heavy-check.lock';
77
94
  const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
@@ -180,15 +197,87 @@ function normalizeCheckEffects(repoRoot: string, value: unknown): ControllerChec
180
197
  };
181
198
  }
182
199
 
200
+ const CHECK_COST_CLASSES = ['L0', 'L1', 'L2', 'L3', 'L4'] as const;
201
+ const CHECK_RISK_FLOORS = ['readonly', 'low', 'medium', 'high', 'destructive'] as const;
202
+ const CHECK_PHASES = ['pre_edit', 'post_edit', 'pre_finalize', 'release'] as const;
203
+
204
+ function inferredCheckSelection(name: string): ControllerCheckSelection {
205
+ const normalized = name.trim().toLowerCase().replace(/^package:/, '');
206
+ if (/(?:^|:)(?:check:)?release$|full[-:]?regression/.test(normalized)) {
207
+ return { costClass: 'L4', riskFloor: 'high', phases: ['release'] };
208
+ }
209
+ if (/(?:^|:)(?:check:)?main$|(?:^|:)ci(?::|$)/.test(normalized)) {
210
+ return { costClass: 'L4', riskFloor: 'medium', phases: ['pre_finalize'] };
211
+ }
212
+ if (/(?:^|:)(?:browser-live|performance|journey)(?::|$)/.test(normalized)) {
213
+ return { costClass: 'L3', riskFloor: 'medium', phases: ['pre_finalize'] };
214
+ }
215
+ if (/(?:^|:)(?:integration|build)(?::|$)/.test(normalized) || normalized === 'test') {
216
+ return { costClass: 'L2', riskFloor: 'medium', phases: ['post_edit', 'pre_finalize'] };
217
+ }
218
+ if (/(?:^|:)(?:architecture-sync|check-scheduling|context-files|bootstrap-files|background-check-overlap)$/.test(normalized)) {
219
+ return { costClass: 'L0', riskFloor: 'low', phases: ['post_edit'] };
220
+ }
221
+ return { costClass: 'L1', riskFloor: 'low', phases: ['post_edit'] };
222
+ }
223
+
224
+ function normalizeCheckSelection(value: unknown, id: string): ControllerCheckSelection {
225
+ const fallback = inferredCheckSelection(id);
226
+ if (value === undefined) return fallback;
227
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('check selection must be an object');
228
+ const raw = value as Record<string, unknown>;
229
+ const unsupported = Object.keys(raw).filter((key) => !['costClass', 'cost_class', 'riskFloor', 'risk_floor', 'phases'].includes(key));
230
+ if (unsupported.length > 0) throw new Error(`unsupported check selection field(s): ${unsupported.join(', ')}`);
231
+ const cost = raw.costClass ?? raw.cost_class;
232
+ const risk = raw.riskFloor ?? raw.risk_floor;
233
+ const phases = raw.phases;
234
+ if (cost !== undefined && (typeof cost !== 'string' || !CHECK_COST_CLASSES.includes(cost as ControllerCheckCostClass))) {
235
+ throw new Error(`check selection.costClass must be one of: ${CHECK_COST_CLASSES.join(', ')}`);
236
+ }
237
+ if (risk !== undefined && (typeof risk !== 'string' || !CHECK_RISK_FLOORS.includes(risk as ControllerCheckRiskFloor))) {
238
+ throw new Error(`check selection.riskFloor must be one of: ${CHECK_RISK_FLOORS.join(', ')}`);
239
+ }
240
+ if (phases !== undefined && (!Array.isArray(phases) || phases.length === 0 || phases.some((entry) => typeof entry !== 'string' || !CHECK_PHASES.includes(entry as ControllerCheckPhase)))) {
241
+ throw new Error(`check selection.phases must contain one or more of: ${CHECK_PHASES.join(', ')}`);
242
+ }
243
+ return {
244
+ costClass: (cost as ControllerCheckCostClass | undefined) ?? fallback.costClass,
245
+ riskFloor: (risk as ControllerCheckRiskFloor | undefined) ?? fallback.riskFloor,
246
+ phases: phases ? [...new Set(phases as ControllerCheckPhase[])] : fallback.phases,
247
+ };
248
+ }
249
+
250
+ export function controllerCheckSelection(check: Pick<ControllerCheck, 'id' | 'selection'>): ControllerCheckSelection {
251
+ return check.selection ?? inferredCheckSelection(check.id);
252
+ }
253
+
183
254
  function inferredPackageCheckEffects(name: string): ControllerCheckEffects | undefined {
184
255
  const normalized = name.trim().toLowerCase();
185
256
  const staticAnalysis = /(?:^|:)(?:type|typecheck|lint|format:check|runtime-architecture|mcp-compatibility|forge-runtime)$/.test(normalized);
186
- return staticAnalysis ? { reads: ['.'], cache: 'write' } : undefined;
257
+ if (staticAnalysis) return { reads: ['.'], cache: 'write' };
258
+ const isolatedReadOnlyCheck = /(?:^|:)(?:quality-harness|evaluation-framework|background-check-overlap|typescript-navigation|check-scheduling|bootstrap-files)$/.test(normalized);
259
+ if (isolatedReadOnlyCheck) return { reads: ['.'], temp: 'isolated', git: 'read' };
260
+ const browserLive = /(?:^|:)browser-live$/.test(normalized);
261
+ if (browserLive) {
262
+ return {
263
+ reads: ['.'],
264
+ temp: 'isolated',
265
+ hostServices: ['browser-live'],
266
+ };
267
+ }
268
+ return undefined;
187
269
  }
188
270
 
189
271
  function configuredChecks(repoRoot: string): ControllerCheck[] {
190
- const path = join(repoRoot, CHECK_CONFIG);
191
- if (!existsSync(path)) return [];
272
+ // `.forge/` may intentionally be machine-local/ignored. Preserve it as the
273
+ // authoritative current location, but inherit the tracked legacy registry in
274
+ // isolated Git checkouts when the current file is absent. Never merge both:
275
+ // deterministic precedence prevents checkout-local definitions from being
276
+ // silently combined with portable repository policy.
277
+ const currentPath = join(repoRoot, CHECK_CONFIG);
278
+ const legacyPath = join(repoRoot, LEGACY_TRACKED_CHECK_CONFIG);
279
+ const path = existsSync(currentPath) ? currentPath : existsSync(legacyPath) ? legacyPath : undefined;
280
+ if (!path) return [];
192
281
  const config = JSON.parse(readFileSync(path, 'utf-8')) as CheckConfig;
193
282
  return Object.entries(config.checks ?? {}).flatMap(([id, value]) => {
194
283
  if (!Array.isArray(value.command) || value.command.length === 0 || value.command.some((part) => typeof part !== 'string' || part.length === 0)) return [];
@@ -200,6 +289,7 @@ function configuredChecks(repoRoot: string): ControllerCheck[] {
200
289
  timeoutMs: boundedTimeout(value.timeoutMs),
201
290
  source: 'repo-config' as const,
202
291
  effects: normalizeCheckEffects(repoRoot, value.effects),
292
+ selection: normalizeCheckSelection(value.selection, id),
203
293
  }];
204
294
  });
205
295
  }
@@ -223,6 +313,7 @@ function packageChecks(repoRoot: string): ControllerCheck[] {
223
313
  timeoutMs: packageScriptTimeoutMs(name),
224
314
  source: 'package-script' as const,
225
315
  effects: inferredPackageCheckEffects(name),
316
+ selection: inferredCheckSelection(name),
226
317
  }];
227
318
  });
228
319
  }
@@ -360,6 +451,7 @@ function validateControllerCheckSnapshot(repoRoot: string, snapshot: ControllerC
360
451
  timeoutMs: boundedTimeout(snapshot.timeoutMs),
361
452
  source: snapshot.source,
362
453
  effects: normalizeCheckEffects(repoRoot, snapshot.effects),
454
+ selection: normalizeCheckSelection(snapshot.selection, snapshot.id),
363
455
  };
364
456
  }
365
457
 
@@ -541,9 +633,15 @@ function readControllerCheckEvidenceByKey(repoRoot: string, id: string, cacheKey
541
633
  }
542
634
  }
543
635
 
636
+ function prepareControllerCheckDependencies(repoRoot: string, check: ControllerCheck): void {
637
+ if (check.source !== 'package-script' || existsSync(join(repoRoot, 'node_modules'))) return;
638
+ materializeManagedWorkspaceCheckDependencies(repoRoot);
639
+ }
640
+
544
641
  export function runControllerCheck(repoRoot: string, id: string, requestedTimeoutMs?: number, snapshot?: ControllerCheckSnapshot): ControllerCheckResult {
545
642
  const check = snapshot ? validateControllerCheckSnapshot(repoRoot, snapshot) : listControllerChecks(repoRoot).find((entry) => entry.id === id);
546
643
  if (!check || check.id !== id) throw new Error(`check not found: ${id}`);
644
+ prepareControllerCheckDependencies(repoRoot, check);
547
645
  const identity = controllerCheckExecutionIdentity(repoRoot, id, requestedTimeoutMs, snapshot);
548
646
  const timeoutMs = identity.timeoutMs;
549
647
  const revision = identity.revision;
@@ -877,6 +975,11 @@ export function runControllerCheckAsync(
877
975
  return Promise.reject(error);
878
976
  }
879
977
  if (!check || check.id !== id) return Promise.reject(new Error(`check not found: ${id}`));
978
+ try {
979
+ prepareControllerCheckDependencies(repoRoot, check);
980
+ } catch (error) {
981
+ return Promise.reject(error);
982
+ }
880
983
  const identity = controllerCheckExecutionIdentity(repoRoot, id, options.requestedTimeoutMs, options.snapshot);
881
984
  const timeoutMs = identity.timeoutMs;
882
985
  const revision = identity.revision;
@@ -124,7 +124,7 @@ export function compositeRunning(input: {
124
124
  keyOutput: boundKeyOutput(input.keyOutput ?? input.summary),
125
125
  evidenceRefs: input.evidenceRefs ?? [],
126
126
  retryable: false,
127
- nextAction: input.nextAction ?? 'poll the same durable request id; do not resubmit',
127
+ nextAction: input.nextAction ?? 'Continue independent work; inspect this request only when its outcome can change the next decision. Do not resubmit or periodically poll.',
128
128
  details: input.details,
129
129
  };
130
130
  }
@@ -0,0 +1,51 @@
1
+ import type { TaskLedgerTaskProjection } from '../task-ledger';
2
+ import type { projectBoard } from '../issue-store';
3
+ import type { buildControllerTaskLedgerProjection } from '../task-ledger';
4
+
5
+ export interface ContextPackIssueFocus { id?: string; title?: string; summary?: string; tasks: ContextPackTaskFocus[] }
6
+ export interface ContextPackTaskFocus { id?: string; title?: string; objective?: string; status?: string }
7
+
8
+ function record(value: unknown): Record<string, unknown> {
9
+ return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
10
+ }
11
+ function string(value: unknown): string | undefined {
12
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
13
+ }
14
+ function task(value: unknown): ContextPackTaskFocus {
15
+ const item = record(value);
16
+ return { id: string(item.id), title: string(item.title), objective: string(item.objective), status: string(item.status) };
17
+ }
18
+ function issue(value: unknown): ContextPackIssueFocus {
19
+ const item = record(value);
20
+ return {
21
+ id: string(item.id), title: string(item.title), summary: string(item.summary),
22
+ tasks: Array.isArray(item.tasks) ? item.tasks.map(task).filter((entry) => Object.keys(entry).length > 0) : [],
23
+ };
24
+ }
25
+
26
+ export function issueTaskFocus(
27
+ board: ReturnType<typeof projectBoard>,
28
+ issueId?: string,
29
+ taskId?: string,
30
+ ): { issue?: ContextPackIssueFocus; task?: ContextPackTaskFocus } {
31
+ const issues = board.issues.map(issue);
32
+ const resolvedIssue = issueId
33
+ ? issues.find((entry) => entry.id === issueId)
34
+ : board.currentIssueId ? issues.find((entry) => entry.id === board.currentIssueId) : undefined;
35
+ return { issue: resolvedIssue, task: resolvedIssue?.tasks.find((entry) => entry.id === taskId) ?? resolvedIssue?.tasks[0] };
36
+ }
37
+
38
+ export function ledgerTask(
39
+ ledger: ReturnType<typeof buildControllerTaskLedgerProjection>,
40
+ issueId?: string,
41
+ taskId?: string,
42
+ ): TaskLedgerTaskProjection | undefined {
43
+ const tasks = ledger.issues.flatMap((entry) => entry.tasks);
44
+ const find = (candidateIssueId?: string, candidateTaskId?: string) => tasks
45
+ .find((entry) => (!candidateIssueId || entry.issueId === candidateIssueId) && (!candidateTaskId || entry.taskId === candidateTaskId));
46
+ if (!issueId && !taskId) {
47
+ const ready = ledger.readyTasks[0];
48
+ return ledger.attention[0] ?? find(ready?.issueId, ready?.taskId) ?? tasks.find((entry) => entry.dispatchable || entry.queueable);
49
+ }
50
+ return find(issueId, taskId);
51
+ }
@@ -0,0 +1,114 @@
1
+ import { readFileSync } from 'fs';
2
+ import { posix, resolve } from 'path';
3
+ import type { McpPolicy } from '../../mcp/types';
4
+ import { readableFile } from './known-paths';
5
+
6
+ const CONTRACT_NAMES = ['AGENTS.md', 'CLAUDE.md'] as const;
7
+ const DEFAULT_MAX_TARGETS = 12;
8
+ const DEFAULT_MAX_CONTRACTS = 12;
9
+ const DEFAULT_MAX_CHARS_PER_CONTRACT = 12_000;
10
+ const DEFAULT_MAX_TOTAL_CHARS = 48_000;
11
+
12
+ export interface RepositoryInstructionContract {
13
+ path: string;
14
+ appliesTo: string[];
15
+ content: string;
16
+ truncated: boolean;
17
+ }
18
+
19
+ export interface RepositoryInstructionContext {
20
+ status: 'none' | 'ready' | 'degraded';
21
+ authority: 'guidance_only';
22
+ targetPaths: string[];
23
+ contracts: RepositoryInstructionContract[];
24
+ coverageGaps: string[];
25
+ truncated: boolean;
26
+ }
27
+
28
+ function isInstructionContract(path: string): boolean {
29
+ const name = posix.basename(path);
30
+ return name === 'AGENTS.md' || name === 'CLAUDE.md';
31
+ }
32
+
33
+ function directoryChain(path: string): string[] {
34
+ const directory = posix.dirname(path);
35
+ if (directory === '.' || directory === '/') return [''];
36
+ const segments = directory.split('/').filter(Boolean);
37
+ const chain = [''];
38
+ for (let index = 1; index <= segments.length; index += 1) chain.push(segments.slice(0, index).join('/'));
39
+ return chain;
40
+ }
41
+
42
+ export function buildRepositoryInstructionContext(
43
+ repoRoot: string,
44
+ policy: McpPolicy,
45
+ targetPaths: string[],
46
+ options: { maxTargets?: number; maxContracts?: number; maxCharsPerContract?: number; maxTotalChars?: number } = {},
47
+ ): RepositoryInstructionContext {
48
+ const maxTargets = Math.max(1, Math.min(30, Math.trunc(options.maxTargets ?? DEFAULT_MAX_TARGETS)));
49
+ const maxContracts = Math.max(1, Math.min(30, Math.trunc(options.maxContracts ?? DEFAULT_MAX_CONTRACTS)));
50
+ const maxCharsPerContract = Math.max(500, Math.min(50_000, Math.trunc(options.maxCharsPerContract ?? DEFAULT_MAX_CHARS_PER_CONTRACT)));
51
+ const maxTotalChars = Math.max(2_000, Math.min(100_000, Math.trunc(options.maxTotalChars ?? DEFAULT_MAX_TOTAL_CHARS)));
52
+ const allTargets = [...new Set(targetPaths.map((path) => path.trim()).filter(Boolean).filter((path) => !isInstructionContract(path)))];
53
+ const targets = allTargets.slice(0, maxTargets);
54
+ const coverageGaps: string[] = [];
55
+ if (allTargets.length > targets.length) coverageGaps.push(`instruction_targets_truncated:${allTargets.length - targets.length}`);
56
+
57
+ const contractTargets = new Map<string, Set<string>>();
58
+ const denied = new Set<string>();
59
+ for (const target of targets) {
60
+ for (const directory of directoryChain(target)) {
61
+ for (const name of CONTRACT_NAMES) {
62
+ const candidate = directory ? `${directory}/${name}` : name;
63
+ const readable = readableFile(repoRoot, policy, candidate);
64
+ if (!readable.ok) {
65
+ if (!readable.reason.startsWith('path does not exist')) denied.add(`${candidate}:${readable.reason}`);
66
+ continue;
67
+ }
68
+ const appliesTo = contractTargets.get(readable.path) ?? new Set<string>();
69
+ appliesTo.add(target);
70
+ contractTargets.set(readable.path, appliesTo);
71
+ }
72
+ }
73
+ }
74
+ if (denied.size > 0) coverageGaps.push(...[...denied].sort().map((entry) => `instruction_contract_unreadable:${entry}`));
75
+
76
+ const orderedPaths = [...contractTargets.keys()].sort((left, right) => {
77
+ const leftDepth = left.split('/').length;
78
+ const rightDepth = right.split('/').length;
79
+ if (leftDepth !== rightDepth) return leftDepth - rightDepth;
80
+ const leftName = posix.basename(left);
81
+ const rightName = posix.basename(right);
82
+ if (leftName !== rightName) return CONTRACT_NAMES.indexOf(leftName as typeof CONTRACT_NAMES[number]) - CONTRACT_NAMES.indexOf(rightName as typeof CONTRACT_NAMES[number]);
83
+ return left.localeCompare(right);
84
+ });
85
+ const selectedPaths = orderedPaths.slice(0, maxContracts);
86
+ if (orderedPaths.length > selectedPaths.length) coverageGaps.push(`instruction_contracts_truncated:${orderedPaths.length - selectedPaths.length}`);
87
+
88
+ const contracts: RepositoryInstructionContract[] = [];
89
+ const fairContractBudget = Math.max(500, Math.min(maxCharsPerContract, Math.floor(maxTotalChars / Math.max(1, selectedPaths.length))));
90
+ for (const path of selectedPaths) {
91
+ try {
92
+ const raw = readFileSync(resolve(repoRoot, path), 'utf8');
93
+ const truncated = raw.length > fairContractBudget;
94
+ contracts.push({
95
+ path,
96
+ appliesTo: [...(contractTargets.get(path) ?? new Set<string>())].sort(),
97
+ content: truncated ? raw.slice(0, fairContractBudget) : raw,
98
+ truncated,
99
+ });
100
+ if (truncated) coverageGaps.push(`instruction_contract_content_truncated:${path}`);
101
+ } catch (error) {
102
+ coverageGaps.push(`instruction_contract_read_failed:${path}:${error instanceof Error ? error.message : String(error)}`);
103
+ }
104
+ }
105
+
106
+ return {
107
+ status: coverageGaps.length > 0 ? 'degraded' : contracts.length > 0 ? 'ready' : 'none',
108
+ authority: 'guidance_only',
109
+ targetPaths: targets,
110
+ contracts,
111
+ coverageGaps: coverageGaps.slice(0, 30),
112
+ truncated: allTargets.length > targets.length || orderedPaths.length > selectedPaths.length || contracts.some((entry) => entry.truncated),
113
+ };
114
+ }