@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
@@ -8,14 +8,16 @@ import {
8
8
  writeFileSync,
9
9
  } from 'fs';
10
10
  import { dirname, join, resolve } from 'path';
11
- import { repositoryControllerRoot } from '../../../cli/repositories/controller-home';
11
+ import { repoLocalNoIndexControllerHome, repositoryControllerRoot } from '../../../cli/repositories/controller-home';
12
12
  import {
13
13
  getRepository,
14
14
  listRepositories,
15
15
  selectRepositoryCheckout,
16
16
  setRepositoryCheckoutLifecycle,
17
17
  } from '../../../cli/repositories/registry';
18
+ import { managedPathInside, managedWorktreeStorageRoot } from '../../../cli/repositories/worktree-storage';
18
19
  import { markRepositoryProjectionDirty } from '../../projections/invalidation';
20
+ import { listControlPlaneRecords } from '../persistence/sqlite-store';
19
21
  import { getWorkContract } from '../facade/work-contract-store';
20
22
  import { getControllerSession, releaseControllerSession } from '../facade/controller-session-store';
21
23
  import { isRepositoryCompletionReceipt, isTerminalWorkContractStatus, type WorkContract } from '../facade/types';
@@ -30,6 +32,7 @@ import {
30
32
  import { getProcessRecord, listProcessRecords } from '../../execution/process-runtime/store';
31
33
  import {
32
34
  listWorkHandles,
35
+ readWorkHandle,
33
36
  transitionWorkHandle,
34
37
  writeWorkHandle,
35
38
  type WorkCleanupReceipt,
@@ -391,6 +394,42 @@ function selectTerminalCleanupTarget(repository: ReturnType<typeof getRepository
391
394
  return repository;
392
395
  }
393
396
 
397
+ function isCurrentControllerManagedWorktree(
398
+ controllerHome: string,
399
+ worktreePath: string,
400
+ ): boolean {
401
+ try {
402
+ const storageRoot = managedWorktreeStorageRoot(
403
+ controllerHome,
404
+ listRepositories(controllerHome, { includeRemoved: true }),
405
+ );
406
+ if (managedPathInside(storageRoot, worktreePath)) return true;
407
+ // Older Controller releases used a namespaced sibling of controller/.
408
+ // Accept only namespaces whose source Home appears in this target's
409
+ // controlled migration receipt; then let the caller's Git common-dir,
410
+ // branch, cleanliness, and preservation checks prove the exact Work before
411
+ // any removal.
412
+ const sourceHomes = new Set<string>([resolve(controllerHome)]);
413
+ for (const record of listControlPlaneRecords<{ sourceHome?: unknown; status?: unknown }>(controllerHome, {
414
+ namespace: 'controller_home_migration',
415
+ limit: 1_000,
416
+ })) {
417
+ if (record.value.status !== 'applied' || typeof record.value.sourceHome !== 'string') continue;
418
+ const sourceHome = resolve(record.value.sourceHome);
419
+ sourceHomes.add(sourceHome);
420
+ const physical = repoLocalNoIndexControllerHome(sourceHome);
421
+ if (physical) sourceHomes.add(physical);
422
+ }
423
+ return [...sourceHomes].some((sourceHome) => {
424
+ const namespace = createHash('sha256').update(sourceHome).digest('hex').slice(0, 16);
425
+ const legacyRoot = join(dirname(resolve(controllerHome)), 'managed-worktrees', namespace);
426
+ return managedPathInside(legacyRoot, worktreePath);
427
+ });
428
+ } catch {
429
+ return false;
430
+ }
431
+ }
432
+
394
433
  export interface TerminalWorkCleanupReconcileOptions {
395
434
  nowMs?: number;
396
435
  minAgeMs?: number;
@@ -417,11 +456,81 @@ function terminalOutcomeForContract(contract: WorkContract): WorkTerminalOutcome
417
456
  return 'failed';
418
457
  }
419
458
 
420
- function cleanupRetainedByRequest(contract: WorkContract, _handle: WorkHandleState): boolean {
459
+ export function recoverTerminalWorkHandle(
460
+ controllerHome: string,
461
+ repositoryId: string,
462
+ workId: string,
463
+ ): WorkHandleState | undefined {
464
+ const existing = readWorkHandle(controllerHome, repositoryId, workId);
465
+ if (existing) return existing;
466
+ const contract = getWorkContract({ controllerHome, repoId: repositoryId }, workId);
467
+ if (!contract || !isTerminalWorkContractStatus(contract.status)) return undefined;
468
+ const worktreePath = contract.worktreeRef?.trim();
469
+ const checkoutId = contract.checkoutId?.trim();
470
+ if (!worktreePath || !checkoutId || !existsSync(worktreePath)) return undefined;
471
+ if (!isCurrentControllerManagedWorktree(controllerHome, worktreePath)) return undefined;
472
+
473
+ const repository = getRepository(repositoryId, controllerHome, { includeRemoved: true });
474
+ // Reconstruct physical cleanup authority from the recorded checkout entry itself.
475
+ // selectRepositoryCheckout returns a RepositoryRecord view rooted at a checkout,
476
+ // not the checkout entry (and terminal cleanup must also tolerate an already
477
+ // archived/removed registry lifecycle while the physical worktree still exists).
478
+ const checkout = repository.checkouts.find((candidate) => candidate.checkoutId === checkoutId);
479
+ if (!checkout || checkout.worktree !== true || resolve(checkout.canonicalRoot) !== resolve(worktreePath)) return undefined;
480
+ const branch = checkout.branch?.trim();
481
+ if (!branch) return undefined;
482
+
483
+ const head = git(worktreePath, ['rev-parse', 'HEAD']);
484
+ if (!head.ok || !head.stdout) return undefined;
485
+ const targetBranch = repository.defaultBranch || 'main';
486
+ const targetExists = branchExists(repository.canonicalRoot, targetBranch);
487
+ const contained = targetExists
488
+ ? git(repository.canonicalRoot, ['merge-base', '--is-ancestor', head.stdout, `refs/heads/${targetBranch}`]).ok
489
+ : false;
490
+ const delivered = contract.status === 'completed' && contained;
491
+ const session = getControllerSession({ controllerHome, repoId: repositoryId }, workId);
492
+ const recordedAt = nowIso();
493
+ return writeWorkHandle(controllerHome, {
494
+ schemaVersion: 1,
495
+ workId,
496
+ workContractId: workId,
497
+ sessionId: session?.sessionId ?? `terminal-recovery:${workId}`,
498
+ principalId: contract.principalId?.trim() || session?.principalId || 'terminal-recovery',
499
+ repositoryId,
500
+ checkoutId,
501
+ sourceCheckoutId: repository.activeCheckoutId,
502
+ worktreePath,
503
+ branch,
504
+ managedWorktree: true,
505
+ baseCommit: contract.baseRevision,
506
+ expectedHead: head.stdout,
507
+ permissionSnapshotVersion: 1,
508
+ state: delivered ? 'merged' : 'failed_terminal_cleanup',
509
+ failureReason: delivered ? undefined : 'Recovered terminal Work ownership for cleanup; target-branch containment was not proven.',
510
+ createdAt: contract.createdAt || recordedAt,
511
+ updatedAt: recordedAt,
512
+ cleanupResponsibility: { owner: 'work_finalizer', registeredAt: recordedAt },
513
+ finalization: {
514
+ validation: contract.status === 'failed' ? 'failed' : 'done',
515
+ commit: 'skipped',
516
+ merge: delivered ? 'done' : 'skipped',
517
+ branchCleanup: 'pending',
518
+ worktreeCleanup: 'pending',
519
+ ...(contract.status === 'failed' ? { lastError: 'Recovered failed terminal Work for cleanup.' } : {}),
520
+ },
521
+ });
522
+ }
523
+
524
+ function cleanupRetainedByRequest(contract: WorkContract, handle: WorkHandleState): boolean {
421
525
  // `skipped` in old WorkHandle finalization records was also used for legacy
422
526
  // no-op/error paths, so it is not sufficient proof of an explicit retention
423
- // request. Only the completion receipt's dedicated maintenance warning is
424
- // authoritative enough to suppress automatic reconciliation.
527
+ // request. New cancelled/terminal Work records persist an explicit physical
528
+ // resource disposition on the WorkHandle. Completed Work keeps the existing
529
+ // completion-receipt warning as the delivery authority.
530
+ if (
531
+ handle.terminalResourceDisposition?.mode === 'retained_by_request'
532
+ && handle.terminalResourceDisposition.retainWorktree === true
533
+ ) return true;
425
534
  const receipt = contract.completionReceipt;
426
535
  if (!receipt || !isRepositoryCompletionReceipt(receipt)) return false;
427
536
  return receipt.cleanup.warnings.some((warning) => warning.code === 'cleanup_retained_by_request');
@@ -588,7 +697,10 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
588
697
  current = persist(input.controllerHome, current, receipt);
589
698
  return { handle: current, receipt };
590
699
  }
591
- if (!registeredPath && existsSync(current.worktreePath)) {
700
+ const migratedManagedWorktree = !registeredPath
701
+ && existsSync(current.worktreePath)
702
+ && isCurrentControllerManagedWorktree(input.controllerHome, current.worktreePath);
703
+ if (!registeredPath && existsSync(current.worktreePath) && !migratedManagedWorktree) {
592
704
  addBlocker(receipt, 'CHECKOUT_NOT_CONTROLLER_REGISTERED');
593
705
  current = persist(input.controllerHome, current, receipt);
594
706
  return { handle: current, receipt };
@@ -686,6 +798,9 @@ export async function cleanupTerminalWork(input: TerminalWorkCleanupInput): Prom
686
798
  addBlocker(receipt, `CHECKOUT_REGISTRY_REMOVE_FAILED: ${receipt.checkoutRegistry.reason}`);
687
799
  }
688
800
  }
801
+ if (!registeredPath && migratedManagedWorktree) {
802
+ receipt.checkoutRegistry.reason = 'Checkout metadata was absent after Controller Home migration; canonical managed-worktree and Git identity checks passed before cleanup.';
803
+ }
689
804
 
690
805
  if (!deleteBranch) {
691
806
  receipt.branchCleanup.status = 'retained';
@@ -789,8 +904,24 @@ export async function reconcileTerminalWorkCleanups(
789
904
  const repositories = listRepositories(controllerHome).filter((repository) => repository.enabled && !repository.removedAt);
790
905
  outer: for (const repository of repositories) {
791
906
  const handles = listWorkHandles(controllerHome, repository.repoId, 10_000)
792
- .filter((handle) => handle.managedWorktree)
793
- .sort((left, right) => left.updatedAt.localeCompare(right.updatedAt));
907
+ .filter((handle) => handle.managedWorktree);
908
+ const knownWorkIds = new Set(handles.map((handle) => handle.workId));
909
+ const recoveredWorkIds = new Set<string>();
910
+ for (const record of listControlPlaneRecords<WorkContract>(controllerHome, {
911
+ namespace: 'work_contract',
912
+ scope: repository.repoId,
913
+ limit: 10_000,
914
+ })) {
915
+ const contract = record.value;
916
+ if (knownWorkIds.has(contract.workId) || !isTerminalWorkContractStatus(contract.status)) continue;
917
+ if (!contract.worktreeRef?.trim() || !existsSync(contract.worktreeRef)) continue;
918
+ const recovered = recoverTerminalWorkHandle(controllerHome, repository.repoId, contract.workId);
919
+ if (!recovered) continue;
920
+ handles.push(recovered);
921
+ knownWorkIds.add(recovered.workId);
922
+ recoveredWorkIds.add(recovered.workId);
923
+ }
924
+ handles.sort((left, right) => left.updatedAt.localeCompare(right.updatedAt));
794
925
  for (const originalHandle of handles) {
795
926
  report.scanned += 1;
796
927
  const contract = getWorkContract({ controllerHome, repoId: repository.repoId }, originalHandle.workContractId ?? originalHandle.workId);
@@ -803,7 +934,11 @@ export async function reconcileTerminalWorkCleanups(
803
934
  report.skippedRetained.push(originalHandle.workId);
804
935
  continue;
805
936
  }
806
- const terminalAt = Math.max(Date.parse(contract.updatedAt) || 0, Date.parse(originalHandle.updatedAt) || 0);
937
+ // Reconstructing a missing handle is metadata recovery, not new Work activity.
938
+ // Do not let that write reset the terminal-age grace period indefinitely.
939
+ const terminalAt = recoveredWorkIds.has(originalHandle.workId)
940
+ ? (Date.parse(contract.updatedAt) || 0)
941
+ : Math.max(Date.parse(contract.updatedAt) || 0, Date.parse(originalHandle.updatedAt) || 0);
807
942
  if (terminalAt > 0 && nowMs - terminalAt < minAgeMs) {
808
943
  report.skippedRecent.push(originalHandle.workId);
809
944
  continue;
@@ -166,27 +166,35 @@ export function reconcileWorkValidation(
166
166
  }
167
167
 
168
168
  try {
169
- const currentIdentity = record.checkExecution
170
- ? controllerCheckExecutionIdentity(handle.worktreePath, checkId, record.checkExecution.timeoutMs)
171
- : undefined;
169
+ if (binding.checkExecution && binding.checkExecution.checkId !== checkId) {
170
+ throw new Error(
171
+ `PROCESS_CHECK_RECEIPT_IDENTITY_MISMATCH: bound check ${binding.checkExecution.checkId} != ${checkId}`,
172
+ );
173
+ }
174
+ const expectedCheckExecution = binding.checkExecution
175
+ ?? (record.checkExecution
176
+ ? controllerCheckExecutionIdentity(handle.worktreePath, checkId, record.checkExecution.timeoutMs)
177
+ : undefined);
172
178
  const receipt = processCheckCompletionReceipt(record, {
173
179
  repoId: handle.repositoryId,
174
180
  checkoutId: handle.checkoutId,
175
181
  workId: handle.workId,
176
182
  checkId,
177
183
  processId: binding.processId,
178
- ...(currentIdentity ? {
184
+ ...(expectedCheckExecution ? {
179
185
  checkExecution: {
180
- cacheKey: currentIdentity.cacheKey,
181
- revision: currentIdentity.revision,
182
- definitionDigest: currentIdentity.definitionDigest,
183
- environmentFingerprint: currentIdentity.environmentFingerprint,
184
- timeoutMs: currentIdentity.timeoutMs,
185
- scopeKey: processCheckSemanticScopeKey({
186
- checkoutId: handle.checkoutId,
187
- workId: handle.workId,
188
- verificationBinding: { executionSessionId: handle.sessionId },
189
- }, currentIdentity.reuseScope),
186
+ cacheKey: expectedCheckExecution.cacheKey,
187
+ revision: expectedCheckExecution.revision,
188
+ definitionDigest: expectedCheckExecution.definitionDigest,
189
+ environmentFingerprint: expectedCheckExecution.environmentFingerprint,
190
+ timeoutMs: expectedCheckExecution.timeoutMs,
191
+ scopeKey: binding.checkExecution
192
+ ? binding.checkExecution.scopeKey
193
+ : processCheckSemanticScopeKey({
194
+ checkoutId: handle.checkoutId,
195
+ workId: handle.workId,
196
+ verificationBinding: { executionSessionId: handle.sessionId },
197
+ }, expectedCheckExecution.reuseScope),
190
198
  },
191
199
  } : {}),
192
200
  });
@@ -5,7 +5,9 @@ import {
5
5
  lstatSync,
6
6
  mkdirSync,
7
7
  mkdtempSync,
8
+ readFileSync,
8
9
  readdirSync,
10
+ rmdirSync,
9
11
  rmSync,
10
12
  statSync,
11
13
  symlinkSync,
@@ -91,11 +93,28 @@ function cloneHead(sourceRoot: string, targetRoot: string, head: string): void {
91
93
  git(targetRoot, ['checkout', '--detach', '--quiet', head]);
92
94
  }
93
95
 
96
+ function pruneEmptySnapshotParents(targetRoot: string, targetPath: string): void {
97
+ const boundary = resolve(targetRoot);
98
+ let current = dirname(resolve(targetPath));
99
+ while (current !== boundary) {
100
+ try {
101
+ if (readdirSync(current).length > 0) return;
102
+ rmdirSync(current);
103
+ } catch {
104
+ return;
105
+ }
106
+ current = dirname(current);
107
+ }
108
+ }
109
+
94
110
  function overlayPath(sourceRoot: string, targetRoot: string, relativePath: string): void {
95
111
  const source = resolve(sourceRoot, relativePath);
96
112
  const target = resolve(targetRoot, relativePath);
97
113
  rmSync(target, { recursive: true, force: true });
98
- if (!existsSync(source)) return;
114
+ if (!existsSync(source)) {
115
+ pruneEmptySnapshotParents(targetRoot, target);
116
+ return;
117
+ }
99
118
  mkdirSync(dirname(target), { recursive: true });
100
119
  const stat = lstatSync(source);
101
120
  if (stat.isSymbolicLink()) {
@@ -105,11 +124,69 @@ function overlayPath(sourceRoot: string, targetRoot: string, relativePath: strin
105
124
  cpSync(source, target, { recursive: stat.isDirectory(), dereference: false, force: true, preserveTimestamps: true });
106
125
  }
107
126
 
108
- function linkIgnoredNodeModules(sourceRoot: string, targetRoot: string): void {
109
- const source = join(sourceRoot, 'node_modules');
127
+ const DEPENDENCY_LOCK_FILES = ['bun.lock', 'bun.lockb', 'pnpm-lock.yaml', 'package-lock.json', 'yarn.lock'] as const;
128
+ const DEPENDENCY_CONFIG_FILES = ['pnpm-workspace.yaml', 'bunfig.toml', '.npmrc', '.yarnrc.yml'] as const;
129
+ const DEPENDENCY_METADATA_NAMES = ['package.json', ...DEPENDENCY_LOCK_FILES, ...DEPENDENCY_CONFIG_FILES] as const;
130
+
131
+ function isDependencyMetadataPath(path: string): boolean {
132
+ const name = path.split('/').pop() ?? path;
133
+ return DEPENDENCY_METADATA_NAMES.some((candidate) => candidate === name);
134
+ }
135
+
136
+ function hasUntrackedDependencyMetadata(root: string): boolean {
137
+ return nulPaths(git(root, ['ls-files', '--others', '--exclude-standard', '-z'])).some(isDependencyMetadataPath);
138
+ }
139
+
140
+ function dependencyMetadataDigest(root: string): string | undefined {
141
+ if (!existsSync(join(root, 'package.json'))) return undefined;
142
+ const rootLock = DEPENDENCY_LOCK_FILES.find((name) => existsSync(join(root, name)));
143
+ if (!rootLock) return undefined;
144
+ const pathspecs = DEPENDENCY_METADATA_NAMES.flatMap((name) => [name, `:(glob)**/${name}`]);
145
+ const tracked = nulPaths(git(root, ['ls-files', '-z', '--', ...pathspecs]));
146
+ const files = [...new Set(tracked)].sort();
147
+ if (!files.includes('package.json') || !files.includes(rootLock)) return undefined;
148
+ const hash = createHash('sha256');
149
+ for (const path of files) {
150
+ hash.update(path);
151
+ hash.update('\0');
152
+ hash.update(readFileSync(join(root, path)));
153
+ hash.update('\0');
154
+ }
155
+ return hash.digest('hex');
156
+ }
157
+
158
+ function primaryWorktreeRoot(sourceRoot: string): string | undefined {
159
+ const commonDirValue = git(sourceRoot, ['rev-parse', '--git-common-dir']).toString('utf8').trim();
160
+ if (!commonDirValue) return undefined;
161
+ const commonDir = resolve(sourceRoot, commonDirValue);
162
+ const candidate = dirname(commonDir);
163
+ if (resolve(candidate) === resolve(sourceRoot)) return undefined;
164
+ if (!existsSync(join(candidate, '.git'))) return undefined;
165
+ return candidate;
166
+ }
167
+
168
+ function linkIgnoredNodeModules(sourceRoot: string, targetRoot: string, dirtyPaths: readonly string[]): void {
169
+ let source = join(sourceRoot, 'node_modules');
110
170
  const target = join(targetRoot, 'node_modules');
111
- if (!existsSync(source) || existsSync(target)) return;
112
- const ignored = spawnSync('git', ['-C', sourceRoot, 'check-ignore', '--quiet', '--', 'node_modules'], { stdio: 'ignore', timeout: 10_000 });
171
+ if (existsSync(target)) return;
172
+ if (!existsSync(source)) {
173
+ if (dirtyPaths.some(isDependencyMetadataPath)) return;
174
+ const primaryRoot = primaryWorktreeRoot(sourceRoot);
175
+ if (!primaryRoot) return;
176
+ const primaryNodeModules = join(primaryRoot, 'node_modules');
177
+ if (!existsSync(primaryNodeModules) || hasUntrackedDependencyMetadata(primaryRoot)) return;
178
+ const sourceDigest = dependencyMetadataDigest(sourceRoot);
179
+ const primaryDigest = dependencyMetadataDigest(primaryRoot);
180
+ if (!sourceDigest || sourceDigest !== primaryDigest) return;
181
+ source = primaryNodeModules;
182
+ }
183
+ // An existing managed-worktree node_modules may itself be a symlink. Git
184
+ // rejects child pathspecs through symlinks, while an absent node_modules
185
+ // needs a virtual child path so directory-only ignore rules still match.
186
+ const ignoreProbe = existsSync(join(sourceRoot, 'node_modules'))
187
+ ? 'node_modules'
188
+ : 'node_modules/.forge-verification-probe';
189
+ const ignored = spawnSync('git', ['-C', sourceRoot, 'check-ignore', '--quiet', '--', ignoreProbe], { stdio: 'ignore', timeout: 10_000 });
113
190
  if (ignored.status !== 0) return;
114
191
  try {
115
192
  symlinkSync(source, target, process.platform === 'win32' ? 'junction' : 'dir');
@@ -136,18 +213,18 @@ export function materializeWorkVerificationSnapshot(input: {
136
213
  excludedPaths.push(path);
137
214
  continue;
138
215
  }
139
- if (input.scope.allowedPaths.length > 0 && matchesAny(input.scope.allowedPaths, path)) {
216
+ if (input.scope.allowedPaths.length === 0 || matchesAny(input.scope.allowedPaths, path)) {
140
217
  includedPaths.push(path);
141
218
  continue;
142
219
  }
143
- throw new Error(`WORK_VERIFICATION_PATH_OWNERSHIP_AMBIGUOUS: ${path} is dirty but is neither allowed nor forbidden for ${input.scope.workId}`);
220
+ throw new Error(`WORK_VERIFICATION_PATH_OWNERSHIP_AMBIGUOUS: ${path} is dirty but is outside the allowed path fence for ${input.scope.workId}`);
144
221
  }
145
222
 
146
223
  const root = snapshotRoot(input.controllerHome, input.repoId, input.scope.workId);
147
224
  try {
148
225
  cloneHead(input.sourceRoot, root, sourceHead);
149
226
  for (const path of includedPaths) overlayPath(input.sourceRoot, root, path);
150
- linkIgnoredNodeModules(input.sourceRoot, root);
227
+ linkIgnoredNodeModules(input.sourceRoot, root, dirtyPaths);
151
228
  const ownershipDigest = createHash('sha256').update(JSON.stringify({
152
229
  sourceHead,
153
230
  workId: input.scope.workId,
@@ -1,5 +1,5 @@
1
1
  import type { AssistantPluginManifest } from '../../plugins/types';
2
- import type { CapabilityDescriptor, CapabilityDomain, CapabilityGroupSummary, CapabilityOperationClass, CapabilityRisk, FacadeTool } from './types';
2
+ import type { CapabilityDescriptor, CapabilityDomain, CapabilityExecutionSurface, CapabilityGroupSummary, CapabilityOperationClass, CapabilityRisk, FacadeTool } from './types';
3
3
 
4
4
  const CORE_CAPABILITIES: CapabilityDescriptor[] = [
5
5
  {
@@ -83,24 +83,14 @@ const CORE_CAPABILITIES: CapabilityDescriptor[] = [
83
83
  summary: 'Diagnose and dry-run repair stuck jobs, stale projections, invalid check pollution, and worker unavailability without treating infrastructure failure as acceptance failure.',
84
84
  },
85
85
  {
86
- capabilityId: 'controller.codex_delegation',
86
+ capabilityId: 'controller.external_controller',
87
87
  domain: 'controller',
88
88
  group: 'controller',
89
89
  operationClass: 'execute',
90
90
  risk: 'workspace_write',
91
91
  exposedVia: 'rh_work',
92
92
  schemaExposure: 'stable_static',
93
- summary: 'Delegate bounded implementation work to Codex or Claude with context packs; workers return evidence and proposals but do not own final acceptance.',
94
- },
95
- {
96
- capabilityId: 'controller.autonomous_goal_loop',
97
- domain: 'controller',
98
- group: 'controller',
99
- operationClass: 'execute',
100
- risk: 'workspace_write',
101
- exposedVia: 'rh_work',
102
- schemaExposure: 'stable_static',
103
- summary: 'Advance daemon-owned goal contracts through deterministic policy, executor routing, verification, repair, and handoff.',
93
+ summary: 'Claim Work ownership and start an external Codex, Claude, or ChatGPT controller through controller_claim and launcher_start, with handoff decisions coordinated through rh_inbox; deprecated delegate is not an execution path.',
104
94
  },
105
95
  {
106
96
  capabilityId: 'controller.work_contract',
@@ -148,9 +138,9 @@ const CORE_CAPABILITIES: CapabilityDescriptor[] = [
148
138
  group: 'browser',
149
139
  operationClass: 'execute',
150
140
  risk: 'unknown',
151
- exposedVia: 'rh_work',
141
+ exposedVia: 'plugin_action_execute',
152
142
  schemaExposure: 'stable_static',
153
- summary: 'Use typed HTTP(S) browser navigation, snapshot, and interaction actions when browser capability is configured.',
143
+ summary: 'Use typed HTTP(S) browser navigation, snapshot, and interaction actions through plugin_action_execute when browser capability is configured.',
154
144
  },
155
145
  {
156
146
  capabilityId: 'platform.ios',
@@ -158,9 +148,9 @@ const CORE_CAPABILITIES: CapabilityDescriptor[] = [
158
148
  group: 'ios',
159
149
  operationClass: 'execute',
160
150
  risk: 'workspace_write',
161
- exposedVia: 'rh_work',
151
+ exposedVia: 'plugin_action_execute',
162
152
  schemaExposure: 'stable_static',
163
- summary: 'Use typed Xcode, simulator launch, screenshot, log, and smoke-test handlers when the local iOS toolchain is ready.',
153
+ summary: 'Use typed Xcode, simulator, screenshot, log, and smoke-review actions through plugin_action_execute when the local iOS toolchain is ready.',
164
154
  },
165
155
  ];
166
156
 
@@ -185,13 +175,16 @@ function domainFromPlugin(pluginId: string): CapabilityDomain {
185
175
  return 'plugin';
186
176
  }
187
177
 
188
- function exposedViaFromPluginAction(readOnly: boolean, risk: string): FacadeTool {
189
- if (readOnly || risk === 'readonly') return 'rh_context';
190
- return 'rh_work';
178
+ function exposedViaFromPluginAction(_readOnly: boolean, _risk: string): CapabilityExecutionSurface {
179
+ // rh_context exposes schema/policy. plugin_action_execute is the sole action executor.
180
+ return 'plugin_action_execute';
191
181
  }
192
182
 
193
- export function pluginCapabilities(manifests: readonly AssistantPluginManifest[] = []): CapabilityDescriptor[] {
194
- return manifests.flatMap((manifest) => manifest.actions.map((action) => ({
183
+ function pluginCapabilityDescriptor(
184
+ manifest: AssistantPluginManifest,
185
+ action: AssistantPluginManifest['actions'][number],
186
+ ): CapabilityDescriptor {
187
+ return {
195
188
  capabilityId: `plugin.${manifest.pluginId}.${action.actionId}`,
196
189
  domain: domainFromPlugin(manifest.pluginId),
197
190
  group: manifest.pluginId === 'browser' ? 'browser' : 'plugin',
@@ -200,7 +193,11 @@ export function pluginCapabilities(manifests: readonly AssistantPluginManifest[]
200
193
  exposedVia: exposedViaFromPluginAction(action.readOnly, action.risk),
201
194
  schemaExposure: 'plugin_manifest',
202
195
  summary: `${manifest.displayName}: ${action.title}. ${action.description}`,
203
- } satisfies CapabilityDescriptor)));
196
+ } satisfies CapabilityDescriptor;
197
+ }
198
+
199
+ export function pluginCapabilities(manifests: readonly AssistantPluginManifest[] = []): CapabilityDescriptor[] {
200
+ return manifests.flatMap((manifest) => manifest.actions.map((action) => pluginCapabilityDescriptor(manifest, action)));
204
201
  }
205
202
 
206
203
  export function listCapabilityDescriptors(manifests: readonly AssistantPluginManifest[] = []): CapabilityDescriptor[] {
@@ -209,6 +206,97 @@ export function listCapabilityDescriptors(manifests: readonly AssistantPluginMan
209
206
  return [...byId.values()].sort((a, b) => a.capabilityId.localeCompare(b.capabilityId));
210
207
  }
211
208
 
209
+ export interface CapabilitySearchMatch {
210
+ capabilityId: string;
211
+ score: number;
212
+ matchedTerms: string[];
213
+ descriptor: CapabilityDescriptor;
214
+ }
215
+
216
+ function normalizedCapabilityText(value: string): string {
217
+ return value
218
+ .toLowerCase()
219
+ .replace(/[_./:-]+/g, ' ')
220
+ .replace(/[^a-z0-9]+/g, ' ')
221
+ .trim();
222
+ }
223
+
224
+ function capabilityIntentTokens(query: string): string[] {
225
+ return [...new Set(normalizedCapabilityText(query).split(' ').filter((token) => token.length >= 2))];
226
+ }
227
+
228
+ function intentBoosts(query: string, capabilityId: string): Array<{ label: string; score: number }> {
229
+ const normalized = normalizedCapabilityText(query);
230
+ const boosts: Array<{ label: string; score: number }> = [];
231
+ const appleDevelopmentIntent = /\b(xcode|provision|provisioning|signing|developer account|apple developer)\b/.test(normalized);
232
+ if (appleDevelopmentIntent) {
233
+ if (capabilityId === 'platform.ios') boosts.push({ label: 'apple-development', score: 90 });
234
+ if (capabilityId.startsWith('plugin.ios.')) boosts.push({ label: 'apple-development', score: 72 });
235
+ if (capabilityId === 'plugin.app_store_connect.auth_status') boosts.push({ label: 'apple-development', score: 88 });
236
+ else if (capabilityId === 'plugin.app_store_connect.configure') boosts.push({ label: 'apple-development', score: 82 });
237
+ else if (capabilityId.startsWith('plugin.app_store_connect.')) boosts.push({ label: 'apple-development', score: 28 });
238
+ if (capabilityId === 'plugin.desktop_operator.desktop_session_open') boosts.push({ label: 'xcode-desktop-fallback', score: 86 });
239
+ else if (capabilityId === 'plugin.desktop_operator.desktop_observe') boosts.push({ label: 'xcode-desktop-fallback', score: 78 });
240
+ else if (capabilityId === 'plugin.desktop_operator.desktop_press') boosts.push({ label: 'xcode-desktop-fallback', score: 74 });
241
+ else if (capabilityId.startsWith('plugin.desktop_operator.')) boosts.push({ label: 'xcode-desktop-fallback', score: 22 });
242
+ }
243
+
244
+ const browserLoginIntent = normalized.includes('browser')
245
+ && /\b(login|log in|signin|sign in|auth|authentication|session)\b/.test(normalized);
246
+ if (browserLoginIntent) {
247
+ if (capabilityId === 'plugin.browser') boosts.push({ label: 'browser-auth', score: 90 });
248
+ else if (capabilityId.startsWith('plugin.browser.')) boosts.push({ label: 'browser-auth', score: 62 });
249
+ if (capabilityId === 'plugin.desktop_operator.desktop_session_open') boosts.push({ label: 'browser-desktop-fallback', score: 70 });
250
+ else if (capabilityId === 'plugin.desktop_operator.desktop_observe') boosts.push({ label: 'browser-desktop-fallback', score: 62 });
251
+ else if (capabilityId.startsWith('plugin.desktop_operator.')) boosts.push({ label: 'browser-desktop-fallback', score: 18 });
252
+ }
253
+ return boosts;
254
+ }
255
+
256
+ export function searchCapabilityDescriptors(
257
+ query: string,
258
+ manifests: readonly AssistantPluginManifest[] = [],
259
+ limit = 12,
260
+ ): CapabilitySearchMatch[] {
261
+ const normalizedQuery = normalizedCapabilityText(query);
262
+ if (!normalizedQuery) return [];
263
+ const tokens = capabilityIntentTokens(query);
264
+ const boundedLimit = Math.max(1, Math.min(24, Math.floor(limit)));
265
+
266
+ return listCapabilityDescriptors(manifests)
267
+ .map((descriptor): CapabilitySearchMatch | undefined => {
268
+ const haystack = normalizedCapabilityText([
269
+ descriptor.capabilityId,
270
+ descriptor.summary,
271
+ descriptor.group,
272
+ descriptor.domain,
273
+ descriptor.operationClass,
274
+ descriptor.exposedVia,
275
+ ].join(' '));
276
+ let score = haystack.includes(normalizedQuery) ? 48 : 0;
277
+ const matchedTerms = new Set<string>();
278
+ for (const token of tokens) {
279
+ if (!haystack.includes(token)) continue;
280
+ matchedTerms.add(token);
281
+ score += descriptor.capabilityId.toLowerCase().includes(token) ? 12 : 5;
282
+ }
283
+ for (const boost of intentBoosts(query, descriptor.capabilityId)) {
284
+ score += boost.score;
285
+ matchedTerms.add(boost.label);
286
+ }
287
+ if (score <= 0) return undefined;
288
+ return {
289
+ capabilityId: descriptor.capabilityId,
290
+ score,
291
+ matchedTerms: [...matchedTerms].sort(),
292
+ descriptor,
293
+ };
294
+ })
295
+ .filter((entry): entry is CapabilitySearchMatch => Boolean(entry))
296
+ .sort((a, b) => b.score - a.score || a.capabilityId.localeCompare(b.capabilityId))
297
+ .slice(0, boundedLimit);
298
+ }
299
+
212
300
  export function summarizeCapabilityGroups(manifests: readonly AssistantPluginManifest[] = []): CapabilityGroupSummary[] {
213
301
  const grouped = new Map<CapabilityDescriptor['group'], CapabilityDescriptor[]>();
214
302
  for (const descriptor of listCapabilityDescriptors(manifests)) {
@@ -222,7 +310,8 @@ export function summarizeCapabilityGroups(manifests: readonly AssistantPluginMan
222
310
  group,
223
311
  capabilityCount: entries.length,
224
312
  domains: [...new Set(entries.map((entry) => entry.domain))].sort(),
225
- facadeTools: [...new Set(entries.map((entry) => entry.exposedVia))].sort(),
313
+ executionSurfaces: [...new Set(entries.map((entry) => entry.exposedVia))].sort(),
314
+ facadeTools: [...new Set(entries.map((entry) => entry.exposedVia).filter((surface): surface is FacadeTool => surface.startsWith('rh_')))].sort(),
226
315
  operationClasses: [...new Set(entries.map((entry) => entry.operationClass))].sort(),
227
316
  risks: [...new Set(entries.map((entry) => entry.risk))].sort(),
228
317
  schemaExposures: [...new Set(entries.map((entry) => entry.schemaExposure))].sort(),
@@ -257,6 +346,12 @@ export function getPluginActionCapabilitySchema(
257
346
  scopes: [...action.scopes],
258
347
  resourceClaims: structuredClone(action.resourceClaims),
259
348
  argumentsSchema: structuredClone(action.argumentsSchema),
349
+ authorizationReuse: action.confirmation === 'authorization'
350
+ ? {
351
+ mode: 'exact_target_persistent_when_adapter_supported',
352
+ hostPermissionBoundary: 'Tool invocation permission remains host-managed; reusable Forge grants never bypass strong_confirmation or destructive gates.',
353
+ }
354
+ : { mode: 'not_reusable' },
260
355
  executeWith: 'plugin_action_execute',
261
356
  };
262
357
  }
@@ -265,5 +360,14 @@ export function getPluginActionCapabilitySchema(
265
360
  }
266
361
 
267
362
  export function getCapabilityDescriptor(capabilityId: string, manifests: readonly AssistantPluginManifest[] = []): CapabilityDescriptor | undefined {
268
- return listCapabilityDescriptors(manifests).find((descriptor) => descriptor.capabilityId === capabilityId);
363
+ const core = CORE_CAPABILITIES.find((descriptor) => descriptor.capabilityId === capabilityId);
364
+ if (core) return core;
365
+ for (const manifest of manifests) {
366
+ for (const action of manifest.actions) {
367
+ if (`plugin.${manifest.pluginId}.${action.actionId}` === capabilityId) {
368
+ return pluginCapabilityDescriptor(manifest, action);
369
+ }
370
+ }
371
+ }
372
+ return undefined;
269
373
  }