@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,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
  }