@moretea-labs/forge 1.7.1 → 1.8.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 (685) hide show
  1. package/AGENTS.md +58 -62
  2. package/CHANGELOG.md +39 -0
  3. package/CLAUDE.md +42 -62
  4. package/README.md +2 -2
  5. package/SKILL.md +0 -1
  6. package/THIRD_PARTY_NOTICES.md +5 -2
  7. package/adapters/browser/index.ts +2 -0
  8. package/adapters/chatgpt/browser-delivery-host.ts +71 -0
  9. package/adapters/chatgpt/browser-delivery-runtime.ts +783 -0
  10. package/adapters/chatgpt/controller-binding-store.ts +55 -0
  11. package/adapters/chatgpt/controller-host.ts +119 -0
  12. package/adapters/chatgpt/controller-round-host.ts +78 -0
  13. package/adapters/chatgpt/controller-round-settlement-store.ts +45 -0
  14. package/adapters/chatgpt/provider-delivery.ts +113 -0
  15. package/adapters/chatgpt/work-conversation-binding-store.ts +192 -0
  16. package/adapters/chatgpt/wsl-bridge-delivery-host.ts +63 -0
  17. package/adapters/computer/desktop-operator-contract.ts +7 -0
  18. package/adapters/computer/desktop-operator-discovery.ts +201 -0
  19. package/adapters/computer/desktop-operator-negotiation.ts +174 -0
  20. package/adapters/computer/desktop-operator-provider.ts +312 -0
  21. package/adapters/computer/index.ts +1 -0
  22. package/adapters/computer/interaction-target-authority.ts +944 -0
  23. package/adapters/controller-process/binding-store.ts +34 -0
  24. package/adapters/controller-process/controller-host.ts +26 -0
  25. package/adapters/mcp/access-mode.ts +132 -0
  26. package/adapters/mcp/audit.ts +32 -0
  27. package/adapters/mcp/auth.ts +407 -0
  28. package/adapters/mcp/controller-round-compatibility.ts +184 -0
  29. package/adapters/mcp/frozen-client-semantic-compatibility.ts +292 -0
  30. package/adapters/mcp/instructions.ts +36 -0
  31. package/adapters/mcp/legacy-context.ts +18 -0
  32. package/adapters/mcp/multi-repository.ts +437 -0
  33. package/adapters/mcp/oauth.ts +391 -0
  34. package/adapters/mcp/paths.ts +140 -0
  35. package/adapters/mcp/policy.ts +232 -0
  36. package/adapters/mcp/proxy-env.ts +62 -0
  37. package/adapters/mcp/readiness-projection.ts +24 -0
  38. package/adapters/mcp/redaction.ts +21 -0
  39. package/adapters/mcp/repo.ts +24 -0
  40. package/adapters/mcp/runtime-gateway/artifact-adapter.ts +52 -0
  41. package/adapters/mcp/runtime-gateway/context-adapter.ts +829 -0
  42. package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +191 -0
  43. package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +147 -0
  44. package/adapters/mcp/runtime-gateway/execution-control-adapter.ts +28 -0
  45. package/adapters/mcp/runtime-gateway/execution-tools.ts +207 -0
  46. package/adapters/mcp/runtime-gateway/filesystem-adapter.ts +38 -0
  47. package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +411 -0
  48. package/adapters/mcp/runtime-gateway/legacy-ios-tool-adapter.ts +105 -0
  49. package/adapters/mcp/runtime-gateway/maintenance-adapter.ts +44 -0
  50. package/adapters/mcp/runtime-gateway/model-adapter.ts +63 -0
  51. package/adapters/mcp/runtime-gateway/persisted-check-process.ts +3 -0
  52. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +308 -0
  53. package/adapters/mcp/runtime-gateway/process-tools.ts +289 -0
  54. package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +76 -0
  55. package/adapters/mcp/runtime-gateway/recovery-adapter.ts +268 -0
  56. package/adapters/mcp/runtime-gateway/recovery-client-adapter.ts +70 -0
  57. package/adapters/mcp/runtime-gateway/recovery-tool-contract.ts +99 -0
  58. package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +110 -0
  59. package/adapters/mcp/runtime-gateway/result-adapter.ts +108 -0
  60. package/adapters/mcp/runtime-gateway/router.ts +728 -0
  61. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1251 -0
  62. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +634 -0
  63. package/adapters/mcp/runtime-gateway/runtime-tool-shared.ts +27 -0
  64. package/adapters/mcp/runtime-gateway/runtime-tools.ts +68 -0
  65. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +186 -0
  66. package/adapters/mcp/runtime-gateway/shared-adapter.ts +25 -0
  67. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +870 -0
  68. package/adapters/mcp/runtime-gateway/work-adapter.ts +3186 -0
  69. package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +502 -0
  70. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +1 -0
  71. package/adapters/mcp/server.ts +1048 -0
  72. package/adapters/mcp/setup.ts +791 -0
  73. package/adapters/mcp/tool-capability-cache.ts +87 -0
  74. package/adapters/mcp/tool-mapping/access-tools.ts +387 -0
  75. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +5265 -0
  76. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +150 -0
  77. package/adapters/mcp/tool-mapping/repository-tools.ts +1691 -0
  78. package/adapters/mcp/tool-mapping/tools.ts +9 -0
  79. package/adapters/mcp/tool-mapping/umbrella-tools.ts +117 -0
  80. package/adapters/mcp/toolset-names.ts +74 -0
  81. package/adapters/mcp/toolset-selection.ts +41 -0
  82. package/adapters/mcp/toolset.ts +336 -0
  83. package/adapters/mcp/transports/http.ts +1456 -0
  84. package/adapters/mcp/transports/session-registry.ts +513 -0
  85. package/adapters/mcp/transports/stdio.ts +8 -0
  86. package/adapters/mcp/tunnels/openai-secure-tunnel.ts +139 -0
  87. package/adapters/mcp/types.ts +41 -0
  88. package/assets/AGENTS.md +2 -16
  89. package/assets/CLAUDE.md +2 -16
  90. package/assets/hooks/AGENTS.md +3 -14
  91. package/assets/hooks/CLAUDE.md +3 -14
  92. package/assets/hooks/hook-input.sh +9 -2
  93. package/assets/hooks/lib/workflow-state.sh +28 -10
  94. package/assets/hooks/prompt-guard.sh +63 -37
  95. package/assets/hooks/security-sentinel.sh +2 -1
  96. package/assets/hooks/session-start-context.sh +7 -5
  97. package/assets/hooks/stop-orchestrator.sh +13 -3
  98. package/assets/initializer-question-pack.v4.json +11 -7
  99. package/assets/partials/05-workflow.partial.md +18 -73
  100. package/assets/partials/07-footer.partial.md +1 -1
  101. package/assets/partials-agents/02-operating-mode.partial.md +1 -2
  102. package/assets/partials-agents/04-task-protocol.partial.md +14 -59
  103. package/assets/partials-agents/06-quality-safety.partial.md +11 -16
  104. package/assets/partials-agents/08-deep-docs.partial.md +1 -2
  105. package/assets/plan-map.json +11 -11
  106. package/assets/plugin-registry.v1.json +9 -3
  107. package/assets/plugin-trust-policy.v1.json +6 -0
  108. package/assets/recovery/ForgeRecovery.ps1 +100 -0
  109. package/assets/recovery/forge-wsl-rescue +271 -0
  110. package/assets/reference-configs/development-protocol.md +11 -15
  111. package/assets/reference-configs/document-generation.md +1 -1
  112. package/assets/reference-configs/global-working-rules.md +9 -1
  113. package/assets/reference-configs/harness-overview.md +22 -112
  114. package/assets/reference-configs/hook-operations.md +1 -1
  115. package/assets/reference-configs/sprint-contracts.md +2 -2
  116. package/assets/skill-version.json +2 -2
  117. package/assets/templates/helpers/architecture-event.ts +3 -34
  118. package/assets/templates/helpers/architecture-queue.sh +61 -10
  119. package/assets/templates/helpers/archive-workflow.sh +90 -180
  120. package/assets/templates/helpers/capability-config.ts +8 -11
  121. package/assets/templates/helpers/capability-resolver.ts +37 -19
  122. package/assets/templates/helpers/capture-plan.sh +2 -2
  123. package/assets/templates/helpers/check-architecture-sync.sh +1 -9
  124. package/assets/templates/helpers/check-task-sync.sh +82 -11
  125. package/assets/templates/helpers/check-task-workflow.sh +1 -3
  126. package/assets/templates/helpers/context-contract-sync.sh +1 -12
  127. package/assets/templates/helpers/contract-worktree.sh +26 -24
  128. package/assets/templates/helpers/ensure-task-workflow.sh +6 -8
  129. package/assets/templates/helpers/inspect-project-state.ts +1 -1
  130. package/assets/templates/helpers/migrate-project-template.sh +1 -1
  131. package/assets/templates/helpers/migrate-workflow-docs.ts +53 -239
  132. package/assets/templates/helpers/new-plan.sh +2 -2
  133. package/assets/templates/helpers/plan-to-todo.sh +3 -31
  134. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  135. package/assets/templates/helpers/switch-plan.sh +1 -1
  136. package/assets/templates/helpers/workflow-contract.ts +1 -0
  137. package/assets/workflow-contract.v1.json +63 -171
  138. package/assets/workflows/xiaohongshu/xiaohongshu.collect-metrics.v1.draft.json +118 -0
  139. package/assets/workflows/xiaohongshu/xiaohongshu.publish-image-note.v1.draft.json +316 -0
  140. package/assets/workflows/xiaohongshu/xiaohongshu.publish-long-text.v1.draft.json +324 -0
  141. package/docs/architecture/CURRENT.md +148 -13
  142. package/docs/architecture/EVOLUTION.md +13 -0
  143. package/docs/architecture/decisions/20260801-controller-home-sqlite-state.md +2 -1
  144. package/docs/architecture/decisions/20260831-independent-windows-wsl-host-rescue.md +59 -0
  145. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +120 -0
  146. package/docs/architecture/decisions/20260904-repository-runtime-data-boundary.md +42 -0
  147. package/docs/architecture/decisions/20260907-assistant-learning-loop.md +35 -0
  148. package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +296 -0
  149. package/docs/architecture/decisions/20260907-maintenance-diagnosis-bounded-read.md +39 -0
  150. package/docs/architecture/domains/verification.md +7 -0
  151. package/docs/architecture/domains/workflow-engine.md +1 -1
  152. package/docs/architecture/index.md +3 -3
  153. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +19 -8
  154. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +124 -0
  155. package/docs/forge-chatgpt-mcp-setup.md +5 -3
  156. package/docs/forge-plugin-management.md +20 -4
  157. package/docs/forge-runtime-self-healing-loop.md +2 -0
  158. package/docs/operations/benchmark-cross-version-freeze.md +38 -0
  159. package/docs/operations/controller-browser-plugin.md +3 -1
  160. package/docs/operations/homebrew.md +2 -2
  161. package/docs/operations/platform-support.md +16 -8
  162. package/docs/operations/platform-support.zh-CN.md +2 -2
  163. package/docs/operations/standalone-disaster-recovery.md +26 -1
  164. package/docs/operations/uu-remote-rescue.md +41 -0
  165. package/docs/operations/windows-wsl-host-rescue.md +133 -0
  166. package/docs/public-usage-guide.md +1 -1
  167. package/docs/public-usage-guide.zh-CN.md +1 -1
  168. package/docs/tutorials/01-install-and-start.md +1 -1
  169. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  170. package/docs/tutorials/02-connect-chatgpt.md +4 -4
  171. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +4 -4
  172. package/docs/wiki/Implementation.md +1 -1
  173. package/package.json +23 -14
  174. package/packages/kernel/controller/api/index.ts +2 -0
  175. package/packages/kernel/controller/application/controller-round-service.ts +29 -0
  176. package/packages/kernel/controller/application/controller-service.ts +30 -0
  177. package/packages/kernel/controller/domain/controller-round-transition-policy.ts +308 -0
  178. package/packages/kernel/controller/domain/controller-round.ts +107 -0
  179. package/packages/kernel/controller/domain/execution-quality.ts +133 -0
  180. package/packages/kernel/controller/domain/types.ts +56 -0
  181. package/packages/kernel/controller/index.ts +1 -0
  182. package/packages/kernel/controller/infrastructure/controller-binding-store.ts +84 -0
  183. package/packages/kernel/controller/infrastructure/controller-round-store.ts +1384 -0
  184. package/packages/kernel/controller/infrastructure/controller-session-store.ts +578 -0
  185. package/packages/kernel/controller/ports/controller-host.ts +16 -0
  186. package/packages/kernel/goal/api/index.ts +1 -0
  187. package/packages/kernel/goal/domain/types.ts +40 -0
  188. package/packages/kernel/goal/index.ts +1 -0
  189. package/packages/kernel/identity/api/index.ts +5 -0
  190. package/packages/kernel/identity/application/identity-service.ts +67 -0
  191. package/packages/kernel/identity/domain/scope.ts +63 -0
  192. package/packages/kernel/identity/domain/semantic-sync.ts +81 -0
  193. package/packages/kernel/identity/domain/types.ts +44 -0
  194. package/packages/kernel/identity/domain/workspace.ts +56 -0
  195. package/packages/kernel/identity/index.ts +1 -0
  196. package/packages/kernel/identity/infrastructure/identity-store.ts +75 -0
  197. package/packages/kernel/memory/api/index.ts +5 -0
  198. package/packages/kernel/memory/application/experience-service.ts +118 -0
  199. package/packages/kernel/memory/application/outcome-service.ts +77 -0
  200. package/packages/kernel/memory/domain/experience.ts +103 -0
  201. package/packages/kernel/memory/domain/operational-prior.ts +92 -0
  202. package/packages/kernel/memory/index.ts +1 -0
  203. package/packages/kernel/progression/api/index.ts +2 -0
  204. package/packages/kernel/progression/application/projector.ts +211 -0
  205. package/packages/kernel/progression/domain/types.ts +116 -0
  206. package/packages/kernel/progression/index.ts +1 -0
  207. package/packages/kernel/scheduler/api/index.ts +6 -0
  208. package/packages/kernel/scheduler/application/continuation-service.ts +212 -0
  209. package/packages/kernel/scheduler/application/eligibility.ts +252 -0
  210. package/packages/kernel/scheduler/application/schedule-service.ts +24 -0
  211. package/packages/kernel/scheduler/application/settlement.ts +197 -0
  212. package/packages/kernel/scheduler/domain/continuation.ts +38 -0
  213. package/packages/kernel/scheduler/domain/schedule.ts +149 -0
  214. package/packages/kernel/scheduler/index.ts +1 -0
  215. package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +38 -0
  216. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +785 -0
  217. package/packages/kernel/work/api/index.ts +15 -0
  218. package/packages/kernel/work/application/work-service.ts +148 -0
  219. package/packages/kernel/work/domain/admission-policy.ts +34 -0
  220. package/packages/kernel/work/domain/check-receipt.ts +43 -0
  221. package/packages/kernel/work/domain/context-closure-receipt.ts +32 -0
  222. package/packages/kernel/work/domain/engineering-contracts.ts +157 -0
  223. package/packages/kernel/work/domain/engineering-design.ts +281 -0
  224. package/packages/kernel/work/domain/engineering-profile.ts +242 -0
  225. package/packages/kernel/work/domain/execution-concurrency.ts +254 -0
  226. package/packages/kernel/work/domain/execution-snapshot.ts +33 -0
  227. package/packages/kernel/work/domain/implementation-review.ts +510 -0
  228. package/packages/kernel/work/domain/project-engineering-contract.ts +202 -0
  229. package/packages/kernel/work/domain/repository-completion-receipt.ts +73 -0
  230. package/packages/kernel/work/domain/state-machine.ts +259 -0
  231. package/packages/kernel/work/domain/types.ts +528 -0
  232. package/packages/kernel/work/index.ts +1 -0
  233. package/packages/kernel/work/infrastructure/work-contract-store.ts +1876 -0
  234. package/packages/kernel/work/ports/work-contract-store.ts +18 -0
  235. package/packages/plugin-runtime/browser/index.ts +3 -0
  236. package/packages/plugin-runtime/browser/provider-registry.ts +110 -0
  237. package/packages/plugin-runtime/browser/runtime-contract.ts +150 -0
  238. package/packages/plugin-runtime/browser/session-authority.ts +61 -0
  239. package/packages/plugin-runtime/computer/index.ts +6 -0
  240. package/packages/plugin-runtime/computer/provider-error.ts +26 -0
  241. package/packages/plugin-runtime/computer/provider-registration.ts +52 -0
  242. package/packages/plugin-runtime/computer/provider-registry.ts +63 -0
  243. package/packages/plugin-runtime/computer/provider.ts +19 -0
  244. package/packages/plugin-runtime/computer/target-authority.ts +190 -0
  245. package/packages/plugin-runtime/computer/target-persistence.ts +54 -0
  246. package/packages/plugin-runtime/external/index.ts +1 -0
  247. package/packages/plugin-runtime/external/unix-jsonl-transport.ts +457 -0
  248. package/packages/plugin-runtime/index.ts +3 -0
  249. package/packages/protocols/browser/index.ts +1 -0
  250. package/packages/protocols/browser/session.ts +98 -0
  251. package/packages/protocols/computer/contract.ts +199 -0
  252. package/packages/protocols/computer/index.ts +1 -0
  253. package/packages/protocols/failure/contract.ts +158 -0
  254. package/packages/protocols/failure/index.ts +1 -0
  255. package/packages/protocols/handoff/index.ts +1 -0
  256. package/packages/protocols/handoff/status.ts +22 -0
  257. package/packages/protocols/mcp/execution-context.ts +11 -0
  258. package/packages/protocols/mcp/index.ts +2 -0
  259. package/packages/protocols/mcp/tool-contract.ts +42 -0
  260. package/packages/workflow-runtime/api/index.ts +2 -0
  261. package/packages/workflow-runtime/application/interpreter.ts +342 -0
  262. package/packages/workflow-runtime/domain/workflow-asset.ts +223 -0
  263. package/packages/workflow-runtime/index.ts +1 -0
  264. package/scripts/assemble-template.ts +1 -1
  265. package/scripts/benchmark-computer-hotpath.ts +255 -0
  266. package/scripts/benchmark-controller-round2.ts +54 -2
  267. package/scripts/benchmark-ios-agent-device.ts +3 -6
  268. package/scripts/benchmark-mcp-transport.ts +3 -4
  269. package/scripts/benchmark-operational-memory-activation.ts +235 -0
  270. package/scripts/benchmark-operational-shadow.ts +28 -0
  271. package/scripts/bootstrap-runtime-maintenance-recovery.sh +4 -1
  272. package/scripts/canary-chatgpt-provider-delivery.ts +97 -0
  273. package/scripts/canary-controller-semantic-wait.ts +195 -0
  274. package/scripts/canary-global-hook.sh +2 -2
  275. package/scripts/check-mcp-compatibility.ts +173 -7
  276. package/scripts/check-platform-support.mjs +28 -2
  277. package/scripts/check-public-docs.sh +1 -1
  278. package/scripts/check-release-readiness.sh +1 -1
  279. package/scripts/check-repository-hygiene.mjs +92 -0
  280. package/scripts/check-runtime-architecture.mjs +1206 -95
  281. package/scripts/check-source-duplication.mjs +9 -1
  282. package/scripts/check-tarball-install-smoke.sh +98 -15
  283. package/scripts/controller-ngrok-rotation.sh +5 -4
  284. package/scripts/create-project-dirs.sh +10 -74
  285. package/scripts/forge-local-recovery-helper.mjs +204 -0
  286. package/scripts/forge-uu-remote-rescue-helper.d.mts +31 -0
  287. package/scripts/forge-uu-remote-rescue-helper.mjs +409 -0
  288. package/scripts/forge-windows-host-recovery-helper.mjs +136 -0
  289. package/scripts/hook-shim.sh +2 -2
  290. package/scripts/init-project.sh +12 -70
  291. package/scripts/install-desktop-operator-registration.ts +4 -2
  292. package/scripts/install-independent-windows-wsl-recovery.ts +125 -0
  293. package/scripts/install-standalone-recovery.ts +122 -10
  294. package/scripts/install-uu-remote-rescue-registration.ts +74 -0
  295. package/scripts/install-windows-host-recovery-registration.ts +22 -0
  296. package/scripts/lib/controller-home-env.sh +6 -3
  297. package/scripts/lib/project-init-lib.sh +39 -582
  298. package/scripts/migrate-chatgpt-automation-schedules.ts +2 -2
  299. package/scripts/migrate-windows-wsl-controller-home.ts +306 -0
  300. package/scripts/open-source-audit-allowlist.txt +12 -1
  301. package/scripts/portability-bindings.mjs +70 -0
  302. package/scripts/run-bounded-command.mjs +55 -0
  303. package/scripts/run-bun-test-file.ts +22 -6
  304. package/scripts/run-governed-gate.ts +4 -1
  305. package/scripts/smoke-mcp-http-runtime.ts +1 -1
  306. package/scripts/smoke-runtime-recovery.ts +34 -1
  307. package/scripts/smoke-schedule-engine.ts +1 -1
  308. package/scripts/stage-runtime-release.ts +6 -1
  309. package/scripts/test-governance.ts +51 -2
  310. package/src/cli/agent-jobs/job-manager.ts +10 -1
  311. package/src/cli/chatgpt-browser/bind-server.ts +2 -1
  312. package/src/cli/chatgpt-browser/binding.ts +20 -10
  313. package/src/cli/chatgpt-browser/bridge-extension.ts +75 -0
  314. package/src/cli/chatgpt-browser/bridge-provider.ts +376 -11
  315. package/src/cli/chatgpt-browser/engine.ts +10 -11
  316. package/src/cli/chatgpt-browser/native-provider.ts +24 -83
  317. package/src/cli/chatgpt-browser/oracle-provider.ts +5 -3
  318. package/src/cli/chatgpt-browser/types.ts +1 -0
  319. package/src/cli/commands/bootstrap-control.ts +334 -0
  320. package/src/cli/commands/brain-assistant.ts +38 -0
  321. package/src/cli/commands/brain.ts +193 -40
  322. package/src/cli/commands/chatgpt.ts +127 -0
  323. package/src/cli/commands/computer.ts +437 -0
  324. package/src/cli/commands/doctor.ts +18 -1
  325. package/src/cli/commands/init-hook.ts +71 -61
  326. package/src/cli/commands/init.ts +3 -1
  327. package/src/cli/commands/mcp.ts +16 -10
  328. package/src/cli/commands/plugin.ts +38 -12
  329. package/src/cli/commands/recovery.ts +493 -80
  330. package/src/cli/commands/runtime.ts +9 -4
  331. package/src/cli/commands/setup-profile.ts +89 -39
  332. package/src/cli/commands/status.ts +2 -1
  333. package/src/cli/controller/check-runner.ts +487 -98
  334. package/src/cli/controller/context/types.ts +7 -1
  335. package/src/cli/controller/context-pack.ts +23 -3
  336. package/src/cli/controller/issue-store.ts +1 -1
  337. package/src/cli/controller/systemd-user.ts +110 -0
  338. package/src/cli/controller/types.ts +10 -70
  339. package/src/cli/controller/worklog.ts +67 -13
  340. package/src/cli/editing/edit-operation-contract.ts +92 -0
  341. package/src/cli/editing/edit-session.ts +289 -13
  342. package/src/cli/github/plugin.ts +17 -12
  343. package/src/cli/hook/route-registry.ts +3 -5
  344. package/src/cli/hook/runtime.ts +49 -3
  345. package/src/cli/index.ts +3 -0
  346. package/src/cli/local-bridge/connector-freshness.ts +2 -2
  347. package/src/cli/local-bridge/console-automations.ts +2 -2
  348. package/src/cli/local-bridge/facade-api.ts +41 -14
  349. package/src/cli/local-bridge/job-store.ts +1 -1
  350. package/src/cli/local-bridge/server.ts +24 -27
  351. package/src/cli/mcp/access-mode.ts +2 -132
  352. package/src/cli/mcp/access-tools.ts +2 -387
  353. package/src/cli/mcp/audit.ts +2 -32
  354. package/src/cli/mcp/auth.ts +2 -404
  355. package/src/cli/mcp/instructions.ts +2 -34
  356. package/src/cli/mcp/legacy-context.ts +2 -16
  357. package/src/cli/mcp/legacy-tool-service.ts +2 -5308
  358. package/src/cli/mcp/multi-repository.ts +2 -436
  359. package/src/cli/mcp/oauth.ts +2 -375
  360. package/src/cli/mcp/openai-secure-tunnel.ts +2 -0
  361. package/src/cli/mcp/paths.ts +2 -140
  362. package/src/cli/mcp/policy.ts +2 -232
  363. package/src/cli/mcp/post-finalize-work-attribution.ts +2 -150
  364. package/src/cli/mcp/proxy-env.ts +2 -62
  365. package/src/cli/mcp/readiness-projection.ts +2 -24
  366. package/src/cli/mcp/redaction.ts +2 -21
  367. package/src/cli/mcp/repo.ts +2 -24
  368. package/src/cli/mcp/repository-tools.ts +2 -1585
  369. package/src/cli/mcp/server.ts +2 -951
  370. package/src/cli/mcp/setup.ts +2 -737
  371. package/src/cli/mcp/tool-capability-cache.ts +2 -87
  372. package/src/cli/mcp/tool-contract.ts +2 -14
  373. package/src/cli/mcp/tools.ts +2 -9
  374. package/src/cli/mcp/toolset-names.ts +2 -70
  375. package/src/cli/mcp/toolset-selection.ts +2 -41
  376. package/src/cli/mcp/toolset.ts +2 -336
  377. package/src/cli/mcp/transports/AGENTS.md +1 -10
  378. package/src/cli/mcp/transports/CLAUDE.md +1 -10
  379. package/src/cli/mcp/transports/http.ts +2 -1414
  380. package/src/cli/mcp/transports/session-registry.ts +2 -490
  381. package/src/cli/mcp/transports/stdio.ts +2 -8
  382. package/src/cli/mcp/types.ts +2 -41
  383. package/src/cli/mcp/umbrella-tools.ts +2 -117
  384. package/src/cli/repositories/command-executor.ts +64 -18
  385. package/src/cli/repositories/command-scope.ts +117 -1
  386. package/src/cli/repositories/controller-home.ts +119 -1
  387. package/src/cli/repositories/identity.ts +13 -0
  388. package/src/cli/repositories/registry.ts +68 -49
  389. package/src/cli/repositories/repository-snapshot.ts +53 -7
  390. package/src/cli/repositories/runtime-storage.ts +27 -2
  391. package/src/cli/repositories/safe-patch.ts +34 -38
  392. package/src/cli/repositories/selected-path-actions.ts +22 -2
  393. package/src/cli/repositories/structured-git.ts +105 -25
  394. package/src/cli/runtime/helper-runner.ts +7 -0
  395. package/src/cli/runtime-invocation.ts +8 -1
  396. package/src/cli/tools/codegraph.ts +44 -25
  397. package/src/core/adoption/manifest-templates.ts +1 -1
  398. package/src/core/adoption/plan.ts +3 -26
  399. package/src/core/adoption/workflow-contract-plan.ts +17 -9
  400. package/src/effects/process-runner.ts +8 -14
  401. package/src/runtime/context/assistant-context.ts +154 -0
  402. package/src/runtime/context/assistant-work-context.ts +147 -0
  403. package/src/runtime/context/codegraph-cache-boundary.ts +255 -0
  404. package/src/runtime/context/codegraph-read-provider.ts +55 -22
  405. package/src/runtime/context/codegraph-sidecar.cjs +8 -1
  406. package/src/runtime/context/context-closure.ts +268 -0
  407. package/src/runtime/context/context-plane.ts +211 -0
  408. package/src/runtime/context/project-engineering-contract.ts +60 -0
  409. package/src/runtime/control-plane/bootstrap/capability-provider.ts +74 -0
  410. package/src/runtime/control-plane/bootstrap/control-api.ts +57 -0
  411. package/src/runtime/control-plane/bootstrap/index.ts +5 -0
  412. package/src/runtime/control-plane/bootstrap/state-machine.ts +141 -0
  413. package/src/runtime/control-plane/bootstrap/store.ts +21 -0
  414. package/src/runtime/control-plane/bootstrap/types.ts +87 -0
  415. package/src/runtime/control-plane/cleanup-artifact-retention.ts +328 -0
  416. package/src/runtime/control-plane/codegraph-cache-retention.ts +164 -0
  417. package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +320 -0
  418. package/src/runtime/control-plane/execution/content-equivalent-commit-authority.ts +169 -0
  419. package/src/runtime/control-plane/execution/controller-authority-recovery.ts +350 -0
  420. package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +85 -0
  421. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +417 -100
  422. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +103 -15
  423. package/src/runtime/control-plane/execution/implementation-review-content.ts +95 -0
  424. package/src/runtime/control-plane/execution/repository-work-attribution.ts +2 -2
  425. package/src/runtime/control-plane/execution/retained-work-resume.ts +336 -6
  426. package/src/runtime/control-plane/execution/validation.ts +28 -3
  427. package/src/runtime/control-plane/execution/work-authority-reconciler.ts +116 -0
  428. package/src/runtime/control-plane/execution/work-completion-authority.ts +44 -2
  429. package/src/runtime/control-plane/execution/work-evidence-policy.ts +34 -3
  430. package/src/runtime/control-plane/execution/work-execution-support.ts +42 -22
  431. package/src/runtime/control-plane/execution/work-finalization-service.ts +1103 -166
  432. package/src/runtime/control-plane/execution/work-handle-authority.ts +267 -16
  433. package/src/runtime/control-plane/execution/work-handle-store.ts +77 -0
  434. package/src/runtime/control-plane/execution/work-head-settlement.ts +75 -0
  435. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +145 -23
  436. package/src/runtime/control-plane/execution/work-operation-service.ts +49 -13
  437. package/src/runtime/control-plane/execution/work-preparation-service.ts +31 -15
  438. package/src/runtime/control-plane/execution/work-remote-delivery.ts +1 -1
  439. package/src/runtime/control-plane/execution/work-task-receipt.ts +20 -1
  440. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +407 -76
  441. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +88 -18
  442. package/src/runtime/control-plane/execution/work-verification-context.ts +1 -1
  443. package/src/runtime/control-plane/execution/work-verification-service.ts +258 -4
  444. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +200 -15
  445. package/src/runtime/control-plane/facade/capability-registry.ts +21 -10
  446. package/src/runtime/control-plane/facade/controller-round-relay.ts +3 -1118
  447. package/src/runtime/control-plane/facade/controller-session-store.ts +2 -488
  448. package/src/runtime/control-plane/facade/goal-workloop-access.ts +6 -3
  449. package/src/runtime/control-plane/facade/goal-workloop.ts +972 -137
  450. package/src/runtime/control-plane/facade/handoff-inbox-application.ts +158 -0
  451. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +10 -0
  452. package/src/runtime/control-plane/facade/index.ts +4 -2
  453. package/src/runtime/control-plane/facade/operation-digest.ts +22 -58
  454. package/src/runtime/control-plane/facade/plan-contract-store.ts +1004 -38
  455. package/src/runtime/control-plane/facade/repository-work-admission.ts +19 -1
  456. package/src/runtime/control-plane/facade/requirement-authority.ts +218 -0
  457. package/src/runtime/control-plane/facade/requirement-board.ts +6 -1
  458. package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +48 -0
  459. package/src/runtime/control-plane/facade/semantic-admission.ts +8 -16
  460. package/src/runtime/control-plane/facade/suggested-actions.ts +4 -1
  461. package/src/runtime/control-plane/facade/types.ts +189 -532
  462. package/src/runtime/control-plane/facade/work-admission-policy.ts +84 -35
  463. package/src/runtime/control-plane/facade/work-contract-store.ts +2 -1043
  464. package/src/runtime/control-plane/facade/work-implementation-review.ts +2 -0
  465. package/src/runtime/control-plane/facade/work-state-machine.ts +2 -250
  466. package/src/runtime/control-plane/generated-cache-retention.ts +239 -18
  467. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +12 -1
  468. package/src/runtime/control-plane/global-scheduler/maintenance.ts +76 -13
  469. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-entry.ts +55 -0
  470. package/src/runtime/control-plane/global-scheduler/periodic-cleanup-process.ts +98 -0
  471. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -1
  472. package/src/runtime/control-plane/global-scheduler/scheduler-worker-entry.ts +10 -0
  473. package/src/runtime/control-plane/global-scheduler/scheduler.ts +173 -81
  474. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +18 -6
  475. package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +397 -0
  476. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +2 -177
  477. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +301 -560
  478. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +20 -4
  479. package/src/runtime/control-plane/launcher/thin-launcher.ts +50 -4
  480. package/src/runtime/control-plane/lifecycle-retention-metrics.ts +60 -0
  481. package/src/runtime/control-plane/persistence/experience-store.ts +169 -0
  482. package/src/runtime/control-plane/persistence/operational-prior-store.ts +348 -0
  483. package/src/runtime/control-plane/persistence/requirement-store.ts +30 -22
  484. package/src/runtime/control-plane/persistence/sqlite-store.ts +545 -76
  485. package/src/runtime/control-plane/persistence/workflow-content-store.ts +98 -0
  486. package/src/runtime/control-plane/persistence/workflow-registry-store.ts +186 -0
  487. package/src/runtime/control-plane/persistence/workflow-run-store.ts +123 -0
  488. package/src/runtime/control-plane/release-retention.ts +56 -16
  489. package/src/runtime/control-plane/repository-namespace-retention.ts +208 -0
  490. package/src/runtime/control-plane/routing/route-policy.ts +19 -0
  491. package/src/runtime/control-plane/runtime-cleanup.ts +574 -37
  492. package/src/runtime/control-plane/runtime-generation.ts +6 -0
  493. package/src/runtime/control-plane/workspace/semantic-sync-service.ts +465 -0
  494. package/src/runtime/control-plane/workspace/workspace-store.ts +80 -0
  495. package/src/runtime/diagnostics/incident-repair.ts +406 -0
  496. package/src/runtime/diagnostics/mcp-timing.ts +125 -6
  497. package/src/runtime/diagnostics/performance.ts +6 -14
  498. package/src/runtime/diagnostics/read-only-tool.ts +1 -1
  499. package/src/runtime/evidence/artifact-store.ts +65 -2
  500. package/src/runtime/evidence/operational-shadow.ts +38 -0
  501. package/src/runtime/evidence/process-check-receipt.ts +5 -43
  502. package/src/runtime/evidence/sensitive-output.ts +4 -4
  503. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -1
  504. package/src/runtime/execution/jobs/store.ts +121 -2
  505. package/src/runtime/execution/managed-workspace.ts +55 -5
  506. package/src/runtime/execution/process-runtime/canary.ts +19 -5
  507. package/src/runtime/execution/process-runtime/check-classification.ts +48 -0
  508. package/src/runtime/execution/process-runtime/check-facade.ts +23 -9
  509. package/src/runtime/execution/process-runtime/check-receipt.ts +1 -1
  510. package/src/runtime/execution/process-runtime/check-result-retention.ts +132 -0
  511. package/src/runtime/execution/process-runtime/check-result.ts +73 -2
  512. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +42 -3
  513. package/src/runtime/execution/process-runtime/check-storage.ts +187 -0
  514. package/src/runtime/execution/process-runtime/command-facade.ts +38 -87
  515. package/src/runtime/execution/process-runtime/index.ts +1 -0
  516. package/src/runtime/execution/process-runtime/interactive-admission.ts +2 -1
  517. package/src/runtime/execution/process-runtime/lightweight-managed.ts +32 -1
  518. package/src/runtime/execution/process-runtime/persisted-check.ts +128 -21
  519. package/src/runtime/execution/process-runtime/resource-claims.ts +60 -3
  520. package/src/runtime/execution/process-runtime/runtime.ts +132 -15
  521. package/src/runtime/execution/process-runtime/types.ts +22 -0
  522. package/src/runtime/execution/workers/executor.ts +16 -13
  523. package/src/runtime/execution/workers/worker-entry.ts +1 -1
  524. package/src/runtime/gateway/mcp/execution-tools.ts +2 -207
  525. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +2 -61
  526. package/src/runtime/gateway/mcp/persisted-check-process.ts +2 -3
  527. package/src/runtime/gateway/mcp/process-tools.ts +2 -289
  528. package/src/runtime/gateway/mcp/router.ts +2 -1125
  529. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +2 -565
  530. package/src/runtime/gateway/mcp/runtime-tools.ts +2 -6651
  531. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +2 -1
  532. package/src/runtime/maintenance/cleanup.ts +38 -24
  533. package/src/runtime/platform/browser-product-discovery.ts +174 -0
  534. package/src/runtime/platform/computer-platform.ts +14 -0
  535. package/src/runtime/platform/executable-discovery.ts +54 -0
  536. package/src/runtime/platform/fetch-http-server.ts +90 -0
  537. package/src/runtime/platform/service-manager.ts +138 -0
  538. package/src/runtime/platform/windows-host-discovery.ts +118 -0
  539. package/src/runtime/platform/windows-recovery-deployment.ts +67 -0
  540. package/src/runtime/plugins/action-application.ts +135 -0
  541. package/src/runtime/plugins/app-store-connect-adapter.ts +9 -21
  542. package/src/runtime/plugins/browser-adapter.ts +140 -100
  543. package/src/runtime/plugins/browser-automation-service.ts +15 -36
  544. package/src/runtime/plugins/browser-macos-bridge.ts +69 -21
  545. package/src/runtime/plugins/browser-manifest-surface.ts +30 -6
  546. package/src/runtime/plugins/browser-native-session-service.ts +1 -1
  547. package/src/runtime/plugins/browser-provider-registry.ts +2 -109
  548. package/src/runtime/plugins/browser-registration.ts +16 -0
  549. package/src/runtime/plugins/browser-runtime-contract.ts +2 -154
  550. package/src/runtime/plugins/browser-runtime.ts +41 -9
  551. package/src/runtime/plugins/browser-session-authority.ts +101 -291
  552. package/src/runtime/plugins/browser-session-legacy-migration.ts +203 -0
  553. package/src/runtime/plugins/browser-session-store.ts +229 -36
  554. package/src/runtime/plugins/browser-session-types.ts +2 -98
  555. package/src/runtime/plugins/capability-authorization-grants.ts +17 -0
  556. package/src/runtime/plugins/catalog-trust.ts +82 -0
  557. package/src/runtime/plugins/computer-registration.ts +839 -0
  558. package/src/runtime/plugins/config-store.ts +119 -0
  559. package/src/runtime/plugins/desktop-operator-external-policy.ts +932 -0
  560. package/src/runtime/plugins/desktop-operator-registration.ts +111 -8
  561. package/src/runtime/plugins/errors.ts +8 -1
  562. package/src/runtime/plugins/external-adapter.ts +45 -322
  563. package/src/runtime/plugins/external-provider-policy.ts +48 -0
  564. package/src/runtime/plugins/external-registration.ts +9 -1
  565. package/src/runtime/plugins/external-unix-socket-probe.cjs +9 -3
  566. package/src/runtime/plugins/external-unix-socket.ts +64 -193
  567. package/src/runtime/plugins/first-party-external-registration.ts +163 -0
  568. package/src/runtime/plugins/first-party-registry.ts +3 -1
  569. package/src/runtime/plugins/github-adapter.ts +1 -2
  570. package/src/runtime/plugins/gmail-adapter.ts +6 -6
  571. package/src/runtime/plugins/google-calendar-adapter.ts +6 -6
  572. package/src/runtime/plugins/google-shared.ts +31 -46
  573. package/src/runtime/plugins/google-tasks-adapter.ts +6 -6
  574. package/src/runtime/plugins/ios-adapter.ts +1 -2
  575. package/src/runtime/plugins/local-system-adapter.ts +180 -0
  576. package/src/runtime/plugins/macos-capability-broker.ts +15 -105
  577. package/src/runtime/plugins/resend-adapter.ts +15 -24
  578. package/src/runtime/plugins/store.ts +493 -51
  579. package/src/runtime/plugins/types.ts +8 -1
  580. package/src/runtime/plugins/uu-remote-rescue-registration.ts +155 -0
  581. package/src/runtime/plugins/windows-host-recovery-install.ts +68 -0
  582. package/src/runtime/plugins/windows-host-recovery-registration.ts +85 -0
  583. package/src/runtime/plugins/xiaohongshu-publish.ts +86 -430
  584. package/src/runtime/projections/controller-context.ts +12 -6
  585. package/src/runtime/projections/git-status-sampler.ts +73 -22
  586. package/src/runtime/projections/materialized-view.ts +14 -8
  587. package/src/runtime/recovery/capability-action-application.ts +228 -0
  588. package/src/runtime/recovery/index.ts +3 -0
  589. package/src/runtime/recovery/lifecycle-inventory.ts +338 -0
  590. package/src/runtime/recovery/local-jobs-repair.ts +14 -14
  591. package/src/runtime/recovery/maintenance-executor.ts +358 -76
  592. package/src/runtime/recovery/quarantine-retention.ts +256 -0
  593. package/src/runtime/release/identity.ts +6 -0
  594. package/src/runtime/resources/leases/store.ts +1 -1
  595. package/src/runtime/root/active-execution-power-assertion.ts +139 -0
  596. package/src/runtime/root/assistant-learning-loop.ts +45 -0
  597. package/src/runtime/root/browser-session-composition.ts +59 -0
  598. package/src/runtime/root/computer-composition.ts +191 -0
  599. package/src/runtime/root/computer-target-composition.ts +16 -0
  600. package/src/runtime/root/computer-target-persistence.ts +39 -0
  601. package/src/runtime/root/controller-round-composition.ts +107 -0
  602. package/src/runtime/root/controller-services.ts +2 -0
  603. package/src/runtime/root/entry.ts +4 -3
  604. package/src/runtime/root/gateway-adapter.ts +3 -3
  605. package/src/runtime/root/local-bridge.ts +5 -2
  606. package/src/runtime/root/mcp-transport.ts +5 -5
  607. package/src/runtime/root/package-connector-service.ts +108 -56
  608. package/src/runtime/root/package-runtime-release.ts +86 -18
  609. package/src/runtime/root/package-runtime-service.ts +63 -86
  610. package/src/runtime/root/release-execution-canary.ts +71 -0
  611. package/src/runtime/root/release-loader.ts +68 -0
  612. package/src/runtime/root/release-manifest.ts +106 -2
  613. package/src/runtime/root/release-materialize.ts +103 -4
  614. package/src/runtime/root/release-store.ts +31 -2
  615. package/src/runtime/root/runtime.ts +240 -25
  616. package/src/runtime/root/scheduled-controller-composition.ts +51 -0
  617. package/src/runtime/root/scheduler.ts +12 -4
  618. package/src/runtime/root/service-runner.ts +1 -1
  619. package/src/runtime/root/service.ts +41 -16
  620. package/src/runtime/root/status.ts +1 -0
  621. package/src/runtime/root/types.ts +24 -1
  622. package/src/runtime/shared/local-bridge-surface.ts +1 -1
  623. package/src/runtime/shared/process-environment.ts +19 -4
  624. package/src/runtime/shared/storage-capacity.ts +68 -0
  625. package/src/runtime/standalone-recovery/controller-home-migration.ts +1116 -0
  626. package/src/runtime/standalone-recovery/core.ts +984 -230
  627. package/src/runtime/standalone-recovery/entry.ts +182 -51
  628. package/src/runtime/standalone-recovery/independent-host-rescue.ts +172 -0
  629. package/src/runtime/standalone-recovery/installer.ts +212 -104
  630. package/src/runtime/standalone-recovery/mcp-server.ts +243 -0
  631. package/src/runtime/standalone-recovery/mutation-identity-contract.ts +21 -0
  632. package/src/runtime/standalone-recovery/operation-lock.ts +160 -0
  633. package/src/runtime/standalone-recovery/performance.ts +135 -0
  634. package/src/runtime/standalone-recovery/release.ts +5 -0
  635. package/src/runtime/watchdog/workflow-watchdog.ts +1 -1
  636. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -1
  637. package/src/runtime/workflow/schedules/engine.ts +191 -343
  638. package/src/runtime/workflow/schedules/pending-activation.ts +1 -1
  639. package/src/runtime/workflow/schedules/settlement.ts +2 -191
  640. package/src/runtime/workflow/schedules/store.ts +2 -607
  641. package/src/runtime/workflow/schedules/types.ts +2 -127
  642. package/src/runtime/workflow/schedules/work-continuation.ts +180 -15
  643. package/src/runtime/workflows/first-party/xiaohongshu.ts +122 -0
  644. package/src/runtime/workflows/runtime.ts +486 -0
  645. package/src/testing/test-governance.ts +90 -29
  646. package/assets/templates/helpers/archive-architecture-request.sh +0 -244
  647. package/docs/architecture/history/README.md +0 -7
  648. package/docs/architecture/history/chatgpt-handoff-facade.md +0 -397
  649. package/docs/architecture/history/global-hook-runtime.md +0 -166
  650. package/docs/architecture/history/ios-semantic-automation-provider-v2.md +0 -170
  651. package/docs/architecture/requests/archive/2026/20260528-154740-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  652. package/docs/architecture/requests/archive/2026/20260528-154816-assets-hooks-assets-hooks-hook-input-sh.md +0 -59
  653. package/docs/architecture/requests/archive/2026/20260528-161851-ai-harness-workflow-contract-json-ai-harness-workflow-contract-json.md +0 -59
  654. package/docs/architecture/requests/archive/2026/20260528-161853-scripts-check-task-workflow-sh-scripts-check-task-workflow-sh.md +0 -59
  655. package/docs/architecture/requests/archive/2026/20260528-161911-assets-workflow-contract-v1-json-assets-workflow-contract-v1-json.md +0 -59
  656. package/docs/architecture/requests/archive/2026/20260528-222416-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  657. package/docs/architecture/requests/archive/2026/20260528-222444-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  658. package/docs/architecture/requests/archive/2026/20260528-222457-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  659. package/docs/architecture/requests/archive/2026/20260528-222648-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
  660. package/docs/architecture/requests/archive/2026/20260528-222705-ai-hooks-ai-hooks-worktree-guard-sh.md +0 -59
  661. package/docs/architecture/requests/archive/2026/20260528-222726-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  662. package/docs/architecture/requests/archive/2026/20260528-222727-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  663. package/docs/architecture/requests/archive/2026/20260528-224736-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
  664. package/docs/architecture/requests/archive/2026/20260528-233259-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  665. package/docs/architecture/requests/archive/2026/20260528-233335-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  666. package/docs/architecture/requests/archive/2026/20260528-233456-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  667. package/docs/architecture/requests/archive/2026/20260528-233457-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  668. package/docs/architecture/requests/archive/2026/20260528-233637-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
  669. package/docs/architecture/requests/archive/2026/20260528-233638-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
  670. package/docs/architecture/requests/archive/2026/20260529-000322-root-package-json.md +0 -59
  671. package/docs/architecture/requests/archive/2026/20260529-004238-ai-hooks-ai-hooks-lib-workflow-state-sh.md +0 -59
  672. package/docs/architecture/requests/archive/2026/20260529-020654-ai-harness-policy-json-ai-harness-policy-json.md +0 -63
  673. package/docs/architecture/requests/archive/2026/20260529-094236-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  674. package/docs/architecture/requests/archive/2026/20260529-094323-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  675. package/docs/architecture/requests/archive/2026/20260529-094331-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
  676. package/docs/architecture/requests/archive/2026/20260529-094339-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
  677. package/docs/architecture/requests/archive/2026/20260529-094430-assets-hooks-assets-hooks-session-start-context-sh.md +0 -60
  678. package/docs/architecture/requests/archive/2026/20260529-094446-ai-hooks-ai-hooks-session-start-context-sh.md +0 -60
  679. package/docs/architecture/requests/archive/2026/20260613-002040-runtime-harness-hook-adapters.md +0 -68
  680. package/docs/architecture/requests/archive/2026/root.md +0 -68
  681. package/docs/architecture/requests/archive/2026/runtime-harness-hook-adapters.md +0 -76
  682. package/docs/architecture/requests/archive/2026/verification-evals-checks.md +0 -68
  683. package/docs/architecture/requests/archive/2026/workflow-engine-contract-assets.md +0 -69
  684. package/scripts/archive-architecture-request.sh +0 -4
  685. package/scripts/run-skill-evals.ts +0 -1022
