@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
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Executable goal-drift guard for the managed-check controller path.
5
+ *
6
+ * It deliberately performs useful rh_context work after run_check returns and
7
+ * before its one final dependency join. A future change that turns this into
8
+ * periodic process_wait polling, re-executes the check, or blocks the initial
9
+ * handle return fails the benchmark rather than only a local Process test.
10
+ */
11
+
12
+ import { execFileSync } from 'child_process';
13
+ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
14
+ import { tmpdir } from 'os';
15
+ import { join } from 'path';
16
+ import { performance } from 'perf_hooks';
17
+ import { createMcpToolContext } from '../src/cli/mcp/server';
18
+ import { registerRepository } from '../src/cli/repositories/registry';
19
+ import { callRuntimeTool } from '../src/runtime/gateway/mcp/runtime-tools';
20
+ import { callProcessTool } from '../src/runtime/gateway/mcp/process-tools';
21
+ import { routeDurableMcpCall } from '../src/runtime/gateway/mcp/router';
22
+ import { getProcessRecord } from '../src/runtime/execution/process-runtime/store';
23
+ import {
24
+ clearGitIdentityCacheForTest,
25
+ clearGitSnapshotCacheForTest,
26
+ gitSnapshotPerformanceSnapshot,
27
+ } from '../src/cli/repository/inspector';
28
+
29
+ const CHECK_DURATION_MS = 1_200;
30
+ const MAX_INITIAL_HANDLE_LATENCY_MS = 700;
31
+ const MAX_TERMINAL_OBSERVATION_LATENCY_MS = 1_000;
32
+ const root = mkdtempSync(join(tmpdir(), 'forge-background-check-overlap-'));
33
+ const controllerHome = join(root, 'controller');
34
+ const repoRoot = join(root, 'repo');
35
+
36
+ const behavioralContract = {
37
+ originalUserFacingGoal: 'A long focused check must run in the background while Forge continues useful independent work, then synchronize once at the true dependency boundary.',
38
+ invariants: {
39
+ backgroundCheck: 'long_check_returns_a_managed_handle_promptly',
40
+ noPollingWhileUsefulWorkExists: 'zero_process_wait_calls_before_independent_work_finishes',
41
+ dependencyDrivenJoin: 'one_final_process_wait_after_the_dependency_boundary',
42
+ exactlyOnce: 'one_physical_check_execution',
43
+ resultCorrectness: 'terminal_result_matches_the_returned_process_id',
44
+ noAdditionalOrchestration: 'no_execution_job_created_for_the_fast_check_route',
45
+ sourceRetrievalStaysFocused: 'ordinary_rh_context_search_skips_execution_preflight',
46
+ summaryStaysCheap: 'rh_status_summary_avoids_detail_diagnostics',
47
+ },
48
+ antiGoals: [
49
+ 'periodic_process_wait_polling',
50
+ 'synchronous_wait_before_independent_work',
51
+ 'duplicate_check_execution',
52
+ 'source_retrieval_building_unrequested_check_readiness',
53
+ 'summary_status_triggering_detail_diagnostics',
54
+ ],
55
+ validation: 'Runs a real managed check, performs rh_context work before the sole join, and separately asserts the rh_status summary hot path.',
56
+ };
57
+
58
+ function percentile(values: number[], ratio: number): number {
59
+ const sorted = [...values].sort((left, right) => left - right);
60
+ return Math.round((sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * ratio))] ?? 0) * 100) / 100;
61
+ }
62
+
63
+ try {
64
+ mkdirSync(join(repoRoot, 'src'), { recursive: true });
65
+ mkdirSync(join(repoRoot, '.forge'), { recursive: true });
66
+ execFileSync('git', ['init', '-q', '-b', 'main'], { cwd: repoRoot });
67
+ execFileSync('git', ['config', 'user.email', 'benchmark@example.test'], { cwd: repoRoot });
68
+ execFileSync('git', ['config', 'user.name', 'Forge Benchmark'], { cwd: repoRoot });
69
+ writeFileSync(join(repoRoot, 'src', 'fixture.ts'), 'export const backgroundFixture = true;\n');
70
+ const executionMarker = join(root, 'physical-check-executions.txt');
71
+ writeFileSync(join(repoRoot, '.forge', 'checks.json'), JSON.stringify({
72
+ version: 1,
73
+ checks: {
74
+ background: {
75
+ description: 'Synthetic long check for controller overlap behavior.',
76
+ command: [
77
+ process.execPath,
78
+ '-e',
79
+ [
80
+ `require('fs').appendFileSync(${JSON.stringify(executionMarker)}, '1');`,
81
+ `setTimeout(() => { process.stdout.write('BACKGROUND_CHECK_DONE\\n'); process.exit(0); }, ${CHECK_DURATION_MS});`,
82
+ ].join(' '),
83
+ ],
84
+ timeoutMs: 10_000,
85
+ },
86
+ },
87
+ }, null, 2));
88
+ execFileSync('git', ['add', '.'], { cwd: repoRoot });
89
+ execFileSync('git', ['commit', '-qm', 'fixture'], { cwd: repoRoot });
90
+
91
+ const repository = registerRepository({ path: repoRoot, controllerHome, displayName: 'background-check-overlap' });
92
+ const ctx = createMcpToolContext({ controllerHome, profile: 'controller', repo: repoRoot });
93
+
94
+ const startedAt = performance.now();
95
+ const started = await routeDurableMcpCall(ctx, 'run_check', {
96
+ repo_id: repository.repoId,
97
+ check_id: 'background',
98
+ interactive_wait_ms: 0,
99
+ });
100
+ const initialHandleLatencyMs = performance.now() - startedAt;
101
+ const startPayload = started?.structuredContent as Record<string, unknown> | undefined;
102
+ const processId = typeof startPayload?.processId === 'string' ? startPayload.processId : '';
103
+
104
+ const independentStartedAt = performance.now();
105
+ const independent = await callRuntimeTool(ctx, 'rh_context', {
106
+ repo_id: repository.repoId,
107
+ operation: 'search',
108
+ query: 'backgroundFixture',
109
+ known_paths: ['src/fixture.ts'],
110
+ structural_context: 'off',
111
+ });
112
+ const independentOperationLatencyMs = performance.now() - independentStartedAt;
113
+ const independentPayload = independent?.structuredContent as { data?: Record<string, unknown> } | undefined;
114
+ const independentData = independentPayload?.data;
115
+ const independentRetrievalPolicy = independentData?.retrievalPolicy as Record<string, unknown> | undefined;
116
+ const sourceRetrievalSkippedExecutionPreflight = independentData?.executionReadiness === undefined
117
+ && independentData?.registeredChecks === undefined
118
+ && independentRetrievalPolicy?.executionReadiness === 'requested_check_ids_only';
119
+ const processWaitCallsBeforeDependencyBoundary = 0;
120
+
121
+ const joinStartedAtMs = Date.now();
122
+ const joined = processId
123
+ ? await callProcessTool(ctx, 'process_wait', {
124
+ repo_id: repository.repoId,
125
+ process_id: processId,
126
+ timeout_ms: 10_000,
127
+ })
128
+ : undefined;
129
+ const resultObservedAtMs = Date.now();
130
+ const finalSynchronizationCount = processId ? 1 : 0;
131
+ const joinedPayload = joined?.structuredContent as { process?: { processId?: string; completed?: boolean; ok?: boolean } } | undefined;
132
+ const record = processId ? getProcessRecord(controllerHome, repository.repoId, processId) : undefined;
133
+ const finishedAtMs = record?.finishedAt ? Date.parse(record.finishedAt) : NaN;
134
+ const completionToObservationLatencyMs = Number.isFinite(finishedAtMs)
135
+ ? Math.max(0, resultObservedAtMs - finishedAtMs)
136
+ : undefined;
137
+ const physicalExecutionCount = readFileSync(executionMarker, 'utf8').length;
138
+
139
+ // Summary is a separate hot path: it must answer readiness without falling
140
+ // into the full Git status/diff-stat or maintenance/detail projection.
141
+ clearGitIdentityCacheForTest();
142
+ clearGitSnapshotCacheForTest();
143
+ const statusSummaryDurations: number[] = [];
144
+ let summaryPayload: Record<string, unknown> | undefined;
145
+ for (let iteration = 0; iteration < 7; iteration += 1) {
146
+ const statusStartedAt = performance.now();
147
+ const status = await callRuntimeTool(ctx, 'rh_status', { repo_id: repository.repoId, detail_level: 'summary' });
148
+ statusSummaryDurations.push(performance.now() - statusStartedAt);
149
+ summaryPayload = status?.structuredContent as Record<string, unknown> | undefined;
150
+ }
151
+ // Snapshot this before the intentional detail call below: detail is allowed
152
+ // to build the full Git projection, while summary is not.
153
+ const summaryGitSnapshot = gitSnapshotPerformanceSnapshot();
154
+ const detailStartedAt = performance.now();
155
+ const detail = await callRuntimeTool(ctx, 'rh_status', { repo_id: repository.repoId, detail_level: 'detail' });
156
+ const detailElapsedMs = performance.now() - detailStartedAt;
157
+ const detailPayload = detail?.structuredContent as Record<string, unknown> | undefined;
158
+ const detailMeta = detailPayload?.responseMeta as Record<string, unknown> | undefined;
159
+ const summaryData = summaryPayload?.data as Record<string, unknown> | undefined;
160
+ const summaryRepositoryState = summaryData?.repositoryState as Record<string, unknown> | undefined;
161
+ const summaryUsesCompactPayload = Boolean(summaryData)
162
+ && summaryData?.access === undefined
163
+ && summaryData?.pendingHandoffCount === undefined
164
+ && summaryData?.activeWorkCount === undefined
165
+ && summaryData?.activeProcessCount === undefined
166
+ && summaryRepositoryState?.status === undefined
167
+ && summaryRepositoryState?.diffStat === undefined;
168
+
169
+ const assertions = {
170
+ initialManagedHandle: Boolean(processId) && startPayload?.path === 'process_managed' && initialHandleLatencyMs <= MAX_INITIAL_HANDLE_LATENCY_MS,
171
+ independentUsefulOperation: independent?.isError !== true,
172
+ sourceRetrievalSkipsExecutionPreflight: sourceRetrievalSkippedExecutionPreflight,
173
+ noPollingBeforeDependencyBoundary: processWaitCallsBeforeDependencyBoundary === 0,
174
+ oneDependencyJoin: finalSynchronizationCount === 1,
175
+ exactlyOnce: physicalExecutionCount === 1,
176
+ terminalResultCorrect: joinedPayload?.process?.processId === processId && joinedPayload?.process?.completed === true && joinedPayload?.process?.ok === true,
177
+ promptResultObservation: completionToObservationLatencyMs === undefined || completionToObservationLatencyMs <= MAX_TERMINAL_OBSERVATION_LATENCY_MS,
178
+ statusSummaryUsesCompactProjection: Boolean(summaryPayload?.data)
179
+ && summaryGitSnapshot.refreshes === 0
180
+ && summaryGitSnapshot.subprocesses === 0
181
+ && summaryUsesCompactPayload,
182
+ };
183
+ const output = {
184
+ schemaVersion: 1,
185
+ behavioralContract,
186
+ check: {
187
+ configuredDurationMs: CHECK_DURATION_MS,
188
+ initialManagedHandleLatencyMs: Number(initialHandleLatencyMs.toFixed(2)),
189
+ processWaitCallsBeforeDependencyBoundary,
190
+ independentOperation: {
191
+ tool: 'rh_context.search',
192
+ latencyMs: Number(independentOperationLatencyMs.toFixed(2)),
193
+ completed: independent?.isError !== true,
194
+ executionPreflight: sourceRetrievalSkippedExecutionPreflight ? 'not_requested' : 'unexpected',
195
+ },
196
+ finalSynchronizationCount,
197
+ finalJoinDurationMs: resultObservedAtMs - joinStartedAtMs,
198
+ physicalExecutionCount,
199
+ terminalCompletionToObservationLatencyMs: completionToObservationLatencyMs,
200
+ processId,
201
+ },
202
+ status: {
203
+ summaryP50Ms: percentile(statusSummaryDurations, 0.5),
204
+ summaryP95Ms: percentile(statusSummaryDurations, 0.95),
205
+ detailElapsedMs: Number(detailElapsedMs.toFixed(2)),
206
+ detailServerDurationMs: detailMeta?.serverDurationMs,
207
+ detailPhaseTimingsMs: detailMeta?.phaseTimingsMs,
208
+ fullGitSnapshot: summaryGitSnapshot,
209
+ summaryOmissions: {
210
+ maintenance: true,
211
+ plugins: true,
212
+ workState: true,
213
+ accessPolicy: true,
214
+ gitStatusAndDiffStat: true,
215
+ },
216
+ },
217
+ thresholds: {
218
+ initialManagedHandleLatencyMs: MAX_INITIAL_HANDLE_LATENCY_MS,
219
+ terminalCompletionToObservationLatencyMs: MAX_TERMINAL_OBSERVATION_LATENCY_MS,
220
+ },
221
+ assertions,
222
+ passed: Object.values(assertions).every(Boolean),
223
+ };
224
+ process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
225
+ if (!output.passed) process.exitCode = 1;
226
+ } finally {
227
+ rmSync(root, { recursive: true, force: true });
228
+ }
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ BUN_BIN="${BUN_BIN:-bun}"
6
+ LABEL="${FORGE_MCP_BENCH_LABEL:-host-runtime-direct}"
7
+ PORT="${FORGE_MCP_BENCH_PORT:-18765}"
8
+ ITERATIONS="${FORGE_MCP_BENCH_ITERATIONS:-50}"
9
+ CONNECT_ITERATIONS="${FORGE_MCP_BENCH_CONNECT_ITERATIONS:-30}"
10
+
11
+ for value in "$PORT" "$ITERATIONS" "$CONNECT_ITERATIONS"; do
12
+ if ! [[ "$value" =~ ^[0-9]+$ ]] || [[ "$value" == "0" ]]; then
13
+ echo "benchmark:mcp-host requires positive integer port/iteration values" >&2
14
+ exit 2
15
+ fi
16
+ done
17
+ if (( PORT < 1024 || PORT > 65535 )); then
18
+ echo "FORGE_MCP_BENCH_PORT must be between 1024 and 65535" >&2
19
+ exit 2
20
+ fi
21
+
22
+ TMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/forge-mcp-host-bench.XXXXXX")"
23
+ CONTROLLER_HOME="$TMP_ROOT/controller-home"
24
+ REGISTER_JSON="$TMP_ROOT/repository.json"
25
+ INSTALL_JSON="$TMP_ROOT/runtime-install.json"
26
+ READY_JSON="$TMP_ROOT/runtime-ready.json"
27
+ STEADY_JSON="$TMP_ROOT/steady.json"
28
+ CONNECT_JSON="$TMP_ROOT/connect.json"
29
+ RUNTIME_PID=""
30
+
31
+ cleanup() {
32
+ if [[ -n "$RUNTIME_PID" ]] && kill -0 "$RUNTIME_PID" 2>/dev/null; then
33
+ kill "$RUNTIME_PID" 2>/dev/null || true
34
+ for _ in $(seq 1 20); do
35
+ kill -0 "$RUNTIME_PID" 2>/dev/null || break
36
+ sleep 0.1
37
+ done
38
+ kill -9 "$RUNTIME_PID" 2>/dev/null || true
39
+ fi
40
+ for _ in $(seq 1 5); do
41
+ rm -rf "$TMP_ROOT" 2>/dev/null && return 0
42
+ sleep 0.1
43
+ done
44
+ rm -rf "$TMP_ROOT" 2>/dev/null || true
45
+ }
46
+ trap cleanup EXIT INT TERM
47
+
48
+ TOKEN="$(node -e "process.stdout.write(require('crypto').randomBytes(32).toString('hex'))")"
49
+ export FORGE_MCP_TOKEN="$TOKEN"
50
+ export FORGE_MCP_BENCH_TOKEN="$TOKEN"
51
+
52
+ cd "$ROOT"
53
+ "$BUN_BIN" bin/forge.mjs repo register "$ROOT" \
54
+ --controller-home "$CONTROLLER_HOME" \
55
+ --json > "$REGISTER_JSON"
56
+ REPO_ID="$(node -e "const x=require(process.argv[1]); const id=x?.repository?.repoId; if(!id) process.exit(2); process.stdout.write(id)" "$REGISTER_JSON")"
57
+
58
+ "$BUN_BIN" bin/forge.mjs runtime service install-package \
59
+ --controller-home "$CONTROLLER_HOME" \
60
+ --host 127.0.0.1 \
61
+ --port "$PORT" \
62
+ --portable > "$INSTALL_JSON"
63
+ RUNTIME_PID="$(node -e "const x=require(process.argv[1]); const pid=x?.pid; if(!Number.isInteger(pid)||pid<=0) process.exit(2); process.stdout.write(String(pid))" "$INSTALL_JSON")"
64
+
65
+ curl --fail --silent --show-error \
66
+ --retry 30 --retry-delay 1 --retry-connrefused \
67
+ "http://127.0.0.1:${PORT}/ready" > "$READY_JSON"
68
+
69
+ "$BUN_BIN" run benchmark:mcp-transport -- \
70
+ --endpoint "http://127.0.0.1:${PORT}/mcp" \
71
+ --label "$LABEL" \
72
+ --repo-id "$REPO_ID" \
73
+ --tool rh_status \
74
+ --iterations "$ITERATIONS" \
75
+ --warmup 3 \
76
+ --timing-log "$CONTROLLER_HOME/audit/mcp-timings.jsonl" > "$STEADY_JSON"
77
+
78
+ "$BUN_BIN" run benchmark:mcp-transport -- \
79
+ --endpoint "http://127.0.0.1:${PORT}/mcp" \
80
+ --label "$LABEL" \
81
+ --repo-id "$REPO_ID" \
82
+ --tool rh_status \
83
+ --iterations "$CONNECT_ITERATIONS" \
84
+ --warmup 0 \
85
+ --include-connect \
86
+ --timing-log "$CONTROLLER_HOME/audit/mcp-timings.jsonl" > "$CONNECT_JSON"
87
+
88
+ node - "$STEADY_JSON" "$CONNECT_JSON" "$READY_JSON" <<'NODE'
89
+ const fs = require('fs');
90
+ const os = require('os');
91
+ const path = require('path');
92
+ const [steadyPath, connectPath, readyPath] = process.argv.slice(2);
93
+ const steady = JSON.parse(fs.readFileSync(steadyPath, 'utf8'));
94
+ const connect = JSON.parse(fs.readFileSync(connectPath, 'utf8'));
95
+ const ready = JSON.parse(fs.readFileSync(readyPath, 'utf8'));
96
+ const report = {
97
+ schemaVersion: 1,
98
+ generatedAt: new Date().toISOString(),
99
+ host: {
100
+ platform: process.platform,
101
+ arch: process.arch,
102
+ cpuCount: os.cpus().length,
103
+ totalMemoryBytes: os.totalmem(),
104
+ },
105
+ ready,
106
+ steady,
107
+ connectInclusive: connect,
108
+ };
109
+ const text = `${JSON.stringify(report, null, 2)}\n`;
110
+ const output = process.env.FORGE_MCP_BENCH_OUTPUT?.trim();
111
+ if (output) {
112
+ const target = path.resolve(output);
113
+ fs.mkdirSync(path.dirname(target), { recursive: true });
114
+ fs.writeFileSync(target, text, 'utf8');
115
+ }
116
+ process.stdout.write(text);
117
+ NODE
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env bun
2
+ import { readFileSync } from 'fs';
3
+ import { performance } from 'perf_hooks';
4
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
5
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
6
+ import { readMcpServiceBearerToken } from '../src/cli/mcp/auth';
7
+ import { resolveRepoPreferredControllerHome } from '../src/cli/repositories/controller-home';
8
+ import {
9
+ aggregateMcpTransportLatencySamples,
10
+ extractForgeServerDuration,
11
+ normalizeMcpTransportLatencySample,
12
+ type McpTransportLatencySample,
13
+ } from '../src/runtime/diagnostics/mcp-transport-benchmark';
14
+
15
+ function option(name: string): string | undefined {
16
+ const index = process.argv.indexOf(name);
17
+ return index >= 0 ? process.argv[index + 1] : undefined;
18
+ }
19
+
20
+ function integerAtLeast(name: string, fallback: number, minimum: number): number {
21
+ const raw = option(name);
22
+ if (!raw) return fallback;
23
+ const value = Number(raw);
24
+ if (!Number.isInteger(value) || value < minimum) {
25
+ const requirement = minimum === 0 ? 'a non-negative integer' : `an integer >= ${minimum}`;
26
+ throw new Error(`${name} must be ${requirement}`);
27
+ }
28
+ return value;
29
+ }
30
+
31
+ function positiveInteger(name: string, fallback: number): number {
32
+ return integerAtLeast(name, fallback, 1);
33
+ }
34
+
35
+ function readSamples(path: string): McpTransportLatencySample[] {
36
+ const text = readFileSync(path, 'utf8').trim();
37
+ if (!text) return [];
38
+ if (text.startsWith('[')) {
39
+ const values = JSON.parse(text);
40
+ if (!Array.isArray(values)) throw new Error('--input JSON must be an array or JSONL');
41
+ return values.map(normalizeMcpTransportLatencySample);
42
+ }
43
+ return text.split(/\r?\n/).filter(Boolean).map((line) => normalizeMcpTransportLatencySample(JSON.parse(line)));
44
+ }
45
+
46
+ export function timingLedgerServerDuration(
47
+ path: string | undefined,
48
+ tool: string,
49
+ meta: Record<string, unknown>,
50
+ ): number | undefined {
51
+ if (!path) return undefined;
52
+ const traceId = typeof meta.traceId === 'string' ? meta.traceId : undefined;
53
+ const requestId = typeof meta.requestId === 'string' ? meta.requestId : undefined;
54
+ if (!traceId && !requestId) return undefined;
55
+ try {
56
+ const text = readFileSync(path, 'utf8').trim();
57
+ if (!text) return undefined;
58
+ const lines = text.split(/\r?\n/).filter(Boolean);
59
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
60
+ let value: Record<string, unknown>;
61
+ try {
62
+ value = JSON.parse(lines[index]!) as Record<string, unknown>;
63
+ } catch {
64
+ continue;
65
+ }
66
+ if (value.tool !== tool) continue;
67
+ if (traceId && value.traceId !== traceId) continue;
68
+ if (requestId && value.requestId !== requestId) continue;
69
+ const duration = value.totalToolDurationMs;
70
+ if (typeof duration === 'number' && Number.isFinite(duration) && duration >= 0) return duration;
71
+ }
72
+ } catch {
73
+ return undefined;
74
+ }
75
+ return undefined;
76
+ }
77
+
78
+ async function probe(): Promise<McpTransportLatencySample[]> {
79
+ const endpointRaw = option('--endpoint');
80
+ if (!endpointRaw) throw new Error('--endpoint is required unless --input is used');
81
+ const endpoint = new URL(endpointRaw);
82
+ const label = option('--label')?.trim() || endpoint.hostname;
83
+ const tool = option('--tool')?.trim() || 'rh_status';
84
+ const iterations = positiveInteger('--iterations', 30);
85
+ const warmup = integerAtLeast('--warmup', 3, 0);
86
+ const timeoutMs = positiveInteger('--timeout-ms', 120_000);
87
+ const timingLog = option('--timing-log')?.trim();
88
+ const includeConnect = process.argv.includes('--include-connect');
89
+ const timingScope = includeConnect ? 'connect_and_tool_call' as const : 'tool_call' as const;
90
+ const tokenEnv = option('--token-env')?.trim() || 'FORGE_MCP_BENCH_TOKEN';
91
+ const localServiceAuth = process.argv.includes('--local-service-auth');
92
+ const token = process.env[tokenEnv]?.trim()
93
+ || (localServiceAuth
94
+ ? readMcpServiceBearerToken(resolveRepoPreferredControllerHome(process.cwd()))?.trim()
95
+ : undefined);
96
+ if (!token) {
97
+ throw new Error(localServiceAuth
98
+ ? 'MCP_BENCH_LOCAL_SERVICE_TOKEN_UNAVAILABLE: no local Forge service bearer credential was found'
99
+ : `${tokenEnv} is not set; use --local-service-auth on the Forge host or keep benchmark bearer credentials in the environment, never CLI args`);
100
+ }
101
+ const argumentsRaw = option('--arguments');
102
+ const args = argumentsRaw ? JSON.parse(argumentsRaw) as Record<string, unknown> : {};
103
+ const repoId = option('--repo-id')?.trim();
104
+ if (repoId && args.repo_id === undefined) args.repo_id = repoId;
105
+
106
+ const createClient = () => {
107
+ const transport = new StreamableHTTPClientTransport(endpoint, {
108
+ requestInit: { headers: { Authorization: `Bearer ${token}` } },
109
+ });
110
+ const client = new Client({ name: 'forge-mcp-transport-benchmark', version: '1.0.0' });
111
+ return { client, transport };
112
+ };
113
+ const steadyState = includeConnect ? undefined : createClient();
114
+ const call = async (): Promise<{ response: unknown; totalMs: number }> => {
115
+ const connection = steadyState ?? createClient();
116
+ const started = performance.now();
117
+ try {
118
+ if (includeConnect) await connection.client.connect(connection.transport);
119
+ const response = await connection.client.callTool({ name: tool, arguments: args }, undefined, { timeout: timeoutMs });
120
+ return { response, totalMs: performance.now() - started };
121
+ } finally {
122
+ if (includeConnect) await connection.client.close().catch(() => undefined);
123
+ }
124
+ };
125
+ const samples: McpTransportLatencySample[] = [];
126
+ try {
127
+ if (steadyState) await steadyState.client.connect(steadyState.transport);
128
+ for (let index = 0; index < warmup; index += 1) await call();
129
+ for (let index = 0; index < iterations; index += 1) {
130
+ const { response, totalMs } = await call();
131
+ const structured = response && typeof response === 'object' && !Array.isArray(response)
132
+ ? (response as Record<string, unknown>).structuredContent
133
+ : undefined;
134
+ const responseMeta = structured && typeof structured === 'object' && !Array.isArray(structured)
135
+ ? (structured as Record<string, unknown>).responseMeta
136
+ : undefined;
137
+ const meta = responseMeta && typeof responseMeta === 'object' && !Array.isArray(responseMeta)
138
+ ? responseMeta as Record<string, unknown>
139
+ : {};
140
+ const serverDurationMs = extractForgeServerDuration(response)
141
+ ?? timingLedgerServerDuration(timingLog, tool, meta);
142
+ if (serverDurationMs === undefined) {
143
+ throw new Error('MCP_SERVER_DURATION_MISSING: target must return Forge responseMeta.serverDurationMs or expose a matching --timing-log ledger entry');
144
+ }
145
+ samples.push({
146
+ label,
147
+ tool,
148
+ timingScope,
149
+ clientTotalMs: totalMs,
150
+ serverDurationMs,
151
+ ...(typeof meta.traceId === 'string' ? { traceId: meta.traceId } : {}),
152
+ ...(typeof meta.requestId === 'string' ? { requestId: meta.requestId } : {}),
153
+ observedAt: new Date().toISOString(),
154
+ });
155
+ }
156
+ } finally {
157
+ if (steadyState) await steadyState.client.close().catch(() => undefined);
158
+ }
159
+ return samples;
160
+ }
161
+
162
+ if (import.meta.main) {
163
+ const input = option('--input');
164
+ const samples = input ? readSamples(input) : await probe();
165
+ const report = aggregateMcpTransportLatencySamples(samples);
166
+ console.log(JSON.stringify({
167
+ generatedAt: new Date().toISOString(),
168
+ ...report,
169
+ }, null, 2));
170
+ }
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { execFileSync, spawn } from 'child_process';
4
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'fs';
5
+ import { tmpdir } from 'os';
6
+ import { join } from 'path';
7
+ import { performance } from 'perf_hooks';
8
+ import { buildControllerContextPackAsync } from '../src/cli/controller/context-pack';
9
+ import { getMcpPolicy } from '../src/cli/mcp/policy';
10
+ import { closeCodeGraphReadProviderSessions } from '../src/runtime/context/codegraph-read-provider';
11
+ import { clearAllSessionCachesForTest } from '../src/cli/repository/session-cache';
12
+ import { startLightweightRepositoryCommand } from '../src/runtime/execution/process-runtime/lightweight-managed';
13
+ import { registerRepository } from '../src/cli/repositories/registry';
14
+
15
+ function percentile(values: number[], ratio: number): number {
16
+ const sorted = [...values].sort((a, b) => a - b);
17
+ return Math.round((sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * ratio))] ?? 0) * 100) / 100;
18
+ }
19
+ async function rawGitStatus(root: string): Promise<number> {
20
+ const started = performance.now();
21
+ await new Promise<void>((resolve, reject) => {
22
+ const child = spawn('git', ['status', '--short'], { cwd: root, stdio: 'ignore' });
23
+ child.on('error', reject); child.on('close', () => resolve());
24
+ });
25
+ return performance.now() - started;
26
+ }
27
+
28
+ const iterationsIndex = process.argv.indexOf('--iterations');
29
+ const iterations = Math.max(2, Number(iterationsIndex >= 0 ? process.argv[iterationsIndex + 1] : 7) || 7);
30
+ const structuralIndex = process.argv.indexOf('--structural');
31
+ const structuralContext = process.argv[structuralIndex + 1] === 'off' ? 'off' as const : 'auto' as const;
32
+ const sandboxRoot = mkdtempSync(join(tmpdir(), 'forge-quality-harness-benchmark-'));
33
+ const root = join(sandboxRoot, 'repo');
34
+ const controllerHome = join(sandboxRoot, 'controller');
35
+ try {
36
+ mkdirSync(root, { recursive: true });
37
+ execFileSync('git', ['init', '-q', '-b', 'main'], { cwd: root });
38
+ execFileSync('git', ['config', 'user.email', 'benchmark@example.test'], { cwd: root });
39
+ execFileSync('git', ['config', 'user.name', 'Forge Benchmark'], { cwd: root });
40
+ mkdirSync(join(root, 'src'), { recursive: true });
41
+ for (let index = 0; index < 120; index += 1) {
42
+ writeFileSync(join(root, 'src', `module-${index}.ts`), `export function symbol${index}(value: number) {\n return value + ${index};\n}\n`);
43
+ }
44
+ writeFileSync(join(root, 'src', 'target.ts'), 'export function benchmarkTarget(value: number) {\n const next = value + 1;\n return next;\n}\n');
45
+ writeFileSync(join(root, 'src', 'consumer.ts'), "import { benchmarkTarget } from './target';\nexport function consumeBenchmarkTarget(value: number) {\n return benchmarkTarget(value) * 2;\n}\n");
46
+ execFileSync('git', ['add', '.'], { cwd: root }); execFileSync('git', ['commit', '-qm', 'fixture'], { cwd: root });
47
+ const repository = registerRepository({ path: root, controllerHome, displayName: 'quality-harness-benchmark' });
48
+
49
+ const contextCold: number[] = [];
50
+ const contextHot: number[] = [];
51
+ const structuralProcess: number[] = [];
52
+ const structuralOpen: number[] = [];
53
+ const structuralQuery: number[] = [];
54
+ let hotCacheEvidence: unknown;
55
+ let contextAccuracyEvidence: { selected: string[]; required: string[]; recall: number; precision: number } | undefined;
56
+ const requiredContextPaths = ['src/target.ts', 'src/consumer.ts'];
57
+ for (let index = 0; index < iterations; index += 1) {
58
+ clearAllSessionCachesForTest();
59
+ const session = { sessionId: `benchmark-${index}`, repoId: repository.repoId, checkoutId: repository.activeCheckoutId };
60
+ let started = performance.now();
61
+ const cold = await buildControllerContextPackAsync(root, getMcpPolicy('controller'), { description: 'benchmarkTarget implementation and impact', searchTerms: ['benchmarkTarget'], knownPaths: ['src/target.ts'], structuralContext, session });
62
+ contextCold.push(performance.now() - started);
63
+ const structuralTimings = cold.structuralContext?.timingsMs;
64
+ if (typeof structuralTimings?.process === 'number') structuralProcess.push(structuralTimings.process);
65
+ if (typeof structuralTimings?.open === 'number') structuralOpen.push(structuralTimings.open);
66
+ if (typeof structuralTimings?.query === 'number') structuralQuery.push(structuralTimings.query);
67
+ started = performance.now();
68
+ const hot = await buildControllerContextPackAsync(root, getMcpPolicy('controller'), { description: 'benchmarkTarget implementation and impact', searchTerms: ['benchmarkTarget'], knownPaths: ['src/target.ts'], structuralContext, session });
69
+ contextHot.push(performance.now() - started);
70
+ hotCacheEvidence = hot.cache;
71
+ const selected = [...new Set(hot.files.map((file) => file.path))].sort();
72
+ const requiredHits = requiredContextPaths.filter((path) => selected.includes(path)).length;
73
+ contextAccuracyEvidence = {
74
+ selected,
75
+ required: requiredContextPaths,
76
+ recall: requiredHits / requiredContextPaths.length,
77
+ precision: selected.length === 0 ? 0 : requiredHits / selected.length,
78
+ };
79
+ }
80
+
81
+ const rawChild: number[] = [];
82
+ const harnessTotal: number[] = [];
83
+ const preSpawnHarness: number[] = [];
84
+ for (let index = 0; index < iterations; index += 1) {
85
+ rawChild.push(await rawGitStatus(root));
86
+ const started = performance.now();
87
+ const run = await startLightweightRepositoryCommand({
88
+ controllerHome, repository, interactiveWaitMs: 5_000, timeoutMs: 30_000,
89
+ execution: {
90
+ command: ['git', 'status', '--short'],
91
+ authorizationDecision: { decision: 'allow', source: 'full_access', reason: 'benchmark fixture' },
92
+ },
93
+ });
94
+ harnessTotal.push(performance.now() - started);
95
+ if (run.metrics.preSpawnHarnessMs !== undefined) preSpawnHarness.push(run.metrics.preSpawnHarnessMs);
96
+ }
97
+
98
+ const steadyContextCold = contextCold.slice(1);
99
+ const output = {
100
+ schemaVersion: 1,
101
+ iterations,
102
+ context: {
103
+ structuralContext,
104
+ coldP50Ms: percentile(contextCold, 0.5), coldP95Ms: percentile(contextCold, 0.95),
105
+ startupColdMs: Math.round((contextCold[0] ?? 0) * 100) / 100,
106
+ steadyColdP50Ms: percentile(steadyContextCold, 0.5),
107
+ steadyColdP95Ms: percentile(steadyContextCold, 0.95),
108
+ coldSamplesMs: contextCold.map((value) => Math.round(value * 100) / 100),
109
+ structuralProcessP50Ms: percentile(structuralProcess, 0.5),
110
+ structuralProcessP95Ms: percentile(structuralProcess, 0.95),
111
+ structuralOpenP50Ms: percentile(structuralOpen, 0.5),
112
+ structuralQueryP50Ms: percentile(structuralQuery, 0.5),
113
+ hotP50Ms: percentile(contextHot, 0.5), hotP95Ms: percentile(contextHot, 0.95),
114
+ hotToColdRatio: Math.round((percentile(contextHot, 0.5) / Math.max(0.01, percentile(contextCold, 0.5))) * 1_000) / 1_000,
115
+ cacheEvidence: hotCacheEvidence,
116
+ accuracy: contextAccuracyEvidence,
117
+ },
118
+ command: {
119
+ rawChildP50Ms: percentile(rawChild, 0.5), lightweightTotalP50Ms: percentile(harnessTotal, 0.5),
120
+ preSpawnHarnessP50Ms: percentile(preSpawnHarness, 0.5),
121
+ preSpawnHarnessP95Ms: percentile(preSpawnHarness, 0.95),
122
+ durableProcessWrites: 0, leaseOperations: 0,
123
+ },
124
+ thresholds: {
125
+ hotContextToColdP50MaxRatio: 0.9,
126
+ contextRecallMin: 1,
127
+ contextPrecisionMin: 1,
128
+ lightweightPreSpawnHarnessP95MaxMs: 750,
129
+ durableProcessWritesMax: 0,
130
+ leaseOperationsMax: 0,
131
+ },
132
+ };
133
+ const assertions = {
134
+ hotContextReuse: output.context.hotToColdRatio <= output.thresholds.hotContextToColdP50MaxRatio,
135
+ completeRequiredContext: (output.context.accuracy?.recall ?? 0) >= output.thresholds.contextRecallMin,
136
+ boundedContextNoise: (output.context.accuracy?.precision ?? 0) >= output.thresholds.contextPrecisionMin,
137
+ boundedPreSpawnHarness: output.command.preSpawnHarnessP95Ms <= output.thresholds.lightweightPreSpawnHarnessP95MaxMs,
138
+ zeroDurableWrites: output.command.durableProcessWrites <= output.thresholds.durableProcessWritesMax,
139
+ zeroLeaseOperations: output.command.leaseOperations <= output.thresholds.leaseOperationsMax,
140
+ };
141
+ process.stdout.write(`${JSON.stringify({ ...output, assertions, passed: Object.values(assertions).every(Boolean) }, null, 2)}\n`);
142
+ if (!Object.values(assertions).every(Boolean)) process.exitCode = 1;
143
+ } finally {
144
+ await closeCodeGraphReadProviderSessions();
145
+ rmSync(sandboxRoot, { recursive: true, force: true });
146
+ }