@moretea-labs/forge 1.6.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) hide show
  1. package/AGENTS.md +4 -0
  2. package/CHANGELOG.md +39 -0
  3. package/CLAUDE.md +4 -0
  4. package/README.md +2 -2
  5. package/README.zh-CN.md +1 -1
  6. package/SKILL.md +2 -2
  7. package/THIRD_PARTY_NOTICES.md +1 -1
  8. package/agents/openai.yaml +1 -1
  9. package/assets/hooks/hook-input.sh +11 -8
  10. package/assets/hooks/lib/session-state.sh +10 -7
  11. package/assets/hooks/lib/workflow-state.sh +13 -177
  12. package/assets/hooks/post-tool-observer.sh +8 -2
  13. package/assets/hooks/prompt-guard.sh +5 -81
  14. package/assets/hooks/session-start-context.sh +56 -1
  15. package/assets/partials/05-workflow.partial.md +1 -1
  16. package/assets/partials-agents/02-operating-mode.partial.md +2 -0
  17. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  18. package/assets/plugin-registry.v1.json +3 -3
  19. package/assets/reference-configs/document-generation.md +1 -1
  20. package/assets/reference-configs/harness-overview.md +1 -1
  21. package/assets/reference-configs/sprint-contracts.md +3 -3
  22. package/assets/skill-version.json +2 -2
  23. package/assets/templates/contract.template.md +2 -7
  24. package/assets/templates/helpers/capture-plan.sh +2 -2
  25. package/assets/templates/helpers/check-architecture-sync.sh +17 -0
  26. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  27. package/assets/templates/helpers/contract-worktree.sh +1 -21
  28. package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
  29. package/assets/templates/helpers/inspect-project-state.ts +8 -6
  30. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  31. package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
  32. package/assets/templates/helpers/new-plan.sh +1 -1
  33. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  34. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  35. package/assets/templates/helpers/verify-contract.sh +1 -111
  36. package/assets/templates/helpers/verify-sprint.sh +3 -180
  37. package/assets/templates/helpers/workflow-contract.ts +5 -1
  38. package/assets/templates/plan.template.md +1 -1
  39. package/assets/templates/review.template.md +9 -34
  40. package/assets/workflow-contract.v1.json +10 -19
  41. package/docs/README.md +10 -10
  42. package/docs/architecture/CURRENT.md +178 -0
  43. package/docs/architecture/EVOLUTION.md +42 -0
  44. package/docs/architecture/current/README.md +4 -156
  45. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  46. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  47. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  48. package/docs/architecture/history/README.md +7 -0
  49. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  50. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  51. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  52. package/docs/architecture/history.md +3 -34
  53. package/docs/architecture/index.md +15 -98
  54. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  55. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  56. package/docs/architecture/versions/1.6.md +38 -0
  57. package/docs/architecture/versions/1.7.md +41 -0
  58. package/docs/forge-chatgpt-browser-engine.md +2 -2
  59. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  60. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  61. package/docs/forge-github-issue-launcher.md +1 -1
  62. package/docs/forge-plugin-management.md +1 -1
  63. package/docs/forge-runtime-self-healing-loop.md +1 -1
  64. package/docs/operations/controller-browser-plugin.md +26 -6
  65. package/docs/operations/controller-performance-and-502.md +143 -0
  66. package/docs/operations/controller-reliability-runbook.md +1 -1
  67. package/docs/operations/features.md +2 -2
  68. package/docs/operations/github-repository.md +1 -1
  69. package/docs/operations/homebrew.md +2 -2
  70. package/docs/operations/mcp-tool-exposure.md +5 -3
  71. package/docs/wiki/Architecture.md +6 -6
  72. package/docs/wiki/Implementation.md +2 -2
  73. package/docs/wiki/Integrations.md +2 -2
  74. package/docs/wiki/Work-Lifecycle.md +1 -1
  75. package/package.json +33 -11
  76. package/scripts/activate-source-baseline.ts +17 -13
  77. package/scripts/architecture-event.ts +2 -820
  78. package/scripts/architecture-queue.sh +2 -645
  79. package/scripts/archive-architecture-request.sh +2 -242
  80. package/scripts/archive-workflow.sh +1 -291
  81. package/scripts/benchmark-background-check-overlap.ts +228 -0
  82. package/scripts/benchmark-mcp-host.sh +117 -0
  83. package/scripts/benchmark-mcp-transport.ts +170 -0
  84. package/scripts/benchmark-quality-harness.ts +146 -0
  85. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  86. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  87. package/scripts/benchmark-typescript-navigation.ts +186 -0
  88. package/scripts/benchmark-work-efficiency.ts +334 -0
  89. package/scripts/capability-config.ts +2 -392
  90. package/scripts/capability-resolver.ts +2 -503
  91. package/scripts/capture-plan.sh +1 -332
  92. package/scripts/check-agent-tooling.sh +2 -1558
  93. package/scripts/check-architecture-sync.sh +2 -371
  94. package/scripts/check-brain-manifest.sh +2 -253
  95. package/scripts/check-context-files.sh +2 -47
  96. package/scripts/check-deploy-sql-order.sh +2 -115
  97. package/scripts/check-mcp-compatibility.ts +14 -0
  98. package/scripts/check-runtime-architecture.mjs +349 -79
  99. package/scripts/check-skill-version.ts +3 -191
  100. package/scripts/check-source-duplication.mjs +61 -0
  101. package/scripts/check-stable-baseline.ts +92 -0
  102. package/scripts/check-task-sync.sh +2 -63
  103. package/scripts/check-task-workflow.sh +2 -1000
  104. package/scripts/codex-handoff-resume.sh +2 -267
  105. package/scripts/context-contract-sync.sh +2 -514
  106. package/scripts/contract-run.ts +2 -463
  107. package/scripts/contract-worktree.sh +1 -722
  108. package/scripts/ensure-task-workflow.sh +1 -1225
  109. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  110. package/scripts/inspect-project-state.ts +4 -279
  111. package/scripts/install-standalone-recovery.ts +24 -0
  112. package/scripts/lib/project-init-lib.sh +22 -46
  113. package/scripts/lib/run-forge-helper.sh +19 -0
  114. package/scripts/lib/run-forge-helper.ts +26 -0
  115. package/scripts/migrate-project-template.sh +1 -1189
  116. package/scripts/migrate-workflow-docs.ts +4 -412
  117. package/scripts/new-plan.sh +1 -180
  118. package/scripts/new-spec.sh +1 -47
  119. package/scripts/new-sprint.sh +1 -46
  120. package/scripts/plan-to-todo.sh +1 -796
  121. package/scripts/prepare-codex-handoff.sh +2 -201
  122. package/scripts/prepare-handoff.sh +1 -78
  123. package/scripts/refresh-current-status.sh +2 -467
  124. package/scripts/run-governed-gate.ts +5 -1
  125. package/scripts/select-agent-context-blocks.sh +2 -64
  126. package/scripts/ship-worktrees.sh +1 -558
  127. package/scripts/smoke-ios-physical-device.ts +4 -2
  128. package/scripts/smoke-runtime-recovery.ts +1 -35
  129. package/scripts/smoke-schedule-engine.ts +1 -20
  130. package/scripts/sprint-backlog.sh +1 -834
  131. package/scripts/stage-runtime-release.ts +2 -1
  132. package/scripts/summarize-failures.sh +2 -123
  133. package/scripts/switch-plan.sh +1 -150
  134. package/scripts/sync-brain-docs.sh +2 -379
  135. package/scripts/sync-generated-authority.mjs +175 -0
  136. package/scripts/verify-contract.sh +2 -703
  137. package/scripts/verify-forge-runtime.sh +0 -1
  138. package/scripts/verify-sprint.sh +1 -677
  139. package/scripts/workflow-contract.ts +4 -205
  140. package/scripts/workstream-sync.sh +2 -261
  141. package/src/cli/agent-jobs/executor-health.ts +1 -1
  142. package/src/cli/commands/init-hook.ts +3 -3
  143. package/src/cli/commands/mcp.ts +9 -5
  144. package/src/cli/commands/plugin.ts +12 -3
  145. package/src/cli/commands/recovery.ts +25 -3
  146. package/src/cli/commands/runtime.ts +55 -3
  147. package/src/cli/commands/security.ts +12 -1
  148. package/src/cli/commands/setup-profile.ts +31 -21
  149. package/src/cli/controller/check-runner.ts +113 -5
  150. package/src/cli/controller/composite-result.ts +1 -1
  151. package/src/cli/controller/context/focus.ts +51 -0
  152. package/src/cli/controller/context/instruction-context.ts +114 -0
  153. package/src/cli/controller/context/known-paths.ts +76 -0
  154. package/src/cli/controller/context/query-planning.ts +110 -0
  155. package/src/cli/controller/context/rule-resolution.ts +532 -0
  156. package/src/cli/controller/context/source-materializer.ts +278 -0
  157. package/src/cli/controller/context/structural.ts +16 -0
  158. package/src/cli/controller/context/types.ts +286 -0
  159. package/src/cli/controller/context-pack.ts +828 -487
  160. package/src/cli/controller/launch-agents.ts +77 -2
  161. package/src/cli/controller/operational-plan.ts +2 -2
  162. package/src/cli/controller/work-mode.ts +6 -4
  163. package/src/cli/editing/edit-session.ts +60 -7
  164. package/src/cli/github/contracts.ts +16 -0
  165. package/src/cli/github/github.ts +135 -16
  166. package/src/cli/github/session.ts +2 -1
  167. package/src/cli/installer/managed-entries.ts +15 -1
  168. package/src/cli/local-bridge/console-automations.ts +54 -78
  169. package/src/cli/local-bridge/console-view-models.ts +1 -35
  170. package/src/cli/local-bridge/facade-api.ts +37 -264
  171. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  172. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
  173. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  174. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
  175. package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
  176. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  177. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
  178. package/src/cli/local-bridge/frontend/src/styles.css +1 -1
  179. package/src/cli/local-bridge/frontend/src/types.ts +5 -8
  180. package/src/cli/local-bridge/job-store.ts +1 -490
  181. package/src/cli/local-bridge/server.ts +2 -515
  182. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  183. package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
  184. package/src/cli/mcp/auth.ts +5 -5
  185. package/src/cli/mcp/instructions.ts +3 -2
  186. package/src/cli/mcp/legacy-tool-service.ts +10 -222
  187. package/src/cli/mcp/multi-repository.ts +16 -5
  188. package/src/cli/mcp/oauth.ts +175 -26
  189. package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
  190. package/src/cli/mcp/repository-tools.ts +282 -146
  191. package/src/cli/mcp/server.ts +586 -116
  192. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  193. package/src/cli/mcp/tool-contract.ts +14 -0
  194. package/src/cli/mcp/toolset-selection.ts +1 -1
  195. package/src/cli/mcp/toolset.ts +44 -7
  196. package/src/cli/mcp/transports/http.ts +124 -14
  197. package/src/cli/mcp/transports/session-registry.ts +43 -5
  198. package/src/cli/mcp/types.ts +3 -3
  199. package/src/cli/repositories/command-classifier.ts +103 -21
  200. package/src/cli/repositories/command-executor.ts +78 -526
  201. package/src/cli/repositories/command-process.ts +108 -0
  202. package/src/cli/repositories/command-scope.ts +99 -41
  203. package/src/cli/repositories/controller-home.ts +7 -8
  204. package/src/cli/repositories/git-command-executor.ts +2 -0
  205. package/src/cli/repositories/locks.ts +53 -16
  206. package/src/cli/repositories/repository-snapshot.ts +124 -0
  207. package/src/cli/repositories/selected-path-actions.ts +17 -9
  208. package/src/cli/repositories/structured-git.ts +118 -6
  209. package/src/cli/repository/inspector.ts +563 -65
  210. package/src/cli/repository/session-cache.ts +58 -17
  211. package/src/cli/v81-entry.ts +1 -4
  212. package/src/effects/fs-transaction.ts +85 -17
  213. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  214. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  215. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  216. package/src/runtime/context/lsp-client.ts +283 -0
  217. package/src/runtime/context/semantic-navigation-contract.ts +61 -0
  218. package/src/runtime/context/semantic-navigation.ts +223 -0
  219. package/src/runtime/context/swift-navigation.ts +565 -0
  220. package/src/runtime/context/typescript-navigation.ts +226 -0
  221. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  222. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +48 -16
  223. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  224. package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
  225. package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
  226. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  227. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  228. package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
  229. package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
  230. package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
  231. package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
  232. package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
  233. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  234. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  235. package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
  236. package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
  237. package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
  238. package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
  239. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  240. package/src/runtime/control-plane/execution/work-task-receipt.ts +22 -18
  241. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +156 -11
  242. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
  243. package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
  244. package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
  245. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +90 -9
  246. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  247. package/src/runtime/control-plane/facade/controller-round-relay.ts +1118 -0
  248. package/src/runtime/control-plane/facade/controller-session-store.ts +302 -12
  249. package/src/runtime/control-plane/facade/goal-workloop-access.ts +17 -1
  250. package/src/runtime/control-plane/facade/goal-workloop.ts +928 -190
  251. package/src/runtime/control-plane/facade/index.ts +3 -0
  252. package/src/runtime/control-plane/facade/plan-contract-store.ts +275 -42
  253. package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
  254. package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
  255. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  256. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  257. package/src/runtime/control-plane/facade/types.ts +90 -10
  258. package/src/runtime/control-plane/facade/work-contract-store.ts +157 -261
  259. package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
  260. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  261. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  262. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  263. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  264. package/src/runtime/control-plane/global-scheduler/maintenance.ts +152 -0
  265. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  266. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  267. package/src/runtime/control-plane/global-scheduler/scheduler.ts +278 -543
  268. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  269. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  270. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  271. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  272. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  273. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  274. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  275. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  276. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  277. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  278. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +63 -0
  279. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +365 -117
  280. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +89 -0
  281. package/src/runtime/control-plane/launcher/thin-launcher.ts +244 -11
  282. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  283. package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
  284. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  285. package/src/runtime/control-plane/release-retention.ts +62 -2
  286. package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
  287. package/src/runtime/control-plane/routing/route-policy.ts +24 -21
  288. package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
  289. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  290. package/src/runtime/diagnostics/mcp-timing.ts +13 -0
  291. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  292. package/src/runtime/diagnostics/performance.ts +24 -1
  293. package/src/runtime/evidence/event-ledger.ts +1 -1
  294. package/src/runtime/evidence/process-check-execution.ts +18 -0
  295. package/src/runtime/execution/jobs/child-reference.ts +6 -2
  296. package/src/runtime/execution/jobs/store.ts +58 -4
  297. package/src/runtime/execution/jobs/types.ts +1 -1
  298. package/src/runtime/execution/managed-workspace.ts +203 -1
  299. package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
  300. package/src/runtime/execution/process-runtime/check-result.ts +88 -0
  301. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
  302. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  303. package/src/runtime/execution/process-runtime/command-facade.ts +367 -40
  304. package/src/runtime/execution/process-runtime/gc.ts +38 -6
  305. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1201 -0
  306. package/src/runtime/execution/process-runtime/persisted-check.ts +51 -5
  307. package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
  308. package/src/runtime/execution/process-runtime/runtime.ts +53 -3
  309. package/src/runtime/execution/process-runtime/store.ts +1 -0
  310. package/src/runtime/execution/process-runtime/types.ts +4 -12
  311. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  312. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  313. package/src/runtime/execution/work-activity.ts +21 -0
  314. package/src/runtime/execution/workers/executor.ts +18 -61
  315. package/src/runtime/gateway/mcp/execution-tools.ts +35 -1852
  316. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
  317. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  318. package/src/runtime/gateway/mcp/router.ts +186 -84
  319. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +565 -0
  320. package/src/runtime/gateway/mcp/runtime-tools.ts +2378 -1947
  321. package/src/runtime/plugins/app-store-connect-adapter.ts +216 -47
  322. package/src/runtime/plugins/browser-adapter.ts +2662 -829
  323. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  324. package/src/runtime/plugins/browser-handoff-host.ts +2 -25
  325. package/src/runtime/plugins/browser-handoff.ts +110 -5
  326. package/src/runtime/plugins/browser-macos-bridge.ts +701 -63
  327. package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
  328. package/src/runtime/plugins/browser-native-session-service.ts +222 -0
  329. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  330. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  331. package/src/runtime/plugins/browser-runtime-contract.ts +154 -0
  332. package/src/runtime/plugins/browser-runtime.ts +193 -0
  333. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  334. package/src/runtime/plugins/browser-session-store.ts +121 -0
  335. package/src/runtime/plugins/browser-session-types.ts +98 -0
  336. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  337. package/src/runtime/plugins/desktop-operator-registration.ts +10 -4
  338. package/src/runtime/plugins/external-adapter.ts +358 -3
  339. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  340. package/src/runtime/plugins/first-party-registry.ts +2 -0
  341. package/src/runtime/plugins/github-adapter.ts +125 -0
  342. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  343. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  344. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  345. package/src/runtime/plugins/ios-adapter.ts +106 -16
  346. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  347. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  348. package/src/runtime/plugins/lightweight-action.ts +153 -0
  349. package/src/runtime/plugins/local-system-adapter.ts +123 -6
  350. package/src/runtime/plugins/macos-capability-broker.ts +47 -9
  351. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  352. package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
  353. package/src/runtime/plugins/resend-adapter.ts +1 -1
  354. package/src/runtime/plugins/store.ts +330 -33
  355. package/src/runtime/plugins/types.ts +38 -3
  356. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  357. package/src/runtime/projections/git-status-sampler.ts +16 -16
  358. package/src/runtime/projections/materialized-view.ts +28 -10
  359. package/src/runtime/recovery/maintenance-executor.ts +426 -25
  360. package/src/runtime/root/mcp-transport.ts +180 -10
  361. package/src/runtime/root/package-connector-service.ts +230 -10
  362. package/src/runtime/root/package-runtime-release.ts +143 -17
  363. package/src/runtime/root/package-runtime-service.ts +461 -22
  364. package/src/runtime/root/release-manifest.ts +144 -18
  365. package/src/runtime/root/release-materialize.ts +125 -32
  366. package/src/runtime/root/release-store.ts +48 -11
  367. package/src/runtime/root/runtime.ts +23 -5
  368. package/src/runtime/root/service.ts +100 -29
  369. package/src/runtime/root/types.ts +12 -0
  370. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  371. package/src/runtime/shared/trusted-executable.ts +62 -0
  372. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  373. package/src/runtime/standalone-recovery/core.ts +1079 -240
  374. package/src/runtime/standalone-recovery/entry.ts +100 -24
  375. package/src/runtime/standalone-recovery/installer.ts +20 -4
  376. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  377. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  378. package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
  379. package/src/runtime/workflow/schedules/engine.ts +364 -202
  380. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  381. package/src/runtime/workflow/schedules/settlement.ts +64 -17
  382. package/src/runtime/workflow/schedules/store.ts +209 -19
  383. package/src/runtime/workflow/schedules/types.ts +2 -4
  384. package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
  385. package/src/testing/test-governance.ts +8 -40
  386. package/assets/reference-configs/heartbeat-triage.md +0 -57
  387. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  388. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  389. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  390. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  391. package/docs/architecture/current/architecture-invariants.md +0 -331
  392. package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
  393. package/docs/architecture/current/codegraph-context-provider.md +0 -238
  394. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  395. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  396. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
  397. package/docs/architecture/current/entity-model.md +0 -612
  398. package/docs/architecture/current/external-plugin-governance.md +0 -396
  399. package/docs/architecture/current/failure-recovery.md +0 -447
  400. package/docs/architecture/current/governance.md +0 -213
  401. package/docs/architecture/current/human-interaction-plane.md +0 -87
  402. package/docs/architecture/current/implementation-status.md +0 -128
  403. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  404. package/docs/architecture/current/legacy-governance.md +0 -58
  405. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  406. package/docs/architecture/current/migration-roadmap.md +0 -105
  407. package/docs/architecture/current/multi-repository-execution.md +0 -371
  408. package/docs/architecture/current/operations-runbook.md +0 -45
  409. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  410. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  411. package/docs/architecture/current/repository-access-modes.md +0 -102
  412. package/docs/architecture/current/route-policy.md +0 -15
  413. package/docs/architecture/current/route-session-concurrency.md +0 -85
  414. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  415. package/docs/architecture/current/runtime-directory-map.md +0 -49
  416. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  417. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  418. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  419. package/docs/architecture/current/system-overview.md +0 -169
  420. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  421. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  422. package/docs/architecture/current/thin-harness-v1.md +0 -321
  423. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  424. package/docs/forge-autonomous-goal-loop.md +0 -9
  425. package/docs/forge-provider-configuration.md +0 -7
  426. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
  427. package/docs/operations/gmail-assistant-routines.md +0 -186
  428. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  429. package/docs/operations/provider-configuration.md +0 -31
  430. package/scripts/harness-trace-grade.sh +0 -150
  431. package/scripts/heartbeat-triage.sh +0 -361
  432. package/scripts/maintenance-triage.sh +0 -167
  433. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  434. package/src/cli/repositories/goal-registry.ts +0 -239
  435. package/src/runtime/assistant/action-proposals.ts +0 -279
  436. package/src/runtime/assistant/intent.ts +0 -395
  437. package/src/runtime/assistant/model-provider.ts +0 -460
  438. package/src/runtime/assistant/openapi.ts +0 -223
  439. package/src/runtime/assistant/policy.ts +0 -89
  440. package/src/runtime/assistant/readiness.ts +0 -258
  441. package/src/runtime/assistant/routine-runtime.ts +0 -477
  442. package/src/runtime/assistant/schedule-binding.ts +0 -171
  443. package/src/runtime/assistant/standing-grants.ts +0 -375
  444. package/src/runtime/assistant/store.ts +0 -243
  445. package/src/runtime/assistant/types.ts +0 -135
  446. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  447. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  448. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  449. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  450. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  451. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  452. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  453. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  454. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  455. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  456. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  457. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  458. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  459. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  460. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  461. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  462. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  463. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  464. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  465. package/src/runtime/workflow/findings/store.ts +0 -204
  466. package/src/runtime/workflow/findings/types.ts +0 -58
  467. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  468. package/src/runtime/workflow/portfolio/store.ts +0 -185
  469. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -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;