@@ -0,0 +1,1691 @@
1
+ import { bindRepositoryEntities } from '../../../src/cli/repositories/entity-migration';
2
+ import { bootstrapLocalProject, diagnoseLatestLocalProjectSource } from '../../../src/cli/repositories/local-project-onboarding';
3
+ import { resolveEphemeralWorkspaceTarget } from '../../../src/cli/repositories/ephemeral-workspace';
4
+ import type { ResolvedExecutionIdentity } from '../../../src/runtime/control-plane/execution/execution-identity';
5
+ import { assertNoBoundExecutionSessionMutation, resolveClaimedRepositoryWorkId, resolveExplicitClaimedRepositoryWork, type RepositoryWorkAttributionCaller } from '../../../src/runtime/control-plane/execution/repository-work-attribution';
6
+ import { getWorkContract } from '../../../packages/kernel/work/api';
7
+ import { assertWorkPathsWithinScope } from '../../../src/runtime/control-plane/execution/work-path-scope';
8
+ import { assertCanonicalRepositoryMutationWorkHandleAvailable, ensureRepositoryMutationWorkHandle, markRepositoryMutationStarted } from '../../../src/runtime/control-plane/execution/work-handle-authority';
9
+ import { isTerminalWorkContractStatus } from '../../../src/runtime/control-plane/facade/types';
10
+ import { executeRepositoryCommand, previewRepositoryCommandExecution } from '../../../src/cli/repositories/command-executor';
11
+ import { withControllerLock } from '../../../src/cli/repositories/locks';
12
+ import {
13
+ disableRepository,
14
+ findIdenticalRepositoryRegistration,
15
+ getRepository,
16
+ listRepositories,
17
+ reconcileRepositoryCheckouts,
18
+ refreshRepository,
19
+ registerRepository,
20
+ removeRepository,
21
+ repositorySummary,
22
+ resolveRepositorySelection,
23
+ updateRepository,
24
+ validateRepository,
25
+ } from '../../../src/cli/repositories/registry';
26
+ import { buildControllerWorkbench } from '../../../src/cli/repositories/workbench';
27
+ import { applySafePatch, buildSafePatchPlan } from '../../../src/cli/repositories/safe-patch';
28
+ import { EDIT_OPERATION_INPUT_SCHEMA } from '../../../src/cli/editing/edit-operation-contract';
29
+ import { getEditSession, getEditSessionDiff, type EditSessionBinding } from '../../../src/cli/editing/edit-session';
30
+ import { buildSyncOperationDigest, classifyUserFacingError } from '../../../src/runtime/control-plane/facade/operation-digest';
31
+ import {
32
+ repositoryGitCommit,
33
+ repositoryGitCreateBranch,
34
+ repositoryGitDeleteBranch,
35
+ repositoryGitDiff,
36
+ repositoryGitFinishWorkflow,
37
+ repositoryGitMergeBranch,
38
+ repositoryGitStatus,
39
+ repositoryGitSwitchBranch,
40
+ resolveRepositoryGitCommitScope,
41
+ } from '../../../src/cli/repositories/structured-git';
42
+ import {
43
+ readRepositoryGitStatusSample,
44
+ writeRepositoryGitStatusSample,
45
+ } from '../../../src/runtime/projections/git-status-sampler';
46
+ import {
47
+ executeLightweightLanes,
48
+ executeRepositoryBatch,
49
+ integratePatchProposals,
50
+ listFastReceipts,
51
+ readFastReceipt,
52
+ routeExecution,
53
+ } from '../../../src/runtime/execution/thin-harness';
54
+ import {
55
+ classifyRawGitCommitScope,
56
+ classifyRepositoryCommandRoute,
57
+ executeRepositoryCommandViaProcessRuntime,
58
+ } from '../../../src/runtime/execution/process-runtime/command-facade';
59
+ import { assessWorkMode, parseExplicitTaskMode } from '../../../src/cli/controller/work-mode';
60
+ import { classifyRepositoryCommand } from '../../../src/cli/repositories/command-classifier';
61
+ import { normalizeRepositoryCommand } from '../../../src/cli/repositories/command-normalization';
62
+ import { readRepositoryRange } from '../../../src/cli/repository/inspector';
63
+ import { getMcpPolicy } from '../policy';
64
+ import { redactMcpText } from '../redaction';
65
+ import type { CallToolResult, McpToolDefinition } from '../../../packages/protocols/mcp/tool-contract';
66
+ import {
67
+ boundUtf8,
68
+ compactCommandOutput,
69
+ compactErrorMessage,
70
+ compactRoutingSummary,
71
+ RESPONSE_BUDGET,
72
+ } from '../../../src/runtime/shared/response-budget';
73
+
74
+ export type RepositoryToolResult = CallToolResult;
75
+
76
+ function definition(
77
+ name: string,
78
+ description: string,
79
+ properties: Record<string, unknown>,
80
+ required: string[] = [],
81
+ readOnlyHint = false,
82
+ destructiveHint = false,
83
+ ): McpToolDefinition {
84
+ return {
85
+ name,
86
+ description,
87
+ inputSchema: {
88
+ type: 'object',
89
+ properties,
90
+ ...(required.length > 0 ? { required } : {}),
91
+ additionalProperties: false,
92
+ },
93
+ annotations: { readOnlyHint, openWorldHint: false, destructiveHint },
94
+ };
95
+ }
96
+
97
+ const repoId = { type: 'string', description: 'Stable Repository Registry repoId.' };
98
+
99
+ export const repositoryToolDefinitions: McpToolDefinition[] = [
100
+ definition('repository_register', 'Register a Git repository with the Controller.', {
101
+ path: { type: 'string' },
102
+ display_name: { type: 'string' },
103
+ remote_url: { type: 'string' },
104
+ default_branch: { type: 'string' },
105
+ detail_level: { type: 'string', enum: ['summary', 'detail'], description: 'Defaults to summary; detail returns full checkout and migration evidence.' },
106
+ }, ['path']),
107
+ definition('repository_latest_source_diagnose', 'Read-only diagnosis that compares sibling project directories and recommends the latest usable source tree.', {
108
+ path: { type: 'string', description: 'Absolute local project path.' },
109
+ repo_id: repoId,
110
+ }, [], true),
111
+ definition('repository_bootstrap_local_project', 'Safely initialize and optionally register a trusted non-Git local project directory.', {
112
+ path: { type: 'string', description: 'Absolute local project path.' },
113
+ display_name: { type: 'string' },
114
+ default_branch: { type: 'string' },
115
+ mode: { type: 'string', enum: ['init_git_only', 'init_git_and_register', 'replace_registration'] },
116
+ replace_registered_repo_id: repoId,
117
+ confirm_authorization: { type: 'boolean', description: 'Must be true to authorize Git initialization and registration.' },
118
+ }, ['path', 'confirm_authorization'], false, true),
119
+ definition('repository_list', 'List registered repositories.', {
120
+ include_removed: { type: 'boolean' },
121
+ }, [], true),
122
+ definition('repository_get', 'Inspect one registered repository.', {
123
+ repo_id: repoId,
124
+ include_removed: { type: 'boolean' },
125
+ detail_level: { type: 'string', enum: ['summary', 'detail'], description: 'Defaults to summary; detail returns the complete checkout history.' },
126
+ }, ['repo_id'], true),
127
+ definition('read_repository_file', 'Read a line range from one policy-readable repository file.', {
128
+ repo_id: repoId,
129
+ checkout_id: { type: 'string' },
130
+ path: { type: 'string' },
131
+ start_line: { type: 'number' },
132
+ end_line: { type: 'number' },
133
+ }, ['path'], true),
134
+ definition('repository_validate', 'Validate repository identity and migrate legacy ownership.', {
135
+ repo_id: repoId,
136
+ detail_level: { type: 'string', enum: ['summary', 'detail'], description: 'Defaults to summary; detail returns full migration evidence.' },
137
+ }, ['repo_id']),
138
+ definition('repository_refresh', 'Refresh repository Git and checkout metadata.', {
139
+ repo_id: repoId,
140
+ detail_level: { type: 'string', enum: ['summary', 'detail'], description: 'Defaults to summary; detail returns full checkout and migration evidence.' },
141
+ }, ['repo_id']),
142
+ definition('repository_update', 'Update mutable repository metadata.', {
143
+ repo_id: repoId,
144
+ display_name: { type: 'string' },
145
+ default_branch: { type: 'string' },
146
+ enabled: { type: 'boolean' },
147
+ }, ['repo_id']),
148
+ definition('repository_disable', 'Disable new execution while retaining audit history.', {
149
+ repo_id: repoId,
150
+ }, ['repo_id']),
151
+ definition('repository_remove', 'Soft-remove a repository while retaining audit history.', {
152
+ repo_id: repoId,
153
+ }, ['repo_id']),
154
+ definition('repository_workbench', 'Return Workbench state or invoke one bounded Thin Harness operation without adding top-level MCP tools. Prefer batch_execute for multi-step status→search→read→diff or read→patch→check flows so ChatGPT pays one routing/receipt ownership cycle.', {
155
+ repo_id: repoId,
156
+ checkout_id: { type: 'string', description: 'Optional checkout identity for repository-scoped operations.' },
157
+ include_removed: { type: 'boolean' },
158
+ operation: {
159
+ type: 'string',
160
+ enum: ['summary', 'batch_execute', 'lanes_execute', 'lanes_integrate', 'fast_receipt_get', 'fast_receipt_list', 'execution_route', 'assess_work_mode'],
161
+ description: 'Defaults to summary. Use batch_execute for multi-step Fast Path (one parent receipt). Use lanes_execute for limited parallel reads. Use assess_work_mode for Fast/Durable routing advice.',
162
+ },
163
+ payload: {
164
+ type: 'object',
165
+ description: 'Operation-specific bounded arguments. Batch write operations should include request_id. For batch_execute: { steps:[{id?, kind, input}], stop_on_error?, allowed_paths?, timeout_ms?, request_id?, purpose? }. For assess_work_mode: { description, known_paths?, expected_files?, requires_parallelism?, independent_task_count?, agent_requested? }. Agent routing is opt-in only.',
166
+ additionalProperties: true,
167
+ },
168
+ }),
169
+ definition('repository_git_status', 'Return a structured Git status snapshot for the selected repository checkout.', {
170
+ repo_id: repoId,
171
+ checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
172
+ refresh: { type: 'boolean', description: 'When true, explicitly refresh the Git status sample. The default public hot path only reads the latest daemon sample.' },
173
+ }, [], true),
174
+
175
+ definition('repository_git_diff', 'Return a bounded structured git diff for the selected repository, optionally staged and path-scoped.', {
176
+ repo_id: repoId,
177
+ checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
178
+ staged: { type: 'boolean' },
179
+ paths: { type: 'array', items: { type: 'string' } },
180
+ max_bytes: { type: 'number' },
181
+ }, [], true),
182
+ definition('repository_git_create_branch', 'Create a safe local Git branch, optionally switching to it.', {
183
+ repo_id: repoId,
184
+ checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
185
+ branch: { type: 'string' },
186
+ start_point: { type: 'string' },
187
+ switch_to: { type: 'boolean', description: 'Defaults to true. False creates the branch without switching.' },
188
+ }, ['branch']),
189
+ definition('repository_git_switch_branch', 'Switch to a safe local Git branch name.', {
190
+ repo_id: repoId,
191
+ checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
192
+ branch: { type: 'string' },
193
+ }, ['branch']),
194
+ definition('repository_git_merge_branch', 'Merge a branch into the current branch using --ff-only by default.', {
195
+ repo_id: repoId,
196
+ checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
197
+ branch: { type: 'string' },
198
+ no_ff: { type: 'boolean', description: 'Use --no-ff instead of the default --ff-only.' },
199
+ }, ['branch']),
200
+ definition('repository_git_delete_branch', 'Delete a safe local Git branch after it has been merged or intentionally discarded.', {
201
+ repo_id: repoId,
202
+ checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
203
+ branch: { type: 'string' },
204
+ force: { type: 'boolean' },
205
+ }, ['branch']),
206
+
207
+ definition('repository_git_commit', 'Stage optional explicit paths and commit through a structured Git action.', {
208
+ repo_id: repoId,
209
+ checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
210
+ message: { type: 'string' },
211
+ paths: { type: 'array', items: { type: 'string' } },
212
+ allow_empty: { type: 'boolean' },
213
+ }, ['message']),
214
+ definition('repository_git_finish_workflow', 'Finish the current feature workflow: require clean tree, switch to target, merge feature branch, and delete it by default.', {
215
+ repo_id: repoId,
216
+ checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
217
+ feature_branch: { type: 'string', description: 'Defaults to the current branch.' },
218
+ target_branch: { type: 'string', description: 'Defaults to repository defaultBranch or main.' },
219
+ delete_branch: { type: 'boolean', description: 'Defaults to true.' },
220
+ no_ff: { type: 'boolean', description: 'Use --no-ff instead of the default --ff-only.' },
221
+ }, []),
222
+ definition('repository_safe_patch_plan', 'Plan a deterministic chunked repository patch with fresh file fingerprints before applying.', {
223
+ repo_id: repoId,
224
+ checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
225
+ operations: { type: 'array', items: EDIT_OPERATION_INPUT_SCHEMA, description: 'Typed edit operations. Use type=create for a new path; write/replace/insert/prepend/append/delete require an existing target and preserve strict preconditions.' },
226
+ chunk_size: { type: 'number', description: 'Maximum operations per deterministic chunk. Capped at 100.' },
227
+ }, ['operations'], true),
228
+ definition('repository_safe_patch_apply', 'Apply one coherent deterministic edit batch and return bounded review evidence. Checks are opt-in: pass check_ids only when the batch is stable. Long checks return managed Process handles instead of blocking the MCP call; use validation_only with the returned session/request ids to join later without replaying the patch.', {
229
+ repo_id: repoId,
230
+ checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
231
+ work_id: { type: 'string', description: 'Optional durable Work identity. Workflow controllers should pass the exact claimed Work id so attribution survives transient MCP transport sessions.' },
232
+ session_id: { type: 'string', description: 'Existing edit session id. Required for validation_only; otherwise omit to create one.' },
233
+ purpose: { type: 'string', description: 'Purpose for a newly created edit session.' },
234
+ operations: { type: 'array', items: EDIT_OPERATION_INPUT_SCHEMA, description: 'Typed edit operations. Use type=create for a new path; write/replace/insert/prepend/append/delete require an existing target and preserve strict preconditions. Required unless validation_only=true.' },
235
+ chunk_size: { type: 'number', description: 'Maximum operations per deterministic chunk. Capped at 100.' },
236
+ expected_revision: { type: 'number', description: 'Expected starting edit-session revision.' },
237
+ allowed_paths: { type: 'array', items: { type: 'string' }, description: 'Optional allowed path globs for a newly created session.' },
238
+ continue_on_error: { type: 'boolean', description: 'Continue applying later independent chunks after a failed chunk. Defaults to false.' },
239
+ refresh_fingerprints: { type: 'boolean', description: 'Refresh file fingerprints before every chunk. Defaults to true.' },
240
+ recover_stale_session: { type: 'boolean', description: 'For new sessions, recover stale edit-session fingerprints into a fresh session. Defaults to true.' },
241
+ check_ids: { type: 'array', items: { type: 'string' }, description: 'Optional focused validation for this stable coherent edit batch. Omit to edit without running tests.' },
242
+ check_timeout_ms: { type: 'number', description: 'Per-check execution budget. Long checks still return managed handles immediately.' },
243
+ lease_wait_ms: { type: 'number', description: 'Bounded resource-lease wait for conflicting validation lanes.' },
244
+ validation_request_id: { type: 'string', description: 'Stable validation identity returned by a prior edit+validation call; reuse with validation_only to join without replaying edits.' },
245
+ validation_only: { type: 'boolean', description: 'When true, do not edit. Reconcile/reuse the existing session validation Processes identified by session_id/check_ids/validation_request_id.' },
246
+ apply_mode: { type: 'string', enum: ['sync', 'async'], description: 'Defaults to sync for interactive development. Set async only for a separately durable operation.' },
247
+ }),
248
+ definition('repository_command_preview', 'Preview one repository-scoped local command with classification, approval token, and Git snapshots.', {
249
+ repo_id: repoId,
250
+ checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
251
+ command: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Legacy shell string or typed argv array. Typed argv executes without a shell.' },
252
+ cwd: { type: 'string', description: 'Optional root-relative working directory.' },
253
+ workspace_root: { type: 'string', description: 'Absolute existing local directory used as an ephemeral execution root. Mutually exclusive with repo_id/checkout_id; does not register or initialize the directory.' },
254
+ }, ['command'], true),
255
+ definition('repository_command_execute', 'Execute one repository-scoped command through the thinnest eligible lane: ephemeral for ordinary local work, an in-memory lightweight handle when it outlives the interaction budget, or explicit Durable handling for Work/external/release effects. Lightweight handles have no SQLite, Lease, recovery, or replay membership. Use rh_context for routine code discovery/reading; shell exploration is fallback-only.', {
256
+ repo_id: repoId,
257
+ checkout_id: { type: 'string', description: 'Optional checkout identity for repositories with multiple local clones.' },
258
+ work_id: { type: 'string', description: 'Optional durable Work identity. Workflow controllers should pass the exact claimed Work id so attribution survives transient MCP transport sessions.' },
259
+ command: { anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }], description: 'Legacy shell string or typed argv array. Typed argv executes without a shell.' },
260
+ cwd: { type: 'string', description: 'Optional root-relative working directory.' },
261
+ workspace_root: { type: 'string', description: 'Absolute existing local directory used as an ephemeral execution root. Mutually exclusive with repo_id/checkout_id; does not register or initialize the directory.' },
262
+ approval_token: { type: 'string', description: 'Exact approval token returned by repository_command_preview.' },
263
+ approval_request_id: { type: 'string', description: 'Resolved approvalRequestId returned by approval_resolve.' },
264
+ timeout_ms: { type: 'number', description: 'Optional execution timeout in milliseconds.' },
265
+ max_output_bytes: { type: 'number', description: 'Optional cap for captured stdout/stderr.' },
266
+ }, ['command']),
267
+
268
+ // Thin Harness V1 is exposed through repository_workbench operations to keep the stable tool surface bounded.
269
+ ];
270
+
271
+ export const repositoryToolNames = repositoryToolDefinitions.map((tool) => tool.name);
272
+
273
+ function result(value: Record<string, unknown>): RepositoryToolResult {
274
+ // Compact text channel by default; structuredContent remains the machine view.
275
+ return {
276
+ content: [{ type: 'text', text: JSON.stringify(value) }],
277
+ structuredContent: value,
278
+ };
279
+ }
280
+
281
+ const DEFAULT_MIGRATION_SAMPLE_LIMIT = 3;
282
+
283
+ function withResponseMeta(payload: Record<string, unknown>, startedAt: number): Record<string, unknown> {
284
+ const response = {
285
+ ...payload,
286
+ responseMeta: {
287
+ serverDurationMs: Math.round((performance.now() - startedAt) * 100) / 100,
288
+ structuredPayloadBytes: 0,
289
+ },
290
+ };
291
+ response.responseMeta.structuredPayloadBytes = Buffer.byteLength(JSON.stringify(response), 'utf8');
292
+ return response;
293
+ }
294
+
295
+ export type RepositoryToolCallerContext = RepositoryWorkAttributionCaller;
296
+
297
+ export function claimedSessionEditBinding(
298
+ controllerHome: string,
299
+ repository: ReturnType<typeof resolveRepositorySelection>,
300
+ caller?: RepositoryToolCallerContext,
301
+ explicitWorkId?: unknown,
302
+ ): EditSessionBinding | undefined {
303
+ const workId = resolveClaimedRepositoryWorkId(controllerHome, repository, caller, explicitWorkId);
304
+ if (!workId || !caller?.principalId?.trim()) return undefined;
305
+ const work = getWorkContract({ controllerHome, repoId: repository.repoId }, workId);
306
+ if (!work) return undefined;
307
+ return {
308
+ workId,
309
+ repoId: repository.repoId,
310
+ checkoutId: repository.activeCheckoutId,
311
+ principalId: caller.principalId.trim(),
312
+ controllerInstanceId: caller.controllerInstanceId,
313
+ routeDecisionFingerprint: work.routeDecisionFingerprint,
314
+ };
315
+ }
316
+
317
+ function safePatchEditBinding(
318
+ controllerHome: string,
319
+ repository: ReturnType<typeof resolveRepositorySelection>,
320
+ caller: RepositoryToolCallerContext | undefined,
321
+ args: Record<string, unknown>,
322
+ ): EditSessionBinding | undefined {
323
+ const explicitWorkId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
324
+ if (explicitWorkId) return claimedSessionEditBinding(controllerHome, repository, caller, explicitWorkId);
325
+ const sessionId = typeof args.session_id === 'string' ? args.session_id.trim() : '';
326
+ if (sessionId) {
327
+ const existingWorkId = getEditSession(repository.canonicalRoot, sessionId).workId?.trim();
328
+ return existingWorkId
329
+ ? claimedSessionEditBinding(controllerHome, repository, caller, existingWorkId)
330
+ : undefined;
331
+ }
332
+ assertNoBoundExecutionSessionMutation(controllerHome, repository, caller);
333
+ return undefined;
334
+ }
335
+
336
+ function resolveRepositorySelectionForClaimedWork(
337
+ controllerHome: string,
338
+ args: Record<string, unknown>,
339
+ repoIdValue: string,
340
+ caller?: RepositoryToolCallerContext,
341
+ ): ReturnType<typeof resolveRepositorySelection> {
342
+ const checkoutId = typeof args.checkout_id === 'string' ? args.checkout_id.trim() : '';
343
+ const explicitWorkId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
344
+ const repository = resolveRepositorySelection({
345
+ repoId: repoIdValue || undefined,
346
+ checkoutId: checkoutId || undefined,
347
+ controllerHome,
348
+ allowSoleRepository: true,
349
+ });
350
+ if (!explicitWorkId || checkoutId) return repository;
351
+ const work = resolveExplicitClaimedRepositoryWork(controllerHome, repository, caller, explicitWorkId);
352
+ if (!work?.checkoutId || work.checkoutId === repository.activeCheckoutId) return repository;
353
+ return resolveRepositorySelection({
354
+ repoId: repository.repoId,
355
+ checkoutId: work.checkoutId,
356
+ controllerHome,
357
+ allowSoleRepository: true,
358
+ });
359
+ }
360
+
361
+ function rawDefaultBranchMergeCommand(repository: ReturnType<typeof resolveRepositorySelection>, command: unknown): boolean {
362
+ if (!repository.defaultBranch) return false;
363
+ const checkout = repository.checkouts.find((entry) => entry.checkoutId === repository.activeCheckoutId);
364
+ if (checkout?.branch !== repository.defaultBranch) return false;
365
+ const normalized = normalizeRepositoryCommand(command);
366
+ if (normalized.kind === 'argv') {
367
+ const executable = normalized.executable?.split('/').pop();
368
+ return executable === 'git' && normalized.args?.[0] === 'merge';
369
+ }
370
+ return /(?:^|[;&|]\s*)git\s+merge\b/.test(normalized.shellCommand ?? '');
371
+ }
372
+
373
+ interface HistoricalReadOnlyWorkContext {
374
+ workId: string;
375
+ status: string;
376
+ mode: 'historical_read_only_observation';
377
+ attribution: 'not_active_work';
378
+ }
379
+
380
+ function historicalReadOnlyWorkContext(
381
+ controllerHome: string,
382
+ repository: ReturnType<typeof resolveRepositorySelection>,
383
+ args: Record<string, unknown>,
384
+ ): HistoricalReadOnlyWorkContext | undefined {
385
+ const requestedWorkId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
386
+ if (!requestedWorkId) return undefined;
387
+ const work = getWorkContract({ controllerHome, repoId: repository.repoId }, requestedWorkId);
388
+ if (!work || !isTerminalWorkContractStatus(work.status)) return undefined;
389
+ const classification = classifyRepositoryCommand(args.command as string | string[], repository.defaultBranch);
390
+ if (classification.risk !== 'readonly') return undefined;
391
+ return {
392
+ workId: work.workId,
393
+ status: work.status,
394
+ mode: 'historical_read_only_observation',
395
+ attribution: 'not_active_work',
396
+ };
397
+ }
398
+
399
+ function resolveRepositoryCommandTarget(
400
+ controllerHome: string,
401
+ args: Record<string, unknown>,
402
+ repoIdValue: string,
403
+ caller?: RepositoryToolCallerContext,
404
+ ): {
405
+ repository: ReturnType<typeof resolveRepositorySelection>;
406
+ executionIdentity: ResolvedExecutionIdentity;
407
+ workspace?: { workspaceId: string; root: string; registered: false };
408
+ historicalWorkContext?: HistoricalReadOnlyWorkContext;
409
+ } {
410
+ const workspaceRoot = typeof args.workspace_root === 'string' ? args.workspace_root.trim() : '';
411
+ const checkoutId = typeof args.checkout_id === 'string' ? args.checkout_id.trim() : '';
412
+ if (workspaceRoot) {
413
+ if (repoIdValue || checkoutId) {
414
+ throw new Error('EPHEMERAL_WORKSPACE_TARGET_CONFLICT: workspace_root cannot be combined with repo_id or checkout_id');
415
+ }
416
+ const target = resolveEphemeralWorkspaceTarget(workspaceRoot, controllerHome);
417
+ return {
418
+ repository: target.repository,
419
+ executionIdentity: {
420
+ schemaVersion: 1,
421
+ authority: 'ephemeral_workspace',
422
+ repositoryId: target.workspaceId,
423
+ checkoutId: target.checkoutId,
424
+ canonicalRoot: target.canonicalRoot,
425
+ },
426
+ workspace: { workspaceId: target.workspaceId, root: target.canonicalRoot, registered: false },
427
+ };
428
+ }
429
+ // A terminal Work no longer owns mutation or checkout authority. An exact
430
+ // terminal work_id may still accompany a read-only observation immediately
431
+ // after finalize, so preserve it as explicit historical context while
432
+ // executing against the caller-selected/current repository checkout. Any
433
+ // non-read-only command continues through claimed Work resolution and fails
434
+ // closed for terminal attribution.
435
+ const selectedRepository = resolveRepositorySelection({
436
+ repoId: repoIdValue || undefined,
437
+ checkoutId: checkoutId || undefined,
438
+ controllerHome,
439
+ allowSoleRepository: true,
440
+ });
441
+ const historicalWorkContext = historicalReadOnlyWorkContext(controllerHome, selectedRepository, args);
442
+ if (historicalWorkContext) {
443
+ return {
444
+ repository: selectedRepository,
445
+ executionIdentity: {
446
+ schemaVersion: 1,
447
+ authority: 'repository',
448
+ repositoryId: selectedRepository.repoId,
449
+ checkoutId: selectedRepository.activeCheckoutId,
450
+ canonicalRoot: selectedRepository.canonicalRoot,
451
+ },
452
+ historicalWorkContext,
453
+ };
454
+ }
455
+
456
+ const repository = resolveRepositorySelectionForClaimedWork(controllerHome, args, repoIdValue, caller);
457
+ const explicitWorkId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
458
+ const workId = explicitWorkId
459
+ ? resolveClaimedRepositoryWorkId(controllerHome, repository, caller, explicitWorkId)
460
+ : undefined;
461
+ return {
462
+ repository,
463
+ executionIdentity: {
464
+ schemaVersion: 1,
465
+ authority: 'repository',
466
+ repositoryId: repository.repoId,
467
+ checkoutId: repository.activeCheckoutId,
468
+ canonicalRoot: repository.canonicalRoot,
469
+ ...(workId ? { workId } : {}),
470
+ },
471
+ };
472
+ }
473
+
474
+ export function summarizeEntityMigrationReport(
475
+ report: ReturnType<typeof bindRepositoryEntities>,
476
+ detail = false,
477
+ ): ReturnType<typeof bindRepositoryEntities> | Record<string, unknown> {
478
+ if (detail) return report;
479
+ const files = report.files.slice(0, DEFAULT_MIGRATION_SAMPLE_LIMIT);
480
+ const errors = report.errors.slice(0, DEFAULT_MIGRATION_SAMPLE_LIMIT);
481
+ return {
482
+ repoId: report.repoId,
483
+ checkoutId: report.checkoutId,
484
+ scanned: report.scanned,
485
+ updated: report.updated,
486
+ unresolved: report.unresolved,
487
+ fileCount: report.files.length,
488
+ errorCount: report.errors.length,
489
+ files,
490
+ errors,
491
+ truncated: report.files.length > files.length || report.errors.length > errors.length,
492
+ omittedFileCount: Math.max(0, report.files.length - files.length),
493
+ omittedErrorCount: Math.max(0, report.errors.length - errors.length),
494
+ };
495
+ }
496
+
497
+ export function summarizeRepositoryRegistration(
498
+ repository: ReturnType<typeof registerRepository>,
499
+ migration: ReturnType<typeof bindRepositoryEntities>,
500
+ detail = false,
501
+ fastPath = false,
502
+ ): Record<string, unknown> {
503
+ if (detail) {
504
+ return { detailLevel: 'detail', repository, migration, fastPath };
505
+ }
506
+ const checkoutCounts = repository.checkouts.reduce<Record<string, number>>((counts, checkout) => {
507
+ const lifecycle = checkout.lifecycle ?? 'active';
508
+ counts[lifecycle] = (counts[lifecycle] ?? 0) + 1;
509
+ return counts;
510
+ }, {});
511
+ return {
512
+ detailLevel: 'summary',
513
+ repository: {
514
+ ...repositorySummary(repository),
515
+ activeCheckoutId: repository.activeCheckoutId,
516
+ checkoutCount: repository.checkouts.length,
517
+ checkoutCounts,
518
+ },
519
+ migration: summarizeEntityMigrationReport(migration),
520
+ fastPath,
521
+ next: 'Re-call with detail_level=detail only when full checkout or migration evidence is required.',
522
+ };
523
+ }
524
+
525
+ export function summarizeRepositoryInspection(
526
+ repository: ReturnType<typeof getRepository>,
527
+ detail = false,
528
+ ): Record<string, unknown> {
529
+ if (detail) return { detailLevel: 'detail', repository };
530
+ const checkoutCounts = repository.checkouts.reduce<Record<string, number>>((counts, checkout) => {
531
+ const lifecycle = checkout.lifecycle ?? 'active';
532
+ counts[lifecycle] = (counts[lifecycle] ?? 0) + 1;
533
+ return counts;
534
+ }, {});
535
+ const activeCheckout = repository.checkouts.find((checkout) => checkout.checkoutId === repository.activeCheckoutId);
536
+ return {
537
+ detailLevel: 'summary',
538
+ repository: {
539
+ ...repositorySummary(repository),
540
+ activeCheckoutId: repository.activeCheckoutId,
541
+ ...(activeCheckout ? { activeCheckout } : {}),
542
+ checkoutCount: repository.checkouts.length,
543
+ checkoutCounts,
544
+ },
545
+ omittedCheckoutCount: Math.max(0, repository.checkouts.length - (activeCheckout ? 1 : 0)),
546
+ next: 'Re-call with detail_level=detail only when complete checkout history is required.',
547
+ };
548
+ }
549
+
550
+ function failure(error: unknown): RepositoryToolResult {
551
+ const message = compactErrorMessage(error);
552
+ const code = message.includes(':') ? message.slice(0, message.indexOf(':')) : 'REPOSITORY_TOOL_FAILED';
553
+ const details = typeof error === 'object' && error !== null && 'details' in error ? (error as { details?: unknown }).details : undefined;
554
+ const compactDetails = details !== undefined && Buffer.byteLength(JSON.stringify(details) ?? '', 'utf8') > RESPONSE_BUDGET.previewBytes
555
+ ? { omitted: true, message: 'Error details exceeded budget; inspect job/artifact/result refs.' }
556
+ : details;
557
+ return { ...result({ error: { code, message, ...(compactDetails !== undefined ? { details: compactDetails } : {}) } }), isError: true };
558
+ }
559
+
560
+ interface RepositoryExplorationGuidance {
561
+ code: 'FRAGMENTED_REPOSITORY_EXPLORATION';
562
+ recommendedTool: 'rh_context';
563
+ recommendedOperation: 'search';
564
+ message: string;
565
+ }
566
+
567
+ function fragmentedRepositoryExplorationGuidance(command: unknown): RepositoryExplorationGuidance | undefined {
568
+ if (typeof command !== 'string') return undefined;
569
+ const segments = command.split(/(?:&&|\|\||[;|])/g);
570
+ const readPrograms = new Set<string>();
571
+ let readStepCount = 0;
572
+ for (const segment of segments) {
573
+ const match = segment.trim().match(/^(?:(?:command|env)\s+)?(?:[A-Za-z_][A-Za-z0-9_]*=[^\s]+\s+)*(rg|grep|sed|cat|head|tail)\b/i);
574
+ if (!match) continue;
575
+ readStepCount += 1;
576
+ readPrograms.add(match[1]!.toLowerCase());
577
+ }
578
+ if (readStepCount < 2) return undefined;
579
+ return {
580
+ code: 'FRAGMENTED_REPOSITORY_EXPLORATION',
581
+ recommendedTool: 'rh_context',
582
+ recommendedOperation: 'search',
583
+ message: `Detected ${readStepCount} chained repository read steps (${[...readPrograms].join(', ')}). Prefer a broad rh_context request first, then repeat rh_context with selected paths or relationships when more evidence can improve correctness; keep shell search as fallback when the Context Plane cannot supply the needed evidence.`,
584
+ };
585
+ }
586
+
587
+ function compactProcessCommandPayload(input: {
588
+ accepted?: boolean;
589
+ mode: string;
590
+ path: string;
591
+ route?: string;
592
+ reasons: string[];
593
+ decision?: unknown;
594
+ repoId: string;
595
+ checkoutId?: string;
596
+ workspace?: { workspaceId: string; root: string; registered: false };
597
+ historicalWorkContext?: HistoricalReadOnlyWorkContext;
598
+ processId?: string;
599
+ process?: unknown;
600
+ completed?: boolean;
601
+ ok?: boolean;
602
+ exitCode?: number | null;
603
+ stdout?: string;
604
+ stderr?: string;
605
+ durableSideEffects?: Record<string, number>;
606
+ next: string;
607
+ /** summary (default) omits nested process / routing dumps; detail restores diagnostics. */
608
+ detailLevel?: 'summary' | 'detail';
609
+ includeFullProcess?: boolean;
610
+ guidance?: RepositoryExplorationGuidance;
611
+ }): Record<string, unknown> {
612
+ const output = compactCommandOutput(input.stdout, input.stderr, { ok: input.ok === true });
613
+ const detail = input.detailLevel === 'detail' || input.includeFullProcess === true;
614
+ const effects = input.durableSideEffects ?? {
615
+ executionJobCount: 0,
616
+ localJobCount: 0,
617
+ workerSpawnCount: 0,
618
+ projectionUpdateCount: 0,
619
+ };
620
+ const reason = input.reasons[0] ?? 'readonly_fast_path';
621
+
622
+ // Default success/failure: one authoritative stdout/stderr pair, no nested process dump.
623
+ if (!detail) {
624
+ const completed = input.completed !== false;
625
+ const ok = input.ok === true;
626
+ const payload: Record<string, unknown> = {
627
+ ...(completed ? { ok } : {}),
628
+ accepted: input.accepted ?? true,
629
+ mode: input.mode,
630
+ path: input.path,
631
+ route: input.route ?? input.path,
632
+ reason,
633
+ repoId: input.repoId,
634
+ ...(input.checkoutId ? { checkoutId: input.checkoutId } : {}),
635
+ ...(input.workspace ? { workspace: input.workspace } : {}),
636
+ ...(input.historicalWorkContext ? { historicalWorkContext: input.historicalWorkContext } : {}),
637
+ ...(input.processId ? { processId: input.processId } : {}),
638
+ ...(completed ? { exitCode: input.exitCode ?? (ok ? 0 : 1) } : {}),
639
+ // A running default response otherwise loses its only dependency-bound
640
+ // continuation advice when the detailed Process record is omitted.
641
+ ...(!completed ? { next: input.next } : {}),
642
+ stdout: output.stdout ?? '',
643
+ stderr: output.stderr ?? '',
644
+ ...(output.stdoutTruncated ? { stdoutTruncated: true, stdoutBytes: output.stdoutBytes } : {}),
645
+ ...(output.stderrTruncated ? { stderrTruncated: true, stderrBytes: output.stderrBytes } : {}),
646
+ ...(input.guidance ? { guidance: input.guidance, suggestedOperation: 'rh_context' } : {}),
647
+ };
648
+ if (completed && !ok) {
649
+ payload.error = {
650
+ code: 'PROCESS_COMMAND_FAILED',
651
+ message: (output.stderr || 'process_direct command failed').slice(0, 800),
652
+ retryable: false,
653
+ exitCode: input.exitCode ?? 1,
654
+ };
655
+ }
656
+ // Zero side-effects are implicit for process_direct summary; only surface non-zero.
657
+ const nonZeroEffects = Object.fromEntries(
658
+ Object.entries(effects).filter(([, value]) => typeof value === 'number' && value > 0),
659
+ );
660
+ if (Object.keys(nonZeroEffects).length > 0) payload.durableSideEffects = nonZeroEffects;
661
+ return payload;
662
+ }
663
+
664
+ return {
665
+ accepted: input.accepted ?? true,
666
+ mode: input.mode,
667
+ path: input.path,
668
+ route: input.route ?? input.path,
669
+ routing: {
670
+ ...compactRoutingSummary({ path: input.path, mode: input.mode, reasons: input.reasons }),
671
+ ...(input.decision ? { decision: input.decision } : {}),
672
+ },
673
+ reason,
674
+ repoId: input.repoId,
675
+ ...(input.checkoutId ? { checkoutId: input.checkoutId } : {}),
676
+ ...(input.workspace ? { workspace: input.workspace } : {}),
677
+ ...(input.historicalWorkContext ? { historicalWorkContext: input.historicalWorkContext } : {}),
678
+ ...(input.processId ? { processId: input.processId } : {}),
679
+ ok: input.ok,
680
+ exitCode: input.exitCode,
681
+ ...output,
682
+ // Nested process only in detail mode; never duplicate stdout/stderr there.
683
+ ...(input.process && typeof input.process === 'object'
684
+ ? {
685
+ process: (() => {
686
+ const record = { ...(input.process as Record<string, unknown>) };
687
+ delete record.stdout;
688
+ delete record.stderr;
689
+ delete record.durableSideEffects;
690
+ return record;
691
+ })(),
692
+ }
693
+ : {}),
694
+ durableSideEffects: effects,
695
+ ...(input.guidance ? { guidance: input.guidance, suggestedOperation: 'rh_context' } : {}),
696
+ next: input.next,
697
+ detailLevel: 'detail',
698
+ };
699
+ }
700
+
701
+ function emptyRepositoryMigration(repository: ReturnType<typeof registerRepository>) {
702
+ return {
703
+ repoId: repository.repoId,
704
+ checkoutId: repository.activeCheckoutId,
705
+ scanned: 0,
706
+ updated: 0,
707
+ unresolved: 0,
708
+ files: [] as string[],
709
+ errors: [] as Array<{ path: string; error: string }>,
710
+ };
711
+ }
712
+
713
+ function registrationOnlyMetadataRefresh(
714
+ match: ReturnType<typeof findIdenticalRepositoryRegistration>,
715
+ ): boolean {
716
+ if (!match || match.identical || match.reasons.length === 0) return false;
717
+ const metadataReasons = new Set([
718
+ 'remote_identity_changed',
719
+ 'default_branch_changed',
720
+ 'display_name_changed',
721
+ ]);
722
+ return match.reasons.every((reason) => metadataReasons.has(reason));
723
+ }
724
+
725
+ function registrationOnlyAttachedExistingWorktree(repository: ReturnType<typeof registerRepository>): boolean {
726
+ const selected = repository.checkouts.find((checkout) => checkout.checkoutId === repository.activeCheckoutId);
727
+ return selected?.worktree === true
728
+ && repository.checkouts.some((checkout) => checkout.checkoutId !== selected.checkoutId);
729
+ }
730
+
731
+ export async function callRepositoryTool(
732
+ controllerHome: string,
733
+ name: string,
734
+ args: Record<string, unknown>,
735
+ caller?: RepositoryToolCallerContext,
736
+ ): Promise<RepositoryToolResult | undefined> {
737
+ if (!name.startsWith('repository_') && name !== 'read_repository_file') return undefined;
738
+ try {
739
+ const repoIdValue = typeof args.repo_id === 'string' ? args.repo_id.trim() : '';
740
+ switch (name) {
741
+ case 'read_repository_file': {
742
+ const repository = resolveRepositorySelection({
743
+ repoId: typeof args.repo_id === 'string' ? args.repo_id : undefined,
744
+ checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined,
745
+ controllerHome,
746
+ allowSoleRepository: true,
747
+ });
748
+ const path = String(args.path ?? '').trim();
749
+ if (!path) throw new Error('REPOSITORY_PATH_REQUIRED: path is required');
750
+ const session = caller?.sessionId
751
+ ? { sessionId: caller.sessionId, repoId: repository.repoId, checkoutId: repository.activeCheckoutId }
752
+ : undefined;
753
+ const range = readRepositoryRange(
754
+ repository.canonicalRoot,
755
+ getMcpPolicy('controller', { repoRoot: repository.canonicalRoot }),
756
+ path,
757
+ typeof args.start_line === 'number' ? args.start_line : 1,
758
+ typeof args.end_line === 'number' ? args.end_line : 200,
759
+ session,
760
+ );
761
+ const redacted = redactMcpText(range.content);
762
+ return result({ ...range, content: redacted.text, redactions: redacted.redactions });
763
+ }
764
+ case 'repository_register': {
765
+ const startedAt = performance.now();
766
+ const registerInput = {
767
+ path: String(args.path ?? ''),
768
+ controllerHome,
769
+ displayName: typeof args.display_name === 'string' ? args.display_name : undefined,
770
+ remoteUrl: typeof args.remote_url === 'string' ? args.remote_url : undefined,
771
+ defaultBranch: typeof args.default_branch === 'string' ? args.default_branch : undefined,
772
+ };
773
+ const identical = findIdenticalRepositoryRegistration(registerInput);
774
+ if (identical?.identical) {
775
+ const repository = identical.repository;
776
+ // Repeat registration with identical identity returns immediately.
777
+ // No legacy migration / edit-session migration / historical issue
778
+ // scan / old issue rebinding runs on this hot path.
779
+ const migration = emptyRepositoryMigration(repository);
780
+ return result(withResponseMeta(
781
+ summarizeRepositoryRegistration(repository, migration, args.detail_level === 'detail', true),
782
+ startedAt,
783
+ ));
784
+ }
785
+ const metadataRefresh = registrationOnlyMetadataRefresh(identical);
786
+ const repository = registerRepository(registerInput);
787
+ // Adding a worktree checkout or changing only display/remote/default-branch
788
+ // metadata preserves repository + checkout authority. Existing entities
789
+ // are already bound to the stable repoId, so a historical migration scan
790
+ // would only add latency and stale unresolved diagnostics.
791
+ const migration = registrationOnlyAttachedExistingWorktree(repository) || metadataRefresh
792
+ ? emptyRepositoryMigration(repository)
793
+ : bindRepositoryEntities(repository);
794
+ return result(withResponseMeta(
795
+ summarizeRepositoryRegistration(repository, migration, args.detail_level === 'detail', metadataRefresh),
796
+ startedAt,
797
+ ));
798
+ }
799
+ case 'repository_latest_source_diagnose':
800
+ return result({
801
+ diagnosis: diagnoseLatestLocalProjectSource({
802
+ path: typeof args.path === 'string' ? args.path : undefined,
803
+ repoId: typeof args.repo_id === 'string' ? args.repo_id : undefined,
804
+ controllerHome,
805
+ }),
806
+ });
807
+ case 'repository_bootstrap_local_project':
808
+ return result({
809
+ bootstrap: bootstrapLocalProject({
810
+ path: String(args.path ?? ''),
811
+ controllerHome,
812
+ displayName: typeof args.display_name === 'string' ? args.display_name : undefined,
813
+ defaultBranch: typeof args.default_branch === 'string' ? args.default_branch : undefined,
814
+ mode: typeof args.mode === 'string' ? args.mode as 'init_git_only' | 'init_git_and_register' | 'replace_registration' : undefined,
815
+ replaceRegisteredRepoId: typeof args.replace_registered_repo_id === 'string' ? args.replace_registered_repo_id : undefined,
816
+ confirmAuthorization: args.confirm_authorization === true,
817
+ }),
818
+ });
819
+ case 'repository_list':
820
+ return result({ repositories: listRepositories(controllerHome, { includeRemoved: args.include_removed === true }).map(repositorySummary) });
821
+ case 'repository_get': {
822
+ // Repository inspection is also the bounded reconciliation point for
823
+ // checkout lifecycle drift caused by worktrees being removed outside
824
+ // Forge. Reuse the existing registry reconciler so stale worktrees are
825
+ // not exposed as lifecycle=active to automation callers.
826
+ reconcileRepositoryCheckouts(repoIdValue, controllerHome);
827
+ const repository = getRepository(repoIdValue, controllerHome, { includeRemoved: args.include_removed === true });
828
+ return result(summarizeRepositoryInspection(repository, args.detail_level === 'detail'));
829
+ }
830
+ case 'repository_validate': {
831
+ const startedAt = performance.now();
832
+ const repository = getRepository(repoIdValue, controllerHome, { includeRemoved: true });
833
+ const payload = {
834
+ detailLevel: args.detail_level === 'detail' ? 'detail' : 'summary',
835
+ validation: validateRepository(repoIdValue, controllerHome),
836
+ migration: summarizeEntityMigrationReport(bindRepositoryEntities(repository), args.detail_level === 'detail'),
837
+ };
838
+ return result(withResponseMeta(payload, startedAt));
839
+ }
840
+ case 'repository_refresh': {
841
+ const startedAt = performance.now();
842
+ const repository = refreshRepository(repoIdValue, controllerHome);
843
+ const migration = bindRepositoryEntities(repository);
844
+ return result(withResponseMeta(
845
+ summarizeRepositoryRegistration(repository, migration, args.detail_level === 'detail'),
846
+ startedAt,
847
+ ));
848
+ }
849
+ case 'repository_update':
850
+ return result({ repository: updateRepository(repoIdValue, {
851
+ displayName: typeof args.display_name === 'string' ? args.display_name : undefined,
852
+ defaultBranch: typeof args.default_branch === 'string' ? args.default_branch : undefined,
853
+ enabled: typeof args.enabled === 'boolean' ? args.enabled : undefined,
854
+ }, controllerHome) });
855
+ case 'repository_disable':
856
+ return result({ repository: disableRepository(repoIdValue, controllerHome) });
857
+ case 'repository_remove':
858
+ return result({ repository: removeRepository(repoIdValue, controllerHome) });
859
+ case 'repository_workbench': {
860
+ const operation = typeof args.operation === 'string' ? args.operation : 'summary';
861
+ if (operation === 'summary') {
862
+ return result({ workbench: buildControllerWorkbench(controllerHome, {
863
+ repoId: repoIdValue || undefined,
864
+ includeRemoved: args.include_removed === true,
865
+ }) });
866
+ }
867
+ const payload = typeof args.payload === 'object' && args.payload !== null
868
+ ? args.payload as Record<string, unknown>
869
+ : {};
870
+ if (operation === 'assess_work_mode') {
871
+ const description = typeof payload.description === 'string'
872
+ ? payload.description
873
+ : typeof args.description === 'string' ? args.description : '';
874
+ const assessment = assessWorkMode({
875
+ description,
876
+ knownPaths: Array.isArray(payload.known_paths) ? payload.known_paths.map(String) : undefined,
877
+ expectedFiles: typeof payload.expected_files === 'number' ? payload.expected_files : undefined,
878
+ expectedChangedLines: typeof payload.expected_changed_lines === 'number' ? payload.expected_changed_lines : undefined,
879
+ requiresInvestigation: payload.requires_investigation === true,
880
+ requiresParallelism: payload.requires_parallelism === true,
881
+ requiresLongRunningChecks: payload.requires_long_running_checks === true,
882
+ needsDependencies: payload.needs_dependencies === true,
883
+ requiresIndependentDeliverables: payload.requires_independent_deliverables === true,
884
+ independentTaskCount: typeof payload.independent_task_count === 'number' ? payload.independent_task_count : undefined,
885
+ requiresRemoteWrite: payload.requires_remote_write === true || payload.remote_write === true,
886
+ requiresRecovery: payload.requires_recovery === true,
887
+ agentRequested: payload.agent_requested === true || payload.requires_worker === true,
888
+ requiresWorkerIsolation: payload.requires_worker_isolation === true,
889
+ risk: typeof payload.risk === 'string' ? payload.risk as 'low' | 'medium' | 'high' | 'destructive' : undefined,
890
+ explicitMode: parseExplicitTaskMode(payload.mode),
891
+ });
892
+ return result({
893
+ assessment,
894
+ routing: {
895
+ path: assessment.executionPath,
896
+ reasons: assessment.reasons,
897
+ recommendedMode: assessment.recommendedMode,
898
+ issueRequired: assessment.issueRequired,
899
+ },
900
+ nextTools: assessment.nextTools,
901
+ });
902
+ }
903
+ const internalTool = {
904
+ batch_execute: 'repository_batch_execute',
905
+ lanes_execute: 'repository_lanes_execute',
906
+ lanes_integrate: 'repository_lanes_integrate',
907
+ fast_receipt_get: 'repository_fast_receipt_get',
908
+ fast_receipt_list: 'repository_fast_receipt_list',
909
+ execution_route: 'repository_execution_route',
910
+ }[operation];
911
+ if (!internalTool) {
912
+ return failure(new Error(`REPOSITORY_WORKBENCH_OPERATION_INVALID: ${operation}`));
913
+ }
914
+ return callRepositoryTool(controllerHome, internalTool, {
915
+ ...payload,
916
+ repo_id: repoIdValue || payload.repo_id,
917
+ checkout_id: typeof args.checkout_id === 'string' ? args.checkout_id : payload.checkout_id,
918
+ });
919
+ }
920
+ case 'repository_git_status': {
921
+ const repository = resolveRepositorySelection({ repoId: repoIdValue || undefined, checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined, controllerHome, allowSoleRepository: true });
922
+ const status = args.refresh === true
923
+ ? writeRepositoryGitStatusSample(controllerHome, repository)
924
+ : readRepositoryGitStatusSample(controllerHome, repository.repoId, repository.activeCheckoutId);
925
+ return result({
926
+ status: status ?? {
927
+ repoId: repository.repoId,
928
+ checkoutId: repository.activeCheckoutId,
929
+ sampleSource: 'daemon-sample',
930
+ sampled: false,
931
+ observedAt: null,
932
+ staleAgeMs: null,
933
+ message: 'Git status has not been sampled by the Forge Runtime yet. Retry after scheduler heartbeat or call with refresh=true for an explicit live refresh.',
934
+ },
935
+ });
936
+ }
937
+
938
+ case 'repository_git_diff': {
939
+ const repository = resolveRepositorySelection({ repoId: repoIdValue || undefined, checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined, controllerHome, allowSoleRepository: true });
940
+ return result({ diff: repositoryGitDiff(repository, { staged: args.staged, paths: args.paths, maxBytes: args.max_bytes }) });
941
+ }
942
+ case 'repository_git_create_branch': {
943
+ const repository = resolveRepositorySelection({ repoId: repoIdValue || undefined, checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined, controllerHome, allowSoleRepository: true });
944
+ return result({ repoId: repository.repoId, checkoutId: repository.activeCheckoutId, ...repositoryGitCreateBranch(controllerHome, repository, { branch: args.branch, startPoint: args.start_point, switchTo: args.switch_to }) as unknown as Record<string, unknown> });
945
+ }
946
+ case 'repository_git_switch_branch': {
947
+ const repository = resolveRepositorySelection({ repoId: repoIdValue || undefined, checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined, controllerHome, allowSoleRepository: true });
948
+ return result({ repoId: repository.repoId, checkoutId: repository.activeCheckoutId, ...repositoryGitSwitchBranch(controllerHome, repository, { branch: args.branch }) as unknown as Record<string, unknown> });
949
+ }
950
+ case 'repository_git_merge_branch': {
951
+ const repository = resolveRepositorySelection({ repoId: repoIdValue || undefined, checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined, controllerHome, allowSoleRepository: true });
952
+ return result({ repoId: repository.repoId, checkoutId: repository.activeCheckoutId, ...repositoryGitMergeBranch(controllerHome, repository, { branch: args.branch, noFf: args.no_ff }) as unknown as Record<string, unknown> });
953
+ }
954
+ case 'repository_git_delete_branch': {
955
+ const repository = resolveRepositorySelection({ repoId: repoIdValue || undefined, checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined, controllerHome, allowSoleRepository: true });
956
+ return result({ repoId: repository.repoId, checkoutId: repository.activeCheckoutId, ...repositoryGitDeleteBranch(controllerHome, repository, { branch: args.branch, force: args.force }) as unknown as Record<string, unknown> });
957
+ }
958
+
959
+ case 'repository_git_commit': {
960
+ const repository = resolveRepositorySelection({ repoId: repoIdValue || undefined, checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined, controllerHome, allowSoleRepository: true });
961
+ return result({ commit: repositoryGitCommit(controllerHome, repository, { message: args.message, paths: args.paths, allowEmpty: args.allow_empty }) });
962
+ }
963
+ case 'repository_git_finish_workflow': {
964
+ const repository = resolveRepositorySelection({ repoId: repoIdValue || undefined, checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined, controllerHome, allowSoleRepository: true });
965
+ return result({ finish: repositoryGitFinishWorkflow(controllerHome, repository, { featureBranch: args.feature_branch, targetBranch: args.target_branch, deleteBranch: args.delete_branch, noFf: args.no_ff }) });
966
+ }
967
+ case 'repository_safe_patch_plan': {
968
+ const repository = resolveRepositorySelection({
969
+ repoId: repoIdValue || undefined,
970
+ checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined,
971
+ controllerHome,
972
+ allowSoleRepository: true,
973
+ });
974
+ return result({
975
+ repoId: repository.repoId,
976
+ checkoutId: repository.activeCheckoutId,
977
+ plan: buildSafePatchPlan(repository, { operations: args.operations, chunkSize: args.chunk_size }),
978
+ });
979
+ }
980
+ case 'repository_safe_patch_apply': {
981
+ const repository = resolveRepositorySelectionForClaimedWork(controllerHome, args, repoIdValue, caller);
982
+ const binding = safePatchEditBinding(controllerHome, repository, caller, args);
983
+ if (binding?.workId) {
984
+ const work = getWorkContract({ controllerHome, repoId: repository.repoId }, binding.workId);
985
+ if (!work) throw new Error(`WORK_NOT_FOUND: ${binding.workId}`);
986
+ const requestedPaths = buildSafePatchPlan(repository, { operations: args.operations, chunkSize: args.chunk_size })
987
+ .chunks.flatMap((chunk) => chunk.paths);
988
+ assertWorkPathsWithinScope(work, requestedPaths, {
989
+ forbidden: 'WORK_MUTATION_FORBIDDEN_PATH',
990
+ outOfScope: 'WORK_MUTATION_PATH_OUT_OF_SCOPE',
991
+ });
992
+ }
993
+ const applied = withControllerLock(
994
+ controllerHome,
995
+ { scope: 'repository', repoId: repository.repoId },
996
+ 'mcp:repository_safe_patch_apply',
997
+ () => {
998
+ if (args.validation_only !== true) {
999
+ assertCanonicalRepositoryMutationWorkHandleAvailable({
1000
+ controllerHome,
1001
+ repositoryId: repository.repoId,
1002
+ checkoutId: repository.activeCheckoutId,
1003
+ workId: binding?.workId,
1004
+ });
1005
+ }
1006
+ const mutationAuthority = binding?.workId
1007
+ ? ensureRepositoryMutationWorkHandle({
1008
+ controllerHome,
1009
+ repository,
1010
+ workId: binding.workId,
1011
+ principalId: binding.principalId ?? caller?.principalId ?? '',
1012
+ })
1013
+ : undefined;
1014
+ const patchResult = applySafePatch(repository, {
1015
+ sessionId: args.session_id,
1016
+ purpose: args.purpose,
1017
+ operations: args.operations,
1018
+ chunkSize: args.chunk_size,
1019
+ expectedRevision: args.expected_revision,
1020
+ allowedPaths: args.allowed_paths,
1021
+ continueOnError: args.continue_on_error,
1022
+ refreshFingerprints: args.refresh_fingerprints,
1023
+ recoverStaleSession: args.recover_stale_session,
1024
+ binding,
1025
+ });
1026
+ if (binding?.workId && mutationAuthority && patchResult.appliedChunks.length > 0) {
1027
+ markRepositoryMutationStarted({
1028
+ controllerHome,
1029
+ repository,
1030
+ workId: binding.workId,
1031
+ expectedDeliveryBase: mutationAuthority.handle.deliveryBaseCommit ?? mutationAuthority.handle.baseCommit,
1032
+ });
1033
+ }
1034
+ return patchResult;
1035
+ },
1036
+ 60_000,
1037
+ );
1038
+ const changedFiles = [
1039
+ ...new Set(
1040
+ (applied.appliedChunks ?? []).flatMap((chunk) => chunk.paths ?? []),
1041
+ ),
1042
+ ];
1043
+ const ok = applied.status === 'applied';
1044
+ const firstFailure = applied.failures?.[0];
1045
+ const editDiff = applied.appliedChunks.length > 0
1046
+ ? getEditSessionDiff(repository.canonicalRoot, applied.session.sessionId)
1047
+ : undefined;
1048
+ const boundedEditDiff = editDiff
1049
+ ? boundUtf8(editDiff.patch, RESPONSE_BUDGET.inlineOutputBytes)
1050
+ : undefined;
1051
+ const reviewEvidence = editDiff && boundedEditDiff
1052
+ ? {
1053
+ source: 'edit_session' as const,
1054
+ sessionId: editDiff.sessionId,
1055
+ revision: editDiff.revision,
1056
+ sha256: editDiff.sha256,
1057
+ patchPreview: boundedEditDiff.text,
1058
+ truncated: boundedEditDiff.truncated,
1059
+ byteLength: boundedEditDiff.byteLength,
1060
+ semanticReviewAuthority: 'chatgpt' as const,
1061
+ next: boundedEditDiff.truncated
1062
+ ? 'The edit diff exceeds the inline budget. Expand only the affected ranges or diff needed for semantic review.'
1063
+ : 'Review this exact edit-session diff before deciding whether more edits or focused validation are needed.',
1064
+ }
1065
+ : undefined;
1066
+ const digest = buildSyncOperationDigest({
1067
+ ok,
1068
+ operation: 'repository_safe_patch_apply',
1069
+ summary: ok
1070
+ ? `补丁已同步应用,涉及 ${changedFiles.length} 个文件。`
1071
+ : applied.status === 'partial'
1072
+ ? `补丁部分应用:${changedFiles.length} 个文件成功,存在失败 chunk。`
1073
+ : `补丁应用失败:${firstFailure?.message || '请检查 failures 摘要'}`,
1074
+ changedFiles,
1075
+ errorClass: ok ? undefined : classifyUserFacingError({
1076
+ code: firstFailure?.code,
1077
+ message: firstFailure?.message,
1078
+ infrastructure: firstFailure?.code === 'APPLY_FAILED',
1079
+ }),
1080
+ errorMessage: firstFailure?.message,
1081
+ });
1082
+ const payload = {
1083
+ repoId: repository.repoId,
1084
+ checkoutId: repository.activeCheckoutId,
1085
+ ...applied as unknown as Record<string, unknown>,
1086
+ phase: digest.phase,
1087
+ statusLabel: digest.statusLabel,
1088
+ summary: digest.summary,
1089
+ terminal: true,
1090
+ applyMode: 'sync',
1091
+ digest,
1092
+ ...(reviewEvidence ? { reviewEvidence } : {}),
1093
+ suggestedNextActions: digest.suggestedNextActions,
1094
+ };
1095
+ return ok ? result(payload) : { ...result(payload), isError: true };
1096
+ }
1097
+ case 'repository_command_preview': {
1098
+ const target = resolveRepositoryCommandTarget(controllerHome, args, repoIdValue, caller);
1099
+ const { repository } = target;
1100
+ const execution = withControllerLock(
1101
+ controllerHome,
1102
+ { scope: 'repository', repoId: repository.repoId },
1103
+ 'mcp:repository_command_preview',
1104
+ () => executeRepositoryCommand(controllerHome, repository, {
1105
+ command: args.command as string | string[],
1106
+ cwd: typeof args.cwd === 'string' ? args.cwd : undefined,
1107
+ dryRun: true,
1108
+ allowNonGitWorkspace: target.workspace !== undefined,
1109
+ }),
1110
+ 60_000,
1111
+ );
1112
+ return result({
1113
+ ...execution as unknown as Record<string, unknown>,
1114
+ ...(target.workspace ? { workspace: target.workspace } : {}),
1115
+ });
1116
+ }
1117
+ case 'repository_command_execute': {
1118
+ const explorationGuidance = fragmentedRepositoryExplorationGuidance(args.command);
1119
+ let target = resolveRepositoryCommandTarget(controllerHome, args, repoIdValue, caller);
1120
+ let { repository, executionIdentity, historicalWorkContext } = target;
1121
+ const explicitWorkId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
1122
+ if (!explicitWorkId) assertNoBoundExecutionSessionMutation(controllerHome, repository, caller);
1123
+ const deliveryWorkId = rawDefaultBranchMergeCommand(repository, args.command)
1124
+ ? executionIdentity.workId
1125
+ : undefined;
1126
+ if (deliveryWorkId) {
1127
+ throw new Error(`WORK_DELIVERY_REQUIRES_FINALIZE: ${deliveryWorkId} must pass Work verification and use rh_work finalize before default-branch integration`);
1128
+ }
1129
+ const timeoutMs = typeof args.timeout_ms === 'number'
1130
+ ? args.timeout_ms
1131
+ : typeof args.timeout_ms === 'string'
1132
+ ? Number(args.timeout_ms)
1133
+ : undefined;
1134
+ const maxOutputBytes = typeof args.max_output_bytes === 'number'
1135
+ ? args.max_output_bytes
1136
+ : typeof args.max_output_bytes === 'string'
1137
+ ? Number(args.max_output_bytes)
1138
+ : undefined;
1139
+ // Worker-owned durable executions must not re-enter Fast Path or create a
1140
+ // nested ExecutionJob. Local Job remains the worker settlement surface.
1141
+ const fromDurableWorker = args.__from_durable_worker === true
1142
+ || typeof args.__execution_job_id === 'string';
1143
+ // Process Runtime owns local async execution. apply_mode=async means
1144
+ // return its Process handle immediately; it must not promote a local
1145
+ // command into the retired ExecutionJob path. Explicit mode=durable
1146
+ // remains a separate external-Controller boundary.
1147
+ const returnHandleImmediately = args.apply_mode === 'async'
1148
+ || args.mode === 'async'
1149
+ || args.async === true
1150
+ || args.background === true;
1151
+ const forceDurable = fromDurableWorker
1152
+ || args.mode === 'durable'
1153
+ || args.force_durable === true;
1154
+ const rawCommitScope = classifyRawGitCommitScope(args.command as string | string[]);
1155
+ const routeClass = classifyRepositoryCommandRoute(args.command as string | string[], {
1156
+ forceDurable,
1157
+ workId: executionIdentity.workId,
1158
+ defaultBranch: repository.defaultBranch,
1159
+ timeoutMs,
1160
+ });
1161
+ const processRequestId = typeof args.request_id === 'string' ? args.request_id.trim() : '';
1162
+ if (executionIdentity.workId
1163
+ && (routeClass.route === 'process_direct' || routeClass.route === 'process_managed' || routeClass.route === 'durable')
1164
+ && !processRequestId) {
1165
+ throw new Error(
1166
+ `WORK_PROCESS_REQUEST_ID_REQUIRED: ${executionIdentity.workId}; `
1167
+ + 'Work-attributed Process Runtime commands require a stable request_id so reconnect/retry can resume the original Process without duplicate execution.',
1168
+ );
1169
+ }
1170
+ let mutationAuthority: ReturnType<typeof ensureRepositoryMutationWorkHandle> | undefined;
1171
+ const mutationClassification = classifyRepositoryCommand(args.command as string | string[], repository.defaultBranch);
1172
+ if (
1173
+ (mutationClassification.risk === 'workspace_write' || mutationClassification.risk === 'destructive')
1174
+ && (routeClass.route === 'process_direct' || routeClass.route === 'process_managed')
1175
+ ) {
1176
+ if (executionIdentity.workId) {
1177
+ mutationAuthority = withControllerLock(
1178
+ controllerHome,
1179
+ { scope: 'repository', repoId: repository.repoId },
1180
+ 'mcp:repository_command_execute:mutation-authority',
1181
+ () => ensureRepositoryMutationWorkHandle({
1182
+ controllerHome,
1183
+ repository,
1184
+ workId: executionIdentity.workId!,
1185
+ principalId: caller?.principalId ?? '',
1186
+ deferEffectPromotion: true,
1187
+ }),
1188
+ 60_000,
1189
+ );
1190
+ // Pre-mutation reconciliation may advance expectedHead. Re-resolve
1191
+ // immutable execution identity after that durable CAS and before spawn.
1192
+ target = resolveRepositoryCommandTarget(controllerHome, args, repoIdValue, caller);
1193
+ ({ repository, executionIdentity, historicalWorkContext } = target);
1194
+ } else {
1195
+ withControllerLock(
1196
+ controllerHome,
1197
+ { scope: 'repository', repoId: repository.repoId },
1198
+ 'mcp:repository_command_execute:unattributed-mutation-authority',
1199
+ () => assertCanonicalRepositoryMutationWorkHandleAvailable({
1200
+ controllerHome,
1201
+ repositoryId: repository.repoId,
1202
+ checkoutId: repository.activeCheckoutId,
1203
+ }),
1204
+ 60_000,
1205
+ );
1206
+ }
1207
+ }
1208
+ if (executionIdentity.workId && (rawCommitScope.kind === 'staged_index' || rawCommitScope.kind === 'explicit_paths')) {
1209
+ const work = getWorkContract({ controllerHome, repoId: repository.repoId }, executionIdentity.workId);
1210
+ if (!work) throw new Error(`WORK_NOT_FOUND: ${executionIdentity.workId}`);
1211
+ const commitScope = resolveRepositoryGitCommitScope(repository, {
1212
+ paths: rawCommitScope.kind === 'explicit_paths' ? rawCommitScope.paths : undefined,
1213
+ });
1214
+ assertWorkPathsWithinScope(work, commitScope.paths, {
1215
+ forbidden: 'WORK_COMMIT_STAGED_PATH_FORBIDDEN',
1216
+ outOfScope: 'WORK_COMMIT_STAGED_PATH_OUT_OF_SCOPE',
1217
+ });
1218
+ }
1219
+ // repository_command_execute owns its command execution architecture.
1220
+ // Do not send ordinary command text through Thin Harness semantic risk
1221
+ // routing: that creates duplicate regex/classifier policy and turns
1222
+ // unknown CLI shapes into false durable/reject decisions. Only an
1223
+ // explicitly requested durable mode uses the generic durable router.
1224
+ const routingDecision = forceDurable
1225
+ ? routeExecution({
1226
+ operation: 'repository_command_execute',
1227
+ mode: 'durable',
1228
+ timeoutMs,
1229
+ background: args.background === true || args.async === true,
1230
+ })
1231
+ : {
1232
+ mode: 'fast' as const,
1233
+ reasons: ['repository_command_process_runtime_authoritative'],
1234
+ risk: 'unknown' as const,
1235
+ estimatedClass: 'unknown' as const,
1236
+ requiresIsolation: false,
1237
+ requiresRecovery: false,
1238
+ suggestedOperation: undefined,
1239
+ effects: {
1240
+ readsWorkspace: true,
1241
+ mutatesWorkspace: true,
1242
+ mutatesGitRefs: false,
1243
+ remoteWrite: false,
1244
+ },
1245
+ };
1246
+ // Unified Process Runtime for local commands (Direct/Managed) when not forced durable.
1247
+ // Ephemeral workspaces cannot persist a recoverable async Process handle,
1248
+ // so their async requests remain promotion-required below.
1249
+ if (!forceDurable && !fromDurableWorker && !(target.workspace && returnHandleImmediately)) {
1250
+ try {
1251
+ if (routeClass.route === 'reject') {
1252
+ return result({
1253
+ accepted: false,
1254
+ mode: 'reject',
1255
+ path: routeClass.reason,
1256
+ route: 'reject',
1257
+ status: 'rejected',
1258
+ repoId: repository.repoId,
1259
+ checkoutId: repository.activeCheckoutId,
1260
+ workspace: target.workspace,
1261
+ message: routeClass.reason === 'standalone_recovery_lifecycle_required'
1262
+ ? 'Canonical Runtime and connector lifecycle operations must be submitted to the standalone Forge Recovery owner, not executed as a child of repository Process Runtime.'
1263
+ : 'Repository command execution was rejected by Process Runtime routing policy.',
1264
+ suggestedOperation: routeClass.reason === 'standalone_recovery_lifecycle_required'
1265
+ ? 'Use the standalone Forge Recovery connector.'
1266
+ : routingDecision.suggestedOperation,
1267
+ durableSideEffects: {
1268
+ executionJobCount: 0,
1269
+ localJobCount: 0,
1270
+ workerSpawnCount: 0,
1271
+ projectionUpdateCount: 0,
1272
+ },
1273
+ });
1274
+ }
1275
+ if (routeClass.route === 'durable' && target.workspace) {
1276
+ return result({
1277
+ accepted: false,
1278
+ mode: 'durable',
1279
+ path: 'ephemeral_workspace_promotion_required',
1280
+ repoId: repository.repoId,
1281
+ checkoutId: repository.activeCheckoutId,
1282
+ workspace: target.workspace,
1283
+ message: 'Ephemeral workspaces must be registered before durable remote effects so Process identity and reconciliation have a stable repository authority.',
1284
+ suggestedOperation: 'repository_register',
1285
+ });
1286
+ }
1287
+ if (routeClass.route === 'process_direct' || routeClass.route === 'process_managed' || routeClass.route === 'durable') {
1288
+ const processResult = await executeRepositoryCommandViaProcessRuntime({
1289
+ controllerHome,
1290
+ repository,
1291
+ command: args.command as string | string[],
1292
+ cwd: typeof args.cwd === 'string' ? args.cwd : undefined,
1293
+ timeoutMs,
1294
+ interactiveWaitMs: returnHandleImmediately
1295
+ ? 0
1296
+ : typeof args.interactive_wait_ms === 'number' ? args.interactive_wait_ms : undefined,
1297
+ maxOutputBytes,
1298
+ returnHandleImmediately,
1299
+ requestId: processRequestId || undefined,
1300
+ workId: executionIdentity.workId,
1301
+ executionIdentity,
1302
+ allowNonGitWorkspace: target.workspace !== undefined,
1303
+ });
1304
+ if (mutationAuthority && (processResult.route === 'process_direct' || processResult.route === 'process_managed')) {
1305
+ const processHandle = processResult.process;
1306
+ const postStatus = repositoryGitStatus(repository);
1307
+ const expectedHead = mutationAuthority.handle.expectedHead;
1308
+ const mutationObserved = (processHandle !== undefined && processHandle.completed !== true)
1309
+ || !postStatus.clean
1310
+ || (Boolean(postStatus.head) && postStatus.head !== expectedHead);
1311
+ if (mutationObserved) {
1312
+ withControllerLock(
1313
+ controllerHome,
1314
+ { scope: 'repository', repoId: repository.repoId },
1315
+ 'mcp:repository_command_execute:mutation-started',
1316
+ () => markRepositoryMutationStarted({
1317
+ controllerHome,
1318
+ repository,
1319
+ workId: executionIdentity.workId!,
1320
+ expectedDeliveryBase: mutationAuthority!.handle.deliveryBaseCommit ?? mutationAuthority!.handle.baseCommit,
1321
+ }),
1322
+ 60_000,
1323
+ );
1324
+ }
1325
+ }
1326
+ if (processResult.route === 'process_direct' || processResult.route === 'process_managed') {
1327
+ const handle = processResult.process;
1328
+ const detailLevel = args.detail_level === 'detail' || args.detail === true
1329
+ ? 'detail'
1330
+ : 'summary';
1331
+ const directWithoutHandle = processResult.route === 'process_direct' && !handle;
1332
+ const directNotExecuted = directWithoutHandle
1333
+ && processResult.executionStatus !== undefined
1334
+ && processResult.executionStatus !== 'executed';
1335
+ if (directNotExecuted) {
1336
+ const authorization = processResult.authorizationDecision ?? {
1337
+ decision: 'deny' as const,
1338
+ reason: 'Command was not executed because the required authorization was not satisfied.',
1339
+ };
1340
+ return result({
1341
+ accepted: false,
1342
+ mode: processResult.route,
1343
+ path: processResult.reason ?? routeClass.reason,
1344
+ route: processResult.route,
1345
+ repoId: repository.repoId,
1346
+ checkoutId: repository.activeCheckoutId,
1347
+ workspace: target.workspace,
1348
+ status: processResult.executionStatus,
1349
+ policyDecision: processResult.policyDecision ?? 'approval_required',
1350
+ authorization,
1351
+ ...(processResult.approvalRequestId ? { approvalRequestId: processResult.approvalRequestId } : {}),
1352
+ message: authorization.decision === 'user_confirmation_required'
1353
+ ? authorization.humanSummary
1354
+ : authorization.reason,
1355
+ suggestedOperation: 'repository_command_preview',
1356
+ });
1357
+ }
1358
+ const completed = directWithoutHandle || handle?.completed === true;
1359
+ const status = directWithoutHandle
1360
+ ? (processResult.ok === true ? 'succeeded' : 'failed')
1361
+ : !handle
1362
+ ? 'rejected'
1363
+ : completed
1364
+ ? (handle.cancelled ? 'cancelled' : handle.timedOut ? 'timed_out' : processResult.ok === true ? 'succeeded' : 'failed')
1365
+ : 'running';
1366
+ const payload = compactProcessCommandPayload({
1367
+ accepted: true,
1368
+ mode: processResult.route,
1369
+ path: processResult.route,
1370
+ route: processResult.route,
1371
+ reasons: [processResult.reason ?? routeClass.reason, ...routingDecision.reasons],
1372
+ decision: detailLevel === 'detail' ? routingDecision : undefined,
1373
+ repoId: repository.repoId,
1374
+ checkoutId: repository.activeCheckoutId,
1375
+ workspace: target.workspace,
1376
+ historicalWorkContext,
1377
+ processId: handle?.processId,
1378
+ process: detailLevel === 'detail' ? handle : undefined,
1379
+ completed,
1380
+ ok: completed ? processResult.ok : undefined,
1381
+ exitCode: completed ? processResult.exitCode : undefined,
1382
+ stdout: processResult.stdout,
1383
+ stderr: processResult.stderr,
1384
+ durableSideEffects: processResult.durableSideEffects,
1385
+ guidance: explorationGuidance,
1386
+ next: directWithoutHandle
1387
+ ? processResult.reason === 'readonly_fast_path'
1388
+ ? 'Bounded readonly execution completed without Process record / Lease / Local Job / ExecutionJob / Worker.'
1389
+ : 'Bounded ephemeral workspace execution completed without Process record / Lease / Local Job / ExecutionJob / Worker.'
1390
+ : processResult.route === 'process_direct' || completed
1391
+ ? 'Process Runtime completed without Local Job / ExecutionJob / Worker.'
1392
+ : `Process Runtime is managing ${handle?.processId}. Continue independent work; use process_get only if an observation can change the next decision, then join with process_wait when this result becomes a dependency. Do not re-run or periodically poll.`,
1393
+ detailLevel,
1394
+ });
1395
+ payload.status = status;
1396
+ payload.processId = handle?.processId;
1397
+ payload.authorization = {
1398
+ decision: 'allow',
1399
+ source: directWithoutHandle ? 'bounded_read_direct' : 'process_runtime',
1400
+ reason: directWithoutHandle
1401
+ ? 'Readonly repository command executed through the bounded non-persistent direct path.'
1402
+ : 'Repository command executed through Unified Process Runtime.',
1403
+ };
1404
+ if (directWithoutHandle && processResult.reason !== 'readonly_fast_path') {
1405
+ payload.authorization = processResult.authorizationDecision?.decision === 'allow'
1406
+ ? processResult.authorizationDecision
1407
+ : {
1408
+ decision: 'allow',
1409
+ source: 'ephemeral_workspace_direct',
1410
+ reason: 'Bounded ephemeral workspace command executed through the non-persistent direct path.',
1411
+ };
1412
+ }
1413
+ if (handle?.processId) {
1414
+ payload.resultRef = { kind: 'process_logs', processId: handle.processId };
1415
+ }
1416
+ // Keep accepted true once the command was admitted to Process Runtime.
1417
+ payload.accepted = true;
1418
+ return processResult.ok === true || !completed
1419
+ ? result(payload)
1420
+ : { ...result(payload), isError: true };
1421
+ }
1422
+ }
1423
+ } catch (error) {
1424
+ // Process Runtime is authoritative. Falling through could spawn the
1425
+ // same command again through the legacy Fast/Local Job path.
1426
+ if (process.env.FORGE_DEBUG_PROCESS_RUNTIME === '1') {
1427
+ console.error('[repository_command_execute] process runtime error', error);
1428
+ }
1429
+ return failure(error);
1430
+ }
1431
+ }
1432
+ if (target.workspace && (forceDurable || returnHandleImmediately)) {
1433
+ return result({
1434
+ accepted: false,
1435
+ mode: 'durable',
1436
+ path: 'ephemeral_workspace_promotion_required',
1437
+ repoId: repository.repoId,
1438
+ checkoutId: repository.activeCheckoutId,
1439
+ workspace: target.workspace,
1440
+ message: 'Ephemeral workspaces support bounded local Direct/Managed execution only. Register the directory before durable, remote, release, or resumable Work.',
1441
+ suggestedOperation: 'repository_register',
1442
+ });
1443
+ }
1444
+ // Durable Worker calls skip Process/Fast above, then use the Local Bridge
1445
+ // compatibility projection below for writes/long commands so the worker
1446
+ // can settle the legacy child without creating a nested ExecutionJob.
1447
+ // Short readonly commands keep the zero-Local-Job direct path.
1448
+ if (fromDurableWorker) {
1449
+ try {
1450
+ const routeClass = classifyRepositoryCommandRoute(args.command as string | string[], {
1451
+ forceDurable: false,
1452
+ defaultBranch: repository.defaultBranch,
1453
+ timeoutMs,
1454
+ });
1455
+ if (routeClass.route === 'process_direct') {
1456
+ const processResult = await executeRepositoryCommandViaProcessRuntime({
1457
+ controllerHome,
1458
+ repository,
1459
+ command: args.command as string | string[],
1460
+ cwd: typeof args.cwd === 'string' ? args.cwd : undefined,
1461
+ timeoutMs,
1462
+ maxOutputBytes,
1463
+ requestId: typeof args.request_id === 'string' ? args.request_id : undefined,
1464
+ workId: executionIdentity.workId,
1465
+ executionIdentity,
1466
+ });
1467
+ if (processResult.route === 'process_direct') {
1468
+ const execRecord = processResult.process as unknown as Record<string, unknown> | undefined;
1469
+ const ok = processResult.ok === true;
1470
+ const inlinePayload = {
1471
+ accepted: ok,
1472
+ mode: 'durable',
1473
+ path: 'durable_worker_inline',
1474
+ routing: compactRoutingSummary({
1475
+ path: 'durable',
1476
+ mode: 'durable',
1477
+ reasons: ['durable_worker_inline_process_direct', routeClass.reason, ...routingDecision.reasons],
1478
+ }),
1479
+ repoId: repository.repoId,
1480
+ checkoutId: repository.activeCheckoutId,
1481
+ ok,
1482
+ processId: typeof execRecord?.processId === 'string' ? execRecord.processId : undefined,
1483
+ status: typeof execRecord?.status === 'string' ? execRecord.status : undefined,
1484
+ exitCode: typeof processResult.exitCode === 'number' ? processResult.exitCode : undefined,
1485
+ ...compactCommandOutput(
1486
+ typeof processResult.stdout === 'string' ? processResult.stdout : undefined,
1487
+ typeof processResult.stderr === 'string' ? processResult.stderr : undefined,
1488
+ { ok },
1489
+ ),
1490
+ durableSideEffects: processResult.durableSideEffects,
1491
+ next: 'Durable Worker executed a short readonly repository command inline without a Local Job.',
1492
+ };
1493
+ return ok ? result(inlinePayload) : { ...result(inlinePayload), isError: true };
1494
+ }
1495
+ }
1496
+ } catch (error) {
1497
+ if (process.env.FORGE_DEBUG_PROCESS_RUNTIME === '1') {
1498
+ console.error('[repository_command_execute] durable worker inline process runtime error', error);
1499
+ }
1500
+ }
1501
+ }
1502
+ return result({
1503
+ accepted: false,
1504
+ mode: 'durable',
1505
+ path: 'external_controller_required',
1506
+ routing: compactRoutingSummary({ path: 'durable', mode: 'durable', reasons: [routingDecision.reasons.join(','), 'local_bridge_execution_retired'] }),
1507
+ message: 'This command requires explicit external Controller handling; Local Bridge Jobs are retired for repository commands.',
1508
+ suggestedOperation: 'Create or claim WorkContract, then use rh_work.launcher_start or a Process Runtime-compatible command.',
1509
+ externalEffect: {
1510
+ outcome: 'not_started',
1511
+ ambiguousFailureOutcome: 'outcome_unknown',
1512
+ replayPolicy: 'never_auto_retry',
1513
+ reconciliation: 'After an ambiguous dispatch failure, inspect the remote ref/registry/release state before deciding whether to retry.',
1514
+ },
1515
+ });
1516
+ }
1517
+ case 'repository_batch_execute': {
1518
+ const repository = resolveRepositorySelection({
1519
+ repoId: repoIdValue || undefined,
1520
+ checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined,
1521
+ controllerHome,
1522
+ allowSoleRepository: true,
1523
+ });
1524
+ const steps = Array.isArray(args.steps)
1525
+ ? args.steps
1526
+ .filter((entry): entry is Record<string, unknown> => typeof entry === 'object' && entry !== null)
1527
+ .map((entry) => ({
1528
+ id: typeof entry.id === 'string' ? entry.id : undefined,
1529
+ kind: String(entry.kind ?? '') as
1530
+ | 'read_file'
1531
+ | 'search'
1532
+ | 'git_status'
1533
+ | 'git_diff'
1534
+ | 'apply_patch'
1535
+ | 'run_short_command'
1536
+ | 'run_focused_check'
1537
+ | 'stage_paths'
1538
+ | 'commit_paths',
1539
+ input: (typeof entry.input === 'object' && entry.input !== null
1540
+ ? entry.input
1541
+ : {}) as Record<string, unknown>,
1542
+ }))
1543
+ : [];
1544
+ const batch = await executeRepositoryBatch(
1545
+ { controllerHome, repository },
1546
+ {
1547
+ repoId: repository.repoId,
1548
+ checkoutId: repository.activeCheckoutId,
1549
+ mode: args.mode === 'fast' || args.mode === 'durable' || args.mode === 'auto' ? args.mode : 'auto',
1550
+ steps,
1551
+ stopOnError: args.stop_on_error !== false,
1552
+ allowedPaths: Array.isArray(args.allowed_paths) ? args.allowed_paths.map(String) : undefined,
1553
+ timeoutMs: typeof args.timeout_ms === 'number' ? args.timeout_ms : undefined,
1554
+ includeLatencyBreakdown: args.include_latency_breakdown === true,
1555
+ purpose: typeof args.purpose === 'string' ? args.purpose : undefined,
1556
+ requestId: typeof args.request_id === 'string' ? args.request_id.trim() || undefined : undefined,
1557
+ },
1558
+ );
1559
+ const payload = batch as unknown as Record<string, unknown>;
1560
+ return batch.ok ? result(payload) : { ...result(payload), isError: true };
1561
+ }
1562
+ case 'repository_lanes_execute': {
1563
+ const repository = resolveRepositorySelection({
1564
+ repoId: repoIdValue || undefined,
1565
+ checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined,
1566
+ controllerHome,
1567
+ allowSoleRepository: true,
1568
+ });
1569
+ const lanes = await executeLightweightLanes(
1570
+ { controllerHome, repository },
1571
+ {
1572
+ repoId: repository.repoId,
1573
+ checkoutId: repository.activeCheckoutId,
1574
+ readLanes: Array.isArray(args.read_lanes)
1575
+ ? args.read_lanes
1576
+ .filter((entry): entry is Record<string, unknown> => typeof entry === 'object' && entry !== null)
1577
+ .map((entry) => ({
1578
+ id: typeof entry.id === 'string' ? entry.id : undefined,
1579
+ kind: String(entry.kind ?? 'search') as 'search' | 'read_file' | 'git_status' | 'git_diff' | 'run_short_command',
1580
+ input: (typeof entry.input === 'object' && entry.input !== null
1581
+ ? entry.input
1582
+ : {}) as Record<string, unknown>,
1583
+ }))
1584
+ : undefined,
1585
+ patchProposalLanes: Array.isArray(args.patch_proposal_lanes)
1586
+ ? args.patch_proposal_lanes
1587
+ .filter((entry): entry is Record<string, unknown> => typeof entry === 'object' && entry !== null)
1588
+ .map((entry) => ({
1589
+ id: typeof entry.id === 'string' ? entry.id : undefined,
1590
+ readPaths: Array.isArray(entry.read_paths) ? entry.read_paths.map(String) : [],
1591
+ writePaths: Array.isArray(entry.write_paths) ? entry.write_paths.map(String) : [],
1592
+ proposedOperations: Array.isArray(entry.proposed_operations) ? entry.proposed_operations : [],
1593
+ assumptions: Array.isArray(entry.assumptions) ? entry.assumptions.map(String) : undefined,
1594
+ riskNotes: Array.isArray(entry.risk_notes) ? entry.risk_notes.map(String) : undefined,
1595
+ suggestedFocusedCheck: entry.suggested_focused_check as string | string[] | undefined,
1596
+ }))
1597
+ : undefined,
1598
+ failFast: args.fail_fast === true,
1599
+ maxConcurrency: typeof args.max_concurrency === 'number' ? args.max_concurrency : undefined,
1600
+ includeLatencyBreakdown: args.include_latency_breakdown === true,
1601
+ },
1602
+ );
1603
+ const payload = lanes as unknown as Record<string, unknown>;
1604
+ return lanes.ok ? result(payload) : { ...result(payload), isError: true };
1605
+ }
1606
+ case 'repository_lanes_integrate': {
1607
+ const repository = resolveRepositorySelection({
1608
+ repoId: repoIdValue || undefined,
1609
+ checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined,
1610
+ controllerHome,
1611
+ allowSoleRepository: true,
1612
+ });
1613
+ const proposals = Array.isArray(args.proposals)
1614
+ ? args.proposals
1615
+ .filter((entry): entry is Record<string, unknown> => typeof entry === 'object' && entry !== null)
1616
+ .map((entry) => ({
1617
+ id: String(entry.id ?? 'proposal'),
1618
+ ok: entry.ok !== false,
1619
+ durationMs: 0,
1620
+ readPaths: Array.isArray(entry.read_paths) ? entry.read_paths.map(String) : [],
1621
+ writePaths: Array.isArray(entry.write_paths) ? entry.write_paths.map(String) : [],
1622
+ proposedOperations: Array.isArray(entry.proposed_operations) ? entry.proposed_operations : [],
1623
+ analysisOnly: entry.analysis_only === true,
1624
+ proposalId: typeof entry.proposal_id === 'string'
1625
+ ? entry.proposal_id
1626
+ : typeof entry.proposalId === 'string'
1627
+ ? entry.proposalId
1628
+ : undefined,
1629
+ }))
1630
+ : [];
1631
+ const integrated = await integratePatchProposals(
1632
+ { controllerHome, repository },
1633
+ proposals,
1634
+ {
1635
+ sessionId: typeof args.session_id === 'string' ? args.session_id : undefined,
1636
+ allowedPaths: Array.isArray(args.allowed_paths) ? args.allowed_paths.map(String) : undefined,
1637
+ purpose: typeof args.purpose === 'string' ? args.purpose : undefined,
1638
+ requestId: typeof args.request_id === 'string' ? args.request_id.trim() || undefined : undefined,
1639
+ continueOnError: args.continue_on_error === true,
1640
+ },
1641
+ );
1642
+ const payload = integrated as unknown as Record<string, unknown>;
1643
+ return integrated.ok ? result(payload) : { ...result(payload), isError: true };
1644
+ }
1645
+ case 'repository_fast_receipt_get': {
1646
+ const repository = resolveRepositorySelection({
1647
+ repoId: repoIdValue || undefined,
1648
+ checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined,
1649
+ controllerHome,
1650
+ allowSoleRepository: true,
1651
+ });
1652
+ const receipt = readFastReceipt(controllerHome, repository.repoId, String(args.execution_id ?? ''));
1653
+ if (!receipt) return failure(new Error(`FAST_RECEIPT_NOT_FOUND: ${String(args.execution_id ?? '')}`));
1654
+ return result({ receipt });
1655
+ }
1656
+ case 'repository_fast_receipt_list': {
1657
+ const repository = resolveRepositorySelection({
1658
+ repoId: repoIdValue || undefined,
1659
+ checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined,
1660
+ controllerHome,
1661
+ allowSoleRepository: true,
1662
+ });
1663
+ const limit = typeof args.limit === 'number' ? args.limit : undefined;
1664
+ return result({ receipts: listFastReceipts(controllerHome, repository.repoId, limit) });
1665
+ }
1666
+ case 'repository_execution_route': {
1667
+ const repository = resolveRepositorySelection({
1668
+ repoId: repoIdValue || undefined,
1669
+ checkoutId: typeof args.checkout_id === 'string' ? args.checkout_id : undefined,
1670
+ controllerHome,
1671
+ allowSoleRepository: true,
1672
+ });
1673
+ const decision = routeExecution({
1674
+ operation: String(args.operation ?? ''),
1675
+ mode: args.mode === 'fast' || args.mode === 'durable' || args.mode === 'auto' ? args.mode : 'auto',
1676
+ command: args.command as string | string[] | undefined,
1677
+ timeoutMs: typeof args.timeout_ms === 'number' ? args.timeout_ms : undefined,
1678
+ background: args.background === true,
1679
+ paths: Array.isArray(args.paths) ? args.paths.map(String) : undefined,
1680
+ allowedPaths: Array.isArray(args.allowed_paths) ? args.allowed_paths.map(String) : undefined,
1681
+ defaultBranch: repository.defaultBranch,
1682
+ });
1683
+ return result({ decision, repoId: repository.repoId, checkoutId: repository.activeCheckoutId });
1684
+ }
1685
+ default:
1686
+ return failure(new Error(`UNKNOWN_REPOSITORY_TOOL: ${name}`));
1687
+ }
1688
+ } catch (error) {
1689
+ return failure(error);
1690
+ }
1691
+ }