@moretea-labs/forge 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (396) hide show
  1. package/AGENTS.md +2 -0
  2. package/CHANGELOG.md +26 -0
  3. package/CLAUDE.md +2 -0
  4. package/README.md +2 -2
  5. package/README.zh-CN.md +1 -1
  6. package/SKILL.md +2 -2
  7. package/THIRD_PARTY_NOTICES.md +1 -1
  8. package/agents/openai.yaml +1 -1
  9. package/assets/hooks/hook-input.sh +11 -8
  10. package/assets/hooks/lib/session-state.sh +10 -7
  11. package/assets/hooks/lib/workflow-state.sh +13 -177
  12. package/assets/hooks/post-tool-observer.sh +8 -2
  13. package/assets/hooks/prompt-guard.sh +5 -81
  14. package/assets/hooks/session-start-context.sh +56 -1
  15. package/assets/partials/05-workflow.partial.md +1 -1
  16. package/assets/partials-agents/03-orchestration.partial.md +1 -1
  17. package/assets/plugin-registry.v1.json +1 -1
  18. package/assets/reference-configs/document-generation.md +1 -1
  19. package/assets/reference-configs/harness-overview.md +1 -1
  20. package/assets/reference-configs/sprint-contracts.md +3 -3
  21. package/assets/skill-version.json +2 -2
  22. package/assets/templates/contract.template.md +2 -7
  23. package/assets/templates/helpers/capture-plan.sh +2 -2
  24. package/assets/templates/helpers/check-task-workflow.sh +4 -4
  25. package/assets/templates/helpers/contract-worktree.sh +1 -21
  26. package/assets/templates/helpers/ensure-task-workflow.sh +11 -36
  27. package/assets/templates/helpers/migrate-project-template.sh +22 -45
  28. package/assets/templates/helpers/new-plan.sh +1 -1
  29. package/assets/templates/helpers/plan-to-todo.sh +12 -37
  30. package/assets/templates/helpers/ship-worktrees.sh +2 -29
  31. package/assets/templates/helpers/verify-contract.sh +1 -111
  32. package/assets/templates/helpers/verify-sprint.sh +3 -180
  33. package/assets/templates/plan.template.md +1 -1
  34. package/assets/templates/review.template.md +9 -34
  35. package/assets/workflow-contract.v1.json +10 -19
  36. package/docs/README.md +10 -10
  37. package/docs/architecture/CURRENT.md +170 -0
  38. package/docs/architecture/EVOLUTION.md +42 -0
  39. package/docs/architecture/current/README.md +4 -156
  40. package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
  41. package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
  42. package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
  43. package/docs/architecture/history/README.md +7 -0
  44. package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
  45. package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
  46. package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
  47. package/docs/architecture/history.md +3 -34
  48. package/docs/architecture/index.md +15 -98
  49. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
  50. package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
  51. package/docs/architecture/versions/1.6.md +38 -0
  52. package/docs/architecture/versions/1.7.md +41 -0
  53. package/docs/forge-chatgpt-browser-engine.md +2 -2
  54. package/docs/forge-chatgpt-mcp-setup.md +4 -28
  55. package/docs/forge-chatgpt-supervised-automation.md +3 -3
  56. package/docs/forge-github-issue-launcher.md +1 -1
  57. package/docs/forge-runtime-self-healing-loop.md +1 -1
  58. package/docs/operations/controller-browser-plugin.md +26 -6
  59. package/docs/operations/controller-performance-and-502.md +124 -0
  60. package/docs/operations/controller-reliability-runbook.md +1 -1
  61. package/docs/operations/features.md +2 -2
  62. package/docs/operations/github-repository.md +1 -1
  63. package/docs/operations/homebrew.md +2 -2
  64. package/docs/operations/mcp-tool-exposure.md +5 -3
  65. package/docs/wiki/Architecture.md +6 -6
  66. package/docs/wiki/Implementation.md +2 -2
  67. package/docs/wiki/Integrations.md +2 -2
  68. package/docs/wiki/Work-Lifecycle.md +1 -1
  69. package/package.json +29 -10
  70. package/scripts/activate-source-baseline.ts +17 -13
  71. package/scripts/benchmark-background-check-overlap.ts +228 -0
  72. package/scripts/benchmark-mcp-host.sh +117 -0
  73. package/scripts/benchmark-mcp-transport.ts +170 -0
  74. package/scripts/benchmark-quality-harness.ts +146 -0
  75. package/scripts/benchmark-route-session-concurrency.ts +207 -6
  76. package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
  77. package/scripts/benchmark-typescript-navigation.ts +186 -0
  78. package/scripts/benchmark-work-efficiency.ts +334 -0
  79. package/scripts/capture-plan.sh +3 -3
  80. package/scripts/check-agent-tooling.sh +8 -35
  81. package/scripts/check-architecture-sync.sh +17 -86
  82. package/scripts/check-mcp-compatibility.ts +14 -0
  83. package/scripts/check-runtime-architecture.mjs +107 -64
  84. package/scripts/check-stable-baseline.ts +92 -0
  85. package/scripts/check-task-workflow.sh +4 -4
  86. package/scripts/contract-worktree.sh +1 -21
  87. package/scripts/ensure-task-workflow.sh +22 -55
  88. package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
  89. package/scripts/install-standalone-recovery.ts +24 -0
  90. package/scripts/lib/project-init-lib.sh +15 -44
  91. package/scripts/migrate-project-template.sh +23 -1179
  92. package/scripts/new-plan.sh +1 -1
  93. package/scripts/plan-to-todo.sh +11 -36
  94. package/scripts/run-governed-gate.ts +2 -1
  95. package/scripts/ship-worktrees.sh +2 -29
  96. package/scripts/smoke-ios-physical-device.ts +4 -2
  97. package/scripts/smoke-runtime-recovery.ts +1 -35
  98. package/scripts/smoke-schedule-engine.ts +1 -20
  99. package/scripts/verify-contract.sh +1 -111
  100. package/scripts/verify-forge-runtime.sh +0 -1
  101. package/scripts/verify-sprint.sh +3 -180
  102. package/scripts/workflow-contract.ts +1 -5
  103. package/src/cli/commands/init-hook.ts +3 -3
  104. package/src/cli/commands/mcp.ts +9 -5
  105. package/src/cli/commands/plugin.ts +1 -1
  106. package/src/cli/commands/recovery.ts +25 -3
  107. package/src/cli/commands/runtime.ts +55 -3
  108. package/src/cli/commands/security.ts +12 -1
  109. package/src/cli/commands/setup-profile.ts +31 -21
  110. package/src/cli/controller/check-runner.ts +106 -3
  111. package/src/cli/controller/composite-result.ts +1 -1
  112. package/src/cli/controller/context/focus.ts +51 -0
  113. package/src/cli/controller/context/instruction-context.ts +114 -0
  114. package/src/cli/controller/context/known-paths.ts +76 -0
  115. package/src/cli/controller/context/query-planning.ts +110 -0
  116. package/src/cli/controller/context/rule-resolution.ts +532 -0
  117. package/src/cli/controller/context/source-materializer.ts +278 -0
  118. package/src/cli/controller/context/structural.ts +16 -0
  119. package/src/cli/controller/context/types.ts +286 -0
  120. package/src/cli/controller/context-pack.ts +785 -469
  121. package/src/cli/controller/launch-agents.ts +77 -2
  122. package/src/cli/controller/operational-plan.ts +2 -2
  123. package/src/cli/controller/work-mode.ts +6 -4
  124. package/src/cli/editing/edit-session.ts +60 -7
  125. package/src/cli/github/github.ts +133 -0
  126. package/src/cli/installer/managed-entries.ts +15 -1
  127. package/src/cli/local-bridge/console-automations.ts +54 -78
  128. package/src/cli/local-bridge/console-view-models.ts +1 -35
  129. package/src/cli/local-bridge/facade-api.ts +0 -217
  130. package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
  131. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
  132. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
  133. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
  134. package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
  135. package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
  136. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
  137. package/src/cli/local-bridge/frontend/src/styles.css +1 -1
  138. package/src/cli/local-bridge/frontend/src/types.ts +5 -8
  139. package/src/cli/local-bridge/job-store.ts +1 -490
  140. package/src/cli/local-bridge/server.ts +0 -513
  141. package/src/cli/local-bridge/ui-dist/app.css +1 -1
  142. package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
  143. package/src/cli/mcp/auth.ts +5 -5
  144. package/src/cli/mcp/instructions.ts +3 -2
  145. package/src/cli/mcp/legacy-tool-service.ts +0 -189
  146. package/src/cli/mcp/multi-repository.ts +16 -5
  147. package/src/cli/mcp/oauth.ts +175 -26
  148. package/src/cli/mcp/repository-tools.ts +247 -107
  149. package/src/cli/mcp/server.ts +537 -111
  150. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  151. package/src/cli/mcp/toolset-selection.ts +1 -1
  152. package/src/cli/mcp/toolset.ts +42 -5
  153. package/src/cli/mcp/transports/http.ts +101 -14
  154. package/src/cli/mcp/transports/session-registry.ts +43 -5
  155. package/src/cli/mcp/types.ts +3 -3
  156. package/src/cli/repositories/command-classifier.ts +101 -19
  157. package/src/cli/repositories/command-executor.ts +55 -521
  158. package/src/cli/repositories/command-process.ts +108 -0
  159. package/src/cli/repositories/command-scope.ts +99 -41
  160. package/src/cli/repositories/controller-home.ts +7 -8
  161. package/src/cli/repositories/git-command-executor.ts +2 -0
  162. package/src/cli/repositories/locks.ts +53 -16
  163. package/src/cli/repositories/repository-snapshot.ts +121 -0
  164. package/src/cli/repositories/selected-path-actions.ts +17 -9
  165. package/src/cli/repositories/structured-git.ts +61 -3
  166. package/src/cli/repository/inspector.ts +553 -63
  167. package/src/cli/repository/session-cache.ts +58 -17
  168. package/src/cli/v81-entry.ts +1 -4
  169. package/src/effects/fs-transaction.ts +85 -17
  170. package/src/runtime/context/codegraph-read-provider.ts +233 -2
  171. package/src/runtime/context/codegraph-sidecar.cjs +74 -40
  172. package/src/runtime/context/generic-lsp-provider.ts +355 -0
  173. package/src/runtime/context/lsp-client.ts +283 -0
  174. package/src/runtime/context/semantic-navigation.ts +275 -0
  175. package/src/runtime/context/swift-navigation.ts +565 -0
  176. package/src/runtime/context/typescript-navigation.ts +226 -0
  177. package/src/runtime/control-plane/dispatch-priority.ts +28 -0
  178. package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +47 -8
  179. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
  180. package/src/runtime/control-plane/execution/session-store.ts +18 -0
  181. package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
  182. package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
  183. package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
  184. package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
  185. package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
  186. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +143 -8
  187. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
  188. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +85 -8
  189. package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
  190. package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
  191. package/src/runtime/control-plane/facade/controller-session-store.ts +132 -1
  192. package/src/runtime/control-plane/facade/goal-workloop-access.ts +2 -0
  193. package/src/runtime/control-plane/facade/goal-workloop.ts +686 -129
  194. package/src/runtime/control-plane/facade/index.ts +1 -0
  195. package/src/runtime/control-plane/facade/plan-contract-store.ts +185 -20
  196. package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
  197. package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
  198. package/src/runtime/control-plane/facade/types.ts +22 -5
  199. package/src/runtime/control-plane/facade/work-contract-store.ts +175 -73
  200. package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
  201. package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
  202. package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
  203. package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
  204. package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
  205. package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
  206. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
  207. package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
  208. package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
  209. package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
  210. package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
  211. package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
  212. package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
  213. package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
  214. package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
  215. package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
  216. package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
  217. package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
  218. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
  219. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +355 -116
  220. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
  221. package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
  222. package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
  223. package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
  224. package/src/runtime/control-plane/release-retention.ts +62 -2
  225. package/src/runtime/control-plane/routing/route-policy.ts +7 -16
  226. package/src/runtime/control-plane/runtime-status-client.ts +0 -2
  227. package/src/runtime/diagnostics/mcp-timing.ts +13 -0
  228. package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
  229. package/src/runtime/diagnostics/performance.ts +24 -1
  230. package/src/runtime/evidence/event-ledger.ts +1 -1
  231. package/src/runtime/execution/jobs/store.ts +58 -4
  232. package/src/runtime/execution/jobs/types.ts +1 -1
  233. package/src/runtime/execution/managed-workspace.ts +203 -1
  234. package/src/runtime/execution/process-runtime/check-facade.ts +51 -4
  235. package/src/runtime/execution/process-runtime/check-result.ts +53 -0
  236. package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
  237. package/src/runtime/execution/process-runtime/command-facade.ts +345 -40
  238. package/src/runtime/execution/process-runtime/gc.ts +38 -6
  239. package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
  240. package/src/runtime/execution/process-runtime/persisted-check.ts +47 -4
  241. package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
  242. package/src/runtime/execution/process-runtime/runtime.ts +43 -3
  243. package/src/runtime/execution/process-runtime/store.ts +1 -0
  244. package/src/runtime/execution/process-runtime/types.ts +2 -0
  245. package/src/runtime/execution/thin-harness/async-process.ts +12 -2
  246. package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
  247. package/src/runtime/execution/workers/executor.ts +0 -48
  248. package/src/runtime/gateway/mcp/execution-tools.ts +1192 -114
  249. package/src/runtime/gateway/mcp/process-tools.ts +83 -37
  250. package/src/runtime/gateway/mcp/router.ts +184 -82
  251. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
  252. package/src/runtime/gateway/mcp/runtime-tools.ts +2224 -1589
  253. package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
  254. package/src/runtime/plugins/browser-adapter.ts +2901 -241
  255. package/src/runtime/plugins/browser-automation-service.ts +10 -0
  256. package/src/runtime/plugins/browser-handoff.ts +92 -2
  257. package/src/runtime/plugins/browser-macos-bridge.ts +582 -49
  258. package/src/runtime/plugins/browser-node-bridge.ts +6 -34
  259. package/src/runtime/plugins/browser-provider-registry.ts +109 -0
  260. package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
  261. package/src/runtime/plugins/browser-runtime.ts +183 -0
  262. package/src/runtime/plugins/browser-session-authority.ts +332 -0
  263. package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
  264. package/src/runtime/plugins/desktop-operator-registration.ts +8 -2
  265. package/src/runtime/plugins/external-adapter.ts +324 -3
  266. package/src/runtime/plugins/external-unix-socket.ts +7 -1
  267. package/src/runtime/plugins/first-party-registry.ts +2 -0
  268. package/src/runtime/plugins/github-adapter.ts +125 -0
  269. package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
  270. package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
  271. package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
  272. package/src/runtime/plugins/ios-adapter.ts +106 -16
  273. package/src/runtime/plugins/ios-agent-device.ts +234 -64
  274. package/src/runtime/plugins/ios-physical-device.ts +1143 -392
  275. package/src/runtime/plugins/lightweight-action.ts +153 -0
  276. package/src/runtime/plugins/local-system-adapter.ts +123 -6
  277. package/src/runtime/plugins/macos-capability-broker.ts +55 -9
  278. package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
  279. package/src/runtime/plugins/plugin-management-adapter.ts +49 -8
  280. package/src/runtime/plugins/resend-adapter.ts +1 -1
  281. package/src/runtime/plugins/store.ts +269 -29
  282. package/src/runtime/plugins/types.ts +35 -2
  283. package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
  284. package/src/runtime/projections/git-status-sampler.ts +16 -16
  285. package/src/runtime/projections/materialized-view.ts +28 -10
  286. package/src/runtime/recovery/maintenance-executor.ts +393 -28
  287. package/src/runtime/root/mcp-transport.ts +180 -10
  288. package/src/runtime/root/package-connector-service.ts +186 -8
  289. package/src/runtime/root/package-runtime-release.ts +143 -17
  290. package/src/runtime/root/package-runtime-service.ts +430 -22
  291. package/src/runtime/root/release-manifest.ts +7 -2
  292. package/src/runtime/root/release-materialize.ts +19 -0
  293. package/src/runtime/root/release-store.ts +48 -11
  294. package/src/runtime/root/runtime.ts +23 -5
  295. package/src/runtime/root/service.ts +100 -29
  296. package/src/runtime/root/types.ts +2 -0
  297. package/src/runtime/safe-tooling/ios-development.ts +321 -63
  298. package/src/runtime/shared/trusted-executable.ts +62 -0
  299. package/src/runtime/shared/trusted-node-executable.ts +48 -0
  300. package/src/runtime/standalone-recovery/core.ts +700 -92
  301. package/src/runtime/standalone-recovery/entry.ts +99 -23
  302. package/src/runtime/standalone-recovery/installer.ts +19 -3
  303. package/src/runtime/standalone-recovery/service-labels.ts +2 -0
  304. package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
  305. package/src/runtime/workflow/schedules/browser-probe.ts +81 -3
  306. package/src/runtime/workflow/schedules/engine.ts +287 -151
  307. package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
  308. package/src/runtime/workflow/schedules/settlement.ts +48 -4
  309. package/src/runtime/workflow/schedules/store.ts +131 -10
  310. package/src/runtime/workflow/schedules/types.ts +2 -4
  311. package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
  312. package/src/testing/test-governance.ts +8 -40
  313. package/assets/reference-configs/heartbeat-triage.md +0 -57
  314. package/assets/templates/helpers/harness-trace-grade.sh +0 -150
  315. package/assets/templates/helpers/heartbeat-triage.sh +0 -361
  316. package/assets/templates/helpers/maintenance-triage.sh +0 -167
  317. package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
  318. package/docs/architecture/current/architecture-invariants.md +0 -331
  319. package/docs/architecture/current/automation-and-schedule-engine.md +0 -459
  320. package/docs/architecture/current/codegraph-context-provider.md +0 -238
  321. package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
  322. package/docs/architecture/current/controller-repository-decoupling.md +0 -148
  323. package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -373
  324. package/docs/architecture/current/entity-model.md +0 -612
  325. package/docs/architecture/current/external-plugin-governance.md +0 -396
  326. package/docs/architecture/current/failure-recovery.md +0 -447
  327. package/docs/architecture/current/governance.md +0 -213
  328. package/docs/architecture/current/human-interaction-plane.md +0 -87
  329. package/docs/architecture/current/implementation-status.md +0 -128
  330. package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
  331. package/docs/architecture/current/legacy-governance.md +0 -58
  332. package/docs/architecture/current/local-workspace-target-grants.md +0 -70
  333. package/docs/architecture/current/migration-roadmap.md +0 -105
  334. package/docs/architecture/current/multi-repository-execution.md +0 -371
  335. package/docs/architecture/current/operations-runbook.md +0 -45
  336. package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
  337. package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
  338. package/docs/architecture/current/repository-access-modes.md +0 -102
  339. package/docs/architecture/current/route-policy.md +0 -15
  340. package/docs/architecture/current/route-session-concurrency.md +0 -85
  341. package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
  342. package/docs/architecture/current/runtime-directory-map.md +0 -49
  343. package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
  344. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
  345. package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
  346. package/docs/architecture/current/system-overview.md +0 -169
  347. package/docs/architecture/current/target-requirements-traceability.md +0 -202
  348. package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
  349. package/docs/architecture/current/thin-harness-v1.md +0 -321
  350. package/docs/architecture/current/verification-and-release-gates.md +0 -391
  351. package/docs/forge-autonomous-goal-loop.md +0 -9
  352. package/docs/forge-provider-configuration.md +0 -7
  353. package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -93
  354. package/docs/operations/gmail-assistant-routines.md +0 -186
  355. package/docs/operations/personal-assistant-operator-guide.md +0 -61
  356. package/docs/operations/provider-configuration.md +0 -31
  357. package/scripts/harness-trace-grade.sh +0 -150
  358. package/scripts/heartbeat-triage.sh +0 -361
  359. package/scripts/maintenance-triage.sh +0 -167
  360. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
  361. package/src/cli/repositories/goal-registry.ts +0 -239
  362. package/src/runtime/assistant/action-proposals.ts +0 -279
  363. package/src/runtime/assistant/intent.ts +0 -395
  364. package/src/runtime/assistant/model-provider.ts +0 -460
  365. package/src/runtime/assistant/openapi.ts +0 -223
  366. package/src/runtime/assistant/policy.ts +0 -89
  367. package/src/runtime/assistant/readiness.ts +0 -258
  368. package/src/runtime/assistant/routine-runtime.ts +0 -477
  369. package/src/runtime/assistant/schedule-binding.ts +0 -171
  370. package/src/runtime/assistant/standing-grants.ts +0 -375
  371. package/src/runtime/assistant/store.ts +0 -243
  372. package/src/runtime/assistant/types.ts +0 -135
  373. package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
  374. package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
  375. package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
  376. package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
  377. package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
  378. package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
  379. package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
  380. package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
  381. package/src/runtime/control-plane/goal-loop/index.ts +0 -13
  382. package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
  383. package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
  384. package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
  385. package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
  386. package/src/runtime/control-plane/goal-loop/types.ts +0 -378
  387. package/src/runtime/control-plane/startup-recovery.ts +0 -198
  388. package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
  389. package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
  390. package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
  391. package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
  392. package/src/runtime/workflow/findings/store.ts +0 -204
  393. package/src/runtime/workflow/findings/types.ts +0 -58
  394. package/src/runtime/workflow/portfolio/engine.ts +0 -20
  395. package/src/runtime/workflow/portfolio/store.ts +0 -185
  396. package/src/runtime/workflow/portfolio/types.ts +0 -29
