@moretea-labs/forge 1.6.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) hide show
  1. package/AGENTS.md +4 -0
  2. package/CHANGELOG.md +39 -0
  3. package/CLAUDE.md +4 -0
  4. package/README.md +2 -2
  5. package/README.zh-CN.md +1 -1
  6. package/SKILL.md +2 -2
  7. package/THIRD_PARTY_NOTICES.md +1 -1
  8. package/agents/openai.yaml +1 -1
  9. package/assets/hooks/hook-input.sh +11 -8
  10. package/assets/hooks/lib/session-state.sh +10 -7
  11. package/assets/hooks/lib/workflow-state.sh +13 -177
  12. package/assets/hooks/post-tool-observer.sh +8 -2
  13. package/assets/hooks/prompt-guard.sh +5 -81
  14. package/assets/hooks/session-start-context.sh +56 -1
  15. package/assets/partials/05-workflow.partial.md +1 -1
  16. package/assets/partials-agents/02-operating-mode.partial.md +2 -0
  17. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  18. package/assets/plugin-registry.v1.json +3 -3
  19. package/assets/reference-configs/document-generation.md +1 -1
  20. package/assets/reference-configs/harness-overview.md +1 -1
  21. package/assets/reference-configs/sprint-contracts.md +3 -3
  22. package/assets/skill-version.json +2 -2
  23. package/assets/templates/contract.template.md +2 -7
  24. package/assets/templates/helpers/capture-plan.sh +2 -2
  25. package/assets/templates/helpers/check-architecture-sync.sh +17 -0
  26. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  27. package/assets/templates/helpers/contract-worktree.sh +1 -21
  28. package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
  29. package/assets/templates/helpers/inspect-project-state.ts +8 -6
  30. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  31. package/assets/templates/helpers/migrate-workflow-docs.ts +9 -7
  32. package/assets/templates/helpers/new-plan.sh +1 -1
  33. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  34. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  35. package/assets/templates/helpers/verify-contract.sh +1 -111
  36. package/assets/templates/helpers/verify-sprint.sh +3 -180
  37. package/assets/templates/helpers/workflow-contract.ts +5 -1
  38. package/assets/templates/plan.template.md +1 -1
  39. package/assets/templates/review.template.md +9 -34
  40. package/assets/workflow-contract.v1.json +10 -19
  41. package/docs/README.md +10 -10
  42. package/docs/architecture/CURRENT.md +178 -0
  43. package/docs/architecture/EVOLUTION.md +42 -0
  44. package/docs/architecture/current/README.md +4 -156
  45. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  46. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  47. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  48. package/docs/architecture/history/README.md +7 -0
  49. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  50. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  51. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  52. package/docs/architecture/history.md +3 -34
  53. package/docs/architecture/index.md +15 -98
  54. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  55. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  56. package/docs/architecture/versions/1.6.md +38 -0
  57. package/docs/architecture/versions/1.7.md +41 -0
  58. package/docs/forge-chatgpt-browser-engine.md +2 -2
  59. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  60. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  61. package/docs/forge-github-issue-launcher.md +1 -1
  62. package/docs/forge-plugin-management.md +1 -1
  63. package/docs/forge-runtime-self-healing-loop.md +1 -1
  64. package/docs/operations/controller-browser-plugin.md +26 -6
  65. package/docs/operations/controller-performance-and-502.md +143 -0
  66. package/docs/operations/controller-reliability-runbook.md +1 -1
  67. package/docs/operations/features.md +2 -2
  68. package/docs/operations/github-repository.md +1 -1
  69. package/docs/operations/homebrew.md +2 -2
  70. package/docs/operations/mcp-tool-exposure.md +5 -3
  71. package/docs/wiki/Architecture.md +6 -6
  72. package/docs/wiki/Implementation.md +2 -2
  73. package/docs/wiki/Integrations.md +2 -2
  74. package/docs/wiki/Work-Lifecycle.md +1 -1
  75. package/package.json +33 -11
  76. package/scripts/activate-source-baseline.ts +17 -13
  77. package/scripts/architecture-event.ts +2 -820
  78. package/scripts/architecture-queue.sh +2 -645
  79. package/scripts/archive-architecture-request.sh +2 -242
  80. package/scripts/archive-workflow.sh +1 -291
  81. package/scripts/benchmark-background-check-overlap.ts +228 -0
  82. package/scripts/benchmark-mcp-host.sh +117 -0
  83. package/scripts/benchmark-mcp-transport.ts +170 -0
  84. package/scripts/benchmark-quality-harness.ts +146 -0
  85. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  86. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  87. package/scripts/benchmark-typescript-navigation.ts +186 -0
  88. package/scripts/benchmark-work-efficiency.ts +334 -0
  89. package/scripts/capability-config.ts +2 -392
  90. package/scripts/capability-resolver.ts +2 -503
  91. package/scripts/capture-plan.sh +1 -332
  92. package/scripts/check-agent-tooling.sh +2 -1558
  93. package/scripts/check-architecture-sync.sh +2 -371
  94. package/scripts/check-brain-manifest.sh +2 -253
  95. package/scripts/check-context-files.sh +2 -47
  96. package/scripts/check-deploy-sql-order.sh +2 -115
  97. package/scripts/check-mcp-compatibility.ts +14 -0
  98. package/scripts/check-runtime-architecture.mjs +349 -79
  99. package/scripts/check-skill-version.ts +3 -191
  100. package/scripts/check-source-duplication.mjs +61 -0
  101. package/scripts/check-stable-baseline.ts +92 -0
  102. package/scripts/check-task-sync.sh +2 -63
  103. package/scripts/check-task-workflow.sh +2 -1000
  104. package/scripts/codex-handoff-resume.sh +2 -267
  105. package/scripts/context-contract-sync.sh +2 -514
  106. package/scripts/contract-run.ts +2 -463
  107. package/scripts/contract-worktree.sh +1 -722
  108. package/scripts/ensure-task-workflow.sh +1 -1225
  109. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  110. package/scripts/inspect-project-state.ts +4 -279
  111. package/scripts/install-standalone-recovery.ts +24 -0
  112. package/scripts/lib/project-init-lib.sh +22 -46
  113. package/scripts/lib/run-forge-helper.sh +19 -0
  114. package/scripts/lib/run-forge-helper.ts +26 -0
  115. package/scripts/migrate-project-template.sh +1 -1189
  116. package/scripts/migrate-workflow-docs.ts +4 -412
  117. package/scripts/new-plan.sh +1 -180
  118. package/scripts/new-spec.sh +1 -47
  119. package/scripts/new-sprint.sh +1 -46
  120. package/scripts/plan-to-todo.sh +1 -796
  121. package/scripts/prepare-codex-handoff.sh +2 -201
  122. package/scripts/prepare-handoff.sh +1 -78
  123. package/scripts/refresh-current-status.sh +2 -467
  124. package/scripts/run-governed-gate.ts +5 -1
  125. package/scripts/select-agent-context-blocks.sh +2 -64
  126. package/scripts/ship-worktrees.sh +1 -558
  127. package/scripts/smoke-ios-physical-device.ts +4 -2
  128. package/scripts/smoke-runtime-recovery.ts +1 -35
  129. package/scripts/smoke-schedule-engine.ts +1 -20
  130. package/scripts/sprint-backlog.sh +1 -834
  131. package/scripts/stage-runtime-release.ts +2 -1
  132. package/scripts/summarize-failures.sh +2 -123
  133. package/scripts/switch-plan.sh +1 -150
  134. package/scripts/sync-brain-docs.sh +2 -379
  135. package/scripts/sync-generated-authority.mjs +175 -0
  136. package/scripts/verify-contract.sh +2 -703
  137. package/scripts/verify-forge-runtime.sh +0 -1
  138. package/scripts/verify-sprint.sh +1 -677
  139. package/scripts/workflow-contract.ts +4 -205
  140. package/scripts/workstream-sync.sh +2 -261
  141. package/src/cli/agent-jobs/executor-health.ts +1 -1
  142. package/src/cli/commands/init-hook.ts +3 -3
  143. package/src/cli/commands/mcp.ts +9 -5
  144. package/src/cli/commands/plugin.ts +12 -3
  145. package/src/cli/commands/recovery.ts +25 -3
  146. package/src/cli/commands/runtime.ts +55 -3
  147. package/src/cli/commands/security.ts +12 -1
  148. package/src/cli/commands/setup-profile.ts +31 -21
  149. package/src/cli/controller/check-runner.ts +113 -5
  150. package/src/cli/controller/composite-result.ts +1 -1
  151. package/src/cli/controller/context/focus.ts +51 -0
  152. package/src/cli/controller/context/instruction-context.ts +114 -0
  153. package/src/cli/controller/context/known-paths.ts +76 -0
  154. package/src/cli/controller/context/query-planning.ts +110 -0
  155. package/src/cli/controller/context/rule-resolution.ts +532 -0
  156. package/src/cli/controller/context/source-materializer.ts +278 -0
  157. package/src/cli/controller/context/structural.ts +16 -0
  158. package/src/cli/controller/context/types.ts +286 -0
  159. package/src/cli/controller/context-pack.ts +828 -487
  160. package/src/cli/controller/launch-agents.ts +77 -2
  161. package/src/cli/controller/operational-plan.ts +2 -2
  162. package/src/cli/controller/work-mode.ts +6 -4
  163. package/src/cli/editing/edit-session.ts +60 -7
  164. package/src/cli/github/contracts.ts +16 -0
  165. package/src/cli/github/github.ts +135 -16
  166. package/src/cli/github/session.ts +2 -1
  167. package/src/cli/installer/managed-entries.ts +15 -1
  168. package/src/cli/local-bridge/console-automations.ts +54 -78
  169. package/src/cli/local-bridge/console-view-models.ts +1 -35
  170. package/src/cli/local-bridge/facade-api.ts +37 -264
  171. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  172. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
  173. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  174. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
  175. package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
  176. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  177. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
  178. package/src/cli/local-bridge/frontend/src/styles.css +1 -1
  179. package/src/cli/local-bridge/frontend/src/types.ts +5 -8
  180. package/src/cli/local-bridge/job-store.ts +1 -490
  181. package/src/cli/local-bridge/server.ts +2 -515
  182. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  183. package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
  184. package/src/cli/mcp/auth.ts +5 -5
  185. package/src/cli/mcp/instructions.ts +3 -2
  186. package/src/cli/mcp/legacy-tool-service.ts +10 -222
  187. package/src/cli/mcp/multi-repository.ts +16 -5
  188. package/src/cli/mcp/oauth.ts +175 -26
  189. package/src/cli/mcp/post-finalize-work-attribution.ts +150 -0
  190. package/src/cli/mcp/repository-tools.ts +282 -146
  191. package/src/cli/mcp/server.ts +586 -116
  192. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  193. package/src/cli/mcp/tool-contract.ts +14 -0
  194. package/src/cli/mcp/toolset-selection.ts +1 -1
  195. package/src/cli/mcp/toolset.ts +44 -7
  196. package/src/cli/mcp/transports/http.ts +124 -14
  197. package/src/cli/mcp/transports/session-registry.ts +43 -5
  198. package/src/cli/mcp/types.ts +3 -3
  199. package/src/cli/repositories/command-classifier.ts +103 -21
  200. package/src/cli/repositories/command-executor.ts +78 -526
  201. package/src/cli/repositories/command-process.ts +108 -0
  202. package/src/cli/repositories/command-scope.ts +99 -41
  203. package/src/cli/repositories/controller-home.ts +7 -8
  204. package/src/cli/repositories/git-command-executor.ts +2 -0
  205. package/src/cli/repositories/locks.ts +53 -16
  206. package/src/cli/repositories/repository-snapshot.ts +124 -0
  207. package/src/cli/repositories/selected-path-actions.ts +17 -9
  208. package/src/cli/repositories/structured-git.ts +118 -6
  209. package/src/cli/repository/inspector.ts +563 -65
  210. package/src/cli/repository/session-cache.ts +58 -17
  211. package/src/cli/v81-entry.ts +1 -4
  212. package/src/effects/fs-transaction.ts +85 -17
  213. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  214. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  215. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  216. package/src/runtime/context/lsp-client.ts +283 -0
  217. package/src/runtime/context/semantic-navigation-contract.ts +61 -0
  218. package/src/runtime/context/semantic-navigation.ts +223 -0
  219. package/src/runtime/context/swift-navigation.ts +565 -0
  220. package/src/runtime/context/typescript-navigation.ts +226 -0
  221. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  222. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +48 -16
  223. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  224. package/src/runtime/control-plane/execution/repository-work-attribution.ts +106 -0
  225. package/src/runtime/control-plane/execution/retained-work-resume.ts +203 -0
  226. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  227. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  228. package/src/runtime/control-plane/execution/work-completion-authority.ts +58 -0
  229. package/src/runtime/control-plane/execution/work-evidence-policy.ts +200 -0
  230. package/src/runtime/control-plane/execution/work-execution-support.ts +386 -0
  231. package/src/runtime/control-plane/execution/work-finalization-service.ts +1913 -0
  232. package/src/runtime/control-plane/execution/work-handle-authority.ts +96 -0
  233. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  234. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  235. package/src/runtime/control-plane/execution/work-operation-service.ts +468 -0
  236. package/src/runtime/control-plane/execution/work-path-scope.ts +37 -0
  237. package/src/runtime/control-plane/execution/work-preparation-service.ts +479 -0
  238. package/src/runtime/control-plane/execution/work-process-evidence.ts +129 -0
  239. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  240. package/src/runtime/control-plane/execution/work-task-receipt.ts +22 -18
  241. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +156 -11
  242. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
  243. package/src/runtime/control-plane/execution/work-verification-context.ts +56 -0
  244. package/src/runtime/control-plane/execution/work-verification-service.ts +388 -0
  245. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +90 -9
  246. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  247. package/src/runtime/control-plane/facade/controller-round-relay.ts +1118 -0
  248. package/src/runtime/control-plane/facade/controller-session-store.ts +302 -12
  249. package/src/runtime/control-plane/facade/goal-workloop-access.ts +17 -1
  250. package/src/runtime/control-plane/facade/goal-workloop.ts +928 -190
  251. package/src/runtime/control-plane/facade/index.ts +3 -0
  252. package/src/runtime/control-plane/facade/plan-contract-store.ts +275 -42
  253. package/src/runtime/control-plane/facade/repository-work-admission.ts +147 -0
  254. package/src/runtime/control-plane/facade/requirement-authority.ts +83 -0
  255. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  256. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  257. package/src/runtime/control-plane/facade/types.ts +90 -10
  258. package/src/runtime/control-plane/facade/work-contract-store.ts +157 -261
  259. package/src/runtime/control-plane/facade/work-state-machine.ts +250 -0
  260. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  261. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  262. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  263. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  264. package/src/runtime/control-plane/global-scheduler/maintenance.ts +152 -0
  265. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  266. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  267. package/src/runtime/control-plane/global-scheduler/scheduler.ts +278 -543
  268. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  269. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  270. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  271. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  272. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  273. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  274. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  275. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  276. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  277. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  278. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +63 -0
  279. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +365 -117
  280. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +89 -0
  281. package/src/runtime/control-plane/launcher/thin-launcher.ts +244 -11
  282. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  283. package/src/runtime/control-plane/persistence/requirement-store.ts +0 -12
  284. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  285. package/src/runtime/control-plane/release-retention.ts +62 -2
  286. package/src/runtime/control-plane/repo-actor/registry.ts +4 -2
  287. package/src/runtime/control-plane/routing/route-policy.ts +24 -21
  288. package/src/runtime/control-plane/routing/workspace-admission.ts +70 -0
  289. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  290. package/src/runtime/diagnostics/mcp-timing.ts +13 -0
  291. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  292. package/src/runtime/diagnostics/performance.ts +24 -1
  293. package/src/runtime/evidence/event-ledger.ts +1 -1
  294. package/src/runtime/evidence/process-check-execution.ts +18 -0
  295. package/src/runtime/execution/jobs/child-reference.ts +6 -2
  296. package/src/runtime/execution/jobs/store.ts +58 -4
  297. package/src/runtime/execution/jobs/types.ts +1 -1
  298. package/src/runtime/execution/managed-workspace.ts +203 -1
  299. package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
  300. package/src/runtime/execution/process-runtime/check-result.ts +88 -0
  301. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +7 -1
  302. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  303. package/src/runtime/execution/process-runtime/command-facade.ts +367 -40
  304. package/src/runtime/execution/process-runtime/gc.ts +38 -6
  305. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1201 -0
  306. package/src/runtime/execution/process-runtime/persisted-check.ts +51 -5
  307. package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
  308. package/src/runtime/execution/process-runtime/runtime.ts +53 -3
  309. package/src/runtime/execution/process-runtime/store.ts +1 -0
  310. package/src/runtime/execution/process-runtime/types.ts +4 -12
  311. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  312. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  313. package/src/runtime/execution/work-activity.ts +21 -0
  314. package/src/runtime/execution/workers/executor.ts +18 -61
  315. package/src/runtime/gateway/mcp/execution-tools.ts +35 -1852
  316. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +61 -0
  317. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  318. package/src/runtime/gateway/mcp/router.ts +186 -84
  319. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +565 -0
  320. package/src/runtime/gateway/mcp/runtime-tools.ts +2378 -1947
  321. package/src/runtime/plugins/app-store-connect-adapter.ts +216 -47
  322. package/src/runtime/plugins/browser-adapter.ts +2662 -829
  323. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  324. package/src/runtime/plugins/browser-handoff-host.ts +2 -25
  325. package/src/runtime/plugins/browser-handoff.ts +110 -5
  326. package/src/runtime/plugins/browser-macos-bridge.ts +701 -63
  327. package/src/runtime/plugins/browser-manifest-surface.ts +603 -0
  328. package/src/runtime/plugins/browser-native-session-service.ts +222 -0
  329. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  330. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  331. package/src/runtime/plugins/browser-runtime-contract.ts +154 -0
  332. package/src/runtime/plugins/browser-runtime.ts +193 -0
  333. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  334. package/src/runtime/plugins/browser-session-store.ts +121 -0
  335. package/src/runtime/plugins/browser-session-types.ts +98 -0
  336. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  337. package/src/runtime/plugins/desktop-operator-registration.ts +10 -4
  338. package/src/runtime/plugins/external-adapter.ts +358 -3
  339. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  340. package/src/runtime/plugins/first-party-registry.ts +2 -0
  341. package/src/runtime/plugins/github-adapter.ts +125 -0
  342. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  343. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  344. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  345. package/src/runtime/plugins/ios-adapter.ts +106 -16
  346. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  347. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  348. package/src/runtime/plugins/lightweight-action.ts +153 -0
  349. package/src/runtime/plugins/local-system-adapter.ts +123 -6
  350. package/src/runtime/plugins/macos-capability-broker.ts +47 -9
  351. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  352. package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
  353. package/src/runtime/plugins/resend-adapter.ts +1 -1
  354. package/src/runtime/plugins/store.ts +330 -33
  355. package/src/runtime/plugins/types.ts +38 -3
  356. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  357. package/src/runtime/projections/git-status-sampler.ts +16 -16
  358. package/src/runtime/projections/materialized-view.ts +28 -10
  359. package/src/runtime/recovery/maintenance-executor.ts +426 -25
  360. package/src/runtime/root/mcp-transport.ts +180 -10
  361. package/src/runtime/root/package-connector-service.ts +230 -10
  362. package/src/runtime/root/package-runtime-release.ts +143 -17
  363. package/src/runtime/root/package-runtime-service.ts +461 -22
  364. package/src/runtime/root/release-manifest.ts +144 -18
  365. package/src/runtime/root/release-materialize.ts +125 -32
  366. package/src/runtime/root/release-store.ts +48 -11
  367. package/src/runtime/root/runtime.ts +23 -5
  368. package/src/runtime/root/service.ts +100 -29
  369. package/src/runtime/root/types.ts +12 -0
  370. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  371. package/src/runtime/shared/trusted-executable.ts +62 -0
  372. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  373. package/src/runtime/standalone-recovery/core.ts +1079 -240
  374. package/src/runtime/standalone-recovery/entry.ts +100 -24
  375. package/src/runtime/standalone-recovery/installer.ts +20 -4
  376. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  377. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  378. package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
  379. package/src/runtime/workflow/schedules/engine.ts +364 -202
  380. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  381. package/src/runtime/workflow/schedules/settlement.ts +64 -17
  382. package/src/runtime/workflow/schedules/store.ts +209 -19
  383. package/src/runtime/workflow/schedules/types.ts +2 -4
  384. package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
  385. package/src/testing/test-governance.ts +8 -40
  386. package/assets/reference-configs/heartbeat-triage.md +0 -57
  387. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  388. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  389. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  390. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  391. package/docs/architecture/current/architecture-invariants.md +0 -331
  392. package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
  393. package/docs/architecture/current/codegraph-context-provider.md +0 -238
  394. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  395. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  396. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
  397. package/docs/architecture/current/entity-model.md +0 -612
  398. package/docs/architecture/current/external-plugin-governance.md +0 -396
  399. package/docs/architecture/current/failure-recovery.md +0 -447
  400. package/docs/architecture/current/governance.md +0 -213
  401. package/docs/architecture/current/human-interaction-plane.md +0 -87
  402. package/docs/architecture/current/implementation-status.md +0 -128
  403. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  404. package/docs/architecture/current/legacy-governance.md +0 -58
  405. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  406. package/docs/architecture/current/migration-roadmap.md +0 -105
  407. package/docs/architecture/current/multi-repository-execution.md +0 -371
  408. package/docs/architecture/current/operations-runbook.md +0 -45
  409. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  410. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  411. package/docs/architecture/current/repository-access-modes.md +0 -102
  412. package/docs/architecture/current/route-policy.md +0 -15
  413. package/docs/architecture/current/route-session-concurrency.md +0 -85
  414. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  415. package/docs/architecture/current/runtime-directory-map.md +0 -49
  416. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  417. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  418. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  419. package/docs/architecture/current/system-overview.md +0 -169
  420. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  421. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  422. package/docs/architecture/current/thin-harness-v1.md +0 -321
  423. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  424. package/docs/forge-autonomous-goal-loop.md +0 -9
  425. package/docs/forge-provider-configuration.md +0 -7
  426. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
  427. package/docs/operations/gmail-assistant-routines.md +0 -186
  428. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  429. package/docs/operations/provider-configuration.md +0 -31
  430. package/scripts/harness-trace-grade.sh +0 -150
  431. package/scripts/heartbeat-triage.sh +0 -361
  432. package/scripts/maintenance-triage.sh +0 -167
  433. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  434. package/src/cli/repositories/goal-registry.ts +0 -239
  435. package/src/runtime/assistant/action-proposals.ts +0 -279
  436. package/src/runtime/assistant/intent.ts +0 -395
  437. package/src/runtime/assistant/model-provider.ts +0 -460
  438. package/src/runtime/assistant/openapi.ts +0 -223
  439. package/src/runtime/assistant/policy.ts +0 -89
  440. package/src/runtime/assistant/readiness.ts +0 -258
  441. package/src/runtime/assistant/routine-runtime.ts +0 -477
  442. package/src/runtime/assistant/schedule-binding.ts +0 -171
  443. package/src/runtime/assistant/standing-grants.ts +0 -375
  444. package/src/runtime/assistant/store.ts +0 -243
  445. package/src/runtime/assistant/types.ts +0 -135
  446. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  447. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  448. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  449. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  450. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  451. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  452. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  453. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  454. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  455. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  456. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  457. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  458. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  459. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  460. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  461. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  462. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  463. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  464. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  465. package/src/runtime/workflow/findings/store.ts +0 -204
  466. package/src/runtime/workflow/findings/types.ts +0 -58
  467. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  468. package/src/runtime/workflow/portfolio/store.ts +0 -185
  469. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -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,12 @@ 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;