@@ -675,6 +773,8 @@ export function runControllerCheck(repoRoot: string, id: string, requestedTimeou
675
773
 
676
774
  export interface AsyncControllerCheckOptions {
677
775
  snapshot?: ControllerCheckSnapshot;
776
+ /** Explicit isolated authority for Candidate verification child processes. */
777
+ isolatedControllerHome?: string;
678
778
  requestedTimeoutMs?: number;
679
779
  onSpawn?: (pid: number) => void;
680
780
  subscriberId?: string;
@@ -814,12 +914,15 @@ async function executeControllerCheckAsync(
814
914
  check: ControllerCheck,
815
915
  timeoutMs: number,
816
916
  onSpawn?: (pid: number) => void,
917
+ isolatedControllerHome?: string,
817
918
  ): Promise<ControllerCheckResult> {
818
919
  const maxOutputBytes = 256 * 1024;
819
920
  const command = [check.command[0], ...check.command.slice(1)];
921
+ const childEnvironment = repositoryChildProcessEnvironment();
922
+ if (isolatedControllerHome?.trim()) childEnvironment.FORGE_CONTROLLER_HOME = resolve(isolatedControllerHome);
820
923
  const supervised = await runBoundedChild(check.command[0], check.command.slice(1), {
821
924
  cwd: resolve(repoRoot, check.cwd),
822
- env: repositoryChildProcessEnvironment(),
925
+ env: childEnvironment,
823
926
  timeoutMs,
824
927
  maxOutputBytes,
825
928
  stdio: 'capture',
@@ -877,6 +980,11 @@ export function runControllerCheckAsync(
877
980
  return Promise.reject(error);
878
981
  }
879
982
  if (!check || check.id !== id) return Promise.reject(new Error(`check not found: ${id}`));
983
+ try {
984
+ prepareControllerCheckDependencies(repoRoot, check);
985
+ } catch (error) {
986
+ return Promise.reject(error);
987
+ }
880
988
  const identity = controllerCheckExecutionIdentity(repoRoot, id, options.requestedTimeoutMs, options.snapshot);
881
989
  const timeoutMs = identity.timeoutMs;
882
990
  const revision = identity.revision;
@@ -947,7 +1055,7 @@ export function runControllerCheckAsync(
947
1055
  const result = await executeControllerCheckAsync(repoRoot, check, timeoutMs, (pid) => {
948
1056
  lease?.setChildPid(pid);
949
1057
  notifySpawn(pid);
950
- });
1058
+ }, options.isolatedControllerHome);
951
1059
  const completedRevision = currentControllerCheckRevision(repoRoot);
952
1060
  const stale = completedRevision !== revision;
953
1061
  const finalized = {
@@ -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
+ }