@@ -206,7 +206,7 @@ export function detectSetupPlatform(options: { platform?: NodeJS.Platform; arch?
206
206
  }
207
207
 
208
208
 
209
- function isLoopbackMcpEndpoint(value: string | undefined): boolean {
209
+ function isLoopbackMcpEndpoint(value: string | undefined): value is string {
210
210
  if (!value) return false;
211
211
  try {
212
212
  const parsed = new URL(value);
@@ -220,6 +220,11 @@ function isLoopbackMcpEndpoint(value: string | undefined): boolean {
220
220
  }
221
221
  }
222
222
 
223
+ function tunnelRuntimeProfileTargetsEndpoint(profilePath: string | undefined, endpoint: string): boolean {
224
+ if (!profilePath) return false;
225
+ try { return readFileSync(profilePath, 'utf8').includes(endpoint); } catch { return false; }
226
+ }
227
+
223
228
  export interface ControllerGuidance {
224
229
  controller: SetupControllerKind;
225
230
  ready: boolean;
@@ -266,8 +271,8 @@ export function resolveControllerGuidance(
266
271
  controller,
267
272
  ready: false,
268
273
  title: controller === 'chatgpt' ? 'Configure the ChatGPT controller endpoint' : 'Configure the remote MCP controller endpoint',
269
- detail: 'Create the user-level Forge OAuth/MCP configuration. This does not install Codex or Claude and does not require a repository.',
270
- command: `forge mcp setup chatgpt --user-level${endpoint}`,
274
+ detail: 'Create or refresh the Forge OAuth/MCP configuration in the selected Controller Home. This does not install Codex or Claude and does not require a repository.',
275
+ command: `forge mcp setup chatgpt --user-level --controller-home ${JSON.stringify(controllerHome)}${endpoint}`,
271
276
  };
272
277
  }
273
278
  continue;
@@ -327,7 +332,7 @@ export function resolveRuntimeGuidance(
327
332
  ready: false,
328
333
  title: 'Install the user-level Forge Runtime',
329
334
  detail: 'Use the packaged Runtime path. It does not require a Git checkout, Bun compilation, CodeGraph, or Standalone Recovery.',
330
- command: 'forge runtime service install-package',
335
+ command: `forge runtime service install-package --controller-home ${JSON.stringify(controllerHome)}`,
331
336
  };
332
337
  }
333
338
 
@@ -364,48 +369,53 @@ export function resolveTunnelGuidance(
364
369
  detail: 'Install the supported tunnel-client binary from OpenAI Platform Tunnels or the official openai/tunnel-client release. The runtime API key remains an environment/file reference owned by tunnel-client; Forge does not store it.',
365
370
  };
366
371
  }
372
+ const localConfig = loadMcpServiceLocalConfig(resolveControllerHome(options.controllerHome));
373
+ const localEndpoint = localConfig?.chatgpt?.localEndpoint;
374
+ if (!isLoopbackMcpEndpoint(localEndpoint)) {
375
+ return {
376
+ provider, ready: false, title: 'Prepare the local ChatGPT OAuth endpoint',
377
+ detail: 'Secure Tunnel must terminate at Forge’s loopback OAuth Gateway, not the bearer-only Canonical Runtime. Refresh the user-level ChatGPT MCP configuration first.',
378
+ command: `forge mcp setup chatgpt --user-level --controller-home ${JSON.stringify(resolveControllerHome(options.controllerHome))}`,
379
+ };
380
+ }
367
381
  const preferredAlias = 'forge';
368
- const aliases = [preferredAlias];
382
+ const configuredAliases: string[] = [];
369
383
  const listed = spawnSync('tunnel-client', ['runtimes', 'list', '--json'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 15_000, env: options.env ?? process.env });
370
384
  if (listed.status === 0) {
371
385
  try {
372
386
  const value = JSON.parse(listed.stdout || '{}') as { aliases?: Array<{ alias?: string; tunnel_id?: string }> };
373
387
  for (const entry of value.aliases ?? []) {
374
- if (entry.tunnel_id === profile.tunnel.tunnelId && entry.alias) aliases.push(entry.alias);
388
+ if (entry.tunnel_id === profile.tunnel.tunnelId && entry.alias) configuredAliases.push(entry.alias);
375
389
  }
376
390
  } catch {
377
391
  // Older clients may not expose local aliases as JSON. Keep the preferred alias fallback.
378
392
  }
379
393
  }
380
- for (const alias of Array.from(new Set(aliases))) {
394
+ const aliases = Array.from(new Set([...configuredAliases, preferredAlias]));
395
+ for (const alias of aliases) {
381
396
  const status = spawnSync('tunnel-client', ['runtimes', 'status', alias, '--json'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 15_000, env: options.env ?? process.env });
382
397
  if (status.status !== 0) continue;
383
398
  try {
384
- const value = JSON.parse(status.stdout || '{}') as { process_running?: boolean; healthy?: boolean; ready?: boolean; tunnel_id?: string };
399
+ const value = JSON.parse(status.stdout || '{}') as { process_running?: boolean; healthy?: boolean; ready?: boolean; tunnel_id?: string; profile_path?: string };
385
400
  if (value.process_running === true && value.healthy === true && value.ready === true
386
- && (!value.tunnel_id || value.tunnel_id === profile.tunnel.tunnelId)) {
401
+ && value.tunnel_id === profile.tunnel.tunnelId
402
+ && tunnelRuntimeProfileTargetsEndpoint(value.profile_path, localEndpoint)) {
387
403
  return {
388
404
  provider, ready: true, title: 'OpenAI Secure MCP Tunnel',
389
- detail: `Managed runtime ${alias} is running, healthy, and ready for ${profile.tunnel.tunnelId}.`,
405
+ detail: `Managed runtime ${alias} is running, healthy, and bound to ${profile.tunnel.tunnelId} -> ${localEndpoint}.`,
390
406
  };
391
407
  }
392
408
  } catch {
393
409
  // Treat malformed or older status output as not-ready instead of claiming success.
394
410
  }
395
411
  }
396
- const alias = preferredAlias;
397
- const localConfig = loadMcpServiceLocalConfig(resolveControllerHome(options.controllerHome));
398
- const localEndpoint = localConfig?.chatgpt?.localEndpoint;
399
- if (!isLoopbackMcpEndpoint(localEndpoint)) {
400
- return {
401
- provider, ready: false, title: 'Prepare the local ChatGPT OAuth endpoint',
402
- detail: 'Secure Tunnel must terminate at Forge’s loopback OAuth Gateway, not the bearer-only Canonical Runtime. Refresh the user-level ChatGPT MCP configuration first.',
403
- command: 'forge mcp setup chatgpt --user-level',
404
- };
405
- }
412
+ // A stopped alias still owns its recorded tunnel identity. Reconnect that
413
+ // alias instead of repointing the conventional `forge` alias, which may
414
+ // legitimately refer to a different tunnel from an earlier setup.
415
+ const alias = configuredAliases[0] ?? preferredAlias;
406
416
  return {
407
417
  provider, ready: false, title: 'Connect OpenAI Secure MCP Tunnel',
408
- detail: 'Create a supervised tunnel-client runtime and verify it with runtimes status. Keep the runtime API key in an environment or file reference owned by tunnel-client; Forge never reads or stores the key.',
418
+ detail: 'Create a supervised tunnel-client runtime and verify its tunnel id and MCP target with runtimes status. Keep the runtime API key in an environment or file reference owned by tunnel-client; Forge never reads or stores the key.',
409
419
  command: `tunnel-client runtimes connect --alias ${alias} --tunnel-id ${profile.tunnel.tunnelId} --runtime-api-key env:CONTROL_PLANE_API_KEY --mcp-server-url ${localEndpoint}`,
410
420
  };
411
421
  }
@@ -11,6 +11,7 @@ import { atomicWriteFileSync } from '../installer/shared';
11
11
  import { runBoundedChild } from '../../runtime/shared/bounded-child-supervisor';
12
12
  import { signalProcessTree } from '../../runtime/shared/process-tree';
13
13
  import { repositoryChildProcessEnvironment, resolveBunExecutable } from '../../runtime/shared/process-environment';
14
+ import { materializeManagedWorkspaceCheckDependencies } from '../../runtime/execution/managed-workspace';
14
15
 
15
16
  export interface ControllerCheckEffects {
16
17
  /** Repository-relative read scopes. Use [\".\"] for the whole checkout. */
@@ -25,6 +26,18 @@ export interface ControllerCheckEffects {
25
26
  hostServices?: string[];
26
27
  }
27
28
 
29
+ export type ControllerCheckCostClass = 'L0' | 'L1' | 'L2' | 'L3' | 'L4';
30
+ export type ControllerCheckRiskFloor = 'readonly' | 'low' | 'medium' | 'high' | 'destructive';
31
+ export type ControllerCheckPhase = 'pre_edit' | 'post_edit' | 'pre_finalize' | 'release';
32
+
33
+ export interface ControllerCheckSelection {
34
+ /** Execution cost only; it does not lower severity or automatically run the check. */
35
+ costClass: ControllerCheckCostClass;
36
+ /** Lowest change-risk band where this check is normally useful when selected mechanically. */
37
+ riskFloor: ControllerCheckRiskFloor;
38
+ phases: ControllerCheckPhase[];
39
+ }
40
+
28
41
  export interface ControllerCheck {
29
42
  id: string;
30
43
  description: string;
@@ -33,6 +46,8 @@ export interface ControllerCheck {
33
46
  timeoutMs: number;
34
47
  source: 'repo-config' | 'package-script';
35
48
  effects?: ControllerCheckEffects;
49
+ /** Selection/preflight metadata; intentionally excluded from physical execution identity. */
50
+ selection?: ControllerCheckSelection;
36
51
  }
37
52
 
38
53
  export interface ControllerCheckSnapshot extends ControllerCheck {
@@ -68,10 +83,12 @@ interface CheckConfig {
68
83
  cwd?: string;
69
84
  timeoutMs?: number;
70
85
  effects?: unknown;
86
+ selection?: unknown;
71
87
  }>;
72
88
  }
73
89
 
74
90
  const CHECK_CONFIG = '.forge/checks.json';
91
+ const LEGACY_TRACKED_CHECK_CONFIG = '.repo-harness/checks.json';
75
92
  const CHECK_EVIDENCE_ROOT = '.ai/harness/checks/controller';
76
93
  const HEAVY_CHECK_LOCK = '.ai/harness/controller/heavy-check.lock';
77
94
  const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
@@ -180,15 +197,87 @@ function normalizeCheckEffects(repoRoot: string, value: unknown): ControllerChec
180
197
  };
181
198
  }
182
199
 
200
+ const CHECK_COST_CLASSES = ['L0', 'L1', 'L2', 'L3', 'L4'] as const;
201
+ const CHECK_RISK_FLOORS = ['readonly', 'low', 'medium', 'high', 'destructive'] as const;
202
+ const CHECK_PHASES = ['pre_edit', 'post_edit', 'pre_finalize', 'release'] as const;
203
+
204
+ function inferredCheckSelection(name: string): ControllerCheckSelection {
205
+ const normalized = name.trim().toLowerCase().replace(/^package:/, '');
206
+ if (/(?:^|:)(?:check:)?release$|full[-:]?regression/.test(normalized)) {
207
+ return { costClass: 'L4', riskFloor: 'high', phases: ['release'] };
208
+ }
209
+ if (/(?:^|:)(?:check:)?main$|(?:^|:)ci(?::|$)/.test(normalized)) {
210
+ return { costClass: 'L4', riskFloor: 'medium', phases: ['pre_finalize'] };
211
+ }
212
+ if (/(?:^|:)(?:browser-live|performance|journey)(?::|$)/.test(normalized)) {
213
+ return { costClass: 'L3', riskFloor: 'medium', phases: ['pre_finalize'] };
214
+ }
215
+ if (/(?:^|:)(?:integration|build)(?::|$)/.test(normalized) || normalized === 'test') {
216
+ return { costClass: 'L2', riskFloor: 'medium', phases: ['post_edit', 'pre_finalize'] };
217
+ }
218
+ if (/(?:^|:)(?:architecture-sync|check-scheduling|context-files|bootstrap-files|background-check-overlap)$/.test(normalized)) {
219
+ return { costClass: 'L0', riskFloor: 'low', phases: ['post_edit'] };
220
+ }
221
+ return { costClass: 'L1', riskFloor: 'low', phases: ['post_edit'] };
222
+ }
223
+
224
+ function normalizeCheckSelection(value: unknown, id: string): ControllerCheckSelection {
225
+ const fallback = inferredCheckSelection(id);
226
+ if (value === undefined) return fallback;
227
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('check selection must be an object');
228
+ const raw = value as Record<string, unknown>;
229
+ const unsupported = Object.keys(raw).filter((key) => !['costClass', 'cost_class', 'riskFloor', 'risk_floor', 'phases'].includes(key));
230
+ if (unsupported.length > 0) throw new Error(`unsupported check selection field(s): ${unsupported.join(', ')}`);
231
+ const cost = raw.costClass ?? raw.cost_class;
232
+ const risk = raw.riskFloor ?? raw.risk_floor;
233
+ const phases = raw.phases;
234
+ if (cost !== undefined && (typeof cost !== 'string' || !CHECK_COST_CLASSES.includes(cost as ControllerCheckCostClass))) {
235
+ throw new Error(`check selection.costClass must be one of: ${CHECK_COST_CLASSES.join(', ')}`);
236
+ }
237
+ if (risk !== undefined && (typeof risk !== 'string' || !CHECK_RISK_FLOORS.includes(risk as ControllerCheckRiskFloor))) {
238
+ throw new Error(`check selection.riskFloor must be one of: ${CHECK_RISK_FLOORS.join(', ')}`);
239
+ }
240
+ if (phases !== undefined && (!Array.isArray(phases) || phases.length === 0 || phases.some((entry) => typeof entry !== 'string' || !CHECK_PHASES.includes(entry as ControllerCheckPhase)))) {
241
+ throw new Error(`check selection.phases must contain one or more of: ${CHECK_PHASES.join(', ')}`);
242
+ }
243
+ return {
244
+ costClass: (cost as ControllerCheckCostClass | undefined) ?? fallback.costClass,
245
+ riskFloor: (risk as ControllerCheckRiskFloor | undefined) ?? fallback.riskFloor,
246
+ phases: phases ? [...new Set(phases as ControllerCheckPhase[])] : fallback.phases,
247
+ };
248
+ }
249
+
250
+ export function controllerCheckSelection(check: Pick<ControllerCheck, 'id' | 'selection'>): ControllerCheckSelection {
251
+ return check.selection ?? inferredCheckSelection(check.id);
252
+ }
253
+
183
254
  function inferredPackageCheckEffects(name: string): ControllerCheckEffects | undefined {
184
255
  const normalized = name.trim().toLowerCase();
185
256
  const staticAnalysis = /(?:^|:)(?:type|typecheck|lint|format:check|runtime-architecture|mcp-compatibility|forge-runtime)$/.test(normalized);
186
- return staticAnalysis ? { reads: ['.'], cache: 'write' } : undefined;
257
+ if (staticAnalysis) return { reads: ['.'], cache: 'write' };
258
+ const isolatedReadOnlyCheck = /(?:^|:)(?:quality-harness|evaluation-framework|background-check-overlap|typescript-navigation|check-scheduling|bootstrap-files)$/.test(normalized);
259
+ if (isolatedReadOnlyCheck) return { reads: ['.'], temp: 'isolated', git: 'read' };
260
+ const browserLive = /(?:^|:)browser-live$/.test(normalized);
261
+ if (browserLive) {
262
+ return {
263
+ reads: ['.'],
264
+ temp: 'isolated',
265
+ hostServices: ['browser-live'],
266
+ };
267
+ }
268
+ return undefined;
187
269
  }
188
270
 
189
271
  function configuredChecks(repoRoot: string): ControllerCheck[] {
190
- const path = join(repoRoot, CHECK_CONFIG);
191
- if (!existsSync(path)) return [];
272
+ // `.forge/` may intentionally be machine-local/ignored. Preserve it as the
273
+ // authoritative current location, but inherit the tracked legacy registry in
274
+ // isolated Git checkouts when the current file is absent. Never merge both:
275
+ // deterministic precedence prevents checkout-local definitions from being
276
+ // silently combined with portable repository policy.
277
+ const currentPath = join(repoRoot, CHECK_CONFIG);
278
+ const legacyPath = join(repoRoot, LEGACY_TRACKED_CHECK_CONFIG);
279
+ const path = existsSync(currentPath) ? currentPath : existsSync(legacyPath) ? legacyPath : undefined;
280
+ if (!path) return [];
192
281
  const config = JSON.parse(readFileSync(path, 'utf-8')) as CheckConfig;
193
282
  return Object.entries(config.checks ?? {}).flatMap(([id, value]) => {
194
283
  if (!Array.isArray(value.command) || value.command.length === 0 || value.command.some((part) => typeof part !== 'string' || part.length === 0)) return [];
@@ -200,6 +289,7 @@ function configuredChecks(repoRoot: string): ControllerCheck[] {
200
289
  timeoutMs: boundedTimeout(value.timeoutMs),
201
290
  source: 'repo-config' as const,
202
291
  effects: normalizeCheckEffects(repoRoot, value.effects),
292
+ selection: normalizeCheckSelection(value.selection, id),
203
293
  }];
204
294
  });
205
295
  }