75
+ /** Internal/test seam: bound dirty-path fingerprint enrichment independently from child execution lifetime. */
76
+ snapshotFingerprintTimeoutMs?: number;
58
77
  }
59
78
 
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
79
 
83
80
  export interface RepositoryCommandExecution {
84
81
  status: 'preview' | 'approval_required' | 'executed';
@@ -103,6 +100,8 @@ export interface RepositoryCommandExecution {
103
100
  authorizationDecision?: AuthorizationDecision;
104
101
  approvalRequestId?: string;
105
102
  infrastructureError?: { code: string; message: string };
103
+ /** Child execution completed, but post-execution repository evidence could not be proven. Mutation state is unknown until a later authoritative inspection. */
104
+ evidenceError?: { code: string; message: string };
106
105
  externalPathUsages?: RepositoryCommandExternalPathUsage[];
107
106
  }
108
107
 
@@ -113,21 +112,6 @@ export interface PreparedRepositoryCommandExecution {
113
112
  execution: RepositoryCommandExecution;
114
113
  }
115
114
 
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
115
 
132
116
  /** Sensible interactive default for ordinary repository commands. */
133
117
  const DEFAULT_TIMEOUT_MS = 120_000;
@@ -154,460 +138,6 @@ function boundedInteger(value: number | undefined, fallback: number, minimum: nu
154
138
  return normalized;
155
139
  }
156
140
 
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
141
  function approvalToken(
612
142
  repository: RepositoryRecord,
613
143
  relativeCwd: string,
@@ -628,20 +158,6 @@ function approvalToken(
628
158
  })).digest('hex');
