@moretea-labs/forge 1.5.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +46 -0
  3. package/CLAUDE.md +2 -0
  4. package/DESIGN.md +7 -2
  5. package/README.md +4 -4
  6. package/README.zh-CN.md +2 -2
  7. package/SKILL.md +2 -2
  8. package/THIRD_PARTY_NOTICES.md +1 -1
  9. package/agents/openai.yaml +1 -1
  10. package/assets/hooks/hook-input.sh +11 -8
  11. package/assets/hooks/lib/session-state.sh +10 -7
  12. package/assets/hooks/lib/workflow-state.sh +13 -177
  13. package/assets/hooks/post-tool-observer.sh +8 -2
  14. package/assets/hooks/pre-edit-guard.sh +8 -39
  15. package/assets/hooks/prompt-guard.sh +7 -83
  16. package/assets/hooks/session-start-context.sh +56 -1
  17. package/assets/partials/05-workflow.partial.md +1 -1
  18. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  19. package/assets/plugin-registry.v1.json +54 -3
  20. package/assets/reference-configs/document-generation.md +1 -1
  21. package/assets/reference-configs/harness-overview.md +1 -1
  22. package/assets/reference-configs/hook-operations.md +1 -1
  23. package/assets/reference-configs/sprint-contracts.md +3 -3
  24. package/assets/skill-version.json +2 -2
  25. package/assets/templates/contract.template.md +2 -7
  26. package/assets/templates/helpers/capture-plan.sh +2 -2
  27. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  28. package/assets/templates/helpers/contract-worktree.sh +1 -21
  29. package/assets/templates/helpers/ensure-task-workflow.sh +13 -38
  30. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  31. package/assets/templates/helpers/new-plan.sh +1 -1
  32. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  33. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  34. package/assets/templates/helpers/sprint-backlog.sh +6 -6
  35. package/assets/templates/helpers/verify-contract.sh +1 -111
  36. package/assets/templates/helpers/verify-sprint.sh +3 -180
  37. package/assets/templates/plan.template.md +1 -1
  38. package/assets/templates/review.template.md +9 -34
  39. package/assets/workflow-contract.v1.json +10 -19
  40. package/docs/README.md +10 -10
  41. package/docs/architecture/CURRENT.md +170 -0
  42. package/docs/architecture/EVOLUTION.md +42 -0
  43. package/docs/architecture/current/README.md +4 -156
  44. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  45. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  46. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  47. package/docs/architecture/history/README.md +7 -0
  48. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  49. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  50. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  51. package/docs/architecture/history.md +3 -34
  52. package/docs/architecture/index.md +15 -98
  53. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  54. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  55. package/docs/architecture/versions/1.6.md +38 -0
  56. package/docs/architecture/versions/1.7.md +41 -0
  57. package/docs/forge-chatgpt-browser-engine.md +2 -2
  58. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  59. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  60. package/docs/forge-github-issue-launcher.md +1 -1
  61. package/docs/forge-runtime-self-healing-loop.md +1 -1
  62. package/docs/operations/controller-browser-plugin.md +38 -22
  63. package/docs/operations/controller-performance-and-502.md +124 -0
  64. package/docs/operations/controller-reliability-runbook.md +1 -1
  65. package/docs/operations/features.md +3 -3
  66. package/docs/operations/github-repository.md +1 -1
  67. package/docs/operations/homebrew.md +2 -2
  68. package/docs/operations/mcp-tool-exposure.md +5 -3
  69. package/docs/tutorials/01-install-and-start.md +1 -1
  70. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  71. package/docs/wiki/Architecture.md +6 -6
  72. package/docs/wiki/Implementation.md +2 -2
  73. package/docs/wiki/Installation.md +1 -1
  74. package/docs/wiki/Integrations.md +2 -2
  75. package/docs/wiki/Quick-Start.md +1 -1
  76. package/docs/wiki/Work-Lifecycle.md +1 -1
  77. package/package.json +29 -10
  78. package/scripts/activate-source-baseline.ts +17 -13
  79. package/scripts/benchmark-background-check-overlap.ts +228 -0
  80. package/scripts/benchmark-mcp-host.sh +117 -0
  81. package/scripts/benchmark-mcp-transport.ts +170 -0
  82. package/scripts/benchmark-quality-harness.ts +146 -0
  83. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  84. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  85. package/scripts/benchmark-typescript-navigation.ts +186 -0
  86. package/scripts/benchmark-work-efficiency.ts +334 -0
  87. package/scripts/capture-plan.sh +3 -3
  88. package/scripts/check-agent-tooling.sh +8 -35
  89. package/scripts/check-architecture-sync.sh +17 -86
  90. package/scripts/check-mcp-compatibility.ts +14 -0
  91. package/scripts/check-public-docs.sh +14 -1
  92. package/scripts/check-runtime-architecture.mjs +107 -64
  93. package/scripts/check-stable-baseline.ts +92 -0
  94. package/scripts/check-task-workflow.sh +4 -4
  95. package/scripts/contract-worktree.sh +1 -21
  96. package/scripts/ensure-task-workflow.sh +22 -55
  97. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  98. package/scripts/install-standalone-recovery.ts +24 -0
  99. package/scripts/lib/project-init-lib.sh +15 -44
  100. package/scripts/migrate-chatgpt-automation-schedules.ts +64 -0
  101. package/scripts/migrate-project-template.sh +23 -1179
  102. package/scripts/new-plan.sh +1 -1
  103. package/scripts/plan-to-todo.sh +11 -36
  104. package/scripts/run-governed-gate.ts +2 -1
  105. package/scripts/ship-worktrees.sh +2 -29
  106. package/scripts/smoke-ios-physical-device.ts +4 -2
  107. package/scripts/smoke-runtime-recovery.ts +1 -35
  108. package/scripts/smoke-schedule-engine.ts +1 -20
  109. package/scripts/sprint-backlog.sh +1 -1
  110. package/scripts/verify-contract.sh +1 -111
  111. package/scripts/verify-forge-runtime.sh +0 -1
  112. package/scripts/verify-sprint.sh +3 -180
  113. package/scripts/workflow-contract.ts +1 -5
  114. package/src/cli/commands/chatgpt.ts +31 -2
  115. package/src/cli/commands/init-hook.ts +3 -3
  116. package/src/cli/commands/mcp.ts +9 -5
  117. package/src/cli/commands/plugin.ts +37 -10
  118. package/src/cli/commands/recovery.ts +25 -3
  119. package/src/cli/commands/runtime.ts +55 -3
  120. package/src/cli/commands/security.ts +12 -1
  121. package/src/cli/commands/setup-profile.ts +31 -21
  122. package/src/cli/controller/check-runner.ts +106 -3
  123. package/src/cli/controller/composite-result.ts +1 -1
  124. package/src/cli/controller/context/focus.ts +51 -0
  125. package/src/cli/controller/context/instruction-context.ts +114 -0
  126. package/src/cli/controller/context/known-paths.ts +76 -0
  127. package/src/cli/controller/context/query-planning.ts +110 -0
  128. package/src/cli/controller/context/rule-resolution.ts +532 -0
  129. package/src/cli/controller/context/source-materializer.ts +278 -0
  130. package/src/cli/controller/context/structural.ts +16 -0
  131. package/src/cli/controller/context/types.ts +286 -0
  132. package/src/cli/controller/context-pack.ts +898 -430
  133. package/src/cli/controller/launch-agents.ts +77 -2
  134. package/src/cli/controller/operational-plan.ts +2 -2
  135. package/src/cli/controller/work-mode.ts +6 -4
  136. package/src/cli/editing/edit-session.ts +60 -7
  137. package/src/cli/github/github.ts +133 -0
  138. package/src/cli/hook/prompt-guard-decision.ts +17 -18
  139. package/src/cli/hook/prompt-intents.ts +1 -1
  140. package/src/cli/installer/managed-entries.ts +15 -1
  141. package/src/cli/local-bridge/console-automations.ts +225 -67
  142. package/src/cli/local-bridge/console-view-models.ts +3 -35
  143. package/src/cli/local-bridge/facade-api.ts +2 -217
  144. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  145. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +153 -3
  146. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  147. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +183 -3
  148. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +1 -1
  149. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +4 -3
  150. package/src/cli/local-bridge/frontend/src/lib/api.ts +18 -11
  151. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  152. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +1 -1
  153. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +4 -3
  154. package/src/cli/local-bridge/frontend/src/styles.css +27 -11
  155. package/src/cli/local-bridge/frontend/src/types.ts +8 -7
  156. package/src/cli/local-bridge/job-store.ts +1 -490
  157. package/src/cli/local-bridge/server.ts +29 -532
  158. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  159. package/src/cli/local-bridge/ui-dist/app.js +5101 -4967
  160. package/src/cli/mcp/auth.ts +5 -5
  161. package/src/cli/mcp/instructions.ts +3 -2
  162. package/src/cli/mcp/legacy-tool-service.ts +0 -189
  163. package/src/cli/mcp/multi-repository.ts +16 -5
  164. package/src/cli/mcp/oauth.ts +175 -26
  165. package/src/cli/mcp/paths.ts +8 -2
  166. package/src/cli/mcp/repository-tools.ts +356 -101
  167. package/src/cli/mcp/server.ts +625 -54
  168. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  169. package/src/cli/mcp/toolset-selection.ts +1 -1
  170. package/src/cli/mcp/toolset.ts +42 -5
  171. package/src/cli/mcp/transports/http.ts +125 -15
  172. package/src/cli/mcp/transports/session-registry.ts +67 -5
  173. package/src/cli/mcp/types.ts +3 -3
  174. package/src/cli/repositories/command-classifier.ts +209 -14
  175. package/src/cli/repositories/command-executor.ts +55 -521
  176. package/src/cli/repositories/command-process.ts +108 -0
  177. package/src/cli/repositories/command-scope.ts +99 -41
  178. package/src/cli/repositories/controller-home.ts +7 -8
  179. package/src/cli/repositories/git-command-executor.ts +2 -0
  180. package/src/cli/repositories/locks.ts +53 -16
  181. package/src/cli/repositories/repository-snapshot.ts +121 -0
  182. package/src/cli/repositories/safe-patch.ts +6 -4
  183. package/src/cli/repositories/selected-path-actions.ts +17 -9
  184. package/src/cli/repositories/structured-git.ts +110 -10
  185. package/src/cli/repository/inspector.ts +565 -62
  186. package/src/cli/repository/session-cache.ts +58 -17
  187. package/src/cli/v81-entry.ts +1 -4
  188. package/src/effects/fs-transaction.ts +85 -17
  189. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  190. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  191. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  192. package/src/runtime/context/lsp-client.ts +283 -0
  193. package/src/runtime/context/semantic-navigation.ts +275 -0
  194. package/src/runtime/context/swift-navigation.ts +565 -0
  195. package/src/runtime/context/typescript-navigation.ts +226 -0
  196. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  197. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +196 -2
  198. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  199. package/src/runtime/control-plane/execution/execution-identity.ts +65 -14
  200. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  201. package/src/runtime/control-plane/execution/validation.ts +15 -2
  202. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  203. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  204. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  205. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  206. package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
  207. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +175 -8
  208. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -21
  209. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +87 -9
  210. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  211. package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
  212. package/src/runtime/control-plane/facade/controller-session-store.ts +144 -14
  213. package/src/runtime/control-plane/facade/goal-workloop-access.ts +9 -2
  214. package/src/runtime/control-plane/facade/goal-workloop.ts +885 -105
  215. package/src/runtime/control-plane/facade/index.ts +1 -0
  216. package/src/runtime/control-plane/facade/plan-contract-store.ts +211 -26
  217. package/src/runtime/control-plane/facade/requirement-board.ts +24 -8
  218. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  219. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  220. package/src/runtime/control-plane/facade/types.ts +55 -11
  221. package/src/runtime/control-plane/facade/work-contract-store.ts +204 -73
  222. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  223. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  224. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  225. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  226. package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
  227. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  228. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  229. package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
  230. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  231. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  232. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  233. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  234. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  235. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  236. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  237. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  238. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  239. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  240. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
  241. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +642 -51
  242. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
  243. package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
  244. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  245. package/src/runtime/control-plane/persistence/requirement-store.ts +15 -11
  246. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  247. package/src/runtime/control-plane/release-retention.ts +62 -2
  248. package/src/runtime/control-plane/routing/route-policy.ts +7 -16
  249. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  250. package/src/runtime/diagnostics/mcp-timing.ts +19 -0
  251. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  252. package/src/runtime/diagnostics/performance.ts +24 -1
  253. package/src/runtime/evidence/event-ledger.ts +1 -1
  254. package/src/runtime/execution/jobs/store.ts +58 -4
  255. package/src/runtime/execution/jobs/types.ts +1 -1
  256. package/src/runtime/execution/managed-workspace.ts +203 -1
  257. package/src/runtime/execution/process-runtime/check-facade.ts +52 -14
  258. package/src/runtime/execution/process-runtime/check-result.ts +63 -2
  259. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  260. package/src/runtime/execution/process-runtime/command-facade.ts +346 -40
  261. package/src/runtime/execution/process-runtime/gc.ts +98 -12
  262. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
  263. package/src/runtime/execution/process-runtime/persisted-check.ts +57 -6
  264. package/src/runtime/execution/process-runtime/resource-claims.ts +97 -8
  265. package/src/runtime/execution/process-runtime/runtime.ts +59 -4
  266. package/src/runtime/execution/process-runtime/store.ts +7 -0
  267. package/src/runtime/execution/process-runtime/types.ts +2 -0
  268. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  269. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  270. package/src/runtime/execution/workers/executor.ts +0 -48
  271. package/src/runtime/gateway/mcp/execution-tools.ts +1219 -126
  272. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  273. package/src/runtime/gateway/mcp/router.ts +185 -83
  274. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
  275. package/src/runtime/gateway/mcp/runtime-tools.ts +2555 -1616
  276. package/src/runtime/model-clients/deepseek-adapter.ts +0 -32
  277. package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
  278. package/src/runtime/plugins/browser-adapter.ts +2925 -318
  279. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  280. package/src/runtime/plugins/browser-handoff-host.ts +6 -31
  281. package/src/runtime/plugins/browser-handoff.ts +92 -5
  282. package/src/runtime/plugins/browser-macos-bridge.ts +586 -45
  283. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  284. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  285. package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
  286. package/src/runtime/plugins/browser-runtime.ts +183 -0
  287. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  288. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  289. package/src/runtime/plugins/desktop-operator-registration.ts +15 -12
  290. package/src/runtime/plugins/external-adapter.ts +324 -3
  291. package/src/runtime/plugins/external-registration.ts +84 -5
  292. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  293. package/src/runtime/plugins/first-party-registry.ts +4 -2
  294. package/src/runtime/plugins/github-adapter.ts +125 -0
  295. package/src/runtime/plugins/gmail-adapter.ts +28 -2
  296. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  297. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  298. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  299. package/src/runtime/plugins/ios-adapter.ts +106 -16
  300. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  301. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  302. package/src/runtime/plugins/lightweight-action.ts +153 -0
  303. package/src/runtime/plugins/local-system-adapter.ts +125 -8
  304. package/src/runtime/plugins/macos-capability-broker.ts +55 -9
  305. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  306. package/src/runtime/plugins/plugin-management-adapter.ts +181 -0
  307. package/src/runtime/plugins/resend-adapter.ts +1 -1
  308. package/src/runtime/plugins/store.ts +366 -24
  309. package/src/runtime/plugins/types.ts +39 -2
  310. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  311. package/src/runtime/projections/git-status-sampler.ts +16 -16
  312. package/src/runtime/projections/materialized-view.ts +28 -10
  313. package/src/runtime/recovery/actions.ts +0 -10
  314. package/src/runtime/recovery/classifier.ts +0 -9
  315. package/src/runtime/recovery/maintenance-executor.ts +415 -18
  316. package/src/runtime/recovery/probe.ts +0 -1
  317. package/src/runtime/recovery/types.ts +0 -1
  318. package/src/runtime/root/mcp-transport.ts +180 -10
  319. package/src/runtime/root/package-connector-service.ts +186 -8
  320. package/src/runtime/root/package-runtime-release.ts +143 -17
  321. package/src/runtime/root/package-runtime-service.ts +430 -22
  322. package/src/runtime/root/release-manifest.ts +7 -2
  323. package/src/runtime/root/release-materialize.ts +162 -19
  324. package/src/runtime/root/release-store.ts +48 -11
  325. package/src/runtime/root/runtime.ts +23 -5
  326. package/src/runtime/root/service.ts +100 -27
  327. package/src/runtime/root/types.ts +2 -0
  328. package/src/runtime/safe-tooling/index.ts +0 -1
  329. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  330. package/src/runtime/safe-tooling/types.ts +0 -30
  331. package/src/runtime/shared/trusted-executable.ts +62 -0
  332. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  333. package/src/runtime/standalone-recovery/core.ts +702 -94
  334. package/src/runtime/standalone-recovery/entry.ts +99 -23
  335. package/src/runtime/standalone-recovery/installer.ts +19 -3
  336. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  337. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  338. package/src/runtime/workflow/schedules/browser-probe.ts +258 -0
  339. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +40 -0
  340. package/src/runtime/workflow/schedules/engine.ts +509 -80
  341. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  342. package/src/runtime/workflow/schedules/settlement.ts +48 -4
  343. package/src/runtime/workflow/schedules/store.ts +131 -10
  344. package/src/runtime/workflow/schedules/types.ts +6 -3
  345. package/src/runtime/workflow/schedules/work-continuation.ts +120 -34
  346. package/src/testing/test-governance.ts +8 -40
  347. package/assets/reference-configs/heartbeat-triage.md +0 -57
  348. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  349. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  350. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  351. package/bin/forge-desktop-helper.mjs +0 -204
  352. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  353. package/docs/architecture/current/architecture-invariants.md +0 -331
  354. package/docs/architecture/current/automation-and-schedule-engine.md +0 -453
  355. package/docs/architecture/current/codegraph-context-provider.md +0 -226
  356. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  357. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  358. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -354
  359. package/docs/architecture/current/entity-model.md +0 -592
  360. package/docs/architecture/current/external-plugin-governance.md +0 -396
  361. package/docs/architecture/current/failure-recovery.md +0 -447
  362. package/docs/architecture/current/governance.md +0 -213
  363. package/docs/architecture/current/human-interaction-plane.md +0 -87
  364. package/docs/architecture/current/implementation-status.md +0 -128
  365. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  366. package/docs/architecture/current/legacy-governance.md +0 -58
  367. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  368. package/docs/architecture/current/migration-roadmap.md +0 -105
  369. package/docs/architecture/current/multi-repository-execution.md +0 -371
  370. package/docs/architecture/current/operations-runbook.md +0 -45
  371. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  372. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  373. package/docs/architecture/current/repository-access-modes.md +0 -102
  374. package/docs/architecture/current/route-policy.md +0 -15
  375. package/docs/architecture/current/route-session-concurrency.md +0 -85
  376. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  377. package/docs/architecture/current/runtime-directory-map.md +0 -49
  378. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  379. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  380. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  381. package/docs/architecture/current/system-overview.md +0 -144
  382. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  383. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  384. package/docs/architecture/current/thin-harness-v1.md +0 -311
  385. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  386. package/docs/forge-autonomous-goal-loop.md +0 -9
  387. package/docs/forge-provider-configuration.md +0 -7
  388. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -83
  389. package/docs/operations/controller-desktop-plugin.md +0 -112
  390. package/docs/operations/gmail-assistant-routines.md +0 -186
  391. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  392. package/docs/operations/provider-configuration.md +0 -31
  393. package/scripts/harness-trace-grade.sh +0 -150
  394. package/scripts/heartbeat-triage.sh +0 -361
  395. package/scripts/maintenance-triage.sh +0 -167
  396. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  397. package/src/cli/repositories/goal-registry.ts +0 -239
  398. package/src/runtime/assistant/action-proposals.ts +0 -279
  399. package/src/runtime/assistant/intent.ts +0 -395
  400. package/src/runtime/assistant/model-provider.ts +0 -460
  401. package/src/runtime/assistant/openapi.ts +0 -223
  402. package/src/runtime/assistant/policy.ts +0 -89
  403. package/src/runtime/assistant/readiness.ts +0 -258
  404. package/src/runtime/assistant/routine-runtime.ts +0 -477
  405. package/src/runtime/assistant/schedule-binding.ts +0 -171
  406. package/src/runtime/assistant/standing-grants.ts +0 -375
  407. package/src/runtime/assistant/store.ts +0 -243
  408. package/src/runtime/assistant/types.ts +0 -135
  409. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  410. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  411. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  412. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  413. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  414. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  415. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  416. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  417. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  418. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  419. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  420. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  421. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  422. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  423. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  424. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  425. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  426. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  427. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  428. package/src/runtime/plugins/desktop-adapter.ts +0 -374
  429. package/src/runtime/safe-tooling/browser-safe.ts +0 -133
  430. package/src/runtime/workflow/findings/store.ts +0 -204
  431. package/src/runtime/workflow/findings/types.ts +0 -58
  432. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  433. package/src/runtime/workflow/portfolio/store.ts +0 -185
  434. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -1,10 +1,8 @@