@@ -223,6 +313,7 @@ function packageChecks(repoRoot: string): ControllerCheck[] {
223
313
  timeoutMs: packageScriptTimeoutMs(name),
224
314
  source: 'package-script' as const,
225
315
  effects: inferredPackageCheckEffects(name),
316
+ selection: inferredCheckSelection(name),
226
317
  }];
227
318
  });
228
319
  }
@@ -360,6 +451,7 @@ function validateControllerCheckSnapshot(repoRoot: string, snapshot: ControllerC
360
451
  timeoutMs: boundedTimeout(snapshot.timeoutMs),
361
452
  source: snapshot.source,
362
453
  effects: normalizeCheckEffects(repoRoot, snapshot.effects),
454
+ selection: normalizeCheckSelection(snapshot.selection, snapshot.id),
363
455
  };
364
456
  }
365
457
 
@@ -541,9 +633,15 @@ function readControllerCheckEvidenceByKey(repoRoot: string, id: string, cacheKey
541
633
  }
542
634
  }
543
635
 
636
+ function prepareControllerCheckDependencies(repoRoot: string, check: ControllerCheck): void {
637
+ if (check.source !== 'package-script' || existsSync(join(repoRoot, 'node_modules'))) return;
638
+ materializeManagedWorkspaceCheckDependencies(repoRoot);
639
+ }
640
+
544
641
  export function runControllerCheck(repoRoot: string, id: string, requestedTimeoutMs?: number, snapshot?: ControllerCheckSnapshot): ControllerCheckResult {
545
642
  const check = snapshot ? validateControllerCheckSnapshot(repoRoot, snapshot) : listControllerChecks(repoRoot).find((entry) => entry.id === id);
546
643
  if (!check || check.id !== id) throw new Error(`check not found: ${id}`);
644
+ prepareControllerCheckDependencies(repoRoot, check);
547
645
  const identity = controllerCheckExecutionIdentity(repoRoot, id, requestedTimeoutMs, snapshot);
548
646
  const timeoutMs = identity.timeoutMs;
549
647
  const revision = identity.revision;
@@ -877,6 +975,11 @@ export function runControllerCheckAsync(
877
975
  return Promise.reject(error);
878
976
  }
879
977
  if (!check || check.id !== id) return Promise.reject(new Error(`check not found: ${id}`));
978
+ try {
979
+ prepareControllerCheckDependencies(repoRoot, check);
980
+ } catch (error) {
981
+ return Promise.reject(error);
982
+ }
880
983
  const identity = controllerCheckExecutionIdentity(repoRoot, id, options.requestedTimeoutMs, options.snapshot);
881
984
  const timeoutMs = identity.timeoutMs;
882
985
  const revision = identity.revision;
@@ -124,7 +124,7 @@ export function compositeRunning(input: {
124
124
  keyOutput: boundKeyOutput(input.keyOutput ?? input.summary),
125
125
  evidenceRefs: input.evidenceRefs ?? [],
126
126
  retryable: false,
127
- nextAction: input.nextAction ?? 'poll the same durable request id; do not resubmit',
127
+ nextAction: input.nextAction ?? 'Continue independent work; inspect this request only when its outcome can change the next decision. Do not resubmit or periodically poll.',
128
128
  details: input.details,
129
129
  };
130
130
  }
@@ -0,0 +1,51 @@
1
+ import type { TaskLedgerTaskProjection } from '../task-ledger';
2
+ import type { projectBoard } from '../issue-store';
3
+ import type { buildControllerTaskLedgerProjection } from '../task-ledger';
4
+
5
+ export interface ContextPackIssueFocus { id?: string; title?: string; summary?: string; tasks: ContextPackTaskFocus[] }
6
+ export interface ContextPackTaskFocus { id?: string; title?: string; objective?: string; status?: string }
7
+
8
+ function record(value: unknown): Record<string, unknown> {
9
+ return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {};
10
+ }
11
+ function string(value: unknown): string | undefined {
12
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
13
+ }
14
+ function task(value: unknown): ContextPackTaskFocus {
15
+ const item = record(value);
16
+ return { id: string(item.id), title: string(item.title), objective: string(item.objective), status: string(item.status) };
17
+ }
18
+ function issue(value: unknown): ContextPackIssueFocus {
19
+ const item = record(value);
20
+ return {
21
+ id: string(item.id), title: string(item.title), summary: string(item.summary),
22
+ tasks: Array.isArray(item.tasks) ? item.tasks.map(task).filter((entry) => Object.keys(entry).length > 0) : [],
23
+ };
24
+ }
25
+
26
+ export function issueTaskFocus(
27
+ board: ReturnType<typeof projectBoard>,
28
+ issueId?: string,
29
+ taskId?: string,
30
+ ): { issue?: ContextPackIssueFocus; task?: ContextPackTaskFocus } {
31
+ const issues = board.issues.map(issue);
32
+ const resolvedIssue = issueId
33
+ ? issues.find((entry) => entry.id === issueId)
34
+ : board.currentIssueId ? issues.find((entry) => entry.id === board.currentIssueId) : undefined;
35
+ return { issue: resolvedIssue, task: resolvedIssue?.tasks.find((entry) => entry.id === taskId) ?? resolvedIssue?.tasks[0] };
36
+ }
37
+
38
+ export function ledgerTask(
39
+ ledger: ReturnType<typeof buildControllerTaskLedgerProjection>,
40
+ issueId?: string,
41
+ taskId?: string,
42
+ ): TaskLedgerTaskProjection | undefined {
43
+ const tasks = ledger.issues.flatMap((entry) => entry.tasks);
44
+ const find = (candidateIssueId?: string, candidateTaskId?: string) => tasks
45
+ .find((entry) => (!candidateIssueId || entry.issueId === candidateIssueId) && (!candidateTaskId || entry.taskId === candidateTaskId));
46
+ if (!issueId && !taskId) {
47
+ const ready = ledger.readyTasks[0];
48
+ return ledger.attention[0] ?? find(ready?.issueId, ready?.taskId) ?? tasks.find((entry) => entry.dispatchable || entry.queueable);
49
+ }
50
+ return find(issueId, taskId);
51
+ }
@@ -0,0 +1,114 @@
1
+ import { readFileSync } from 'fs';
2
+ import { posix, resolve } from 'path';
3
+ import type { McpPolicy } from '../../mcp/types';
4
+ import { readableFile } from './known-paths';
5
+
6
+ const CONTRACT_NAMES = ['AGENTS.md', 'CLAUDE.md'] as const;
7
+ const DEFAULT_MAX_TARGETS = 12;
8
+ const DEFAULT_MAX_CONTRACTS = 12;
9
+ const DEFAULT_MAX_CHARS_PER_CONTRACT = 12_000;
10
+ const DEFAULT_MAX_TOTAL_CHARS = 48_000;
11
+
12
+ export interface RepositoryInstructionContract {
13
+ path: string;
14
+ appliesTo: string[];
15
+ content: string;
16
+ truncated: boolean;
17
+ }
18
+
19
+ export interface RepositoryInstructionContext {
20
+ status: 'none' | 'ready' | 'degraded';
21
+ authority: 'guidance_only';
22
+ targetPaths: string[];
23
+ contracts: RepositoryInstructionContract[];
24
+ coverageGaps: string[];
25
+ truncated: boolean;
26
+ }
27
+
28
+ function isInstructionContract(path: string): boolean {
29
+ const name = posix.basename(path);
30
+ return name === 'AGENTS.md' || name === 'CLAUDE.md';
31
+ }
32
+
33
+ function directoryChain(path: string): string[] {
34
+ const directory = posix.dirname(path);
35
+ if (directory === '.' || directory === '/') return [''];
36
+ const segments = directory.split('/').filter(Boolean);
37
+ const chain = [''];
38
+ for (let index = 1; index <= segments.length; index += 1) chain.push(segments.slice(0, index).join('/'));
39
+ return chain;
40
+ }
41
+
42
+ export function buildRepositoryInstructionContext(
43
+ repoRoot: string,
44
+ policy: McpPolicy,
45
+ targetPaths: string[],
46
+ options: { maxTargets?: number; maxContracts?: number; maxCharsPerContract?: number; maxTotalChars?: number } = {},
47
+ ): RepositoryInstructionContext {
48
+ const maxTargets = Math.max(1, Math.min(30, Math.trunc(options.maxTargets ?? DEFAULT_MAX_TARGETS)));
49
+ const maxContracts = Math.max(1, Math.min(30, Math.trunc(options.maxContracts ?? DEFAULT_MAX_CONTRACTS)));
50
+ const maxCharsPerContract = Math.max(500, Math.min(50_000, Math.trunc(options.maxCharsPerContract ?? DEFAULT_MAX_CHARS_PER_CONTRACT)));
51
+ const maxTotalChars = Math.max(2_000, Math.min(100_000, Math.trunc(options.maxTotalChars ?? DEFAULT_MAX_TOTAL_CHARS)));
52
+ const allTargets = [...new Set(targetPaths.map((path) => path.trim()).filter(Boolean).filter((path) => !isInstructionContract(path)))];
53
+ const targets = allTargets.slice(0, maxTargets);
54
+ const coverageGaps: string[] = [];
55
+ if (allTargets.length > targets.length) coverageGaps.push(`instruction_targets_truncated:${allTargets.length - targets.length}`);
56
+
57
+ const contractTargets = new Map<string, Set<string>>();
58
+ const denied = new Set<string>();
59
+ for (const target of targets) {
60
+ for (const directory of directoryChain(target)) {
61
+ for (const name of CONTRACT_NAMES) {
62
+ const candidate = directory ? `${directory}/${name}` : name;
63
+ const readable = readableFile(repoRoot, policy, candidate);
64
+ if (!readable.ok) {
65
+ if (!readable.reason.startsWith('path does not exist')) denied.add(`${candidate}:${readable.reason}`);
66
+ continue;
67
+ }
68
+ const appliesTo = contractTargets.get(readable.path) ?? new Set<string>();
69
+ appliesTo.add(target);
70
+ contractTargets.set(readable.path, appliesTo);
71
+ }
72
+ }
73
+ }
74
+ if (denied.size > 0) coverageGaps.push(...[...denied].sort().map((entry) => `instruction_contract_unreadable:${entry}`));
75
+
76
+ const orderedPaths = [...contractTargets.keys()].sort((left, right) => {
77
+ const leftDepth = left.split('/').length;
78
+ const rightDepth = right.split('/').length;
79
+ if (leftDepth !== rightDepth) return leftDepth - rightDepth;
80
+ const leftName = posix.basename(left);
81
+ const rightName = posix.basename(right);
82
+ if (leftName !== rightName) return CONTRACT_NAMES.indexOf(leftName as typeof CONTRACT_NAMES[number]) - CONTRACT_NAMES.indexOf(rightName as typeof CONTRACT_NAMES[number]);
83
+ return left.localeCompare(right);
84
+ });
85
+ const selectedPaths = orderedPaths.slice(0, maxContracts);
86
+ if (orderedPaths.length > selectedPaths.length) coverageGaps.push(`instruction_contracts_truncated:${orderedPaths.length - selectedPaths.length}`);
87
+
88
+ const contracts: RepositoryInstructionContract[] = [];
89
+ const fairContractBudget = Math.max(500, Math.min(maxCharsPerContract, Math.floor(maxTotalChars / Math.max(1, selectedPaths.length))));
90
+ for (const path of selectedPaths) {
91
+ try {
92
+ const raw = readFileSync(resolve(repoRoot, path), 'utf8');
93
+ const truncated = raw.length > fairContractBudget;
94
+ contracts.push({
95
+ path,
96
+ appliesTo: [...(contractTargets.get(path) ?? new Set<string>())].sort(),
97
+ content: truncated ? raw.slice(0, fairContractBudget) : raw,
98
+ truncated,
99
+ });
100
+ if (truncated) coverageGaps.push(`instruction_contract_content_truncated:${path}`);
101
+ } catch (error) {
102
+ coverageGaps.push(`instruction_contract_read_failed:${path}:${error instanceof Error ? error.message : String(error)}`);
103
+ }
104
+ }
105
+
106
+ return {
107
+ status: coverageGaps.length > 0 ? 'degraded' : contracts.length > 0 ? 'ready' : 'none',
108
+ authority: 'guidance_only',
109
+ targetPaths: targets,
110
+ contracts,
111
+ coverageGaps: coverageGaps.slice(0, 30),
112
+ truncated: allTargets.length > targets.length || orderedPaths.length > selectedPaths.length || contracts.some((entry) => entry.truncated),
113
+ };
114
+ }
@@ -0,0 +1,76 @@
1
+ import { existsSync, lstatSync, readdirSync, statSync } from 'fs';
2
+ import { globMatches, resolveMcpPath } from '../../mcp/paths';
3
+ import type { McpPolicy } from '../../mcp/types';
4
+
5
+ export function looksLikeGlob(path: string): boolean { return /[*?{[]/.test(path); }
6
+ export function coveredByGlob(path: string, globs: string[]): boolean {
7
+ return globs.length === 0 || globs.some((glob) => globMatches(glob, path));
8
+ }
9
+ export function addReason(
10
+ map: Map<string, { reasons: Set<string>; lines: Set<number> }>,
11
+ path: string,
12
+ reason: string,
13
+ line?: number,
14
+ ): void {
15
+ const entry = map.get(path) ?? { reasons: new Set<string>(), lines: new Set<number>() };
16
+ entry.reasons.add(reason);
17
+ if (typeof line === 'number' && Number.isFinite(line)) entry.lines.add(Math.max(1, Math.trunc(line)));
18
+ map.set(path, entry);
19
+ }
20
+
21
+ export function readableFile(repoRoot: string, policy: McpPolicy, path: string): { ok: true; path: string } | { ok: false; path: string; reason: string } {
22
+ const decision = resolveMcpPath(repoRoot, path, policy, 'read');
23
+ if (!decision.ok || !decision.relativePath || !decision.absolutePath) {
24
+ return { ok: false, path: decision.relativePath ?? path, reason: decision.reason ?? 'path denied' };
25
+ }
26
+ if (!existsSync(decision.absolutePath)) return { ok: false, path: decision.relativePath, reason: 'path does not exist' };
27
+ if (!statSync(decision.absolutePath).isFile()) return { ok: false, path: decision.relativePath, reason: 'path is not a file' };
28
+ return { ok: true, path: decision.relativePath };
29
+ }
30
+
31
+ export interface ExpandedKnownPath {
32
+ files: string[];
33
+ denied: Array<{ path: string; reason: string }>;
34
+ directory?: string;
35
+ truncated: boolean;
36
+ }
37
+
38
+ export function expandKnownPath(repoRoot: string, policy: McpPolicy, path: string, maxFiles: number): ExpandedKnownPath {
39
+ const decision = resolveMcpPath(repoRoot, path, policy, 'read');
40
+ if (!decision.ok || !decision.relativePath || !decision.absolutePath) {
41
+ return { files: [], denied: [{ path: decision.relativePath ?? path, reason: decision.reason ?? 'path denied' }], truncated: false };
42
+ }
43
+ if (!existsSync(decision.absolutePath)) return { files: [], denied: [{ path: decision.relativePath, reason: 'path does not exist' }], truncated: false };
44
+ const rootStat = lstatSync(decision.absolutePath);
45
+ if (rootStat.isSymbolicLink()) return { files: [], denied: [{ path: decision.relativePath, reason: 'symbolic links are not followed' }], truncated: false };
46
+ if (rootStat.isFile()) return { files: [decision.relativePath], denied: [], truncated: false };
47
+ if (!rootStat.isDirectory()) return { files: [], denied: [{ path: decision.relativePath, reason: 'path is neither a regular file nor directory' }], truncated: false };
48
+
49
+ const files: string[] = [];
50
+ const denied: Array<{ path: string; reason: string }> = [];
51
+ let truncated = false;
52
+ const walk = (relativeDirectory: string, depth: number): void => {
53
+ if (files.length >= maxFiles) { truncated = true; return; }
54
+ if (depth > 8) { denied.push({ path: relativeDirectory, reason: 'directory recursion depth exceeded' }); return; }
55
+ const directory = resolveMcpPath(repoRoot, relativeDirectory, policy, 'read');
56
+ if (!directory.ok || !directory.absolutePath || !directory.relativePath) {
57
+ denied.push({ path: directory.relativePath ?? relativeDirectory, reason: directory.reason ?? 'path denied' });
58
+ return;
59
+ }
60
+ let entries;
61
+ try { entries = readdirSync(directory.absolutePath, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name)); }
62
+ catch (error) { denied.push({ path: directory.relativePath, reason: error instanceof Error ? error.message : String(error) }); return; }
63
+ for (const entry of entries) {
64
+ if (files.length >= maxFiles) { truncated = true; break; }
65
+ const child = `${directory.relativePath}/${entry.name}`.replace(/^\.\//, '');
66
+ if (entry.isSymbolicLink()) denied.push({ path: child, reason: 'symbolic links are not followed' });
67
+ else if (entry.isDirectory()) walk(child, depth + 1);
68
+ else if (entry.isFile()) {
69
+ const readable = readableFile(repoRoot, policy, child);
70
+ if (readable.ok) files.push(readable.path); else denied.push(readable);
71
+ }
72
+ }
73
+ };
74
+ walk(decision.relativePath, 0);
75
+ return { files, denied, directory: decision.relativePath, truncated };
76
+ }