@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
@@ -25,7 +25,7 @@ export interface McpLocalConfig {
25
25
  localEndpoint?: string;
26
26
  };
27
27
  profile?: string;
28
- toolset?: 'core' | 'advanced' | 'full';
28
+ toolset?: 'facade' | 'core' | 'advanced' | 'full';
29
29
  /** Compatibility marker retained for previously persisted toolset choices. */
30
30
  toolsetExplicit?: boolean;
31
31
  accessMode?: AccessMode;
@@ -88,7 +88,7 @@ export interface McpRuntimeState {
88
88
  forgeVersion?: string;
89
89
  toolSurfaceFingerprint?: string;
90
90
  runtimeToolSurfaceFingerprint?: string;
91
- toolset?: 'core' | 'advanced' | 'full';
91
+ toolset?: 'facade' | 'core' | 'advanced' | 'full';
92
92
  configuredAccessMode?: AccessMode;
93
93
  effectiveAccessMode?: AccessMode;
94
94
  effectiveToolset?: McpToolset;
@@ -131,7 +131,7 @@ export interface McpRuntimeState {
131
131
  }>;
132
132
  }
133
133
 
134
- export type McpHttpAuthMode = 'oauth' | 'bearer';
134
+ export type McpHttpAuthMode = 'oauth' | 'bearer' | 'none';
135
135
 