629
159
  }
630
160
 
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
161
  function finalizePreparedExecution(
646
162
  repository: RepositoryRecord,
647
163
  input: ExecuteRepositoryCommandInput,
@@ -670,7 +186,11 @@ function finalizePreparedExecution(
670
186
  const isGit = command.kind === 'argv'
671
187
  ? command.executable?.split(/[\\/]/).at(-1)?.toLowerCase() === 'git'
672
188
  : /^\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';
189
+ // Command classification is observability/replay metadata, not execution
190
+ // authority. Raw repository commands follow the host AI permission model once
191
+ // repository/cwd/external-path scope has been resolved. Typed catastrophic
192
+ // Forge operations retain their own explicit confirmation contracts.
193
+ const risk = isGit ? 'local_git' : 'local_command';
674
194
  const delegated = input.authorizationDecision ?? (controllerHome ? decideAuthorization({
675
195
  controllerHome,
676
196
  accessMode: permission?.mode ?? 'request',
@@ -701,16 +221,16 @@ function finalizePreparedExecution(
701
221
  approvalToken: token,
702
222
  authorization: input.authorization,
703
223
  before,
704
- policyDecision: input.dryRun === true || classification.risk === 'readonly'
224
+ policyDecision: input.dryRun === true || !controllerHome || effectiveDecision?.decision === 'allow'
705
225
  ? 'allowed'
706
- : effectiveDecision?.decision === 'allow' ? 'allowed' : 'approval_required',
226
+ : 'approval_required',
707
227
  ...(effectiveDecision ? { authorizationDecision: effectiveDecision } : {}),
708
228
  ...(effectiveDecision?.decision === 'user_confirmation_required' ? { approvalRequestId: effectiveDecision.approvalRequestId } : {}),
709
229
  externalPathUsages: externalPathUsages.length > 0 ? externalPathUsages : undefined,
710
230
  };
711
231
  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;
232
+ const delegatedAllowed = !controllerHome || effectiveDecision?.decision === 'allow';
233
+ const executable = input.dryRun === true || confirmed || delegatedAllowed;
714
234
  execution.policyDecision = executable ? 'allowed' : 'approval_required';
715
235
  return {
716
236
  root,
@@ -741,7 +261,9 @@ function prepareRepositoryCommandExecution(
741
261
  externalPathUsages: RepositoryCommandExternalPathUsage[];
742
262
  } {
743
263
  const { root, cwd, relativeCwd } = resolveRepositoryCommandCwd(repository, input.cwd);
744
- const command = assertRepositoryCommandInputAllowed(input.command);
264
+ const command = assertRepositoryCommandInputAllowed(input.command, {
265
+ allowOpaqueLocalScript: input.allowOpaqueLocalScript,
266
+ });
745
267
  const externalGrants = loadExternalFilesystemGrants(root).grants;
746
268
  const externalPathUsages = assertCommandPathOperandsStayInRepository(command, cwd, root, externalGrants);
747
269
  const classification = classifyRepositoryCommand(command, repository.defaultBranch);
@@ -776,11 +298,21 @@ async function prepareRepositoryCommandExecutionAsync(
776
298
  externalPathUsages: RepositoryCommandExternalPathUsage[];
777
299
  }> {
778
300
  const { root, cwd, relativeCwd } = resolveRepositoryCommandCwd(repository, input.cwd);
779
- const command = assertRepositoryCommandInputAllowed(input.command);
301
+ const command = assertRepositoryCommandInputAllowed(input.command, {
302
+ allowOpaqueLocalScript: input.allowOpaqueLocalScript,
303
+ });
780
304
  const externalGrants = loadExternalFilesystemGrants(root).grants;
781
305
  const externalPathUsages = assertCommandPathOperandsStayInRepository(command, cwd, root, externalGrants);
782
306
  const classification = classifyRepositoryCommand(command, repository.defaultBranch);
783
- const before = input.reuseSnapshot ?? (input.allowNonGitWorkspace ? emptyWorkspaceSnapshot() : await repositorySnapshotAsync(root, input.signal));
307
+ // A proven readonly direct/preview lane never compares before/after mutation.
308
+ // Skip per-path fingerprints there so a large dirty worktree cannot block a
309
+ // harmless read; write-capable lanes keep the strict fingerprint cap.
310
+ const before = input.reuseSnapshot ?? (input.allowNonGitWorkspace
311
+ ? emptyWorkspaceSnapshot()
312
+ : await repositorySnapshotAsync(root, input.signal, {
313
+ pathFingerprints: !(classification.risk === 'readonly' && controllerHome === undefined),
314
+ fingerprintTimeoutMs: input.snapshotFingerprintTimeoutMs,
315
+ }));
784
316
  return finalizePreparedExecution(
785
317
  repository,
786
318
  input,
@@ -907,6 +439,7 @@ export function executeRepositoryCommand(
907
439
  message: error || `repository command failed with exit ${String(result.status ?? 1)}`,
908
440
  } : undefined,
909
441
  };
442
+ if (execution.repositoryChanged) invalidateRepositoryReadCaches(root);
910
443
  auditCommand(controllerHome, repository, execution);
911
444
  return execution;
912
445
  }
@@ -956,7 +489,12 @@ export async function executeRepositoryReadOnlyCommandDirect(
956
489
  }
957
490
 
958
491
  const result = await runCanonicalCommand(command, cwd, timeoutMs, maxOutputBytes, { signal });
959
- const after = input.allowNonGitWorkspace ? before : await repositorySnapshotAsync(root, signal?.aborted ? undefined : signal);
492
+ // This entry point is reachable only after the bounded classifier has proven
493
+ // the command readonly. A second full repository snapshot would start four
494
+ // extra Git processes merely to rediscover the invariant we already used to
495
+ // select this lane. Keep the captured pre-execution snapshot as both sides of
496
+ // the receipt; write-capable lanes still take independent before/after snapshots.
497
+ const after = before;
960
498
  return {
961
499
  ...base,
962
500
  status: 'executed',
@@ -967,8 +505,8 @@ export async function executeRepositoryReadOnlyCommandDirect(
967
505
  stdout: result.stdout,
968
506
  stderr: result.stderr,
969
507
  after,
970
- repositoryChanged: snapshotChanged(before, after),
971
- changedPaths: changedSnapshotPaths(before, after),
508
+ repositoryChanged: false,
509
+ changedPaths: [],
972
510
  policyDecision: 'allowed',
973
511
  externalPathUsages: externalPathUsages.length > 0 ? externalPathUsages : undefined,
974
512
  infrastructureError: result.timedOut
@@ -1032,9 +570,19 @@ export async function executeRepositoryCommandAsync(
1032
570
  ...hooks,
1033
571
  signal,
1034
572
  });
1035
- const after = input.allowNonGitWorkspace
1036
- ? before
1037
- : await repositorySnapshotAsync(root, signal?.aborted ? undefined : signal);
573
+ let after: RepositoryCommandSnapshot | undefined = input.allowNonGitWorkspace ? before : undefined;
574
+ let evidenceError: RepositoryCommandExecution['evidenceError'];
575
+ if (!input.allowNonGitWorkspace) {
576
+ try {
577
+ after = await repositorySnapshotAsync(root, signal?.aborted ? undefined : signal, {
578
+ fingerprintTimeoutMs: input.snapshotFingerprintTimeoutMs,
579
+ });
580
+ } catch (error) {
581
+ const message = error instanceof Error ? error.message : String(error);
582
+ const code = message.includes(':') ? message.slice(0, message.indexOf(':')) : 'POST_EXECUTION_EVIDENCE_FAILED';
583
+ evidenceError = { code, message };
584
+ }
585
+ }
1038
586
  const execution: RepositoryCommandExecution = {
1039
587
  ...base,
1040
588
  status: 'executed',
@@ -1045,8 +593,9 @@ export async function executeRepositoryCommandAsync(
1045
593
  stdout: result.stdout,
1046
594
  stderr: result.stderr,
1047
595
  after,
1048
- repositoryChanged: snapshotChanged(before, after),
1049
- changedPaths: changedSnapshotPaths(before, after),
596
+ repositoryChanged: after ? snapshotChanged(before, after) : undefined,
597
+ changedPaths: after ? changedSnapshotPaths(before, after) : undefined,
598
+ evidenceError,
1050
599
  policyDecision: 'allowed',
1051
600
  externalPathUsages: externalPathUsages.length > 0 ? externalPathUsages : undefined,
1052
601
  infrastructureError: result.timedOut
@@ -1061,6 +610,9 @@ export async function executeRepositoryCommandAsync(
1061
610
  }
1062
611
  : undefined,
1063
612
  };
613
+ // Unknown post-execution mutation evidence is conservatively cache-invalidating;
614
+ // later reads must re-observe the repository rather than reuse a false unchanged state.
615
+ if (execution.repositoryChanged === true || execution.evidenceError) invalidateRepositoryReadCaches(root);
1064
616
  auditCommand(controllerHome, repository, execution);
1065
617
  return execution;
1066
618
  }