1
1
  import { createHash } from 'crypto';
2
- import { spawn, spawnSync, type ChildProcess } from 'child_process';
3
- import { appendFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync } from 'fs';
4
- import { dirname, join, resolve } from 'path';
2
+ import { spawnSync } from 'child_process';
3
+ import { appendFileSync, mkdirSync } from 'fs';
4
+ import { dirname, join } from 'path';
5
5
  import { capProcessOutput, redactProcessOutput } from '../../effects/process-runner';
6
- import { repositoryChildProcessEnvironment } from '../../runtime/shared/process-environment';
7
- import { terminateProcessTree } from '../../runtime/shared/process-tree';
8
6
  import { MAX_AGENT_TIMEOUT_MS, MIN_AGENT_TIMEOUT_MS } from '../controller/runtime-config';
9
7
  import { repositoryControllerRoot } from './controller-home';
10
8
  import {
@@ -22,8 +20,25 @@ import { commandValue, type CanonicalRepositoryCommand, type RepositoryCommandVa
22
20
  import { loadExternalFilesystemGrants } from '../../runtime/safe-tooling/external-filesystem';
23
21
  import type { RepositoryRecord } from './types';
24
22
  import { readRepositoryAccessPolicy } from '../../runtime/control-plane/governance/access-policy';
23
+ import { invalidateRepositoryReadCaches } from '../repository/inspector';
25
24
  import { assertResolvedAuthorization, decideAuthorization, type AuthorizationDecision } from '../../runtime/control-plane/governance/authorization';
26
25
  import { assertRuntimeMayWriteOrThrow } from '../../runtime/root/write-fence';
26
+ import { commandEnvironment, runCanonicalCommand, type RepositoryCommandAsyncHooks, type SpawnCommandResult } from './command-process';
27
+ import {
28
+ changedSnapshotPaths,
29
+ emptyWorkspaceSnapshot,
30
+ repositorySnapshot,
31
+ repositorySnapshotAsync,
32
+ snapshotChanged,
33
+ type RepositoryCommandSnapshot,
34
+ } from './repository-snapshot';
35
+
36
+ export type { RepositoryCommandSnapshot } from './repository-snapshot';
37
+ export type { RepositoryCommandAsyncHooks, SpawnCommandResult } from './command-process';
38
+ // Compatibility exports for supported callers; implementations live in their
39
+ // responsibility-owned modules.
40
+ export { runCanonicalCommand } from './command-process';
41
+ export { repositorySnapshotAsync } from './repository-snapshot';
27
42
 
28
43
  export { classifyRepositoryCommand } from './command-classifier';
29
44
  export type {
@@ -55,30 +70,10 @@ export interface ExecuteRepositoryCommandInput {
55
70
  reuseSnapshot?: RepositoryCommandSnapshot;
56
71
  /** Internal: explicit ephemeral workspace authority may operate without a Git repository. */
57
72
  allowNonGitWorkspace?: boolean;
73
+ /** Internal: command-facade already classified this as a bounded local script; retain policy/snapshot/audit without durable Process state. */
74
+ allowOpaqueLocalScript?: boolean;
58
75
  }
59
76
 
60
- export interface RepositoryCommandSnapshot {
61
- head: string | null;
62
- branch: string | null;
63
- /** User-owned workspace state. Harness runtime storage is intentionally excluded. */
64
- status: string;
65
- dirty: boolean;
66
- refsHash: string;
67
- paths: string[];
68
- pathFingerprints: Record<string, string>;
69
- }
70
-
71
- function emptyWorkspaceSnapshot(): RepositoryCommandSnapshot {
72
- return {
73
- head: null,
74
- branch: null,
75
- status: '',
76
- dirty: false,
77
- refsHash: createHash('sha256').update('').digest('hex'),
78
- paths: [],
79
- pathFingerprints: {},
80
- };
81
- }
82
77
 
83
78
  export interface RepositoryCommandExecution {
84
79
  status: 'preview' | 'approval_required' | 'executed';
@@ -113,21 +108,6 @@ export interface PreparedRepositoryCommandExecution {
113
108
  execution: RepositoryCommandExecution;
114
109
  }
115
110
 
116
- export interface SpawnCommandResult {
117
- ok: boolean;
118
- exitCode: number;
119
- timedOut: boolean;
120
- cancelled: boolean;
121
- stdout: string;
122
- stderr: string;
123
- }
124
-
125
- export interface RepositoryCommandAsyncHooks {
126
- onSpawn?: (pid: number) => void;
127
- onStdout?: (chunk: string) => void;
128
- onStderr?: (chunk: string) => void;
129
- signal?: AbortSignal;
130
- }
131
111
 
132
112
  /** Sensible interactive default for ordinary repository commands. */
133
113
  const DEFAULT_TIMEOUT_MS = 120_000;
@@ -154,460 +134,6 @@ function boundedInteger(value: number | undefined, fallback: number, minimum: nu
154
134
  return normalized;
155
135
  }
156
136
 
157
- function commandEnvironment(): NodeJS.ProcessEnv {
158
- const allowed = [
159
- 'PATH', 'HOME', 'USER', 'LOGNAME', 'TMPDIR', 'TMP', 'TEMP', 'SHELL',
160
- 'LANG', 'LC_ALL', 'TERM', 'SSH_AUTH_SOCK', 'GPG_TTY', 'XDG_CONFIG_HOME',
161
- ];
162
- const env: NodeJS.ProcessEnv = {};
163
- for (const key of allowed) if (process.env[key] !== undefined) env[key] = process.env[key];
164
- env.GIT_TERMINAL_PROMPT = '0';
165
- env.CI = '1';
166
- return repositoryChildProcessEnvironment(env);
167
- }
168
-
169
- function truncatedOutput(chunks: Buffer[], truncated: boolean, maxOutputBytes: number): string {
170
- const text = Buffer.concat(chunks).toString('utf8');
171
- const redacted = redactProcessOutput(text);
172
- return truncated ? capProcessOutput(redacted, maxOutputBytes) : redacted;
173
- }
174
-
175
- function collectOutput(maxOutputBytes: number): {
176
- write(chunk: string | Buffer): void;
177
- complete(): string;
178
- } {
179
- const chunks: Buffer[] = [];
180
- let totalBytes = 0;
181
- let truncated = false;
182
- return {
183
- write(chunk: string | Buffer) {
184
- const buffer = typeof chunk === 'string' ? Buffer.from(chunk, 'utf8') : chunk;
185
- if (buffer.length === 0) return;
186
- if (totalBytes >= maxOutputBytes) {
187
- truncated = true;
188
- return;
189
- }
190
- const remaining = maxOutputBytes - totalBytes;
191
- if (buffer.length <= remaining) {
192
- chunks.push(buffer);
193
- totalBytes += buffer.length;
194
- return;
195
- }
196
- chunks.push(buffer.subarray(0, remaining));
197
- totalBytes += remaining;
198
- truncated = true;
199
- },
200
- complete() {
201
- return truncatedOutput(chunks, truncated, maxOutputBytes);
202
- },
203
- };
204
- }
205
-
206
- async function killCommandTree(child: ChildProcess): Promise<void> {
207
- const pid = child.pid;
208
- if (!pid) return;
209
- try {
210
- if (process.platform !== 'win32') {
211
- try {
212
- process.kill(-pid, 'SIGTERM');
213
- } catch {
214
- child.kill('SIGTERM');
215
- }
216
- } else {
217
- child.kill();
218
- }
219
- } catch {
220
- /* already exited */
221
- }
222
- await terminateProcessTree(pid, { gracePeriodMs: 200, killAfterMs: 1_000, pollIntervalMs: 25 });
223
- }
224
-
225
- export async function runCanonicalCommand(
226
- command: CanonicalRepositoryCommand,
227
- cwd: string,
228
- timeoutMs: number,
229
- maxOutputBytes: number,
230
- hooks: RepositoryCommandAsyncHooks = {},
231
- ): Promise<SpawnCommandResult> {
232
- if (hooks.signal?.aborted) {
233
- return {
234
- ok: false,
235
- exitCode: 1,
236
- timedOut: false,
237
- cancelled: true,
238
- stdout: '',
239
- stderr: 'cancelled before spawn',
240
- };
241
- }
242
-
243
- const executable = command.kind === 'argv'
244
- ? command.executable!
245
- : process.platform === 'win32' ? 'cmd.exe' : '/bin/sh';
246
- const args = command.kind === 'argv'
247
- ? [...(command.args ?? [])]
248
- : process.platform === 'win32' ? ['/d', '/s', '/c', command.shellCommand!] : ['-c', command.shellCommand!];
249
- const display = typeof command.value === 'string' ? command.value : JSON.stringify(command.value);
250
- const stdoutCollector = collectOutput(maxOutputBytes);
251
- const stderrCollector = collectOutput(maxOutputBytes);
252
- const useProcessGroup = process.platform !== 'win32';
253
-
254
- return await new Promise<SpawnCommandResult>((resolve) => {
255
- const child = spawn(executable, args, {
256
- cwd,
257
- env: commandEnvironment(),
258
- stdio: ['ignore', 'pipe', 'pipe'],
259
- detached: useProcessGroup,
260
- });
261
- if (child.pid) hooks.onSpawn?.(child.pid);
262
- let settled = false;
263
- let timedOut = false;
264
- let cancelled = false;
265
- let spawnError = '';
266
- let timeoutHandle: NodeJS.Timeout | undefined;
267
-
268
- const finish = (exitCode: number) => {
269
- if (settled) return;
270
- settled = true;
271
- if (timeoutHandle) clearTimeout(timeoutHandle);
272
- hooks.signal?.removeEventListener('abort', onAbort);
273
- const stderrParts = [stderrCollector.complete()];
274
- if (timedOut) stderrParts.push(`process timed out after ${timeoutMs}ms: ${redactProcessOutput(display)}`);
275
- if (cancelled) stderrParts.push('process cancelled');
276
- if (spawnError) stderrParts.push(redactProcessOutput(spawnError));
277
- resolve({
278
- ok: exitCode === 0 && !timedOut && !cancelled && !spawnError,
279
- exitCode,
280
- timedOut,
281
- cancelled,
282
- stdout: stdoutCollector.complete(),
283
- stderr: capProcessOutput(stderrParts.filter(Boolean).join('\n'), maxOutputBytes),
284
- });
285
- };
286
-
287
- const onAbort = () => {
288
- cancelled = true;
289
- void killCommandTree(child).finally(() => finish(1));
290
- };
291
-
292
- timeoutHandle = setTimeout(() => {
293
- timedOut = true;
294
- void killCommandTree(child).finally(() => finish(1));
295
- }, timeoutMs);
296
- timeoutHandle.unref();
297
-
298
- hooks.signal?.addEventListener('abort', onAbort, { once: true });
299
-
300
- child.stdout?.on('data', (chunk) => {
301
- stdoutCollector.write(chunk);
302
- hooks.onStdout?.(chunk.toString());
303
- });
304
- child.stderr?.on('data', (chunk) => {
305
- stderrCollector.write(chunk);
306
- hooks.onStderr?.(chunk.toString());
307
- });
308
- child.on('error', (error) => {
309
- spawnError = error.message;
310
- });
311
- child.on('close', (code) => {
312
- finish(code ?? 1);
313
- });
314
- });
315
- }
316
-
317
- function commandOutput(command: string, args: string[], cwd: string, maxOutputBytes: number): {
318
- ok: boolean;
319
- stdout: string;
320
- } {
321
- const result = spawnSync(command, args, {
322
- cwd,
323
- env: commandEnvironment(),
324
- encoding: 'utf-8',
325
- stdio: ['ignore', 'pipe', 'pipe'],
326
- timeout: 10_000,
327
- maxBuffer: Math.max(maxOutputBytes, 1024 * 1024),
328
- });
329
- return {
330
- ok: result.status === 0 && !result.error,
331
- stdout: capProcessOutput(
332
- redactProcessOutput(typeof result.stdout === 'string' ? result.stdout : ''),
333
- maxOutputBytes,
334
- ),
335
- };
336
- }
337
-
338
- function gitText(root: string, args: string[]): string {
339
- const output = commandOutput('git', ['-C', root, ...args], root, 256 * 1024);
340
- return output.ok ? output.stdout.trim() : '';
341
- }
342
-
343
- const REPOSITORY_SNAPSHOT_PATHS = [
344
- '.',
345
- ':(exclude).ai/harness/**',
346
- ':(exclude)_ops/controller-home/**',
347
- ];
348
-
349
- function statusPath(line: string): string | undefined {
350
- if (!line.trim() || line.startsWith('##')) return undefined;
351
- const raw = line.length > 3 ? line.slice(3) : '';
352
- const path = raw.includes(' -> ') ? raw.split(' -> ').at(-1) : raw;
353
- return path?.replace(/^"|"$/g, '');
354
- }
355
-
356
- function repositoryPathFingerprint(root: string, relativePath: string, statusLines: string[]): string {
357
- const hash = createHash('sha256').update(statusLines.join('\n'));
358
- const absolute = resolve(root, relativePath);
359
- if (!existsSync(absolute)) return hash.update('\nmissing').digest('hex');
360
- try {
361
- const stat = lstatSync(absolute);
362
- if (stat.isSymbolicLink()) hash.update(`\nsymlink:${readlinkSync(absolute)}`);
363
- else if (stat.isFile()) hash.update('\nfile:').update(readFileSync(absolute));
364
- else hash.update(`\nmode:${stat.mode}:size:${stat.size}`);
365
- } catch (error) {
366
- hash.update(`\nunreadable:${error instanceof Error ? error.message : String(error)}`);
367
- }
368
- return hash.digest('hex');
369
- }
370
-
371
- function buildSnapshotFromGitTexts(
372
- root: string,
373
- head: string | null,
374
- branch: string | null,
375
- status: string,
376
- refs: string,
377
- ): RepositoryCommandSnapshot {
378
- const lines = status.split(/\r?\n/).filter((line) => line && !line.startsWith('##'));
379
- const statusByPath = new Map<string, string[]>();
380
- for (const line of lines) {
381
- const path = statusPath(line);
382
- if (!path) continue;
383
- const entries = statusByPath.get(path) ?? [];
384
- entries.push(line);
385
- statusByPath.set(path, entries);
386
- }
387
- const paths = [...statusByPath.keys()].sort();
388
- const pathFingerprints = Object.fromEntries(paths.map((path) => [
389
- path,
390
- repositoryPathFingerprint(root, path, statusByPath.get(path) ?? []),
391
- ]));
392
- return {
393
- head,
394
- branch,
395
- status,
396
- dirty: paths.length > 0,
397
- refsHash: createHash('sha256').update(refs).digest('hex'),
398
- paths,
399
- pathFingerprints,
400
- };
401
- }
402
-
403
- function repositorySnapshot(root: string): RepositoryCommandSnapshot {
404
- const head = gitText(root, ['rev-parse', '--verify', 'HEAD']) || null;
405
- const branch = gitText(root, ['branch', '--show-current']) || null;
406
- const status = gitText(root, ['status', '--porcelain=v1', '--branch', '--untracked-files=all', '--', ...REPOSITORY_SNAPSHOT_PATHS]);
407
- const refs = gitText(root, ['show-ref']);
408
- return buildSnapshotFromGitTexts(root, head, branch, status, refs);
409
- }
410
-
411
- interface GitTextAsyncResult {
412
- ok: boolean;
413
- exitCode: number;
414
- stdout: string;
415
- stderr: string;
416
- timedOut: boolean;
417
- cancelled: boolean;
418
- }
419
-
420
- async function gitTextAsync(root: string, args: string[], signal?: AbortSignal): Promise<GitTextAsyncResult> {
421
- if (signal?.aborted) {
422
- return { ok: false, exitCode: 1, stdout: '', stderr: 'cancelled', timedOut: false, cancelled: true };
423
- }
424
- return await new Promise<GitTextAsyncResult>((resolve) => {
425
- const child = spawn('git', ['-C', root, ...args], {
426
- cwd: root,
427
- env: commandEnvironment(),
428
- stdio: ['ignore', 'pipe', 'pipe'],
429
- detached: process.platform !== 'win32',
430
- });
431
- const stdout = collectOutput(256 * 1024);
432
- const stderr = collectOutput(64 * 1024);
433
- let settled = false;
434
- let timedOut = false;
435
- let cancelled = false;
436
- const timeout = setTimeout(() => {
437
- timedOut = true;
438
- void killCommandTree(child).finally(() => {
439
- if (!settled) {
440
- settled = true;
441
- resolve({
442
- ok: false,
443
- exitCode: 1,
444
- stdout: stdout.complete().trim(),
445
- stderr: `git ${args.join(' ')} timed out`,
446
- timedOut: true,
447
- cancelled: false,
448
- });
449
- }
450
- });
451
- }, 10_000);
452
- timeout.unref();
453
- const onAbort = () => {
454
- cancelled = true;
455
- void killCommandTree(child).finally(() => {
456
- if (!settled) {
457
- settled = true;
458
- clearTimeout(timeout);
459
- resolve({
460
- ok: false,
461
- exitCode: 1,
462
- stdout: '',
463
- stderr: 'cancelled',
464
- timedOut: false,
465
- cancelled: true,
466
- });
467
- }
468
- });
469
- };
470
- signal?.addEventListener('abort', onAbort, { once: true });
471
- child.stdout?.on('data', (chunk) => stdout.write(chunk));
472
- child.stderr?.on('data', (chunk) => stderr.write(chunk));
473
- child.on('error', (error) => {
474
- if (!settled) {
475
- settled = true;
476
- clearTimeout(timeout);
477
- signal?.removeEventListener('abort', onAbort);
478
- resolve({
479
- ok: false,
480
- exitCode: 1,
481
- stdout: '',
482
- stderr: error.message,
483
- timedOut: false,
484
- cancelled: false,
485
- });
486
- }
487
- });
488
- child.on('close', (code) => {
489
- if (settled) return;
490
- settled = true;
491
- clearTimeout(timeout);
492
- signal?.removeEventListener('abort', onAbort);
493
- const exitCode = code ?? 1;
494
- resolve({
495
- ok: exitCode === 0 && !timedOut && !cancelled,
496
- exitCode,
497
- stdout: stdout.complete().trim(),
498
- stderr: stderr.complete(),
499
- timedOut,
500
- cancelled,
501
- });
502
- });
503
- });
504
- }
505
-
506
- const MAX_DIRTY_PATHS_FOR_FINGERPRINT = 200;
507
- const MAX_FINGERPRINT_FILE_BYTES = 256 * 1024;
508
- const MAX_FINGERPRINT_TOTAL_BYTES = 8 * 1024 * 1024;
509
- const MAX_FINGERPRINT_WORKER_MS = 5_000;
510
-
511
- /**
512
- * Async repository snapshot for Fast Path — Git via async spawn, fail-closed.
513
- * Dirty path fingerprints run in a Worker Thread with hard budgets.
514
- * Over-budget / git errors → throw (caller may escalate to Durable).
515
- * Only unborn HEAD and empty refs are allowed non-zero exceptions.
516
- */
517
- export async function repositorySnapshotAsync(
518
- root: string,
519
- signal?: AbortSignal,
520
- ): Promise<RepositoryCommandSnapshot> {
521
- const [headResult, branchResult, statusResult, refsResult] = await Promise.all([
522
- gitTextAsync(root, ['rev-parse', '--verify', 'HEAD'], signal),
523
- gitTextAsync(root, ['branch', '--show-current'], signal),
524
- gitTextAsync(root, ['status', '--porcelain=v1', '--branch', '--untracked-files=all', '--', ...REPOSITORY_SNAPSHOT_PATHS], signal),
525
- gitTextAsync(root, ['show-ref'], signal),
526
- ]);
527
-
528
- if (statusResult.cancelled || headResult.cancelled || branchResult.cancelled || refsResult.cancelled) {
529
- throw new Error('CANCELLED: repository snapshot aborted');
530
- }
531
- if (statusResult.timedOut || headResult.timedOut) {
532
- throw new Error('SNAPSHOT_TIMEOUT: git snapshot timed out');
533
- }
534
- // status is required — fail closed on any non-ok (empty porcelain is ok only when ok=true)
535
- if (!statusResult.ok) {
536
- throw new Error(`SNAPSHOT_FAILED: git status exit ${statusResult.exitCode}: ${statusResult.stderr}`);
537
- }
538
-
539
- // unborn HEAD: rev-parse exit 128 / known messages → null; other failures fail-closed
540
- let head: string | null = null;
541
- if (headResult.ok) {
542
- head = headResult.stdout || null;
543
- } else {
544
- const unborn = headResult.exitCode === 128
545
- || /unknown revision|bad revision|Needed a single revision|ambiguous argument|not a valid object name/i.test(headResult.stderr);
546
- if (!unborn) {
547
- throw new Error(`SNAPSHOT_FAILED: git rev-parse HEAD exit ${headResult.exitCode}: ${headResult.stderr}`);
548
- }
549
- }
550
-
551
- // show-ref: empty repo has exit 1 with empty stdout — allowed; other failures fail-closed
552
- let refs = '';
553
- if (refsResult.ok) {
554
- refs = refsResult.stdout;
555
- } else if (refsResult.exitCode === 1 && !refsResult.stderr.trim()) {
556
- refs = refsResult.stdout || '';
557
- } else if (refsResult.exitCode === 1 && /expected|no match|no references/i.test(refsResult.stderr)) {
558
- refs = refsResult.stdout || '';
559
- } else {
560
- throw new Error(`SNAPSHOT_FAILED: git show-ref exit ${refsResult.exitCode}: ${refsResult.stderr}`);
561
- }
562
-
563
- // branch --show-current may fail on detached/unborn — null is fine when status worked
564
- const branch = branchResult.ok ? (branchResult.stdout || null) : null;
565
- const status = statusResult.stdout;
566
-
567
- if (signal?.aborted) throw new Error('CANCELLED: repository snapshot aborted');
568
-
569
- const lines = status.split(/\r?\n/).filter((line) => line && !line.startsWith('##'));
570
- if (lines.length > MAX_DIRTY_PATHS_FOR_FINGERPRINT) {
571
- throw new Error(`SNAPSHOT_TOO_DIRTY: ${lines.length} dirty paths exceeds Fast Path cap ${MAX_DIRTY_PATHS_FOR_FINGERPRINT}`);
572
- }
573
-
574
- const statusByPath = new Map<string, string[]>();
575
- for (const line of lines) {
576
- const path = statusPath(line);
577
- if (!path) continue;
578
- const entries = statusByPath.get(path) ?? [];
579
- entries.push(line);
580
- statusByPath.set(path, entries);
581
- }
582
- const paths = [...statusByPath.keys()].sort();
583
-
584
- // Offload fingerprint I/O to Worker Thread (or bounded sync for tiny sets).
585
- const { computePathFingerprintsAsync } = await import(
586
- '../../runtime/execution/thin-harness/fingerprint-worker'
587
- );
588
- const fingerprintResult = await computePathFingerprintsAsync(
589
- {
590
- root,
591
- paths,
592
- statusByPath: Object.fromEntries(statusByPath),
593
- maxFileBytes: MAX_FINGERPRINT_FILE_BYTES,
594
- maxTotalBytes: MAX_FINGERPRINT_TOTAL_BYTES,
595
- maxPaths: MAX_DIRTY_PATHS_FOR_FINGERPRINT,
596
- },
597
- { signal, timeoutMs: MAX_FINGERPRINT_WORKER_MS },
598
- );
599
-
600
- return {
601
- head,
602
- branch,
603
- status,
604
- dirty: paths.length > 0,
605
- refsHash: createHash('sha256').update(refs).digest('hex'),
606
- paths,
607
- pathFingerprints: fingerprintResult.pathFingerprints,
608
- };
609
- }
610
-
611
137
  function approvalToken(
612
138
  repository: RepositoryRecord,
613
139
  relativeCwd: string,
@@ -628,20 +154,6 @@ function approvalToken(
628
154
  })).digest('hex');
629
155
  }
630
156
 
631
- function changedSnapshotPaths(before: RepositoryCommandSnapshot, after: RepositoryCommandSnapshot): string[] {
632
- const paths = new Set([...before.paths, ...after.paths]);
633
- return [...paths].filter((path) =>
634
- before.pathFingerprints[path] !== after.pathFingerprints[path]
635
- ).sort();
636
- }
637
-
638
- function snapshotChanged(before: RepositoryCommandSnapshot, after: RepositoryCommandSnapshot): boolean {
639
- return before.head !== after.head
640
- || before.branch !== after.branch
641
- || before.refsHash !== after.refsHash
642
- || changedSnapshotPaths(before, after).length > 0;
643
- }
644
-
645
157
  function finalizePreparedExecution(
646
158
  repository: RepositoryRecord,
647
159
  input: ExecuteRepositoryCommandInput,
@@ -670,7 +182,11 @@ function finalizePreparedExecution(
670
182
  const isGit = command.kind === 'argv'
671
183
  ? command.executable?.split(/[\\/]/).at(-1)?.toLowerCase() === 'git'
672
184
  : /^\s*git\s+/i.test(command.shellCommand!);
673
- const risk = classification.risk === 'readonly' ? 'readonly' : classification.risk === 'remote_write' ? 'remote_write' : classification.risk === 'destructive' ? 'destructive' : isGit ? 'local_git' : 'workspace_write';
185
+ // Command classification is observability/replay metadata, not execution
186
+ // authority. Raw repository commands follow the host AI permission model once
187
+ // repository/cwd/external-path scope has been resolved. Typed catastrophic
188
+ // Forge operations retain their own explicit confirmation contracts.
189
+ const risk = isGit ? 'local_git' : 'local_command';
674
190
  const delegated = input.authorizationDecision ?? (controllerHome ? decideAuthorization({
675
191
  controllerHome,
676
192
  accessMode: permission?.mode ?? 'request',
@@ -701,16 +217,16 @@ function finalizePreparedExecution(
701
217
  approvalToken: token,
702
218
  authorization: input.authorization,
703
219
  before,
704
- policyDecision: input.dryRun === true || classification.risk === 'readonly'
220
+ policyDecision: input.dryRun === true || !controllerHome || effectiveDecision?.decision === 'allow'
705
221
  ? 'allowed'
706
- : effectiveDecision?.decision === 'allow' ? 'allowed' : 'approval_required',
222
+ : 'approval_required',
707
223
  ...(effectiveDecision ? { authorizationDecision: effectiveDecision } : {}),
708
224
  ...(effectiveDecision?.decision === 'user_confirmation_required' ? { approvalRequestId: effectiveDecision.approvalRequestId } : {}),
709
225
  externalPathUsages: externalPathUsages.length > 0 ? externalPathUsages : undefined,
710
226
  };
711
227
  const confirmed = input.authorization === 'confirmed_plan' && input.approvalToken === token;
712
- const delegatedAllowed = effectiveDecision?.decision === 'allow';
713
- const executable = input.dryRun === true || classification.risk === 'readonly' || confirmed || delegatedAllowed;
228
+ const delegatedAllowed = !controllerHome || effectiveDecision?.decision === 'allow';
229
+ const executable = input.dryRun === true || confirmed || delegatedAllowed;
714
230
  execution.policyDecision = executable ? 'allowed' : 'approval_required';
715
231
  return {
716
232
  root,
@@ -741,7 +257,9 @@ function prepareRepositoryCommandExecution(
741
257
  externalPathUsages: RepositoryCommandExternalPathUsage[];
742
258
  } {
743
259
  const { root, cwd, relativeCwd } = resolveRepositoryCommandCwd(repository, input.cwd);
744
- const command = assertRepositoryCommandInputAllowed(input.command);
260
+ const command = assertRepositoryCommandInputAllowed(input.command, {
261
+ allowOpaqueLocalScript: input.allowOpaqueLocalScript,
262
+ });
745
263
  const externalGrants = loadExternalFilesystemGrants(root).grants;
746
264
  const externalPathUsages = assertCommandPathOperandsStayInRepository(command, cwd, root, externalGrants);
747
265
  const classification = classifyRepositoryCommand(command, repository.defaultBranch);
@@ -776,11 +294,20 @@ async function prepareRepositoryCommandExecutionAsync(
776
294
  externalPathUsages: RepositoryCommandExternalPathUsage[];
777
295
  }> {
778
296
  const { root, cwd, relativeCwd } = resolveRepositoryCommandCwd(repository, input.cwd);
779
- const command = assertRepositoryCommandInputAllowed(input.command);
297
+ const command = assertRepositoryCommandInputAllowed(input.command, {
298
+ allowOpaqueLocalScript: input.allowOpaqueLocalScript,
299
+ });
780
300
  const externalGrants = loadExternalFilesystemGrants(root).grants;
781
301
  const externalPathUsages = assertCommandPathOperandsStayInRepository(command, cwd, root, externalGrants);
782
302
  const classification = classifyRepositoryCommand(command, repository.defaultBranch);
783
- const before = input.reuseSnapshot ?? (input.allowNonGitWorkspace ? emptyWorkspaceSnapshot() : await repositorySnapshotAsync(root, input.signal));
303
+ // A proven readonly direct/preview lane never compares before/after mutation.
304
+ // Skip per-path fingerprints there so a large dirty worktree cannot block a
305
+ // harmless read; write-capable lanes keep the strict fingerprint cap.
306
+ const before = input.reuseSnapshot ?? (input.allowNonGitWorkspace
307
+ ? emptyWorkspaceSnapshot()
308
+ : await repositorySnapshotAsync(root, input.signal, {
309
+ pathFingerprints: !(classification.risk === 'readonly' && controllerHome === undefined),
310
+ }));
784
311
  return finalizePreparedExecution(
785
312
  repository,
786
313
  input,
@@ -907,6 +434,7 @@ export function executeRepositoryCommand(
907
434
  message: error || `repository command failed with exit ${String(result.status ?? 1)}`,
908
435
  } : undefined,
909
436
  };
437
+ if (execution.repositoryChanged) invalidateRepositoryReadCaches(root);
910
438
  auditCommand(controllerHome, repository, execution);
911
439
  return execution;
912
440
  }
@@ -956,7 +484,12 @@ export async function executeRepositoryReadOnlyCommandDirect(
956
484
  }
957
485
 
958
486
  const result = await runCanonicalCommand(command, cwd, timeoutMs, maxOutputBytes, { signal });
959
- const after = input.allowNonGitWorkspace ? before : await repositorySnapshotAsync(root, signal?.aborted ? undefined : signal);
487
+ // This entry point is reachable only after the bounded classifier has proven
488
+ // the command readonly. A second full repository snapshot would start four
489
+ // extra Git processes merely to rediscover the invariant we already used to
490
+ // select this lane. Keep the captured pre-execution snapshot as both sides of
491
+ // the receipt; write-capable lanes still take independent before/after snapshots.
492
+ const after = before;
960
493
  return {
961
494
  ...base,
962
495
  status: 'executed',
@@ -967,8 +500,8 @@ export async function executeRepositoryReadOnlyCommandDirect(
967
500
  stdout: result.stdout,
968
501
  stderr: result.stderr,
969
502
  after,
970
- repositoryChanged: snapshotChanged(before, after),
971
- changedPaths: changedSnapshotPaths(before, after),
503
+ repositoryChanged: false,
504
+ changedPaths: [],
972
505
  policyDecision: 'allowed',
973
506
  externalPathUsages: externalPathUsages.length > 0 ? externalPathUsages : undefined,
974
507
  infrastructureError: result.timedOut
@@ -1061,6 +594,7 @@ export async function executeRepositoryCommandAsync(
1061
594
  }
1062
595
  : undefined,
1063
596
  };
597
+ if (execution.repositoryChanged) invalidateRepositoryReadCaches(root);
1064
598
  auditCommand(controllerHome, repository, execution);
1065
599
  return execution;
1066
600
  }