@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
@@ -49,6 +49,7 @@ export interface SessionCacheSnapshot {
49
49
  metrics: SessionCacheMetrics;
50
50
  fileCount: number;
51
51
  searchCount: number;
52
+ structuralCount: number;
52
53
  }
53
54
 
54
55
  function shaText(value: string): string {
@@ -101,9 +102,9 @@ function rangeKey(path: string, start: number, end: number): string {
101
102
  export class RepositorySessionCache {
102
103
  private identity: SessionIdentity;
103
104
  private readonly repoRoot: string;
104
- private readonly fileSha = new Map<string, string>();
105
105
  private readonly ranges = new Map<string, FileRangeCacheEntry>();
106
106
  private readonly searches = new Map<string, SearchCacheEntry>();
107
+ private readonly structural = new Map<string, { at: number; value: unknown }>();
107
108
  private readonly gitSnapshots = new Map<string, { at: number; value: unknown }>();
108
109
  private readonly checks = new Map<string, unknown>();
109
110
  private runtimeGeneration?: string;
@@ -141,6 +142,7 @@ export class RepositorySessionCache {
141
142
  metrics: this.getMetrics(),
142
143
  fileCount: this.ranges.size,
143
144
  searchCount: this.searches.size,
145
+ structuralCount: this.structural.size,
144
146
  };
145
147
  }
146
148
 
@@ -148,6 +150,10 @@ export class RepositorySessionCache {
148
150
  return { ...this.identity };
149
151
  }
150
152
 
153
+ repositoryRootPath(): string {
154
+ return this.repoRoot;
155
+ }
156
+
151
157
  refreshIdentity(next: SessionIdentity): void {
152
158
  const prev = this.identity;
153
159
  this.identity = next;
@@ -178,16 +184,12 @@ export class RepositorySessionCache {
178
184
  }
179
185
 
180
186
  getFileSha(relativePath: string): string | null {
181
- const cached = this.fileSha.get(relativePath);
182
- if (cached) {
183
- this.metrics.cacheHit += 1;
184
- return cached;
185
- }
186
- const absolute = join(this.repoRoot, relativePath);
187
- const sha = fileShaOf(absolute);
188
- if (sha) this.fileSha.set(relativePath, sha);
189
- this.metrics.cacheMiss += 1;
190
- return sha;
187
+ // Range reuse is correctness-sensitive: Git identity is intentionally sampled
188
+ // with a short TTL, so it can lag an edit by a few seconds. Hash the current
189
+ // file bytes before serving a cached range instead of trusting an older
190
+ // session-level SHA. Warm filesystem hashing is sub-millisecond for normal
191
+ // source files and still preserves the expensive range/materialization reuse.
192
+ return fileShaOf(join(this.repoRoot, relativePath));
191
193
  }
192
194
 
193
195
  /**
@@ -213,15 +215,16 @@ export class RepositorySessionCache {
213
215
  entry.path === relativePath
214
216
  && entry.fileSha === sha
215
217
  && entry.startLine <= startLine
216
- && entry.endLine >= endLine
218
+ && (entry.endLine >= endLine || entry.endLine >= entry.totalLines)
217
219
  ) {
218
220
  this.metrics.cacheHit += 1;
219
221
  this.metrics.bytesAvoided += Math.max(0, entry.bytes);
222
+ const resolvedEndLine = Math.min(endLine, entry.totalLines);
220
223
  return {
221
224
  ...entry,
222
225
  startLine,
223
- endLine,
224
- content: sliceNumberedContent(entry.content, entry.startLine, startLine, endLine),
226
+ endLine: resolvedEndLine,
227
+ content: sliceNumberedContent(entry.content, entry.startLine, startLine, resolvedEndLine),
225
228
  };
226
229
  }
227
230
  }
@@ -230,7 +233,6 @@ export class RepositorySessionCache {
230
233
  }
231
234
 
232
235
  putRange(entry: FileRangeCacheEntry): void {
233
- this.fileSha.set(entry.path, entry.fileSha);
234
236
  this.ranges.set(rangeKey(entry.path, entry.startLine, entry.endLine), entry);
235
237
  }
236
238
 
@@ -251,6 +253,33 @@ export class RepositorySessionCache {
251
253
  this.searches.set(key, entry);
252
254
  }
253
255
 
256
+ getStructural(key: string, ttlMs = 30_000): unknown | null {
257
+ const scopedKey = `${this.identity.head}|${this.identity.workingTreeFingerprint}|${key}`;
258
+ const hit = this.structural.get(scopedKey);
259
+ if (hit && Date.now() - hit.at <= ttlMs) {
260
+ this.metrics.cacheHit += 1;
261
+ this.metrics.scanAvoided += 1;
262
+ return hit.value;
263
+ }
264
+ if (hit) this.structural.delete(scopedKey);
265
+ this.metrics.cacheMiss += 1;
266
+ return null;
267
+ }
268
+
269
+ /** Inspect structural reuse eligibility without changing request metrics. */
270
+ peekStructural(key: string, ttlMs = 30_000): unknown | null {
271
+ const scopedKey = `${this.identity.head}|${this.identity.workingTreeFingerprint}|${key}`;
272
+ const hit = this.structural.get(scopedKey);
273
+ if (hit && Date.now() - hit.at <= ttlMs) return hit.value;
274
+ if (hit) this.structural.delete(scopedKey);
275
+ return null;
276
+ }
277
+
278
+ putStructural(key: string, value: unknown): void {
279
+ const scopedKey = `${this.identity.head}|${this.identity.workingTreeFingerprint}|${key}`;
280
+ this.structural.set(scopedKey, { at: Date.now(), value });
281
+ }
282
+
254
283
  getGitSnapshot(): unknown | null {
255
284
  const key = `${this.identity.head}|${this.identity.workingTreeFingerprint}`;
256
285
  const hit = this.gitSnapshots.get(key);
@@ -284,7 +313,6 @@ export class RepositorySessionCache {
284
313
 
285
314
  /** Precise invalidation when one file changes. */
286
315
  invalidateFile(relativePath: string): void {
287
- this.fileSha.delete(relativePath);
288
316
  for (const key of [...this.ranges.keys()]) {
289
317
  if (key.startsWith(`${relativePath}:`)) this.ranges.delete(key);
290
318
  }
@@ -294,9 +322,9 @@ export class RepositorySessionCache {
294
322
  }
295
323
 
296
324
  invalidateCheckout(): void {
297
- this.fileSha.clear();
298
325
  this.ranges.clear();
299
326
  this.searches.clear();
327
+ this.structural.clear();
300
328
  this.gitSnapshots.clear();
301
329
  this.checks.clear();
302
330
  this.metrics.invalidations += 1;
@@ -304,6 +332,7 @@ export class RepositorySessionCache {
304
332
 
305
333
  invalidateHeadRelated(): void {
306
334
  this.searches.clear();
335
+ this.structural.clear();
307
336
  this.gitSnapshots.clear();
308
337
  this.checks.clear();
309
338
  this.metrics.invalidations += 1;
@@ -311,6 +340,7 @@ export class RepositorySessionCache {
311
340
 
312
341
  invalidateWorkingTree(): void {
313
342
  this.searches.clear();
343
+ this.structural.clear();
314
344
  this.gitSnapshots.clear();
315
345
  this.checks.clear();
316
346
  this.metrics.invalidations += 1;
@@ -406,6 +436,17 @@ export function pruneSessionCaches(nowMs = Date.now()): number {
406
436
  }
407
437
 
408
438
  /** Called by MCP transport close/eviction so read caches do not outlive a session. */
439
+ export function invalidateSessionCachesForRepository(repoRoot: string): number {
440
+ const root = resolve(repoRoot);
441
+ let invalidated = 0;
442
+ for (const cache of globalSessions.values()) {
443
+ if (cache.repositoryRootPath() !== root) continue;
444
+ cache.invalidateWorkingTree();
445
+ invalidated += 1;
446
+ }
447
+ return invalidated;
448
+ }
449
+
409
450
  export function clearSessionCachesForSession(sessionId: string): number {
410
451
  let removed = 0;
411
452
  for (const [key, cache] of globalSessions) {
@@ -1,11 +1,8 @@
1
1
  import { buildProgram } from './index';
2
- import { buildRepositoryCommand } from './commands/repository';
3
2
  import { CLI_VERSION } from './commands/status';
4
3
 
5
4
  export function buildV81Program() {
6
- const program = buildProgram();
7
- program.addCommand(buildRepositoryCommand());
8
- return program;
5
+ return buildProgram();
9
6
  }
10
7
 
11
8
  export async function runV81Cli(argv: string[] = process.argv): Promise<void> {
@@ -234,6 +234,26 @@ export function atomicWriteFile(
234
234
  });
235
235
  }
236
236
 
237
+ /** Durable replacement for transaction metadata and rollback restores. Unlike
238
+ * atomicWriteFile, this intentionally does not create another recovery backup. */
239
+ function replaceFileDurably(repoRoot: string, path: string, content: string, mode?: number): void {
240
+ const target = resolveInsideRepo(repoRoot, path);
241
+ if (!target.ok || !target.path) throw new Error(target.error ?? "invalid path");
242
+ const parentError = ensureParent(repoRoot, path);
243
+ if (parentError) throw new Error(parentError);
244
+ const targetPath = target.path;
245
+ withTargetLock(targetPath, () => {
246
+ const tempPath = resolve(dirname(targetPath), `.${basename(targetPath)}.${process.pid}.${Date.now()}.tmp`);
247
+ try {
248
+ writeFileDurably(tempPath, content, mode);
249
+ renameSync(tempPath, targetPath);
250
+ fsyncDirectory(dirname(targetPath));
251
+ } finally {
252
+ rmSync(tempPath, { force: true });
253
+ }
254
+ });
255
+ }
256
+
237
257
  export function applyMkdirOperation(repoRoot: string, operation: MkdirOperation, dryRun = false): ApplyOperationResult {
238
258
  const target = resolveInsideRepo(repoRoot, operation.path);
239
259
  if (!target.ok || !target.path) return failure(operation, target.error ?? "invalid path");
@@ -311,12 +331,17 @@ export function applyAppendManagedBlockOperation(
311
331
  }
312
332
  }
313
333
 
314
- function writeTransactionManifest(plan: AdoptionPlan, transactionDir: string, results: readonly ApplyOperationResult[]): string {
334
+ function writeTransactionManifest(
335
+ plan: AdoptionPlan,
336
+ transactionDir: string,
337
+ createdAt: string,
338
+ results: readonly ApplyOperationResult[],
339
+ ): string {
315
340
  const manifestPath = `${transactionDir}/manifest.json`;
316
341
  const manifest: FsTransactionManifest = {
317
342
  protocol: 1,
318
343
  command: "adopt",
319
- createdAt: new Date().toISOString(),
344
+ createdAt,
320
345
  repoRoot: plan.repoRoot,
321
346
  mode: plan.mode,
322
347
  operations: results.map((result) => {
@@ -336,7 +361,7 @@ function writeTransactionManifest(plan: AdoptionPlan, transactionDir: string, re
336
361
  command: `forge adopt rollback --transaction ${manifestPath}`,
337
362
  },
338
363
  };
339
- atomicWriteFile(plan.repoRoot, manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o600 });
364
+ replaceFileDurably(plan.repoRoot, manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 0o600);
340
365
  return manifestPath;
341
366
  }
342
367
 
@@ -350,21 +375,64 @@ export function applyAdoptionPlan(plan: AdoptionPlan, dryRun = false): ApplyAdop
350
375
  };
351
376
  }
352
377
 
353
- const transactionDir = dryRun ? undefined : transactionDirFor();
354
- const results = plan.operations.map((operation) => {
355
- switch (operation.kind) {
356
- case "mkdir":
357
- return applyMkdirOperation(plan.repoRoot, operation, dryRun);
358
- case "writeFile":
359
- return applyWriteFileOperation(plan.repoRoot, operation, dryRun, transactionDir);
360
- case "appendManagedBlock":
361
- return applyAppendManagedBlockOperation(plan.repoRoot, operation, dryRun, transactionDir);
362
- default:
363
- return failure(operation, `unsupported operation kind: ${operation.kind}`);
378
+ if (dryRun) {
379
+ const results = plan.operations.map((operation) => {
380
+ switch (operation.kind) {
381
+ case "mkdir": return applyMkdirOperation(plan.repoRoot, operation, true);
382
+ case "writeFile": return applyWriteFileOperation(plan.repoRoot, operation, true);
383
+ case "appendManagedBlock": return applyAppendManagedBlockOperation(plan.repoRoot, operation, true);
384
+ default: return failure(operation, `unsupported operation kind: ${operation.kind}`);
385
+ }
386
+ });
387
+ return { ok: results.every((result) => result.status !== "failed"), dryRun: true, results };
388
+ }
389
+
390
+ const transactionDir = transactionDirFor();
391
+ const createdAt = new Date().toISOString();
392
+ const results: ApplyOperationResult[] = [];
393
+ let transactionManifestPath: string;
394
+ try {
395
+ // Establish recovery evidence before the first adoption operation mutates
396
+ // repository state. Each completed/failed operation is checkpointed below.
397
+ transactionManifestPath = writeTransactionManifest(plan, transactionDir, createdAt, results);
398
+ } catch (error) {
399
+ return {
400
+ ok: false,
401
+ dryRun: false,
402
+ results: [{
403
+ id: "transaction-manifest",
404
+ kind: "runCheck",
405
+ status: "failed",
406
+ error: `failed to initialize transaction manifest: ${errorMessage(error)}`,
407
+ }],
408
+ };
409
+ }
410
+
411
+ for (const operation of plan.operations) {
412
+ const result = (() => {
413
+ switch (operation.kind) {
414
+ case "mkdir": return applyMkdirOperation(plan.repoRoot, operation, false);
415
+ case "writeFile": return applyWriteFileOperation(plan.repoRoot, operation, false, transactionDir);
416
+ case "appendManagedBlock": return applyAppendManagedBlockOperation(plan.repoRoot, operation, false, transactionDir);
417
+ default: return failure(operation, `unsupported operation kind: ${operation.kind}`);
418
+ }
419
+ })();
420
+ results.push(result);
421
+ try {
422
+ writeTransactionManifest(plan, transactionDir, createdAt, results);
423
+ } catch (error) {
424
+ results.push({
425
+ id: "transaction-manifest",
426
+ kind: "runCheck",
427
+ status: "failed",
428
+ error: `failed to checkpoint transaction manifest: ${errorMessage(error)}`,
429
+ });
430
+ break;
364
431
  }
365
- });
432
+ if (result.status === "failed") break;
433
+ }
434
+
366
435
  const ok = results.every((result) => result.status !== "failed");
367
- const transactionManifestPath = !dryRun && ok && transactionDir ? writeTransactionManifest(plan, transactionDir, results) : undefined;
368
436
 
369
437
  return {
370
438
  ok,
@@ -413,7 +481,7 @@ function rollbackFileOperation(repoRoot: string, operation: FsTransactionManifes
413
481
  return rollbackFailed(operation, "restore_backup", "current file hash differs from transaction content hash");
414
482
  }
415
483
  }
416
- atomicWriteFile(repoRoot, operation.path, readFileSync(backup.path, "utf-8"));
484
+ replaceFileDurably(repoRoot, operation.path, readFileSync(backup.path, "utf-8"));
417
485
  return { id: operation.id, kind: operation.kind, path: operation.path, status: "rolled_back", action: "restore_backup" };
418
486
  } catch (error) {
419
487
  return rollbackFailed(operation, "restore_backup", errorMessage(error));
@@ -1,7 +1,8 @@
1
- import { spawnSync } from 'child_process';
1
+ import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from 'child_process';
2
2
  import { accessSync, constants, existsSync } from 'fs';
3
3
  import { createRequire } from 'module';
4
4
  import { dirname, join, resolve } from 'path';
5
+ import { createInterface, type Interface as ReadlineInterface } from 'readline';
5
6
  import { fileURLToPath } from 'url';
6
7
 
7
8
  const require = createRequire(import.meta.url);
@@ -10,6 +11,7 @@ const MAX_REQUEST_BYTES = 64 * 1024;
10
11
  const MAX_RESPONSE_BYTES = 1024 * 1024;
11
12
  const MAX_STDERR_CHARS = 8_000;
12
13
  const DEFAULT_TIMEOUT_MS = 15_000;
14
+ const PERSISTENT_SIDECAR_IDLE_MS = 30_000;
13
15
  const ALLOWED_OPERATIONS = new Set<CodeGraphReadOperation>(['status', 'search', 'context', 'impact', 'file_dependencies']);
14
16
 
15
17
  export type CodeGraphReadOperation = 'status' | 'search' | 'context' | 'impact' | 'file_dependencies';
@@ -132,7 +134,8 @@ export function resolveCodeGraphBundledRuntime(options: { runtimeRoot?: string }
132
134
  } {
133
135
  const target = `${process.platform}-${process.arch}`;
134
136
  const platformPackage = `@colbymchenry/codegraph-${target}`;
135
- const runtimeRoot = options.runtimeRoot ?? dirname(process.execPath);
137
+ const configuredReleaseRoot = process.env.FORGE_RELEASE_PATH?.trim();
138
+ const runtimeRoot = options.runtimeRoot ?? (configuredReleaseRoot ? resolve(configuredReleaseRoot) : dirname(process.execPath));
136
139
  const releaseNode = join(runtimeRoot, 'codegraph-node');
137
140
  const releaseSidecar = join(runtimeRoot, 'codegraph-sidecar.cjs');
138
141
  const releaseLibrary = join(runtimeRoot, 'codegraph-lib', 'dist', 'index.js');
@@ -345,3 +348,231 @@ export function queryCodeGraphReadProvider(
345
348
  },
346
349
  };
347
350
  }
351
+
352
+ interface PersistentCodeGraphSession {
353
+ repoRoot: string;
354
+ runtimeKey: string;
355
+ child: ChildProcessWithoutNullStreams;
356
+ reader: ReadlineInterface;
357
+ lines: AsyncIterator<string>;
358
+ stderr: string;
359
+ closed: boolean;
360
+ idleTimer?: NodeJS.Timeout;
361
+ queue: Promise<void>;
362
+ }
363
+
364
+ const persistentSessions = new Map<string, PersistentCodeGraphSession>();
365
+ let persistentSessionSpawns = 0;
366
+ let persistentSessionReuses = 0;
367
+ let persistentSessionRequests = 0;
368
+
369
+ function persistentRuntimeKey(runtime: ReturnType<typeof resolveCodeGraphBundledRuntime>): string {
370
+ return `${runtime.nodeExecutable}\u0000${runtime.sidecarPath}\u0000${runtime.libraryPath}`;
371
+ }
372
+
373
+ function closePersistentSessionNow(session: PersistentCodeGraphSession): void {
374
+ if (session.closed) return;
375
+ session.closed = true;
376
+ if (session.idleTimer) clearTimeout(session.idleTimer);
377
+ if (persistentSessions.get(session.repoRoot) === session) persistentSessions.delete(session.repoRoot);
378
+ try { session.reader.close(); } catch {}
379
+ try { session.child.stdin.end(); } catch {}
380
+ try { session.child.kill(); } catch {}
381
+ }
382
+
383
+ function schedulePersistentSessionIdle(session: PersistentCodeGraphSession): void {
384
+ if (session.idleTimer) clearTimeout(session.idleTimer);
385
+ const timer = setTimeout(() => closePersistentSessionNow(session), PERSISTENT_SIDECAR_IDLE_MS);
386
+ timer.unref();
387
+ session.idleTimer = timer;
388
+ }
389
+
390
+ function createPersistentSession(
391
+ repoRoot: string,
392
+ runtime: ReturnType<typeof resolveCodeGraphBundledRuntime>,
393
+ ): PersistentCodeGraphSession {
394
+ const child = spawn(runtime.nodeExecutable, [runtime.sidecarPath], {
395
+ cwd: repoRoot,
396
+ env: { ...minimalEnvironment(runtime), FORGE_CODEGRAPH_PERSISTENT: '1' },
397
+ stdio: ['pipe', 'pipe', 'pipe'],
398
+ windowsHide: true,
399
+ shell: false,
400
+ });
401
+ child.unref();
402
+ const reader = createInterface({ input: child.stdout, crlfDelay: Infinity });
403
+ const session: PersistentCodeGraphSession = {
404
+ repoRoot,
405
+ runtimeKey: persistentRuntimeKey(runtime),
406
+ child,
407
+ reader,
408
+ lines: reader[Symbol.asyncIterator](),
409
+ stderr: '',
410
+ closed: false,
411
+ queue: Promise.resolve(),
412
+ };
413
+ child.stderr.on('data', (chunk: Buffer) => {
414
+ if (session.stderr.length >= MAX_STDERR_CHARS) return;
415
+ session.stderr += chunk.toString('utf8').slice(0, MAX_STDERR_CHARS - session.stderr.length);
416
+ });
417
+ child.on('error', () => closePersistentSessionNow(session));
418
+ child.on('close', () => {
419
+ session.closed = true;
420
+ if (session.idleTimer) clearTimeout(session.idleTimer);
421
+ if (persistentSessions.get(repoRoot) === session) persistentSessions.delete(repoRoot);
422
+ });
423
+ persistentSessions.set(repoRoot, session);
424
+ persistentSessionSpawns += 1;
425
+ return session;
426
+ }
427
+
428
+ function getPersistentSession(
429
+ repoRoot: string,
430
+ runtime: ReturnType<typeof resolveCodeGraphBundledRuntime>,
431
+ ): PersistentCodeGraphSession {
432
+ const key = persistentRuntimeKey(runtime);
433
+ const current = persistentSessions.get(repoRoot);
434
+ if (current && !current.closed && current.runtimeKey === key) {
435
+ if (current.idleTimer) clearTimeout(current.idleTimer);
436
+ current.idleTimer = undefined;
437
+ persistentSessionReuses += 1;
438
+ return current;
439
+ }
440
+ if (current) closePersistentSessionNow(current);
441
+ return createPersistentSession(repoRoot, runtime);
442
+ }
443
+
444
+ async function serializedPersistentRequest<T>(session: PersistentCodeGraphSession, operation: () => Promise<T>): Promise<T> {
445
+ const previous = session.queue;
446
+ let release!: () => void;
447
+ session.queue = new Promise<void>((resolveQueue) => { release = resolveQueue; });
448
+ await previous.catch(() => undefined);
449
+ try {
450
+ return await operation();
451
+ } finally {
452
+ release();
453
+ }
454
+ }
455
+
456
+ async function nextPersistentLine(session: PersistentCodeGraphSession, timeoutMs: number): Promise<string> {
457
+ let timer: NodeJS.Timeout | undefined;
458
+ try {
459
+ const timeout = new Promise<never>((_, reject) => {
460
+ timer = setTimeout(() => reject(new Error('CODEGRAPH_TIMEOUT')), timeoutMs);
461
+ timer.unref();
462
+ });
463
+ const next = await Promise.race([session.lines.next(), timeout]);
464
+ if (next.done || typeof next.value !== 'string') {
465
+ throw new Error(session.stderr || 'CODEGRAPH_SIDECAR_CLOSED');
466
+ }
467
+ return next.value;
468
+ } finally {
469
+ if (timer) clearTimeout(timer);
470
+ }
471
+ }
472
+
473
+ export async function closeCodeGraphReadProviderSessions(): Promise<void> {
474
+ const sessions = [...persistentSessions.values()];
475
+ persistentSessions.clear();
476
+ for (const session of sessions) closePersistentSessionNow(session);
477
+ }
478
+
479
+ export async function clearCodeGraphReadProviderSessionsForTest(): Promise<void> {
480
+ await closeCodeGraphReadProviderSessions();
481
+ persistentSessionSpawns = 0;
482
+ persistentSessionReuses = 0;
483
+ persistentSessionRequests = 0;
484
+ }
485
+
486
+ export function codeGraphReadProviderSessionSnapshotForTest(): {
487
+ active: number;
488
+ spawns: number;
489
+ reuses: number;
490
+ requests: number;
491
+ } {
492
+ return {
493
+ active: persistentSessions.size,
494
+ spawns: persistentSessionSpawns,
495
+ reuses: persistentSessionReuses,
496
+ requests: persistentSessionRequests,
497
+ };
498
+ }
499
+
500
+ /** Async sidecar path used by the first broad Context Plane fan-in. */
501
+ export async function queryCodeGraphReadProviderAsync(
502
+ repoRoot: string,
503
+ request: CodeGraphReadRequest,
504
+ options: { timeoutMs?: number } = {},
505
+ ): Promise<CodeGraphReadProviderResponse> {
506
+ const startedAt = performance.now();
507
+ if (!ALLOWED_OPERATIONS.has(request.operation)) {
508
+ return errorResponse(request.operation, 'CODEGRAPH_OPERATION_NOT_ALLOWED', `Unsupported read operation: ${String(request.operation)}`, performance.now() - startedAt);
509
+ }
510
+ let runtime: ReturnType<typeof resolveCodeGraphBundledRuntime>;
511
+ try { runtime = resolveCodeGraphBundledRuntime(); }
512
+ catch (error) {
513
+ const message = error instanceof Error ? error.message : String(error);
514
+ return errorResponse(request.operation, message.split(':', 1)[0] || 'CODEGRAPH_RUNTIME_UNAVAILABLE', message, performance.now() - startedAt);
515
+ }
516
+ const resolvedRepoRoot = resolve(repoRoot);
517
+ const input = JSON.stringify({
518
+ schemaVersion: SCHEMA_VERSION,
519
+ operation: request.operation,
520
+ projectRoot: resolvedRepoRoot,
521
+ ...(typeof request.query === 'string' ? { query: request.query.slice(0, 2_000) } : {}),
522
+ ...(typeof request.nodeId === 'string' ? { nodeId: request.nodeId.slice(0, 1_000) } : {}),
523
+ ...(typeof request.filePath === 'string' ? { filePath: request.filePath.slice(0, 2_000) } : {}),
524
+ limit: boundedInteger(request.limit, 12, 1, 40),
525
+ maxNodes: boundedInteger(request.maxNodes, 40, 1, 80),
526
+ maxDepth: boundedInteger(request.maxDepth, 2, 1, 5),
527
+ });
528
+ if (Buffer.byteLength(input) > MAX_REQUEST_BYTES) {
529
+ return errorResponse(request.operation, 'CODEGRAPH_REQUEST_TOO_LARGE', 'CodeGraph request exceeded the bounded input limit.', performance.now() - startedAt);
530
+ }
531
+
532
+ let session = getPersistentSession(resolvedRepoRoot, runtime);
533
+ return await serializedPersistentRequest(session, async () => {
534
+ if (session.closed) session = getPersistentSession(resolvedRepoRoot, runtime);
535
+ const processStartedAt = performance.now();
536
+ persistentSessionRequests += 1;
537
+ try {
538
+ await new Promise<void>((resolveWrite, rejectWrite) => {
539
+ session.child.stdin.write(`${input}\n`, (error) => error ? rejectWrite(error) : resolveWrite());
540
+ });
541
+ const raw = await nextPersistentLine(
542
+ session,
543
+ boundedInteger(options.timeoutMs, DEFAULT_TIMEOUT_MS, 1_000, 60_000),
544
+ );
545
+ if (Buffer.byteLength(raw) > MAX_RESPONSE_BYTES) {
546
+ closePersistentSessionNow(session);
547
+ return errorResponse(request.operation, 'CODEGRAPH_RESPONSE_TOO_LARGE', 'CodeGraph response exceeded the bounded output limit.', performance.now() - startedAt);
548
+ }
549
+ const parsed = parseSidecarResponse(raw);
550
+ if (!parsed.ok) {
551
+ schedulePersistentSessionIdle(session);
552
+ return errorResponse(request.operation, parsed.error.code, parsed.error.message, performance.now() - startedAt);
553
+ }
554
+ const metadata = filterGitIgnoredCodeGraphChanges(resolvedRepoRoot, parsed.metadata);
555
+ schedulePersistentSessionIdle(session);
556
+ return {
557
+ schemaVersion: 1,
558
+ provider: 'codegraph',
559
+ operation: request.operation,
560
+ ok: metadata.initialized,
561
+ status: providerStatus(metadata),
562
+ metadata,
563
+ result: parsed.result,
564
+ timingsMs: {
565
+ total: Math.round((performance.now() - startedAt) * 100) / 100,
566
+ process: Math.round((performance.now() - processStartedAt) * 100) / 100,
567
+ ...(typeof parsed.timingsMs?.open === 'number' ? { open: parsed.timingsMs.open } : {}),
568
+ ...(typeof parsed.timingsMs?.query === 'number' ? { query: parsed.timingsMs.query } : {}),
569
+ },
570
+ };
571
+ } catch (error) {
572
+ closePersistentSessionNow(session);
573
+ const message = error instanceof Error ? error.message : String(error);
574
+ const code = message === 'CODEGRAPH_TIMEOUT' ? 'CODEGRAPH_TIMEOUT' : 'CODEGRAPH_SIDECAR_FAILED';
575
+ return errorResponse(request.operation, code, message, performance.now() - startedAt);
576
+ }
577
+ });
578
+ }