@moretea-labs/forge 1.6.0 → 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 (396) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +26 -0
  3. package/CLAUDE.md +2 -0
  4. package/README.md +2 -2
  5. package/README.zh-CN.md +1 -1
  6. package/SKILL.md +2 -2
  7. package/THIRD_PARTY_NOTICES.md +1 -1
  8. package/agents/openai.yaml +1 -1
  9. package/assets/hooks/hook-input.sh +11 -8
  10. package/assets/hooks/lib/session-state.sh +10 -7
  11. package/assets/hooks/lib/workflow-state.sh +13 -177
  12. package/assets/hooks/post-tool-observer.sh +8 -2
  13. package/assets/hooks/prompt-guard.sh +5 -81
  14. package/assets/hooks/session-start-context.sh +56 -1
  15. package/assets/partials/05-workflow.partial.md +1 -1
  16. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  17. package/assets/plugin-registry.v1.json +1 -1
  18. package/assets/reference-configs/document-generation.md +1 -1
  19. package/assets/reference-configs/harness-overview.md +1 -1
  20. package/assets/reference-configs/sprint-contracts.md +3 -3
  21. package/assets/skill-version.json +2 -2
  22. package/assets/templates/contract.template.md +2 -7
  23. package/assets/templates/helpers/capture-plan.sh +2 -2
  24. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  25. package/assets/templates/helpers/contract-worktree.sh +1 -21
  26. package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
  27. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  28. package/assets/templates/helpers/new-plan.sh +1 -1
  29. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  30. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  31. package/assets/templates/helpers/verify-contract.sh +1 -111
  32. package/assets/templates/helpers/verify-sprint.sh +3 -180
  33. package/assets/templates/plan.template.md +1 -1
  34. package/assets/templates/review.template.md +9 -34
  35. package/assets/workflow-contract.v1.json +10 -19
  36. package/docs/README.md +10 -10
  37. package/docs/architecture/CURRENT.md +170 -0
  38. package/docs/architecture/EVOLUTION.md +42 -0
  39. package/docs/architecture/current/README.md +4 -156
  40. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  41. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  42. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  43. package/docs/architecture/history/README.md +7 -0
  44. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  45. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  46. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  47. package/docs/architecture/history.md +3 -34
  48. package/docs/architecture/index.md +15 -98
  49. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  50. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  51. package/docs/architecture/versions/1.6.md +38 -0
  52. package/docs/architecture/versions/1.7.md +41 -0
  53. package/docs/forge-chatgpt-browser-engine.md +2 -2
  54. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  55. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  56. package/docs/forge-github-issue-launcher.md +1 -1
  57. package/docs/forge-runtime-self-healing-loop.md +1 -1
  58. package/docs/operations/controller-browser-plugin.md +26 -6
  59. package/docs/operations/controller-performance-and-502.md +124 -0
  60. package/docs/operations/controller-reliability-runbook.md +1 -1
  61. package/docs/operations/features.md +2 -2
  62. package/docs/operations/github-repository.md +1 -1
  63. package/docs/operations/homebrew.md +2 -2
  64. package/docs/operations/mcp-tool-exposure.md +5 -3
  65. package/docs/wiki/Architecture.md +6 -6
  66. package/docs/wiki/Implementation.md +2 -2
  67. package/docs/wiki/Integrations.md +2 -2
  68. package/docs/wiki/Work-Lifecycle.md +1 -1
  69. package/package.json +29 -10
  70. package/scripts/activate-source-baseline.ts +17 -13
  71. package/scripts/benchmark-background-check-overlap.ts +228 -0
  72. package/scripts/benchmark-mcp-host.sh +117 -0
  73. package/scripts/benchmark-mcp-transport.ts +170 -0
  74. package/scripts/benchmark-quality-harness.ts +146 -0
  75. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  76. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  77. package/scripts/benchmark-typescript-navigation.ts +186 -0
  78. package/scripts/benchmark-work-efficiency.ts +334 -0
  79. package/scripts/capture-plan.sh +3 -3
  80. package/scripts/check-agent-tooling.sh +8 -35
  81. package/scripts/check-architecture-sync.sh +17 -86
  82. package/scripts/check-mcp-compatibility.ts +14 -0
  83. package/scripts/check-runtime-architecture.mjs +107 -64
  84. package/scripts/check-stable-baseline.ts +92 -0
  85. package/scripts/check-task-workflow.sh +4 -4
  86. package/scripts/contract-worktree.sh +1 -21
  87. package/scripts/ensure-task-workflow.sh +22 -55
  88. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  89. package/scripts/install-standalone-recovery.ts +24 -0
  90. package/scripts/lib/project-init-lib.sh +15 -44
  91. package/scripts/migrate-project-template.sh +23 -1179
  92. package/scripts/new-plan.sh +1 -1
  93. package/scripts/plan-to-todo.sh +11 -36
  94. package/scripts/run-governed-gate.ts +2 -1
  95. package/scripts/ship-worktrees.sh +2 -29
  96. package/scripts/smoke-ios-physical-device.ts +4 -2
  97. package/scripts/smoke-runtime-recovery.ts +1 -35
  98. package/scripts/smoke-schedule-engine.ts +1 -20
  99. package/scripts/verify-contract.sh +1 -111
  100. package/scripts/verify-forge-runtime.sh +0 -1
  101. package/scripts/verify-sprint.sh +3 -180
  102. package/scripts/workflow-contract.ts +1 -5
  103. package/src/cli/commands/init-hook.ts +3 -3
  104. package/src/cli/commands/mcp.ts +9 -5
  105. package/src/cli/commands/plugin.ts +1 -1
  106. package/src/cli/commands/recovery.ts +25 -3
  107. package/src/cli/commands/runtime.ts +55 -3
  108. package/src/cli/commands/security.ts +12 -1
  109. package/src/cli/commands/setup-profile.ts +31 -21
  110. package/src/cli/controller/check-runner.ts +106 -3
  111. package/src/cli/controller/composite-result.ts +1 -1
  112. package/src/cli/controller/context/focus.ts +51 -0
  113. package/src/cli/controller/context/instruction-context.ts +114 -0
  114. package/src/cli/controller/context/known-paths.ts +76 -0
  115. package/src/cli/controller/context/query-planning.ts +110 -0
  116. package/src/cli/controller/context/rule-resolution.ts +532 -0
  117. package/src/cli/controller/context/source-materializer.ts +278 -0
  118. package/src/cli/controller/context/structural.ts +16 -0
  119. package/src/cli/controller/context/types.ts +286 -0
  120. package/src/cli/controller/context-pack.ts +785 -469
  121. package/src/cli/controller/launch-agents.ts +77 -2
  122. package/src/cli/controller/operational-plan.ts +2 -2
  123. package/src/cli/controller/work-mode.ts +6 -4
  124. package/src/cli/editing/edit-session.ts +60 -7
  125. package/src/cli/github/github.ts +133 -0
  126. package/src/cli/installer/managed-entries.ts +15 -1
  127. package/src/cli/local-bridge/console-automations.ts +54 -78
  128. package/src/cli/local-bridge/console-view-models.ts +1 -35
  129. package/src/cli/local-bridge/facade-api.ts +0 -217
  130. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  131. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
  132. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  133. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
  134. package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
  135. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  136. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
  137. package/src/cli/local-bridge/frontend/src/styles.css +1 -1
  138. package/src/cli/local-bridge/frontend/src/types.ts +5 -8
  139. package/src/cli/local-bridge/job-store.ts +1 -490
  140. package/src/cli/local-bridge/server.ts +0 -513
  141. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  142. package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
  143. package/src/cli/mcp/auth.ts +5 -5
  144. package/src/cli/mcp/instructions.ts +3 -2
  145. package/src/cli/mcp/legacy-tool-service.ts +0 -189
  146. package/src/cli/mcp/multi-repository.ts +16 -5
  147. package/src/cli/mcp/oauth.ts +175 -26
  148. package/src/cli/mcp/repository-tools.ts +247 -107
  149. package/src/cli/mcp/server.ts +537 -111
  150. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  151. package/src/cli/mcp/toolset-selection.ts +1 -1
  152. package/src/cli/mcp/toolset.ts +42 -5
  153. package/src/cli/mcp/transports/http.ts +101 -14
  154. package/src/cli/mcp/transports/session-registry.ts +43 -5
  155. package/src/cli/mcp/types.ts +3 -3
  156. package/src/cli/repositories/command-classifier.ts +101 -19
  157. package/src/cli/repositories/command-executor.ts +55 -521
  158. package/src/cli/repositories/command-process.ts +108 -0
  159. package/src/cli/repositories/command-scope.ts +99 -41
  160. package/src/cli/repositories/controller-home.ts +7 -8
  161. package/src/cli/repositories/git-command-executor.ts +2 -0
  162. package/src/cli/repositories/locks.ts +53 -16
  163. package/src/cli/repositories/repository-snapshot.ts +121 -0
  164. package/src/cli/repositories/selected-path-actions.ts +17 -9
  165. package/src/cli/repositories/structured-git.ts +61 -3
  166. package/src/cli/repository/inspector.ts +553 -63
  167. package/src/cli/repository/session-cache.ts +58 -17
  168. package/src/cli/v81-entry.ts +1 -4
  169. package/src/effects/fs-transaction.ts +85 -17
  170. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  171. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  172. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  173. package/src/runtime/context/lsp-client.ts +283 -0
  174. package/src/runtime/context/semantic-navigation.ts +275 -0
  175. package/src/runtime/context/swift-navigation.ts +565 -0
  176. package/src/runtime/context/typescript-navigation.ts +226 -0
  177. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  178. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +47 -8
  179. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  180. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  181. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  182. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  183. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  184. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  185. package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
  186. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +143 -8
  187. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
  188. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +85 -8
  189. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  190. package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
  191. package/src/runtime/control-plane/facade/controller-session-store.ts +132 -1
  192. package/src/runtime/control-plane/facade/goal-workloop-access.ts +2 -0
  193. package/src/runtime/control-plane/facade/goal-workloop.ts +686 -129
  194. package/src/runtime/control-plane/facade/index.ts +1 -0
  195. package/src/runtime/control-plane/facade/plan-contract-store.ts +185 -20
  196. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  197. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  198. package/src/runtime/control-plane/facade/types.ts +22 -5
  199. package/src/runtime/control-plane/facade/work-contract-store.ts +175 -73
  200. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  201. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  202. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  203. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  204. package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
  205. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  206. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  207. package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
  208. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  209. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  210. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  211. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  212. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  213. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  214. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  215. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  216. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  217. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  218. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
  219. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +355 -116
  220. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
  221. package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
  222. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  223. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  224. package/src/runtime/control-plane/release-retention.ts +62 -2
  225. package/src/runtime/control-plane/routing/route-policy.ts +7 -16
  226. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  227. package/src/runtime/diagnostics/mcp-timing.ts +13 -0
  228. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  229. package/src/runtime/diagnostics/performance.ts +24 -1
  230. package/src/runtime/evidence/event-ledger.ts +1 -1
  231. package/src/runtime/execution/jobs/store.ts +58 -4
  232. package/src/runtime/execution/jobs/types.ts +1 -1
  233. package/src/runtime/execution/managed-workspace.ts +203 -1
  234. package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
  235. package/src/runtime/execution/process-runtime/check-result.ts +53 -0
  236. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  237. package/src/runtime/execution/process-runtime/command-facade.ts +345 -40
  238. package/src/runtime/execution/process-runtime/gc.ts +38 -6
  239. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
  240. package/src/runtime/execution/process-runtime/persisted-check.ts +47 -4
  241. package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
  242. package/src/runtime/execution/process-runtime/runtime.ts +43 -3
  243. package/src/runtime/execution/process-runtime/store.ts +1 -0
  244. package/src/runtime/execution/process-runtime/types.ts +2 -0
  245. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  246. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  247. package/src/runtime/execution/workers/executor.ts +0 -48
  248. package/src/runtime/gateway/mcp/execution-tools.ts +1192 -114
  249. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  250. package/src/runtime/gateway/mcp/router.ts +184 -82
  251. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
  252. package/src/runtime/gateway/mcp/runtime-tools.ts +2224 -1589
  253. package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
  254. package/src/runtime/plugins/browser-adapter.ts +2901 -241
  255. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  256. package/src/runtime/plugins/browser-handoff.ts +92 -2
  257. package/src/runtime/plugins/browser-macos-bridge.ts +582 -49
  258. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  259. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  260. package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
  261. package/src/runtime/plugins/browser-runtime.ts +183 -0
  262. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  263. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  264. package/src/runtime/plugins/desktop-operator-registration.ts +8 -2
  265. package/src/runtime/plugins/external-adapter.ts +324 -3
  266. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  267. package/src/runtime/plugins/first-party-registry.ts +2 -0
  268. package/src/runtime/plugins/github-adapter.ts +125 -0
  269. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  270. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  271. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  272. package/src/runtime/plugins/ios-adapter.ts +106 -16
  273. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  274. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  275. package/src/runtime/plugins/lightweight-action.ts +153 -0
  276. package/src/runtime/plugins/local-system-adapter.ts +123 -6
  277. package/src/runtime/plugins/macos-capability-broker.ts +55 -9
  278. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  279. package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
  280. package/src/runtime/plugins/resend-adapter.ts +1 -1
  281. package/src/runtime/plugins/store.ts +269 -29
  282. package/src/runtime/plugins/types.ts +35 -2
  283. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  284. package/src/runtime/projections/git-status-sampler.ts +16 -16
  285. package/src/runtime/projections/materialized-view.ts +28 -10
  286. package/src/runtime/recovery/maintenance-executor.ts +393 -28
  287. package/src/runtime/root/mcp-transport.ts +180 -10
  288. package/src/runtime/root/package-connector-service.ts +186 -8
  289. package/src/runtime/root/package-runtime-release.ts +143 -17
  290. package/src/runtime/root/package-runtime-service.ts +430 -22
  291. package/src/runtime/root/release-manifest.ts +7 -2
  292. package/src/runtime/root/release-materialize.ts +19 -0
  293. package/src/runtime/root/release-store.ts +48 -11
  294. package/src/runtime/root/runtime.ts +23 -5
  295. package/src/runtime/root/service.ts +100 -29
  296. package/src/runtime/root/types.ts +2 -0
  297. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  298. package/src/runtime/shared/trusted-executable.ts +62 -0
  299. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  300. package/src/runtime/standalone-recovery/core.ts +700 -92
  301. package/src/runtime/standalone-recovery/entry.ts +99 -23
  302. package/src/runtime/standalone-recovery/installer.ts +19 -3
  303. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  304. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  305. package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
  306. package/src/runtime/workflow/schedules/engine.ts +287 -151
  307. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  308. package/src/runtime/workflow/schedules/settlement.ts +48 -4
  309. package/src/runtime/workflow/schedules/store.ts +131 -10
  310. package/src/runtime/workflow/schedules/types.ts +2 -4
  311. package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
  312. package/src/testing/test-governance.ts +8 -40
  313. package/assets/reference-configs/heartbeat-triage.md +0 -57
  314. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  315. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  316. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  317. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  318. package/docs/architecture/current/architecture-invariants.md +0 -331
  319. package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
  320. package/docs/architecture/current/codegraph-context-provider.md +0 -238
  321. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  322. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  323. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
  324. package/docs/architecture/current/entity-model.md +0 -612
  325. package/docs/architecture/current/external-plugin-governance.md +0 -396
  326. package/docs/architecture/current/failure-recovery.md +0 -447
  327. package/docs/architecture/current/governance.md +0 -213
  328. package/docs/architecture/current/human-interaction-plane.md +0 -87
  329. package/docs/architecture/current/implementation-status.md +0 -128
  330. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  331. package/docs/architecture/current/legacy-governance.md +0 -58
  332. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  333. package/docs/architecture/current/migration-roadmap.md +0 -105
  334. package/docs/architecture/current/multi-repository-execution.md +0 -371
  335. package/docs/architecture/current/operations-runbook.md +0 -45
  336. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  337. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  338. package/docs/architecture/current/repository-access-modes.md +0 -102
  339. package/docs/architecture/current/route-policy.md +0 -15
  340. package/docs/architecture/current/route-session-concurrency.md +0 -85
  341. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  342. package/docs/architecture/current/runtime-directory-map.md +0 -49
  343. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  344. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  345. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  346. package/docs/architecture/current/system-overview.md +0 -169
  347. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  348. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  349. package/docs/architecture/current/thin-harness-v1.md +0 -321
  350. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  351. package/docs/forge-autonomous-goal-loop.md +0 -9
  352. package/docs/forge-provider-configuration.md +0 -7
  353. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
  354. package/docs/operations/gmail-assistant-routines.md +0 -186
  355. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  356. package/docs/operations/provider-configuration.md +0 -31
  357. package/scripts/harness-trace-grade.sh +0 -150
  358. package/scripts/heartbeat-triage.sh +0 -361
  359. package/scripts/maintenance-triage.sh +0 -167
  360. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  361. package/src/cli/repositories/goal-registry.ts +0 -239
  362. package/src/runtime/assistant/action-proposals.ts +0 -279
  363. package/src/runtime/assistant/intent.ts +0 -395
  364. package/src/runtime/assistant/model-provider.ts +0 -460
  365. package/src/runtime/assistant/openapi.ts +0 -223
  366. package/src/runtime/assistant/policy.ts +0 -89
  367. package/src/runtime/assistant/readiness.ts +0 -258
  368. package/src/runtime/assistant/routine-runtime.ts +0 -477
  369. package/src/runtime/assistant/schedule-binding.ts +0 -171
  370. package/src/runtime/assistant/standing-grants.ts +0 -375
  371. package/src/runtime/assistant/store.ts +0 -243
  372. package/src/runtime/assistant/types.ts +0 -135
  373. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  374. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  375. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  376. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  377. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  378. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  379. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  380. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  381. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  382. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  383. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  384. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  385. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  386. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  387. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  388. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  389. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  390. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  391. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  392. package/src/runtime/workflow/findings/store.ts +0 -204
  393. package/src/runtime/workflow/findings/types.ts +0 -58
  394. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  395. package/src/runtime/workflow/portfolio/store.ts +0 -185
  396. 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
+ }