@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
@@ -1,6 +1,6 @@
1
1
  import { createHash } from 'crypto';
2
2
 
3
- export const ROUTE_POLICY_VERSION = 'route-policy-v3' as const;
3
+ export const ROUTE_POLICY_VERSION = 'route-policy-v4' as const;
4
4
 
5
5
  export type RouteExecutionMode = 'direct_control' | 'goal_workloop' | 'handoff_only';
6
6
  export type RouteWorkMode = 'direct_edit' | 'bounded_work' | 'quick_agent' | 'issue_task';
@@ -238,8 +238,6 @@ export function decideRoute(input: RoutePolicyInput): RouteDecision {
238
238
  ? false
239
239
  : input.recovery.isolationRequired === true || explicitParallelMode;
240
240
  const requiresRecovery = input.recovery.required === true
241
- || input.intent.requiresLongRunningChecks === true
242
- || explicitMode === 'debug'
243
241
  || explicitMode === 'release'
244
242
  || explicitMode === 'scale';
245
243
  const coordinationRequired = explicitParallelMode || input.intent.requiresIndependentDeliverables === true
@@ -289,26 +287,21 @@ export function decideRoute(input: RoutePolicyInput): RouteDecision {
289
287
  }
290
288
 
291
289
  if (input.workspace.dirty && mutation) {
292
- reasons.push({ code: 'dirty_workspace_requires_explicit_adoption', message: 'Mutation against a dirty workspace requires an exact reviewed adoption or a clean isolated checkout.' });
293
- return {
294
- executionMode: 'handoff_only', executorKind: 'handoff_only', selectedProviderId: null,
295
- workMode: 'direct_edit', executionPath: 'durable', requiresWork: true, requiresApproval: false,
296
- requiresIsolation: true, requiresRecovery: true, createHandoff: true, waitForUser: false,
297
- approvalState: 'approval_not_required', alternatives: [], ...decisionBase(input, reasons),
298
- };
290
+ reasons.push({
291
+ code: 'dirty_workspace_preserve_existing_changes',
292
+ message: 'The checkout is already dirty; preserve unrelated changes and treat inspected/actual scope as evidence instead of creating Work solely for adoption.',
293
+ });
299
294
  }
300
295
  if (explicitMode) reasons.push({ code: `explicit_${explicitMode}`, message: `Explicit -${explicitMode} mode overrides automatic work topology while policy gates remain authoritative.` });
301
296
  if (protectedPath) reasons.push({ code: 'protected_path', message: 'The predicted scope includes a protected or release-sensitive path.' });
302
297
  if (requiresRecovery) reasons.push({ code: 'recovery_required', message: 'The operation needs resumable Work and bounded recovery.' });
303
298
  if (requiresIsolation) reasons.push({ code: 'isolation_required', message: 'The operation requires an isolated checkout or serialized lane.' });
304
- if (input.intent.requiresLongRunningChecks) reasons.push({ code: 'long_checks', message: 'Long-running checks require durable continuation.' });
299
+ if (input.intent.requiresLongRunningChecks) reasons.push({ code: 'long_checks', message: 'Long-running checks may use a lightweight handle; duration alone does not require durable Work.' });
305
300
  if (input.intent.requiresInvestigation) reasons.push({ code: 'investigation', message: 'Investigation is required before or during implementation.' });
306
301
  if (input.intent.needsDependencies) reasons.push({ code: 'dependencies', message: 'Dependency ordering requires durable Work.' });
307
302
  if (coordinationRequired) reasons.push({ code: 'independent_deliverables', message: 'Multiple independent deliverables require durable PlanContract/Work coordination.' });
308
303
 
309
304
  const explicitBoundedMode = explicitMode === 'plan'
310
- || explicitMode === 'debug'
311
- || explicitMode === 'review'
312
305
  || explicitMode === 'release';
313
306
  const complex = explicitMode === 'direct'
314
307
  ? false
@@ -320,11 +313,9 @@ export function decideRoute(input: RoutePolicyInput): RouteDecision {
320
313
  || input.capabilities.requiresWorker === true
321
314
  || input.capabilities.requiresExternalEffect === true
322
315
  || input.intent.needsDependencies === true
323
- || protectedPath
324
316
  || destructive
325
317
  || remoteWrite
326
- || secretAccess
327
- || (mutation && (expectedFiles > 4 || expectedChangedLines > 200));
318
+ || secretAccess;
328
319
  const executionMode: RouteExecutionMode = complex ? 'goal_workloop' : 'direct_control';
329
320
  // Work topology is independent from executor/provider choice. Independent or
330
321
  // parallel deliverables stay in the durable Goal Workloop and are decomposed by
@@ -1,6 +1,5 @@
1
1
  import { resolveControllerHome } from '../../cli/repositories/controller-home';
2
2
  import { readSchedulerHealthSnapshot } from './global-scheduler/scheduler';
3
- import type { ControllerStartupRecoveryResult } from './startup-recovery';
4
3
  import type { RuntimeSourceIdentity } from './runtime-generation';
5
4
  import { readRuntimeOwner } from '../root/ownership';
6
5
  import { observeRuntimeStatus } from '../root/status';
@@ -26,7 +25,6 @@ export interface ForgeRuntimeStatus {
26
25
  gatewaySeparated?: boolean;
27
26
  workerIsolation?: boolean;
28
27
  degraded?: boolean;
29
- recovery?: ControllerStartupRecoveryResult;
30
28
  generation?: string;
31
29
  source?: RuntimeSourceIdentity;
32
30
  restartRequired?: boolean;
@@ -21,6 +21,19 @@ export interface McpTimingTrace {
21
21
  commandExecutionMs?: number;
22
22
  resultSerializationMs?: number;
23
23
  resultPersistenceMs?: number;
24
+ /** Public Gateway only: resolve current Canonical Runtime identity/token/endpoint. */
25
+ gatewayProxyResolveMs?: number;
26
+ /** Public Gateway only: establish or await a Canonical Runtime MCP client connection. */
27
+ gatewayProxyConnectMs?: number;
28
+ /** Public Gateway only: MCP call duration after a client is acquired, including Canonical Runtime work. */
29
+ gatewayProxyCallMs?: number;
30
+ /** Public Gateway only: time from forwarding the tool call until the Canonical Runtime handler starts. */
31
+ gatewayProxyCanonicalDispatchLagMs?: number;
32
+ /** Canonical Runtime server duration projected back through the proxy response. */
33
+ gatewayProxyCanonicalDurationMs?: number;
34
+ /** Public Gateway only: response/transport tail after canonical handler completion. */
35
+ gatewayProxyReturnMs?: number;
36
+ gatewayProxyConnectionState?: 'reused' | 'cold_connect' | 'coalesced_connect' | 'identity_reconnect';
24
37
  totalToolDurationMs: number;
25
38
  sessionId?: string;
26
39
  repoId?: string;
@@ -0,0 +1,129 @@
1
+ export type McpTransportTimingScope = 'tool_call' | 'connect_and_tool_call';
2
+
3
+ export interface McpTransportLatencySample {
4
+ label: string;
5
+ tool: string;
6
+ timingScope?: McpTransportTimingScope;
7
+ clientTotalMs: number;
8
+ serverDurationMs: number;
9
+ traceId?: string;
10
+ requestId?: string;
11
+ observedAt?: string;
12
+ }
13
+
14
+ export interface McpTransportLatencyStats {
15
+ p50: number;
16
+ p95: number;
17
+ max: number;
18
+ }
19
+
20
+ export interface McpTransportLatencyGroup {
21
+ label: string;
22
+ tool: string;
23
+ timingScope: McpTransportTimingScope;
24
+ sampleCount: number;
25
+ clientTotalMs: McpTransportLatencyStats;
26
+ serverDurationMs: McpTransportLatencyStats;
27
+ externalResidualMs: McpTransportLatencyStats;
28
+ externalSharePct: McpTransportLatencyStats;
29
+ serverExceedsClientCount: number;
30
+ }
31
+
32
+ export interface McpTransportLatencyReport {
33
+ schemaVersion: 1;
34
+ sampleCount: number;
35
+ groups: McpTransportLatencyGroup[];
36
+ }
37
+
38
+ function round(value: number): number {
39
+ return Math.round(value * 100) / 100;
40
+ }
41
+
42
+ function percentile(values: number[], fraction: number): number {
43
+ if (values.length === 0) return 0;
44
+ const sorted = [...values].sort((left, right) => left - right);
45
+ const index = Math.max(0, Math.min(sorted.length - 1, Math.ceil(sorted.length * fraction) - 1));
46
+ return round(sorted[index]!);
47
+ }
48
+
49
+ function stats(values: number[]): McpTransportLatencyStats {
50
+ return {
51
+ p50: percentile(values, 0.5),
52
+ p95: percentile(values, 0.95),
53
+ max: round(Math.max(...values, 0)),
54
+ };
55
+ }
56
+
57
+ function validDuration(value: unknown): value is number {
58
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0;
59
+ }
60
+
61
+ export function normalizeMcpTransportLatencySample(value: unknown): McpTransportLatencySample {
62
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
63
+ throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: sample must be an object');
64
+ }
65
+ const record = value as Record<string, unknown>;
66
+ const label = typeof record.label === 'string' ? record.label.trim() : '';
67
+ const tool = typeof record.tool === 'string' ? record.tool.trim() : '';
68
+ const timingScope = record.timingScope === undefined || record.timingScope === 'tool_call'
69
+ ? 'tool_call'
70
+ : record.timingScope === 'connect_and_tool_call'
71
+ ? 'connect_and_tool_call'
72
+ : undefined;
73
+ if (!label) throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: label is required');
74
+ if (!tool) throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: tool is required');
75
+ if (!timingScope) throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: timingScope must be tool_call or connect_and_tool_call');
76
+ if (!validDuration(record.clientTotalMs)) throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: clientTotalMs must be a non-negative finite number');
77
+ if (!validDuration(record.serverDurationMs)) throw new Error('MCP_TRANSPORT_SAMPLE_INVALID: serverDurationMs must be a non-negative finite number');
78
+ return {
79
+ label,
80
+ tool,
81
+ timingScope,
82
+ clientTotalMs: record.clientTotalMs,
83
+ serverDurationMs: record.serverDurationMs,
84
+ ...(typeof record.traceId === 'string' && record.traceId.trim() ? { traceId: record.traceId.trim() } : {}),
85
+ ...(typeof record.requestId === 'string' && record.requestId.trim() ? { requestId: record.requestId.trim() } : {}),
86
+ ...(typeof record.observedAt === 'string' && record.observedAt.trim() ? { observedAt: record.observedAt.trim() } : {}),
87
+ };
88
+ }
89
+
90
+ export function extractForgeServerDuration(result: unknown): number | undefined {
91
+ if (!result || typeof result !== 'object' || Array.isArray(result)) return undefined;
92
+ const structured = (result as Record<string, unknown>).structuredContent;
93
+ if (!structured || typeof structured !== 'object' || Array.isArray(structured)) return undefined;
94
+ const responseMeta = (structured as Record<string, unknown>).responseMeta;
95
+ if (!responseMeta || typeof responseMeta !== 'object' || Array.isArray(responseMeta)) return undefined;
96
+ const duration = (responseMeta as Record<string, unknown>).serverDurationMs;
97
+ return validDuration(duration) ? duration : undefined;
98
+ }
99
+
100
+ export function aggregateMcpTransportLatencySamples(values: unknown[]): McpTransportLatencyReport {
101
+ const samples = values.map(normalizeMcpTransportLatencySample);
102
+ const grouped = new Map<string, McpTransportLatencySample[]>();
103
+ for (const sample of samples) {
104
+ const key = `${sample.label}\u0000${sample.tool}\u0000${sample.timingScope ?? 'tool_call'}`;
105
+ const entries = grouped.get(key) ?? [];
106
+ entries.push(sample);
107
+ grouped.set(key, entries);
108
+ }
109
+ const groups = [...grouped.values()].map((entries): McpTransportLatencyGroup => {
110
+ const client = entries.map((sample) => sample.clientTotalMs);
111
+ const server = entries.map((sample) => sample.serverDurationMs);
112
+ const residual = entries.map((sample) => Math.max(0, sample.clientTotalMs - sample.serverDurationMs));
113
+ const share = entries.map((sample, index) => client[index]! > 0 ? residual[index]! / client[index]! * 100 : 0);
114
+ return {
115
+ label: entries[0]!.label,
116
+ tool: entries[0]!.tool,
117
+ timingScope: entries[0]!.timingScope ?? 'tool_call',
118
+ sampleCount: entries.length,
119
+ clientTotalMs: stats(client),
120
+ serverDurationMs: stats(server),
121
+ externalResidualMs: stats(residual),
122
+ externalSharePct: stats(share),
123
+ serverExceedsClientCount: entries.filter((sample) => sample.serverDurationMs > sample.clientTotalMs).length,
124
+ };
125
+ }).sort((left, right) => left.label.localeCompare(right.label)
126
+ || left.tool.localeCompare(right.tool)
127
+ || left.timingScope.localeCompare(right.timingScope));
128
+ return { schemaVersion: 1, sampleCount: samples.length, groups };
129
+ }
@@ -1,5 +1,5 @@
1
1
  import { execFileSync } from 'child_process';
2
- import { existsSync, lstatSync, readdirSync, rmSync } from 'fs';
2
+ import { existsSync, lstatSync, readdirSync, rmSync, statfsSync } from 'fs';
3
3
  import { tmpdir } from 'os';
4
4
  import { basename, dirname, join, resolve } from 'path';
5
5
 
@@ -46,6 +46,7 @@ export interface RuntimePerformanceDiagnostics {
46
46
  };
47
47
  processes: RuntimeProcessSample[];
48
48
  temp: { scannedRoots: string[]; totalEntries: number; cleanupCandidates: number; entries: RuntimeTempEntry[] };
49
+ storage: { availableBytes?: number; availableGiB?: number; pressure: 'normal' | 'warning' | 'critical' | 'unknown'; managedCacheRoots: string[] };
49
50
  cleanupPreview?: { safeToTerminate: RuntimeProcessSample[]; safeToRemoveTempEntries: RuntimeTempEntry[]; requiresConfirmation: true };
50
51
  truncated: {
51
52
  summary: boolean;
@@ -322,7 +323,27 @@ export function collectRuntimePerformanceDiagnostics(input: DiagnosticsInput): R
322
323
  const temp = input.includeTempDirs === false
323
324
  ? { roots: [] as string[], entries: [] as RuntimeTempEntry[] }
324
325
  : scanRuntimeTempEntries(processes);
326
+ let availableBytes: number | undefined;
327
+ try {
328
+ const fs = statfsSync(input.repoRoot);
329
+ availableBytes = Number(fs.bavail) * Number(fs.bsize);
330
+ } catch {}
331
+ const availableGiB = availableBytes === undefined ? undefined : Math.round((availableBytes / (1024 ** 3)) * 10) / 10;
332
+ const storagePressure: RuntimePerformanceDiagnostics['storage']['pressure'] = availableGiB === undefined
333
+ ? 'unknown'
334
+ : availableGiB < 30
335
+ ? 'critical'
336
+ : availableGiB < 50
337
+ ? 'warning'
338
+ : 'normal';
339
+ const managedCacheRoots = [
340
+ join(input.repoRoot, '.forge', 'ios', 'DerivedData'),
341
+ join(input.repoRoot, '.ai', 'harness'),
342
+ join(input.repoRoot, '.repo-harness'),
343
+ ].filter((path) => existsSync(path));
325
344
  const findings: RuntimePerformanceDiagnostics['findings'] = [];
345
+ if (storagePressure === 'critical') findings.push({ severity: 'critical', code: 'LOW_DISK_SPACE', message: `Only ${availableGiB} GiB of repository volume space is available.` });
346
+ else if (storagePressure === 'warning') findings.push({ severity: 'warning', code: 'LOW_DISK_SPACE', message: `Repository volume free space is ${availableGiB} GiB.` });
326
347
  if (orphanWorkers.length > 0) findings.push({ severity: orphanWorkers.length >= 3 ? 'critical' : 'warning', code: 'ORPHAN_WORKERS', message: 'Detected ' + orphanWorkers.length + ' orphan worker process(es).' });
327
348
  const totalCpu = Math.round(repoProcesses.reduce((sum, process) => sum + process.cpu, 0) * 10) / 10;
328
349
  if (totalCpu >= 100 || highCpu.length >= 3) findings.push({ severity: 'warning', code: 'HIGH_FORGE_CPU', message: 'Forge-related CPU is ' + totalCpu + '%.' });
@@ -341,6 +362,7 @@ export function collectRuntimePerformanceDiagnostics(input: DiagnosticsInput): R
341
362
  ...(orphanWorkers.length ? ['Terminate orphan workers only after reviewing cleanupPreview.safeToTerminate.'] : []),
342
363
  ...(tempCleanupCandidates.length ? ['Remove stale temp entries through runtime_maintenance_apply after reviewing cleanupPreview.safeToRemoveTempEntries.'] : []),
343
364
  ...(highCpu.length ? ['Inspect highCpuProcesses before restarting the controller stack.'] : []),
365
+ ...(storagePressure === 'critical' ? ['Allow Forge generated-cache retention to reclaim stale DerivedData before starting another heavy iOS verification.'] : []),
344
366
  ];
345
367
  return {
346
368
  schemaVersion: 1,
@@ -358,6 +380,7 @@ export function collectRuntimePerformanceDiagnostics(input: DiagnosticsInput): R
358
380
  cleanupCandidates: tempCleanupCandidates.length,
359
381
  entries: temp.entries.slice(0, MAX_DIAGNOSTIC_TEMP_ENTRIES),
360
382
  },
383
+ storage: { availableBytes, availableGiB, pressure: storagePressure, managedCacheRoots },
361
384
  cleanupPreview: input.cleanupPreview === true
362
385
  ? {
363
386
  safeToTerminate: rawCleanupProcesses,
@@ -18,7 +18,7 @@ export interface RuntimeEntityEvent {
18
18
  eventId: string;
19
19
  eventType: string;
20
20
  repoId: string;
21
- entityType: 'job' | 'plugin' | 'schedule' | 'occurrence' | 'portfolio' | 'candidate-finding' | 'release' | 'lease' | 'schedule-decision' | 'assistant-action-proposal' | 'assistant-standing-grant';
21
+ entityType: 'job' | 'plugin' | 'schedule' | 'occurrence' | 'portfolio' | 'release' | 'lease' | 'schedule-decision';
22
22
  entityId: string;
23
23
  correlationId?: string;
24
24
  causationId?: string;
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from 'crypto';
2
2
  import { existsSync, readdirSync } from 'fs';
3
3
  import { join } from 'path';
4
4
  import { ensureControllerHome, ensureRepositoryControllerLayout, repositoryControllerRoot } from '../../../cli/repositories/controller-home';
5
- import { withControllerLock } from '../../../cli/repositories/locks';
5
+ import { releaseControllerLock, tryAcquireControllerLock, withControllerLock } from '../../../cli/repositories/locks';
6
6
  import { appendJobEvent } from '../../evidence/event-ledger';
7
7
  import { writeExecutionArtifact } from '../../evidence/artifact-store';
8
8
  import { markRepositoryProjectionDirty } from '../../projections/invalidation';
@@ -206,7 +206,9 @@ function writeActiveIndex(controllerHome: string, index: ActiveJobIndex): void {
206
206
  writeJsonAtomic(activeIndexPath(controllerHome), {
207
207
  schemaVersion: 1,
208
208
  updatedAt: now(),
209
- jobs: [...deduped.values()].sort((a, b) => a.queuedAt.localeCompare(b.queuedAt)).slice(-5000),
209
+ // Active work is correctness state, not a presentation window. Dropping an
210
+ // entry here makes Scheduler/recovery reads forget a durable non-terminal Job.
211
+ jobs: [...deduped.values()].sort((a, b) => a.queuedAt.localeCompare(b.queuedAt)),
210
212
  } satisfies ActiveJobIndex);
211
213
  }
212
214
 
@@ -258,7 +260,9 @@ export function rebuildExecutionJobIndexes(controllerHome: string, repoIds: stri
258
260
  for (const repoId of targetRepos) {
259
261
  const records = join(executionJobRoot(controllerHome, repoId), 'records');
260
262
  let names: string[] = [];
261
- try { names = readdirSync(records).filter((name) => name.endsWith('.json')).slice(0, 5000); } catch { continue; }
263
+ // Startup repair must rebuild every active and request authority. Only the
264
+ // recent-history projection below is intentionally bounded.
265
+ try { names = readdirSync(records).filter((name) => name.endsWith('.json')); } catch { continue; }
262
266
  for (const name of names) {
263
267
  try {
264
268
  const job = readJsonFile<ExecutionJob>(join(records, name));
@@ -648,11 +652,61 @@ export function listActiveExecutionJobs(controllerHome: string, repoId?: string)
648
652
  try {
649
653
  const job = getExecutionJob(controllerHome, entry.repoId, entry.jobId);
650
654
  if (ACTIVE_JOB_STATUSES.has(job.status)) output.push(job);
651
- } catch { /* stale index repaired below */ }
655
+ } catch { /* maintenance reconciliation owns stale-index repair */ }
652
656
  }
653
657
  return output;
654
658
  }
655
659
 
660
+ /**
661
+ * Maintenance-only active-index convergence. The active index itself is the
662
+ * bounded candidate set; this never scans historical Job directories. Ordinary
663
+ * read APIs remain side-effect free, while the Scheduler can remove terminal or
664
+ * missing entries once and make subsequent idle reads genuinely cheap.
665
+ */
666
+ export function reconcileActiveExecutionJobIndex(
667
+ controllerHome: string,
668
+ repoId?: string,
669
+ ): ExecutionJob[] {
670
+ const key = { scope: 'global' as const, resource: 'execution-index' };
671
+ const owner = `execution-index-active-reconcile:${repoId ?? 'all'}`;
672
+ const acquired = tryAcquireControllerLock(controllerHome, key, owner);
673
+ if (!acquired.acquired) return listActiveExecutionJobs(controllerHome, repoId);
674
+ try {
675
+ const index = readActiveIndex(controllerHome);
676
+ const jobs: ExecutionJob[] = [];
677
+ const retained: ActiveJobIndex['jobs'] = [];
678
+ let removed = 0;
679
+ for (const entry of index.jobs) {
680
+ if (repoId && entry.repoId !== repoId) {
681
+ retained.push(entry);
682
+ continue;
683
+ }
684
+ try {
685
+ const job = getExecutionJob(controllerHome, entry.repoId, entry.jobId);
686
+ if (!ACTIVE_JOB_STATUSES.has(job.status)) {
687
+ removed += 1;
688
+ continue;
689
+ }
690
+ jobs.push(job);
691
+ retained.push({
692
+ jobId: job.jobId,
693
+ repoId: job.repoId,
694
+ status: job.status,
695
+ priority: job.priority,
696
+ queuedAt: job.queuedAt,
697
+ updatedAt: job.updatedAt,
698
+ });
699
+ } catch {
700
+ removed += 1;
701
+ }
702
+ }
703
+ if (removed > 0) writeActiveIndex(controllerHome, { ...index, jobs: retained });
704
+ return jobs;
705
+ } finally {
706
+ releaseControllerLock(controllerHome, key, acquired.lock.lockId);
707
+ }
708
+ }
709
+
656
710
  function executionJobApprovalRequestId(job: ExecutionJob): string | undefined {
657
711
  if (typeof job.result?.approvalRequestId === 'string') return job.result.approvalRequestId;
658
712
  const authorization = job.result?.authorization;
@@ -45,7 +45,7 @@ export interface ResourceClaimSpec {
45
45
  }
46
46
 
47
47
  export interface ExecutionJobOrigin {
48
- surface: 'mcp' | 'local-ui' | 'mobile-intent' | 'chatgpt-action' | 'assistant-routine' | 'standing-grant' | 'cli' | 'schedule' | 'reconciliation' | 'system';
48
+ surface: 'mcp' | 'local-ui' | 'mobile-intent' | 'chatgpt-action' | 'cli' | 'schedule' | 'reconciliation' | 'system';
49
49
  actor?: string;
50
50
  correlationId?: string;
51
51
  causationId?: string;
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'crypto';
2
- import { existsSync, mkdirSync, realpathSync } from 'fs';
2
+ import { existsSync, mkdirSync, readFileSync, realpathSync, symlinkSync } from 'fs';
3
3
  import { dirname, join, resolve } from 'path';
4
4
  import { repositoryControllerRoot } from '../../cli/repositories/controller-home';
5
5
  import type { RepositoryRecord } from '../../cli/repositories/types';
@@ -14,6 +14,7 @@ import { branchSlugSegment, validateBranchName } from '../../cli/repositories/br
14
14
  import { withControllerLock } from '../../cli/repositories/locks';
15
15
  import { resolveGitExecutable } from '../../effects/git-executable';
16
16
  import { runProcess } from '../../effects/process-runner';
17
+ import { repositoryChildProcessEnvironment, resolveBunExecutable } from '../shared/process-environment';
17
18
  import { readJsonFile, writeJsonAtomic } from '../shared/json-files';
18
19
 
19
20
  export interface EnsureManagedWorkspaceInput {
@@ -21,6 +22,24 @@ export interface EnsureManagedWorkspaceInput {
21
22
  title: string;
22
23
  baseRef?: string;
23
24
  branchName?: string;
25
+ /** Explicitly materialize lockfile-backed Node dependencies in this isolated worktree. */
26
+ prepareDependencies?: boolean;
27
+ }
28
+
29
+ export interface ManagedWorkspaceDependencyBootstrap {
30
+ packageManager: 'bun' | 'pnpm' | 'npm' | 'yarn';
31
+ lockfile: string;
32
+ command: string[];
33
+ }
34
+
35
+ export interface ManagedWorkspaceDependencyPreparation {
36
+ mode: 'already_ready' | 'canonical_reuse' | 'installed';
37
+ nodeModulesRoot: string;
38
+ canonicalRoot?: string;
39
+ }
40
+
41
+ export interface ManagedWorkspaceDependencies {
42
+ materializeDependencies?: (workspaceRoot: string) => void;
24
43
  }
25
44
 
26
45
  export interface ManagedWorkspace {
@@ -73,6 +92,180 @@ function assertBranch(root: string, branch: string): string {
73
92
  return normalized;
74
93
  }
75
94
 
95
+ export function managedWorkspaceDependencyBootstrap(repoRoot: string): ManagedWorkspaceDependencyBootstrap | undefined {
96
+ if (!existsSync(join(repoRoot, 'package.json')) || existsSync(join(repoRoot, 'node_modules'))) return undefined;
97
+ const lockCandidates = [
98
+ ['bun', 'bun.lock', ['bun', 'install', '--frozen-lockfile']],
99
+ ['bun', 'bun.lockb', ['bun', 'install', '--frozen-lockfile']],
100
+ ['pnpm', 'pnpm-lock.yaml', ['pnpm', 'install', '--frozen-lockfile']],
101
+ ['npm', 'package-lock.json', ['npm', 'ci']],
102
+ ['yarn', 'yarn.lock', ['yarn', 'install', '--frozen-lockfile']],
103
+ ] as const;
104
+ const detected = lockCandidates.find(([, lockfile]) => existsSync(join(repoRoot, lockfile)));
105
+ if (!detected) return undefined;
106
+ return { packageManager: detected[0], lockfile: detected[1], command: [...detected[2]] };
107
+ }
108
+
109
+ const DEPENDENCY_REUSE_CONFIG_PATHS = [
110
+ '.npmrc',
111
+ 'bunfig.toml',
112
+ 'pnpm-workspace.yaml',
113
+ '.yarnrc.yml',
114
+ ] as const;
115
+
116
+ function dependencyInputMatches(leftRoot: string, rightRoot: string, path: string): boolean {
117
+ const left = join(leftRoot, path);
118
+ const right = join(rightRoot, path);
119
+ const leftExists = existsSync(left);
120
+ const rightExists = existsSync(right);
121
+ if (leftExists !== rightExists) return false;
122
+ return !leftExists || readFileSync(left).equals(readFileSync(right));
123
+ }
124
+
125
+ function linkedWorkspaceCanonicalRoot(repoRoot: string): string | undefined {
126
+ const common = runProcess(resolveGitExecutable(), ['-C', repoRoot, 'rev-parse', '--path-format=absolute', '--git-common-dir'], {
127
+ timeoutMs: 15_000,
128
+ maxOutputBytes: 8 * 1024,
129
+ });
130
+ if (!common.ok || !common.stdout.trim()) return undefined;
131
+ const canonicalRoot = dirname(resolve(common.stdout.trim()));
132
+ if (!existsSync(canonicalRoot)) return undefined;
133
+ try {
134
+ if (realpathSync(canonicalRoot) === realpathSync(repoRoot)) return undefined;
135
+ } catch {
136
+ return undefined;
137
+ }
138
+ const topLevel = runProcess(resolveGitExecutable(), ['-C', canonicalRoot, 'rev-parse', '--show-toplevel'], {
139
+ timeoutMs: 15_000,
140
+ maxOutputBytes: 8 * 1024,
141
+ });
142
+ if (!topLevel.ok || !topLevel.stdout.trim()) return undefined;
143
+ try {
144
+ return realpathSync(topLevel.stdout.trim()) === realpathSync(canonicalRoot) ? canonicalRoot : undefined;
145
+ } catch {
146
+ return undefined;
147
+ }
148
+ }
149
+
150
+ export function resolveManagedWorkspaceCanonicalDependencies(repoRoot: string): {
151
+ canonicalRoot: string;
152
+ nodeModulesRoot: string;
153
+ } | undefined {
154
+ const bootstrap = managedWorkspaceDependencyBootstrap(repoRoot);
155
+ if (!bootstrap) return undefined;
156
+ const canonicalRoot = linkedWorkspaceCanonicalRoot(repoRoot);
157
+ if (!canonicalRoot) return undefined;
158
+ const nodeModulesRoot = join(canonicalRoot, 'node_modules');
159
+ if (!existsSync(nodeModulesRoot)) return undefined;
160
+ const dependencyInputs = ['package.json', bootstrap.lockfile, ...DEPENDENCY_REUSE_CONFIG_PATHS];
161
+ if (!dependencyInputs.every((path) => dependencyInputMatches(repoRoot, canonicalRoot, path))) return undefined;
162
+ return { canonicalRoot, nodeModulesRoot: realpathSync(nodeModulesRoot) };
163
+ }
164
+
165
+ function packageManifestRequiresDependencyMaterialization(repoRoot: string): boolean {
166
+ const packagePath = join(repoRoot, 'package.json');
167
+ if (!existsSync(packagePath)) return false;
168
+ let manifest: Record<string, unknown>;
169
+ try {
170
+ const parsed = JSON.parse(readFileSync(packagePath, 'utf8')) as unknown;
171
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return true;
172
+ manifest = parsed as Record<string, unknown>;
173
+ } catch {
174
+ // Preserve fail-closed dependency preparation for malformed manifests. The
175
+ // package manager remains the authority for the concrete parse error once a
176
+ // supported frozen lockfile is present.
177
+ return true;
178
+ }
179
+
180
+ const dependencyFields = [
181
+ 'dependencies',
182
+ 'devDependencies',
183
+ 'optionalDependencies',
184
+ 'peerDependencies',
185
+ 'bundleDependencies',
186
+ 'bundledDependencies',
187
+ ] as const;
188
+ if (dependencyFields.some((field) => {
189
+ const value = manifest[field];
190
+ return Array.isArray(value)
191
+ ? value.length > 0
192
+ : Boolean(value && typeof value === 'object' && Object.keys(value as Record<string, unknown>).length > 0);
193
+ })) return true;
194
+
195
+ const workspaces = manifest.workspaces;
196
+ if (Array.isArray(workspaces)) return workspaces.length > 0;
197
+ if (workspaces && typeof workspaces === 'object') {
198
+ const packages = (workspaces as Record<string, unknown>).packages;
199
+ if (Array.isArray(packages)) return packages.length > 0;
200
+ return Object.keys(workspaces as Record<string, unknown>).length > 0;
201
+ }
202
+ return false;
203
+ }
204
+
205
+ function reuseManagedWorkspaceCanonicalDependencies(repoRoot: string): ManagedWorkspaceDependencyPreparation | undefined {
206
+ const nodeModulesRoot = join(repoRoot, 'node_modules');
207
+ const reusable = resolveManagedWorkspaceCanonicalDependencies(repoRoot);
208
+ if (!reusable) return undefined;
209
+ try {
210
+ symlinkSync(reusable.nodeModulesRoot, nodeModulesRoot, process.platform === 'win32' ? 'junction' : 'dir');
211
+ } catch (error) {
212
+ if (!existsSync(nodeModulesRoot)) {
213
+ const detail = error instanceof Error ? error.message : String(error);
214
+ throw new Error(`MANAGED_WORKSPACE_DEPENDENCY_REUSE_FAILED: ${detail}`);
215
+ }
216
+ }
217
+ return {
218
+ mode: 'canonical_reuse',
219
+ nodeModulesRoot: realpathSync(nodeModulesRoot),
220
+ canonicalRoot: reusable.canonicalRoot,
221
+ };
222
+ }
223
+
224
+ export function materializeManagedWorkspaceDependencies(repoRoot: string): ManagedWorkspaceDependencyPreparation | undefined {
225
+ if (!existsSync(join(repoRoot, 'package.json'))) return undefined;
226
+ const nodeModulesRoot = join(repoRoot, 'node_modules');
227
+ if (existsSync(nodeModulesRoot)) {
228
+ return { mode: 'already_ready', nodeModulesRoot: realpathSync(nodeModulesRoot) };
229
+ }
230
+ // A linked worktree may safely reuse canonical dependencies even when the
231
+ // package manifest itself declares no dependencies. The supported lockfile
232
+ // and dependency-reuse inputs remain the authority for proving equivalence.
233
+ const reused = reuseManagedWorkspaceCanonicalDependencies(repoRoot);
234
+ if (reused) return reused;
235
+ // A package manifest can define dependency-free scripts (for example a
236
+ // built-in `node -e` check). Such checks do not need node_modules and must not
237
+ // be rejected merely because the repository intentionally has no lockfile.
238
+ if (!packageManifestRequiresDependencyMaterialization(repoRoot)) return undefined;
239
+ const bootstrap = managedWorkspaceDependencyBootstrap(repoRoot);
240
+ if (!bootstrap) {
241
+ throw new Error('MANAGED_WORKSPACE_DEPENDENCY_LOCK_REQUIRED: package.json declares dependencies but no supported lockfile was found');
242
+ }
243
+ const childEnv = repositoryChildProcessEnvironment();
244
+ const executable = bootstrap.packageManager === 'bun'
245
+ ? resolveBunExecutable(process.execPath, childEnv)
246
+ : bootstrap.command[0]!;
247
+ const result = runProcess(executable, bootstrap.command.slice(1), {
248
+ cwd: repoRoot,
249
+ env: childEnv,
250
+ timeoutMs: 10 * 60_000,
251
+ maxOutputBytes: 4 * 1024 * 1024,
252
+ });
253
+ if (!result.ok) {
254
+ const detail = result.stderr || result.error || `exit ${result.status}`;
255
+ throw new Error(`MANAGED_WORKSPACE_DEPENDENCY_INSTALL_FAILED: ${bootstrap.command.join(' ')}: ${detail}`);
256
+ }
257
+ if (!existsSync(nodeModulesRoot)) {
258
+ throw new Error(`MANAGED_WORKSPACE_DEPENDENCY_INSTALL_INCOMPLETE: ${bootstrap.command.join(' ')} completed without node_modules`);
259
+ }
260
+ return { mode: 'installed', nodeModulesRoot: realpathSync(nodeModulesRoot) };
261
+ }
262
+
263
+ export function materializeManagedWorkspaceCheckDependencies(repoRoot: string): ManagedWorkspaceDependencyPreparation | undefined {
264
+ if (!existsSync(join(repoRoot, 'package.json')) || existsSync(join(repoRoot, 'node_modules'))) return undefined;
265
+ if (!linkedWorkspaceCanonicalRoot(repoRoot)) return undefined;
266
+ return materializeManagedWorkspaceDependencies(repoRoot);
267
+ }
268
+
76
269
  function existingWorkspace(path: string, branch: string): boolean {
77
270
  if (!existsSync(path)) return false;
78
271
  const root = runProcess(resolveGitExecutable(), ['-C', path, 'rev-parse', '--show-toplevel'], {
@@ -109,6 +302,7 @@ export function ensureManagedWorkspace(
109
302
  controllerHome: string,
110
303
  repository: RepositoryRecord,
111
304
  input: EnsureManagedWorkspaceInput,
305
+ dependencies: ManagedWorkspaceDependencies = {},
112
306
  ): ManagedWorkspace {
113
307
  const requestId = input.requestId.trim();
114
308
  if (!requestId) throw new Error('MANAGED_WORKSPACE_REQUEST_ID_REQUIRED');
@@ -159,6 +353,14 @@ export function ensureManagedWorkspace(
159
353
  if (!repositoryCheckoutRootMatches(repository, path)) {
160
354
  throw new Error(`MANAGED_WORKSPACE_REPOSITORY_MISMATCH: ${path}`);
161
355
  }
356
+ if (input.prepareDependencies === true) {
357
+ (dependencies.materializeDependencies ?? materializeManagedWorkspaceDependencies)(path);
358
+ } else {
359
+ // Reusing a byte-equivalent canonical dependency tree is cheap and
360
+ // deterministic, so isolated Work checkouts should get it even when a
361
+ // full package-manager install was not explicitly requested.
362
+ reuseManagedWorkspaceCanonicalDependencies(path);
363
+ }
162
364
 
163
365
  const record = addRepositoryCheckout({
164
366
  repoId: repository.repoId,