136
136
  export function mcpLocalConfigPath(repoRoot: string): string {
137
137
  return join(repoRoot, '.forge', 'mcp.local.json');
@@ -356,8 +356,8 @@ export function syncMcpControllerHomeBearerToken(
356
356
 
357
357
  export function parseMcpHttpAuthMode(value: string | undefined): McpHttpAuthMode {
358
358
  const mode = (value ?? 'oauth').trim().toLowerCase();
359
- if (mode === 'oauth' || mode === 'bearer') return mode;
360
- throw new Error(`invalid --auth "${value}" (expected: oauth, bearer)`);
359
+ if (mode === 'oauth' || mode === 'bearer' || mode === 'none') return mode;
360
+ throw new Error(`invalid --auth "${value}" (expected: oauth, bearer, none)`);
361
361
  }
362
362
 
363
363
  export function readMcpOAuthPassphrase(repoRoot: string): string | null {
@@ -10,10 +10,11 @@ export function mcpServerInstructions(profile: McpProfileName): string {
10
10
  return [
11
11
  ...common,
12
12
  'Use the shortest valid execution path. Investigation or parallelism alone must not create durable Work, Issues, Plans, Agents, isolation, or worktrees; escalate only for actual recovery, dependency, risk, scope, external-effect, or long-running requirements.',
13
- 'For bounded implementation, use rh_context(operation=search, retrieval_mode=implementation) once as the default code retrieval router when the exact path is unknown. Its current raw snippets and file SHA identities are valid implementation evidence; ChatGPT decides semantic sufficiency and should request wider exact ranges only when impact ambiguity, truncation, drift, or an expansion signal can materially change the edit. For cross-cutting state/time/schema/cache/API/event/concurrency changes, ChatGPT should select relevant impact_domains in that first search so Forge expands those evidence dimensions mechanically in the same call; Forge never infers that the selected domains are semantically complete. Use retrieval_mode=plan|debug|review when the task intentionally needs a broader evidence surface. Do not fall back to shell grep unless retrieval is unavailable or raw Git semantics are explicitly needed. The low-level search_repository handler is compatibility/internal surface, not a second default search path. Prefer repository_command_execute for ordinary local Git and shell operations, repository_safe_patch_apply for bounded source changes, and rh_work only when the task genuinely requires durable orchestration.',
13
+ 'For bounded implementation, start with rh_context(operation=search, retrieval_mode=implementation) as the default code retrieval router when the exact path is unknown, then repeat it with exact paths, symbols, tests, relationships, or impact domains whenever more evidence can materially improve correctness. Its current raw snippets and file SHA identities are valid implementation evidence; ChatGPT alone decides semantic sufficiency. For cross-cutting state/time/schema/cache/API/event/concurrency changes, ChatGPT should select relevant impact_domains so Forge expands those evidence dimensions mechanically; Forge never infers that the selected domains are semantically complete. Use retrieval_mode=plan|debug|review when the task intentionally needs a broader evidence surface. Do not fall back to shell grep unless retrieval is unavailable or raw Git semantics are explicitly needed. The low-level search_repository handler is compatibility/internal surface, not a second default search path. Prefer repository_command_execute for ordinary local Git and shell operations, repository_safe_patch_apply for bounded source changes, and rh_work only when the task genuinely requires durable orchestration.',
14
14
  'Do not preflight unrelated Git history, prior Work, plugins, permissions, TCC, browser mode, maintenance, or recovery state. Inspect them only when the requested operation depends on them or a real failure points there.',
15
+ 'Minimize mechanically redundant MCP round trips. For an ongoing Forge-controlled task, prefer one rh_status(summary) as the initial controller snapshot: it includes bounded readiness, repository identity, top active Work, active Plans, and pending handoffs. Do not immediately follow it with rh_work(plan_list) or rh_context(list/get) unless the next decision needs details that the snapshot does not contain. Use rh_context(work_id=..., detail_level=summary) for one Work, and request detail/raw or capability hydration only when that extra evidence can change the decision.',
15
16
  'Run independent reads, searches, diagnostics, checks, repositories, checkouts, and tasks concurrently by default. Serialize only on a real dependency or Resource Claim conflict, and reuse existing projections, process handles, and check evidence instead of repeating successful observations.',
16
- 'Validation must be proportional to the change: cheap patch-safety checks belong to every coherent edit batch, focused tests run after that batch is stable, and expensive full-suite checks belong at candidate/release or explicitly high-risk boundaries. When a Direct Edit batch is stable, repository_safe_patch_apply may receive explicit check_ids to start revision-bound validation; omit check_ids during intermediate edits. Long checks return managed Processes: continue independent read/review work, then use validation_only with the returned edit-session/validation ids as the synchronization join instead of polling repeatedly. Do not perform maintenance or deep runtime diagnosis before ordinary work unless execution is actually blocked.',
17
+ 'Validation must be proportional to the change: cheap patch-safety checks belong to every coherent edit batch, focused tests run after that batch is stable, and expensive full-suite checks belong at candidate/release or explicitly high-risk boundaries. When a Direct Edit batch is stable, repository_safe_patch_apply may receive explicit check_ids to start revision-bound validation; omit check_ids during intermediate edits. Long checks return managed Processes: continue independent read/review work. Do not call process_wait while useful independent work remains. Use process_get only when a non-blocking observation can change the next decision; when the exact check result becomes a dependency, attach with process_wait (or validation_only with the returned edit-session/validation ids). An attach is transport-bounded: if it returns a running handle, resume useful work and attach again only at a later real dependency boundary, never as periodic polling. Do not perform maintenance or deep runtime diagnosis before ordinary work unless execution is actually blocked.',
17
18
  'repository_safe_patch_apply returns bounded edit-session diff evidence for semantic review. Review that evidence directly instead of issuing a mandatory follow-up diff/status call; expand it only when truncated or semantically ambiguous. After a bounded successful change, finish the requested delivery directly. Avoid additional investigation whose result cannot change implementation or acceptance, and do not invoke hidden atomic compatibility tools merely because they exist in the full profile.',
18
19
  ].join(' ');
19
20
  }
@@ -113,7 +113,6 @@ import type {
113
113
  import {
114
114
  gitDiff,
115
115
  gitSnapshot,
116
- readRepositoryRange,
117
116
  searchRepository,
118
117
  } from "../repository/inspector";
119
118
  import {
@@ -1634,29 +1633,6 @@ export function buildMcpToolDefinitions(
1634
1633
  },
1635
1634
  annotations: readOnly,
1636
1635
  },
1637
- {
1638
- name: "controller_context_pack",
1639
- description:
1640
- "Build a bounded code context pack for a task: relevant files, search-hit line ranges, and raw snippets. This is for scoped investigation, not blind implementation.",
1641
- inputSchema: {
1642
- type: "object",
1643
- properties: {
1644
- description: { type: "string" },
1645
- issue_id: { type: "string" },
1646
- task_id: { type: "string" },
1647
- known_paths: { type: "array", items: { type: "string" } },
1648
- include_globs: { type: "array", items: { type: "string" } },
1649
- exclude_globs: { type: "array", items: { type: "string" } },
1650
- search_terms: { type: "array", items: { type: "string" } },
1651
- max_files: { type: "number" },
1652
- max_snippets: { type: "number" },
1653
- max_chars_per_snippet: { type: "number" },
1654
- structural_context: { type: "string", enum: ["off", "auto", "required"], description: "Optional CodeGraph structural context. Defaults to off so ordinary Direct work pays no graph startup cost." },
1655
- },
1656
- additionalProperties: false,
1657
- },
1658
- annotations: readOnly,
1659
- },
1660
1636
  {
1661
1637
  name: "quick_agent_session",
1662
1638
  description:
@@ -1807,22 +1783,6 @@ export function buildMcpToolDefinitions(
1807
1783
  },
1808
1784
  annotations: readOnly,
1809
1785
  },
1810
- {
1811
- name: "read_repository_file",
1812
- description:
1813
- "Read a line range from one policy-readable repository file.",
1814
- inputSchema: {
1815
- type: "object",
1816
- properties: {
1817
- path: { type: "string" },
1818
- start_line: { type: "number" },
1819
- end_line: { type: "number" },
1820
- },
1821
- required: ["path"],
1822
- additionalProperties: false,
1823
- },
1824
- annotations: readOnly,
1825
- },
1826
1786
  {
1827
1787
  name: "get_git_diff",
1828
1788
  description:
@@ -1844,25 +1804,6 @@ export function buildMcpToolDefinitions(
1844
1804
  inputSchema: EMPTY_SCHEMA,
1845
1805
  annotations: readOnly,
1846
1806
  },
1847
- {
1848
- name: "run_check",
1849
- description:
1850
- "Start one named focused repository check as a Local Job and return immediately; inspect it with get_local_job.",
1851
- inputSchema: {
1852
- type: "object",
1853
- properties: {
1854
- check_id: { type: "string" },
1855
- timeout_ms: { type: "number" },
1856
- },
1857
- required: ["check_id"],
1858
- additionalProperties: false,
1859
- },
1860
- annotations: {
1861
- readOnlyHint: false,
1862
- openWorldHint: false,
1863
- destructiveHint: false,
1864
- },
1865
- },
1866
1807
  {
1867
1808
  name: "list_issues",
1868
1809
  description: "List controller-managed issues and task summaries.",
@@ -3306,28 +3247,6 @@ export async function callMcpTool(
3306
3247
  audit(ctx, name, "ok", args);
3307
3248
  return textResult(payload);
3308
3249
  }
3309
- case "controller_context_pack": {
3310
- if (ctx.policy.profile !== "controller")
3311
- return errorResult(
3312
- "TOOL_DISABLED",
3313
- "controller_context_pack requires the controller profile",
3314
- );
3315
- const pack = buildControllerContextPack(ctx.repoRoot, ctx.policy, {
3316
- description: typeof args.description === "string" ? args.description : undefined,
3317
- issueId: typeof args.issue_id === "string" ? args.issue_id : undefined,
3318
- taskId: typeof args.task_id === "string" ? args.task_id : undefined,
3319
- knownPaths: stringList(args.known_paths),
3320
- includeGlobs: stringList(args.include_globs),
3321
- excludeGlobs: stringList(args.exclude_globs),
3322
- searchTerms: stringList(args.search_terms),
3323
- maxFiles: typeof args.max_files === "number" ? args.max_files : undefined,
3324
- maxSnippets: typeof args.max_snippets === "number" ? args.max_snippets : undefined,
3325
- maxCharsPerSnippet: typeof args.max_chars_per_snippet === "number" ? args.max_chars_per_snippet : undefined,
3326
- structuralContext: args.structural_context === "auto" || args.structural_context === "required" ? args.structural_context : "off",
3327
- });
3328
- audit(ctx, name, "ok", args);
3329
- return textResult(pack);
3330
- }
3331
3250
  case "controller_context": {
3332
3251
  if (ctx.policy.profile !== "controller")
3333
3252
  return errorResult(
@@ -3563,36 +3482,6 @@ export async function callMcpTool(
3563
3482
  audit(ctx, name, "ok", args);
3564
3483
  return textResult(result);
3565
3484
  }
3566
- case "read_repository_file": {
3567
- if (ctx.policy.profile !== "controller")
3568
- return errorResult(
3569
- "TOOL_DISABLED",
3570
- "read_repository_file requires the controller profile",
3571
- );
3572
- const path = String(args.path ?? "");
3573
- const session = ctx.sessionId && ctx.repoId
3574
- ? {
3575
- sessionId: ctx.sessionId,
3576
- repoId: ctx.repoId,
3577
- checkoutId: ctx.checkoutId ?? ctx.repoId,
3578
- }
3579
- : undefined;
3580
- const result = readRepositoryRange(
3581
- ctx.repoRoot,
3582
- ctx.policy,
3583
- path,
3584
- typeof args.start_line === "number" ? args.start_line : 1,
3585
- typeof args.end_line === "number" ? args.end_line : 200,
3586
- session,
3587
- );
3588
- const redacted = redactMcpText(result.content);
3589
- audit(ctx, name, "ok", args, result.path);
3590
- return textResult({
3591
- ...result,
3592
- content: redacted.text,
3593
- redactions: redacted.redactions,
3594
- });
3595
- }
3596
3485
  case "get_git_diff": {
3597
3486
  if (ctx.policy.profile !== "controller")
3598
3487
  return errorResult(
@@ -3640,84 +3529,6 @@ export async function callMcpTool(
3640
3529
  audit(ctx, name, "ok", args);
3641
3530
  return textResult({ checks });
3642
3531
  }
3643
- case "run_check": {
3644
- if (ctx.policy.profile !== "controller")
3645
- return errorResult(
3646
- "TOOL_DISABLED",
3647
- "run_check requires the controller profile",
3648
- );
3649
- // Prefer Unified Process Runtime for ordinary checks. Multi-phase and
3650
- // release checks require an explicit external Controller instead.
3651
- const checkId = String(args.check_id ?? "").trim();
3652
- const forceDurable = args.force_durable === true
3653
- || args.apply_mode === "async"
3654
- || args.mode === "durable";
3655
- if (!forceDurable && checkId) {
3656
- try {
3657
- const { checkRequiresDurableWorkflow, runCheckViaProcessRuntime } = await import(
3658
- "../../runtime/execution/process-runtime"
3659
- );
3660
- const { resolveRepoPreferredControllerHome } = await import("../repositories/controller-home");
3661
- const { resolveRepositorySelection } = await import("../repositories/registry");
3662
- if (!checkRequiresDurableWorkflow(checkId)) {
3663
- const controllerHome = resolveRepoPreferredControllerHome(ctx.repoRoot);
3664
- const repository = resolveRepositorySelection({
3665
- explicitPath: ctx.repoRoot,
3666
- controllerHome,
3667
- allowSoleRepository: true,
3668
- });
3669
- if (repository) {
3670
- const facade = await runCheckViaProcessRuntime({
3671
- controllerHome,
3672
- repoId: repository.repoId,
3673
- checkoutId: repository.activeCheckoutId,
3674
- repoRoot: repository.canonicalRoot,
3675
- checkId,
3676
- timeoutMs: typeof args.timeout_ms === "number" ? args.timeout_ms : undefined,
3677
- requestId: typeof args.request_id === "string" ? args.request_id.trim() || undefined : undefined,
3678
- executionIdentity: {
3679
- schemaVersion: 1,
3680
- repositoryId: repository.repoId,
3681
- checkoutId: repository.activeCheckoutId,
3682
- canonicalRoot: repository.canonicalRoot,
3683
- },
3684
- });
3685
- if (facade.mode !== "durable" && facade.process) {
3686
- audit(ctx, name, "ok", args);
3687
- return textResult({
3688
- mode: facade.mode,
3689
- path: facade.mode === "direct" ? "process_direct" : "process_managed",
3690
- checkId: facade.checkId,
3691
- processId: facade.process.processId,
3692
- status: facade.process.status,
3693
- completed: facade.process.completed === true,
3694
- ok: facade.process.ok,
3695
- exitCode: facade.process.exitCode,
3696
- timedOut: facade.process.timedOut,
3697
- stdout: facade.process.stdout,
3698
- stderr: facade.process.stderr,
3699
- durableSideEffects: facade.durableSideEffects,
3700
- next: facade.process.completed
3701
- ? "Check finished on Process Runtime without LocalBridgeJob / ExecutionJob / Worker."
3702
- : `Managed process ${facade.process.processId} still running; poll process_get / process_wait / process_logs.`,
3703
- });
3704
- }
3705
- }
3706
- }
3707
- } catch (error) {
3708
- const message = error instanceof Error ? error.message : String(error);
3709
- const code = message.includes(":") ? message.slice(0, message.indexOf(":")) : "PROCESS_RUNTIME_FAILED";
3710
- audit(ctx, name, "failed", args, undefined, message);
3711
- return errorResult(code, message, { checkId });
3712
- }
3713
- }
3714
- audit(ctx, name, "blocked", args);
3715
- return errorResult(
3716
- "PROCESS_RUNTIME_REQUIRED",
3717
- "This check cannot create a Local Bridge Job. Run a registered ordinary check through Process Runtime or use external Controller handling for durable checks.",
3718
- { checkId },
3719
- );
3720
- }
3721
3532
  case "list_issues": {
3722
3533
  if (ctx.policy.profile !== "controller")
3723
3534
  return errorResult(
@@ -87,8 +87,8 @@ export function parseMcpToolset(value: unknown, profile: string): McpToolset {
87
87
  if (profile !== 'controller') return 'full';
88
88
  const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
89
89
  if (!normalized) return 'advanced';
90
- if (normalized === 'core' || normalized === 'advanced' || normalized === 'full') return normalized;
91
- throw new Error(`invalid MCP toolset "${String(value)}" (expected: core, advanced, or full)`);
90
+ if (normalized === 'facade' || normalized === 'core' || normalized === 'advanced' || normalized === 'full') return normalized;
91
+ throw new Error(`invalid MCP toolset "${String(value)}" (expected: facade, core, advanced, or full)`);
92
92
  }
93
93
 
94
94
  function repositoryForExplicitPath(path: string, controllerHome: string): RepositoryRecord | undefined {
@@ -295,13 +295,24 @@ export function repositoryScopedToolArgs(
295
295
  input: Record<string, unknown>,
296
296
  repository: RepositoryRecord,
297
297
  ): Record<string, unknown> {
298
- // Preserve resolved repository/checkout identity for every downstream call.
299
- // Deleting these fields forced re-resolution and allowed silent route drift.
298
+ // Preserve resolved repository identity for every downstream call. Exact
299
+ // claimed Work calls are the one exception for checkout injection: when the
300
+ // caller omitted checkout_id, repository tools must resolve the Work-owned
301
+ // checkout after validating the authenticated principal instead of inheriting
302
+ // the repository's currently active checkout.
303
+ const workOwnedCheckoutResolution = [
304
+ 'repository_command_preview',
305
+ 'repository_command_execute',
306
+ 'repository_safe_patch_apply',
307
+ ].includes(name)
308
+ && typeof input.work_id === 'string'
309
+ && input.work_id.trim().length > 0
310
+ && !(typeof input.checkout_id === 'string' && input.checkout_id.trim().length > 0);
300
311
  const args: Record<string, unknown> = {
301
312
  ...input,
302
313
  repo_id: repository.repoId,
303
- checkout_id: repository.activeCheckoutId,
304
314
  };
315
+ if (!workOwnedCheckoutResolution) args.checkout_id = repository.activeCheckoutId;
305
316
 
306
317
  const github = repository.github;
307
318
  const githubRepo = github ? `${github.owner}/${github.repo}` : undefined;
@@ -1,5 +1,16 @@
1
1
  import { randomUUID } from 'crypto';
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from 'fs';
2
+ import {
3
+ chmodSync,
4
+ closeSync,
5
+ existsSync,
6
+ fsyncSync,
7
+ mkdirSync,
8
+ openSync,
9
+ readFileSync,
10
+ renameSync,
11
+ rmSync,
12
+ writeFileSync,
13
+ } from 'fs';
3
14
  import { dirname } from 'path';
4
15
  import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients.js';
5
16
  import { InvalidGrantError, InvalidTokenError } from '@modelcontextprotocol/sdk/server/auth/errors.js';
@@ -25,6 +36,20 @@ function issueToken(): string {
25
36
  return randomUUID();
26
37
  }
27
38
 
39
+ function fsyncFile(path: string): void {
40
+ const fd = openSync(path, 'r');
41
+ try { fsyncSync(fd); } finally { closeSync(fd); }
42
+ }
43
+
44
+ function fsyncDirectory(path: string): void {
45
+ try {
46
+ const fd = openSync(path, 'r');
47
+ try { fsyncSync(fd); } finally { closeSync(fd); }
48
+ } catch {
49
+ // Directory fsync is unavailable on some supported platforms/filesystems.
50
+ }
51
+ }
52
+
28
53
  export class McpOAuthTokenStore implements OAuthRegisteredClientsStore {
29
54
  private accessTokens = new Map<string, AuthInfo>();
30
55
  private refreshTokens = new Map<string, string>();
@@ -33,8 +58,12 @@ export class McpOAuthTokenStore implements OAuthRegisteredClientsStore {
33
58
  constructor(private readonly path: string, private readonly fallbackPaths: string[] = []) {}
34
59
 
35
60
  load(): void {
61
+ this.accessTokens.clear();
62
+ this.refreshTokens.clear();
63
+ this.clients.clear();
36
64
  let loaded = false;
37
65
  let mergedFallback = false;
66
+ let primaryCorrupt = false;
38
67
  for (const [index, path] of [this.path, ...this.fallbackPaths].entries()) {
39
68
  if (!existsSync(path)) continue;
40
69
  try {
@@ -59,22 +88,35 @@ export class McpOAuthTokenStore implements OAuthRegisteredClientsStore {
59
88
  }
60
89
  loaded = true;
61
90
  } catch (_error) {
62
- // Corrupt local auth state should not prevent starting the server.
91
+ if (index === 0) primaryCorrupt = true;
63
92
  }
64
93
  }
65
- if (loaded && mergedFallback) this.flush();
94
+ if (primaryCorrupt && !loaded) {
95
+ throw new Error('MCP_OAUTH_STORE_CORRUPT: primary OAuth state is unreadable and no valid fallback is available');
96
+ }
97
+ if (loaded && (mergedFallback || primaryCorrupt)) this.flush();
66
98
  }
67
99
 
68
100
  flush(): void {
69
- mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
70
- chmodSync(dirname(this.path), 0o700);
101
+ const directory = dirname(this.path);
102
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
103
+ chmodSync(directory, 0o700);
71
104
  const data: TokenData = {
72
105
  accessTokens: Object.fromEntries(this.accessTokens),
73
106
  refreshTokens: Object.fromEntries(this.refreshTokens),
74
107
  clients: Object.fromEntries(this.clients),
75
108
  };
76
- writeFileSync(this.path, `${JSON.stringify(data, null, 2)}\n`, { encoding: 'utf-8', mode: 0o600 });
77
- chmodSync(this.path, 0o600);
109
+ const temporary = `${this.path}.${process.pid}.${randomUUID()}.tmp`;
110
+ try {
111
+ writeFileSync(temporary, `${JSON.stringify(data, null, 2)}\n`, { encoding: 'utf-8', mode: 0o600 });
112
+ chmodSync(temporary, 0o600);
113
+ fsyncFile(temporary);
114
+ renameSync(temporary, this.path);
115
+ chmodSync(this.path, 0o600);
116
+ fsyncDirectory(directory);
117
+ } finally {
118
+ rmSync(temporary, { force: true });
119
+ }
78
120
  }
79
121
 
80
122
  getClient(clientId: string): OAuthClientInformationFull | undefined {
@@ -127,10 +169,115 @@ export class McpOAuthTokenStore implements OAuthRegisteredClientsStore {
127
169
  }
128
170
  return undefined;
129
171
  }
172
+
173
+ setTokenPair(accessToken: string, info: AuthInfo, refreshToken: string): void {
174
+ this.accessTokens.set(accessToken, info);
175
+ this.refreshTokens.set(refreshToken, accessToken);
176
+ this.flush();
177
+ }
178
+
179
+ rotateRefreshToken(
180
+ previousRefreshToken: string,
181
+ nextRefreshToken: string,
182
+ accessToken: string,
183
+ info: AuthInfo,
184
+ ): void {
185
+ this.refreshTokens.delete(previousRefreshToken);
186
+ this.accessTokens.set(accessToken, info);
187
+ this.refreshTokens.set(nextRefreshToken, accessToken);
188
+ this.flush();
189
+ }
190
+
191
+ revokeToken(token: string): void {
192
+ const linkedAccessToken = this.refreshTokens.get(token);
193
+ if (linkedAccessToken) {
194
+ this.refreshTokens.delete(token);
195
+ this.accessTokens.delete(linkedAccessToken);
196
+ for (const [refreshToken, accessToken] of this.refreshTokens) {
197
+ if (accessToken === linkedAccessToken) this.refreshTokens.delete(refreshToken);
198
+ }
199
+ this.flush();
200
+ return;
201
+ }
202
+
203
+ this.accessTokens.delete(token);
204
+ for (const [refreshToken, accessToken] of this.refreshTokens) {
205
+ if (accessToken === token) this.refreshTokens.delete(refreshToken);
206
+ }
207
+ this.flush();
208
+ }
130
209
  }
131
210
 
132
- export function createMcpOAuthProvider(store: McpOAuthTokenStore): OAuthServerProvider {
133
- const authCodes = new Map<string, { challenge: string; clientId: string }>();
211
+ interface PendingAuthorizationCode {
212
+ challenge: string;
213
+ clientId: string;
214
+ issuedAt: number;
215
+ expiresAt: number;
216
+ }
217
+
218
+ export interface McpOAuthProviderOptions {
219
+ now?: () => number;
220
+ authorizationCodeTtlSeconds?: number;
221
+ maxPendingAuthorizationCodes?: number;
222
+ maxPendingAuthorizationCodesPerClient?: number;
223
+ }
224
+
225
+ const DEFAULT_AUTHORIZATION_CODE_TTL_SECONDS = 10 * 60;
226
+ const DEFAULT_MAX_PENDING_AUTHORIZATION_CODES = 512;
227
+ const DEFAULT_MAX_PENDING_AUTHORIZATION_CODES_PER_CLIENT = 32;
228
+
229
+ export function createMcpOAuthProvider(
230
+ store: McpOAuthTokenStore,
231
+ options: McpOAuthProviderOptions = {},
232
+ ): OAuthServerProvider {
233
+ const authCodes = new Map<string, PendingAuthorizationCode>();
234
+ const currentTime = options.now ?? nowSeconds;
235
+ const ttlSeconds = Math.max(30, Math.trunc(options.authorizationCodeTtlSeconds ?? DEFAULT_AUTHORIZATION_CODE_TTL_SECONDS));
236
+ const globalLimit = Math.max(8, Math.trunc(options.maxPendingAuthorizationCodes ?? DEFAULT_MAX_PENDING_AUTHORIZATION_CODES));
237
+ const clientLimit = Math.max(2, Math.min(globalLimit, Math.trunc(
238
+ options.maxPendingAuthorizationCodesPerClient ?? DEFAULT_MAX_PENDING_AUTHORIZATION_CODES_PER_CLIENT,
239
+ )));
240
+
241
+ const purgeExpiredCodes = (now = currentTime()): void => {
242
+ for (const [code, pending] of authCodes) {
243
+ if (pending.expiresAt <= now) authCodes.delete(code);
244
+ }
245
+ };
246
+
247
+ const evictOldest = (predicate: (entry: PendingAuthorizationCode) => boolean): boolean => {
248
+ let oldest: { code: string; issuedAt: number } | undefined;
249
+ for (const [code, pending] of authCodes) {
250
+ if (!predicate(pending)) continue;
251
+ if (!oldest || pending.issuedAt < oldest.issuedAt) oldest = { code, issuedAt: pending.issuedAt };
252
+ }
253
+ if (!oldest) return false;
254
+ authCodes.delete(oldest.code);
255
+ return true;
256
+ };
257
+
258
+ const reserveAuthorizationCodeCapacity = (clientId: string, now: number): void => {
259
+ purgeExpiredCodes(now);
260
+ while ([...authCodes.values()].filter((pending) => pending.clientId === clientId).length >= clientLimit) {
261
+ if (!evictOldest((pending) => pending.clientId === clientId)) break;
262
+ }
263
+ while (authCodes.size >= globalLimit) {
264
+ if (!evictOldest(() => true)) break;
265
+ }
266
+ };
267
+
268
+ const pendingAuthorizationCode = (
269
+ client: OAuthClientInformationFull,
270
+ authorizationCode: string,
271
+ ): PendingAuthorizationCode => {
272
+ const now = currentTime();
273
+ purgeExpiredCodes(now);
274
+ const stored = authCodes.get(authorizationCode);
275
+ if (!stored || stored.clientId !== client.client_id || stored.expiresAt <= now) {
276
+ if (stored?.expiresAt !== undefined && stored.expiresAt <= now) authCodes.delete(authorizationCode);
277
+ throw new InvalidGrantError('Invalid authorization code');
278
+ }
279
+ return stored;
280
+ };
134
281
 
135
282
  return {
136
283
  get clientsStore(): OAuthRegisteredClientsStore {
@@ -138,10 +285,14 @@ export function createMcpOAuthProvider(store: McpOAuthTokenStore): OAuthServerPr
138
285
  },
139
286
 
140
287
  async authorize(client: OAuthClientInformationFull, params: AuthorizationParams, res): Promise<void> {
288
+ const now = currentTime();
289
+ reserveAuthorizationCodeCapacity(client.client_id, now);
141
290
  const code = issueToken();
142
291
  authCodes.set(code, {
143
292
  challenge: params.codeChallenge,
144
293
  clientId: client.client_id,
294
+ issuedAt: now,
295
+ expiresAt: now + ttlSeconds,
145
296
  });
146
297
  const redirectUrl = new URL(params.redirectUri);
147
298
  redirectUrl.searchParams.set('code', code);
@@ -149,10 +300,8 @@ export function createMcpOAuthProvider(store: McpOAuthTokenStore): OAuthServerPr
149
300
  res.redirect(302, redirectUrl.toString());
150
301
  },
151
302
 
152
- async challengeForAuthorizationCode(_client: OAuthClientInformationFull, authorizationCode: string): Promise<string> {
153
- const stored = authCodes.get(authorizationCode);
154
- if (!stored) throw new InvalidGrantError('Invalid authorization code');
155
- return stored.challenge;
303
+ async challengeForAuthorizationCode(client: OAuthClientInformationFull, authorizationCode: string): Promise<string> {
304
+ return pendingAuthorizationCode(client, authorizationCode).challenge;
156
305
  },
157
306
 
158
307
  async exchangeAuthorizationCode(
@@ -160,22 +309,21 @@ export function createMcpOAuthProvider(store: McpOAuthTokenStore): OAuthServerPr
160
309
  authorizationCode: string,
161
310
  _codeVerifier?: string,
162
311
  ): Promise<OAuthTokens> {
163
- const stored = authCodes.get(authorizationCode);
164
- if (!stored || stored.clientId !== client.client_id) {
165
- throw new InvalidGrantError('Invalid authorization code');
166
- }
312
+ pendingAuthorizationCode(client, authorizationCode);
313
+ // Delete before issuing credentials. A failed downstream persistence is
314
+ // retried as a new OAuth flow rather than making an authorization code
315
+ // replayable after a partial token response.
167
316
  authCodes.delete(authorizationCode);
168
317
  const accessToken = issueToken();
169
318
  const refreshToken = issueToken();
170
319
  const expiresIn = 30 * 24 * 60 * 60;
171
320
  const expiresAt = nowSeconds() + expiresIn;
172
- store.setAccessToken(accessToken, {
321
+ store.setTokenPair(accessToken, {
173
322
  token: accessToken,
174
323
  clientId: client.client_id,
175
324
  scopes: ['forge'],
176
325
  expiresAt,
177
- });
178
- store.setRefreshToken(refreshToken, accessToken);
326
+ }, refreshToken);
179
327
  return {
180
328
  access_token: accessToken,
181
329
  token_type: 'Bearer',
@@ -191,11 +339,14 @@ export function createMcpOAuthProvider(store: McpOAuthTokenStore): OAuthServerPr
191
339
  if (!accessToken || !existing || existing.clientId !== client.client_id) {
192
340
  throw new InvalidGrantError('Invalid refresh token');
193
341
  }
194
- store.deleteRefreshToken(refreshToken);
195
342
  const nextRefreshToken = issueToken();
196
343
  const expiresIn = 30 * 24 * 60 * 60;
197
- store.setAccessToken(accessToken, { ...existing, expiresAt: nowSeconds() + expiresIn });
198
- store.setRefreshToken(nextRefreshToken, accessToken);
344
+ store.rotateRefreshToken(
345
+ refreshToken,
346
+ nextRefreshToken,
347
+ accessToken,
348
+ { ...existing, expiresAt: nowSeconds() + expiresIn },
349
+ );
199
350
  return {
200
351
  access_token: accessToken,
201
352
  token_type: 'Bearer',
@@ -218,9 +369,7 @@ export function createMcpOAuthProvider(store: McpOAuthTokenStore): OAuthServerPr
218
369
  },
219
370
 
220
371
  async revokeToken(_client: OAuthClientInformationFull, request: OAuthTokenRevocationRequest): Promise<void> {
221
- store.deleteAccessToken(request.token);
222
- const refreshToken = store.findRefreshTokenByAccessToken(request.token);
223
- if (refreshToken) store.deleteRefreshToken(refreshToken);
372
+ store.revokeToken(request.token);
224
373
  },
225
374
  };
226
375
  }
@@ -93,7 +93,13 @@ function nearestExistingPath(path: string): string | undefined {
93
93
  return current;
94
94
  }
95
95
 
96
- export function resolveMcpPath(repoRoot: string, inputPath: string, policy: McpPolicy, intent: McpPathIntent): McpPathDecision {
96
+ export function resolveMcpPath(
97
+ repoRoot: string,
98
+ inputPath: string,
99
+ policy: McpPolicy,
100
+ intent: McpPathIntent,
101
+ canonicalRepoRoot?: string,
102
+ ): McpPathDecision {
97
103
  const normalized = normalizeMcpRelativePath(inputPath);
98
104
  if (!normalized.ok || !normalized.relativePath) return normalized;
99
105
 
@@ -107,7 +113,7 @@ export function resolveMcpPath(repoRoot: string, inputPath: string, policy: McpP
107
113
  return { ok: false, relativePath, reason: `path is not allowed for ${intent}: ${relativePath}` };
108
114
  }
109
115
 
110
- const repoRealpath = realpathSync(repoRoot);
116
+ const repoRealpath = canonicalRepoRoot ?? realpathSync(repoRoot);
111
117
  const absolutePath = resolve(repoRealpath, relativePath);
112
118
  if (intent === 'read' && !existsSync(absolutePath)) {
113
119
  return { ok: false, relativePath, reason: `path does not exist: ${relativePath}` };