@moretea-labs/forge 1.6.0 → 1.7.1

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