@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
@@ -0,0 +1,110 @@
1
+ import type { ControllerContextImpactDomain, ControllerContextRetrievalMode } from './types';
2
+
3
+ export const DEFAULT_MAX_FILES = 8;
4
+ export const DEFAULT_MAX_SNIPPETS = 20;
5
+ export const DEFAULT_MAX_CHARS_PER_SNIPPET = 8000;
6
+ export const DEFAULT_SEARCH_EXCLUDE_GLOBS = [
7
+ '.git/**', '_ops/**', '.forge/**', '.ai/harness/**', 'node_modules/**', 'dist/**', 'coverage/**', '**/*.bak',
8
+ ] as const;
9
+ export const MAX_TOTAL_SEARCHED_FILES = 800;
10
+
11
+ const STOPWORDS = new Set([
12
+ 'about', 'after', 'again', 'also', 'and', 'around', 'because', 'before', 'between', 'change', 'code', 'config',
13
+ 'context', 'current', 'does', 'file', 'from', 'have', 'into', 'issue', 'make', 'need', 'needs', 'only', 'path',
14
+ 'repo', 'repository', 'runtime', 'should', 'task', 'that', 'this', 'through', 'todo', 'update', 'when', 'with',
15
+ ]);
16
+
17
+ export const IMPACT_DOMAIN_TERMS: Record<ControllerContextImpactDomain, readonly string[]> = {
18
+ persistence: ['persist', 'database', 'repository'], scheduler: ['scheduler', 'schedule', 'reminder'],
19
+ notification: ['notification', 'notify', 'push'], timeline: ['timeline', 'history', 'activity'],
20
+ events: ['event', 'publish', 'subscribe'], cache: ['cache', 'invalidate', 'memo'],
21
+ api: ['api', 'dto', 'controller'], concurrency: ['transaction', 'lock', 'atomic'],
22
+ };
23
+
24
+ export function cleanList(value: string[] | undefined): string[] {
25
+ return Array.from(new Set((value ?? []).map((entry) => entry.trim()).filter(Boolean)));
26
+ }
27
+
28
+ export function clamp(value: number | undefined, fallback: number, min: number, max: number): number {
29
+ if (typeof value !== 'number' || !Number.isFinite(value)) return fallback;
30
+ return Math.min(Math.max(Math.trunc(value), min), max);
31
+ }
32
+
33
+ export function pathNoisePenalty(path: string, intent: string, retrievalMode: ControllerContextRetrievalMode): number {
34
+ const normalizedPath = path.toLowerCase();
35
+ const normalizedIntent = intent.toLowerCase();
36
+ let penalty = 0;
37
+ if (/(^|\/)(?:scratch|tmp|fixtures?|examples?)(\/|$)/.test(normalizedPath)
38
+ && !/\b(?:scratch|fixture|example|sample)\b/.test(normalizedIntent)) penalty += 50;
39
+ if (/(^|\/)(?:ios|android|mobile)(\/|$)/.test(normalizedPath)
40
+ && !/\b(?:ios|android|mobile|swift|device|simulator)\b/.test(normalizedIntent)) penalty += 15;
41
+ if (/(^|\/)(?:__tests__|tests?|spec)(\/|$)|\.(?:test|spec)\.[^.]+$/.test(normalizedPath)
42
+ && retrievalMode === 'implementation'
43
+ && !/\b(?:test|spec|verification|regression)\b/.test(normalizedIntent)) penalty += 5;
44
+ return penalty;
45
+ }
46
+
47
+ export function textTokens(value: string): string[] {
48
+ const split = value.replace(/([a-z0-9])([A-Z])/g, '$1 $2').split(/[^\p{L}\p{N}_./:-]+/u)
49
+ .map((entry) => entry.trim()).filter(Boolean);
50
+ return Array.from(new Set(split
51
+ .map((entry) => entry.replace(/^[\'"`]+|[\'"`]+$/g, ''))
52
+ .filter((entry) => entry.length >= 3)
53
+ .filter((entry) => !STOPWORDS.has(entry.toLowerCase()))
54
+ .filter((entry) => !/^\d+$/.test(entry)))).slice(0, 12);
55
+ }
56
+
57
+ /** A caller-supplied symbol or path is an exact retrieval needle, not a broad hint. */
58
+ export function isCodeShapedTerm(value: string): boolean {
59
+ const term = value.trim();
60
+ return term.includes('/')
61
+ || term.includes('_')
62
+ || /[a-z0-9][A-Z]/.test(term)
63
+ || /^[A-Z][A-Za-z0-9]*$/.test(term)
64
+ || /\.[A-Za-z0-9]+$/.test(term);
65
+ }
66
+
67
+ function codeShapedAnchors(value: string): string[] {
68
+ const raw = value.split(/[^\p{L}\p{N}_./:-]+/u)
69
+ .map((entry) => entry.replace(/^[\'"`]+|[\'"`]+$/g, '').trim())
70
+ .filter((entry) => entry.length >= 3)
71
+ .filter((entry) => entry.includes('/')
72
+ || entry.includes('_')
73
+ || /[a-z0-9][A-Z]/.test(entry)
74
+ || /^[A-Z][A-Za-z0-9]*$/.test(entry)
75
+ || /\.[A-Za-z0-9]+$/.test(entry));
76
+ return Array.from(new Set(raw)).slice(0, 8);
77
+ }
78
+
79
+ /**
80
+ * CodeGraph's semantic query cost grows materially with long natural-language
81
+ * prompts. Keep short intents exact, but compact long intents around concrete
82
+ * code anchors plus bounded semantic tokens. This affects only structural
83
+ * discovery; lexical search and raw-source retrieval still consume the full
84
+ * controller description.
85
+ */
86
+ export function structuralIntentQuery(
87
+ description: string,
88
+ fallbackTerms: readonly string[] = [],
89
+ impactDomains: readonly string[] = [],
90
+ ): string {
91
+ const normalized = description.trim();
92
+ const intent = !normalized
93
+ ? cleanList([...fallbackTerms]).slice(0, 16).join(' ')
94
+ : normalized.length <= 160
95
+ ? normalized
96
+ : Array.from(new Set([...codeShapedAnchors(normalized), ...textTokens(normalized)])).slice(0, 16).join(' ');
97
+ return [intent, ...impactDomains].filter(Boolean).join(' ');
98
+ }
99
+
100
+ export function gitStatusChangedPaths(status: string): string[] {
101
+ const paths: string[] = [];
102
+ for (const line of status.split(/\r?\n/)) {
103
+ if (!line.trim() || line.startsWith('##') || line.length < 4) continue;
104
+ let path = line.slice(3).trim();
105
+ if (path.includes(' -> ')) path = path.slice(path.lastIndexOf(' -> ') + 4).trim();
106
+ if (path.startsWith('"') && path.endsWith('"')) path = path.slice(1, -1);
107
+ if (path && !paths.includes(path)) paths.push(path);
108
+ }
109
+ return paths;
110
+ }
@@ -0,0 +1,532 @@
1
+ import { closeSync, openSync, readSync, statSync } from 'fs';
2
+ import { extname, resolve } from 'path';
3
+ import { globMatches } from '../../mcp/paths';
4
+ import type { McpPolicy } from '../../mcp/types';
5
+ import { readableFile } from './known-paths';
6
+
7
+ export const GOVERNANCE_RULES_PATH = '.ai/context/governance-rules.json';
8
+ export const GOVERNANCE_EXCEPTIONS_PATH = '.ai/context/governance-exceptions.json';
9
+ export const GOVERNANCE_MAX_RULES = 256;
10
+ export const GOVERNANCE_MAX_EXCEPTIONS = 256;
11
+ export const GOVERNANCE_MAX_CONFIG_BYTES = 256 * 1024;
12
+ export const GOVERNANCE_MAX_SOURCE_FILES = 24;
13
+ export const GOVERNANCE_MAX_SOURCE_BYTES = 512 * 1024;
14
+ export const GOVERNANCE_MAX_SOURCE_BYTES_PER_FILE = 64 * 1024;
15
+
16
+ export type GovernanceRuleLevel = 'kernel' | 'capability' | 'platform' | 'project';
17
+ export type GovernanceRuleLifecycle = 'draft' | 'active' | 'deprecated' | 'superseded' | 'removed';
18
+ export type GovernanceRuleSeverity = 'info' | 'warning' | 'error';
19
+ export type GovernanceActivationMatch = 'all' | 'any';
20
+
21
+ export interface GovernanceRuleActivation {
22
+ match: GovernanceActivationMatch;
23
+ paths: string[];
24
+ extensions: string[];
25
+ symbols: string[];
26
+ text: string[];
27
+ goalTerms: string[];
28
+ }
29
+
30
+ export interface GovernanceRule {
31
+ id: string;
32
+ title: string;
33
+ level: GovernanceRuleLevel;
34
+ lifecycle: GovernanceRuleLifecycle;
35
+ severity: GovernanceRuleSeverity;
36
+ invariant: string;
37
+ owner?: string;
38
+ version?: number;
39
+ supersedes: string[];
40
+ activation: GovernanceRuleActivation;
41
+ validation: { checkIds: string[] };
42
+ }
43
+
44
+ export interface GovernanceException {
45
+ id: string;
46
+ ruleId: string;
47
+ paths: string[];
48
+ reason: string;
49
+ owner?: string;
50
+ expiresAt?: string;
51
+ status: 'active' | 'revoked';
52
+ }
53
+
54
+ export interface GovernanceRuleActivationEvidence {
55
+ id: string;
56
+ title: string;
57
+ level: GovernanceRuleLevel;
58
+ severity: GovernanceRuleSeverity;
59
+ invariant: string;
60
+ owner?: string;
61
+ version?: number;
62
+ matchedPaths: string[];
63
+ matchedSignals: string[];
64
+ checkIds: string[];
65
+ partialExceptionIds: string[];
66
+ suppressedPaths: string[];
67
+ }
68
+
69
+ export interface GovernanceSuppressedRule extends GovernanceRuleActivationEvidence {
70
+ exceptionIds: string[];
71
+ }
72
+
73
+ export interface RepositoryGovernanceResolution {
74
+ status: 'none' | 'ready' | 'degraded';
75
+ authority: 'repository_rule_registry';
76
+ registry: { rulesPath: string; exceptionsPath: string };
77
+ activeRules: GovernanceRuleActivationEvidence[];
78
+ suppressedRules: GovernanceSuppressedRule[];
79
+ recommendedCheckIds: string[];
80
+ expiredExceptionIds: string[];
81
+ coverageGaps: string[];
82
+ metrics: {
83
+ rulesLoaded: number;
84
+ rulesEvaluated: number;
85
+ exceptionsLoaded: number;
86
+ filesScanned: number;
87
+ bytesScanned: number;
88
+ elapsedMs: number;
89
+ };
90
+ }
91
+
92
+ interface GovernanceResolutionInput {
93
+ goal?: string;
94
+ targetPaths?: string[];
95
+ changedPaths?: string[];
96
+ now?: Date;
97
+ }
98
+
99
+ interface BoundedReadResult {
100
+ status: 'ok' | 'missing' | 'denied' | 'too_large' | 'failed';
101
+ content?: string;
102
+ bytes?: number;
103
+ truncated?: boolean;
104
+ reason?: string;
105
+ }
106
+
107
+ function cleanStrings(value: unknown, max = 64): string[] {
108
+ if (!Array.isArray(value)) return [];
109
+ return [...new Set(value
110
+ .filter((entry): entry is string => typeof entry === 'string')
111
+ .map((entry) => entry.trim())
112
+ .filter(Boolean))].slice(0, max);
113
+ }
114
+
115
+ function cleanRepoPath(value: string): string | undefined {
116
+ const normalized = value.trim().replace(/\\/g, '/').replace(/^\.\//, '').replace(/^\/+/, '');
117
+ if (!normalized || normalized === '.' || normalized.split('/').some((part) => part === '..')) return undefined;
118
+ return normalized;
119
+ }
120
+
121
+ function cleanRepoPaths(values: readonly string[] | undefined): string[] {
122
+ return [...new Set((values ?? []).flatMap((value) => {
123
+ const clean = cleanRepoPath(value);
124
+ return clean ? [clean] : [];
125
+ }))].sort();
126
+ }
127
+
128
+ function enumValue<T extends string>(value: unknown, allowed: readonly T[], fallback: T): T {
129
+ return typeof value === 'string' && allowed.includes(value as T) ? value as T : fallback;
130
+ }
131
+
132
+ function normalizeRule(value: unknown, index: number, gaps: string[]): GovernanceRule | undefined {
133
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
134
+ gaps.push(`governance_rule_invalid:${index}`);
135
+ return undefined;
136
+ }
137
+ const raw = value as Record<string, unknown>;
138
+ const id = typeof raw.id === 'string' ? raw.id.trim() : '';
139
+ const title = typeof raw.title === 'string' ? raw.title.trim() : '';
140
+ const invariant = typeof raw.invariant === 'string' ? raw.invariant.trim() : '';
141
+ if (!id || !title || !invariant) {
142
+ gaps.push(`governance_rule_missing_required_fields:${id || index}`);
143
+ return undefined;
144
+ }
145
+ const activationRaw = raw.activation && typeof raw.activation === 'object' && !Array.isArray(raw.activation)
146
+ ? raw.activation as Record<string, unknown>
147
+ : {};
148
+ const validationRaw = raw.validation && typeof raw.validation === 'object' && !Array.isArray(raw.validation)
149
+ ? raw.validation as Record<string, unknown>
150
+ : {};
151
+ const extensions = cleanStrings(activationRaw.extensions).map((entry) => entry.startsWith('.') ? entry.toLowerCase() : `.${entry.toLowerCase()}`);
152
+ const version = typeof raw.version === 'number' && Number.isFinite(raw.version) && raw.version > 0 ? Math.trunc(raw.version) : undefined;
153
+ const enumChecks: Array<[string, unknown, readonly string[]]> = [
154
+ ['level', raw.level, ['kernel', 'capability', 'platform', 'project']],
155
+ ['lifecycle', raw.lifecycle, ['draft', 'active', 'deprecated', 'superseded', 'removed']],
156
+ ['severity', raw.severity, ['info', 'warning', 'error']],
157
+ ['activation.match', activationRaw.match, ['all', 'any']],
158
+ ];
159
+ for (const [field, candidate, allowed] of enumChecks) {
160
+ if (candidate !== undefined && (typeof candidate !== 'string' || !allowed.includes(candidate))) {
161
+ gaps.push(`governance_rule_invalid_${field.replace('.', '_')}:${id}`);
162
+ return undefined;
163
+ }
164
+ }
165
+ return {
166
+ id,
167
+ title,
168
+ level: enumValue(raw.level, ['kernel', 'capability', 'platform', 'project'] as const, 'project'),
169
+ lifecycle: enumValue(raw.lifecycle, ['draft', 'active', 'deprecated', 'superseded', 'removed'] as const, 'active'),
170
+ severity: enumValue(raw.severity, ['info', 'warning', 'error'] as const, 'warning'),
171
+ invariant,
172
+ ...(typeof raw.owner === 'string' && raw.owner.trim() ? { owner: raw.owner.trim() } : {}),
173
+ ...(version ? { version } : {}),
174
+ supersedes: cleanStrings(raw.supersedes),
175
+ activation: {
176
+ match: enumValue(activationRaw.match, ['all', 'any'] as const, 'all'),
177
+ paths: cleanStrings(activationRaw.paths),
178
+ extensions,
179
+ symbols: cleanStrings(activationRaw.symbols),
180
+ text: cleanStrings(activationRaw.text),
181
+ goalTerms: cleanStrings(activationRaw.goalTerms),
182
+ },
183
+ validation: { checkIds: cleanStrings(validationRaw.checkIds ?? validationRaw.check_ids) },
184
+ };
185
+ }
186
+
187
+ function normalizeException(value: unknown, index: number, gaps: string[]): GovernanceException | undefined {
188
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
189
+ gaps.push(`governance_exception_invalid:${index}`);
190
+ return undefined;
191
+ }
192
+ const raw = value as Record<string, unknown>;
193
+ const id = typeof raw.id === 'string' ? raw.id.trim() : '';
194
+ const ruleId = typeof raw.ruleId === 'string' ? raw.ruleId.trim() : typeof raw.rule_id === 'string' ? raw.rule_id.trim() : '';
195
+ const reason = typeof raw.reason === 'string' ? raw.reason.trim() : '';
196
+ if (!id || !ruleId || !reason) {
197
+ gaps.push(`governance_exception_missing_required_fields:${id || index}`);
198
+ return undefined;
199
+ }
200
+ const expiresAt = typeof raw.expiresAt === 'string' && raw.expiresAt.trim()
201
+ ? raw.expiresAt.trim()
202
+ : typeof raw.expires_at === 'string' && raw.expires_at.trim()
203
+ ? raw.expires_at.trim()
204
+ : undefined;
205
+ if (expiresAt && !Number.isFinite(Date.parse(expiresAt))) {
206
+ gaps.push(`governance_exception_invalid_expiry:${id}`);
207
+ return undefined;
208
+ }
209
+ if (raw.status !== undefined && (typeof raw.status !== 'string' || !['active', 'revoked'].includes(raw.status))) {
210
+ gaps.push(`governance_exception_invalid_status:${id}`);
211
+ return undefined;
212
+ }
213
+ return {
214
+ id,
215
+ ruleId,
216
+ paths: cleanStrings(raw.paths),
217
+ reason,
218
+ ...(typeof raw.owner === 'string' && raw.owner.trim() ? { owner: raw.owner.trim() } : {}),
219
+ ...(expiresAt ? { expiresAt } : {}),
220
+ status: enumValue(raw.status, ['active', 'revoked'] as const, 'active'),
221
+ };
222
+ }
223
+
224
+ function readBoundedRepositoryFile(
225
+ repoRoot: string,
226
+ policy: McpPolicy,
227
+ path: string,
228
+ maxBytes: number,
229
+ allowTruncate = false,
230
+ ): BoundedReadResult {
231
+ const readable = readableFile(repoRoot, policy, path);
232
+ if (!readable.ok) {
233
+ return readable.reason.startsWith('path does not exist')
234
+ ? { status: 'missing', reason: readable.reason }
235
+ : { status: 'denied', reason: readable.reason };
236
+ }
237
+ const absolute = resolve(repoRoot, readable.path);
238
+ try {
239
+ const stat = statSync(absolute);
240
+ if (!stat.isFile()) return { status: 'failed', reason: 'not a regular file' };
241
+ const truncated = stat.size > maxBytes;
242
+ if (truncated && !allowTruncate) return { status: 'too_large', bytes: stat.size, reason: `file exceeds ${maxBytes} bytes` };
243
+ const length = Math.max(0, Math.min(stat.size, maxBytes));
244
+ const buffer = Buffer.alloc(length);
245
+ const fd = openSync(absolute, 'r');
246
+ try {
247
+ const bytes = length > 0 ? readSync(fd, buffer, 0, length, 0) : 0;
248
+ return { status: 'ok', content: buffer.subarray(0, bytes).toString('utf8'), bytes, truncated };
249
+ } finally {
250
+ closeSync(fd);
251
+ }
252
+ } catch (error) {
253
+ return { status: 'failed', reason: error instanceof Error ? error.message : String(error) };
254
+ }
255
+ }
256
+
257
+ function parseRegistry<T>(
258
+ repoRoot: string,
259
+ policy: McpPolicy,
260
+ path: string,
261
+ key: 'rules' | 'exceptions',
262
+ cap: number,
263
+ normalize: (value: unknown, index: number, gaps: string[]) => T | undefined,
264
+ gaps: string[],
265
+ ): { found: boolean; values: T[] } {
266
+ const read = readBoundedRepositoryFile(repoRoot, policy, path, GOVERNANCE_MAX_CONFIG_BYTES);
267
+ if (read.status === 'missing') return { found: false, values: [] };
268
+ if (read.status !== 'ok') {
269
+ gaps.push(`governance_registry_${read.status}:${path}${read.reason ? `:${read.reason}` : ''}`);
270
+ return { found: true, values: [] };
271
+ }
272
+ try {
273
+ const parsed = JSON.parse(read.content ?? '{}') as Record<string, unknown>;
274
+ const list = Array.isArray(parsed[key]) ? parsed[key] : undefined;
275
+ if (!list) {
276
+ gaps.push(`governance_registry_missing_array:${path}:${key}`);
277
+ return { found: true, values: [] };
278
+ }
279
+ if (list.length > cap) gaps.push(`governance_registry_truncated:${path}:${list.length - cap}`);
280
+ return {
281
+ found: true,
282
+ values: list.slice(0, cap).flatMap((entry, index) => {
283
+ const normalized = normalize(entry, index, gaps);
284
+ return normalized ? [normalized] : [];
285
+ }),
286
+ };
287
+ } catch (error) {
288
+ gaps.push(`governance_registry_invalid_json:${path}:${error instanceof Error ? error.message : String(error)}`);
289
+ return { found: true, values: [] };
290
+ }
291
+ }
292
+
293
+ function uniqueById<T extends { id: string }>(values: T[], kind: 'rule' | 'exception', gaps: string[]): T[] {
294
+ const seen = new Set<string>();
295
+ const unique: T[] = [];
296
+ for (const value of values) {
297
+ if (seen.has(value.id)) {
298
+ gaps.push(`governance_duplicate_${kind}_id:${value.id}`);
299
+ continue;
300
+ }
301
+ seen.add(value.id);
302
+ unique.push(value);
303
+ }
304
+ return unique;
305
+ }
306
+
307
+ function pathMatches(globs: readonly string[], path: string): boolean {
308
+ return globs.length === 0 || globs.some((glob) => globMatches(glob, path));
309
+ }
310
+
311
+ function exceptionExpired(exception: GovernanceException, nowMs: number): boolean {
312
+ if (!exception.expiresAt) return false;
313
+ const expiresAt = Date.parse(exception.expiresAt);
314
+ return Number.isFinite(expiresAt) && expiresAt <= nowMs;
315
+ }
316
+
317
+ export function resolveRepositoryGovernance(
318
+ repoRoot: string,
319
+ policy: McpPolicy,
320
+ input: GovernanceResolutionInput = {},
321
+ ): RepositoryGovernanceResolution {
322
+ const startedAt = performance.now();
323
+ const coverageGaps: string[] = [];
324
+ const rulesRegistry = parseRegistry(
325
+ repoRoot,
326
+ policy,
327
+ GOVERNANCE_RULES_PATH,
328
+ 'rules',
329
+ GOVERNANCE_MAX_RULES,
330
+ normalizeRule,
331
+ coverageGaps,
332
+ );
333
+ const base = {
334
+ authority: 'repository_rule_registry' as const,
335
+ registry: { rulesPath: GOVERNANCE_RULES_PATH, exceptionsPath: GOVERNANCE_EXCEPTIONS_PATH },
336
+ };
337
+ if (!rulesRegistry.found) {
338
+ return {
339
+ ...base,
340
+ status: coverageGaps.length > 0 ? 'degraded' : 'none',
341
+ activeRules: [],
342
+ suppressedRules: [],
343
+ recommendedCheckIds: [],
344
+ expiredExceptionIds: [],
345
+ coverageGaps,
346
+ metrics: {
347
+ rulesLoaded: 0,
348
+ rulesEvaluated: 0,
349
+ exceptionsLoaded: 0,
350
+ filesScanned: 0,
351
+ bytesScanned: 0,
352
+ elapsedMs: Math.round((performance.now() - startedAt) * 100) / 100,
353
+ },
354
+ };
355
+ }
356
+
357
+ const exceptionsRegistry = parseRegistry(
358
+ repoRoot,
359
+ policy,
360
+ GOVERNANCE_EXCEPTIONS_PATH,
361
+ 'exceptions',
362
+ GOVERNANCE_MAX_EXCEPTIONS,
363
+ normalizeException,
364
+ coverageGaps,
365
+ );
366
+ const rules = uniqueById(rulesRegistry.values, 'rule', coverageGaps);
367
+ const exceptions = uniqueById(exceptionsRegistry.values, 'exception', coverageGaps);
368
+ const targetPaths = cleanRepoPaths([...(input.targetPaths ?? []), ...(input.changedPaths ?? [])]);
369
+ const goal = (input.goal ?? '').toLowerCase();
370
+ const nowMs = (input.now ?? new Date()).getTime();
371
+ const activeExceptions = exceptions.filter((entry) => entry.status === 'active');
372
+ const expiredExceptionIds = activeExceptions.filter((entry) => exceptionExpired(entry, nowMs)).map((entry) => entry.id).sort();
373
+ const validExceptions = activeExceptions.filter((entry) => !exceptionExpired(entry, nowMs));
374
+ const sourceCache = new Map<string, string | undefined>();
375
+ let filesScanned = 0;
376
+ let bytesScanned = 0;
377
+ let sourceBudgetReported = false;
378
+
379
+ const sourceForPath = (path: string): string | undefined => {
380
+ if (sourceCache.has(path)) return sourceCache.get(path);
381
+ if (filesScanned >= GOVERNANCE_MAX_SOURCE_FILES || bytesScanned >= GOVERNANCE_MAX_SOURCE_BYTES) {
382
+ if (!sourceBudgetReported) {
383
+ coverageGaps.push('governance_source_scan_budget_reached');
384
+ sourceBudgetReported = true;
385
+ }
386
+ sourceCache.set(path, undefined);
387
+ return undefined;
388
+ }
389
+ const remaining = Math.max(0, GOVERNANCE_MAX_SOURCE_BYTES - bytesScanned);
390
+ const maxBytes = Math.min(GOVERNANCE_MAX_SOURCE_BYTES_PER_FILE, remaining);
391
+ if (maxBytes <= 0) {
392
+ if (!sourceBudgetReported) {
393
+ coverageGaps.push('governance_source_scan_budget_reached');
394
+ sourceBudgetReported = true;
395
+ }
396
+ sourceCache.set(path, undefined);
397
+ return undefined;
398
+ }
399
+ const read = readBoundedRepositoryFile(repoRoot, policy, path, maxBytes, true);
400
+ if (read.status !== 'ok') {
401
+ if (!['missing'].includes(read.status)) coverageGaps.push(`governance_source_${read.status}:${path}`);
402
+ sourceCache.set(path, undefined);
403
+ return undefined;
404
+ }
405
+ filesScanned += 1;
406
+ bytesScanned += read.bytes ?? 0;
407
+ if (read.truncated) coverageGaps.push(`governance_source_truncated:${path}`);
408
+ sourceCache.set(path, read.content ?? '');
409
+ return read.content ?? '';
410
+ };
411
+
412
+ const activeRules: GovernanceRuleActivationEvidence[] = [];
413
+ const suppressedRules: GovernanceSuppressedRule[] = [];
414
+ let rulesEvaluated = 0;
415
+ for (const rule of rules) {
416
+ if (rule.lifecycle !== 'active') continue;
417
+ rulesEvaluated += 1;
418
+ const activation = rule.activation;
419
+ const hasLocalCriteria = activation.paths.length > 0
420
+ || activation.extensions.length > 0
421
+ || activation.symbols.length > 0
422
+ || activation.text.length > 0;
423
+ const hasGoalCriteria = activation.goalTerms.length > 0;
424
+ const matchedGoalTerms = activation.goalTerms.filter((term) => goal.includes(term.toLowerCase()));
425
+ const goalMatched = matchedGoalTerms.length > 0;
426
+ const matchedSignals: string[] = matchedGoalTerms.map((term) => `goal:${term}`);
427
+ const matchedPaths = new Set<string>();
428
+
429
+ for (const path of targetPaths) {
430
+ const localSignals: string[] = [];
431
+ const localKindMatches: boolean[] = [];
432
+ const pathMatched = pathMatches(activation.paths, path);
433
+ if (activation.paths.length > 0) {
434
+ localKindMatches.push(pathMatched);
435
+ if (pathMatched) {
436
+ const glob = activation.paths.find((candidate) => globMatches(candidate, path));
437
+ localSignals.push(`path:${glob ?? '*'}:${path}`);
438
+ }
439
+ }
440
+ const extension = extname(path).toLowerCase();
441
+ const extensionMatched = activation.extensions.length === 0 || activation.extensions.includes(extension);
442
+ if (activation.extensions.length > 0) {
443
+ localKindMatches.push(extensionMatched);
444
+ if (extensionMatched) localSignals.push(`extension:${extension}:${path}`);
445
+ }
446
+
447
+ const sourcePrerequisitesMatched = activation.match === 'any'
448
+ || ((activation.paths.length === 0 || pathMatched) && (activation.extensions.length === 0 || extensionMatched));
449
+ let source: string | undefined;
450
+ if (sourcePrerequisitesMatched && (activation.symbols.length > 0 || activation.text.length > 0)) {
451
+ source = sourceForPath(path);
452
+ }
453
+ if (activation.symbols.length > 0) {
454
+ const matchingSymbols = source === undefined ? [] : activation.symbols.filter((symbol) => source!.includes(symbol));
455
+ localKindMatches.push(matchingSymbols.length > 0);
456
+ for (const symbol of matchingSymbols) localSignals.push(`symbol:${symbol}:${path}`);
457
+ }
458
+ if (activation.text.length > 0) {
459
+ const matchingText = source === undefined ? [] : activation.text.filter((text) => source!.includes(text));
460
+ localKindMatches.push(matchingText.length > 0);
461
+ for (const text of matchingText) localSignals.push(`text:${text}:${path}`);
462
+ }
463
+
464
+ const localMatched = localKindMatches.length > 0 && (
465
+ activation.match === 'all'
466
+ ? localKindMatches.every(Boolean)
467
+ : localKindMatches.some(Boolean)
468
+ );
469
+ if (!localMatched) continue;
470
+ matchedPaths.add(path);
471
+ matchedSignals.push(...localSignals);
472
+ }
473
+
474
+ const matches = !hasLocalCriteria && !hasGoalCriteria
475
+ ? true
476
+ : activation.match === 'any'
477
+ ? goalMatched || matchedPaths.size > 0
478
+ : (!hasGoalCriteria || goalMatched) && (!hasLocalCriteria || matchedPaths.size > 0);
479
+ if (!matches) continue;
480
+
481
+ const ruleExceptions = validExceptions.filter((entry) => entry.ruleId === rule.id);
482
+ const globalExceptions = ruleExceptions.filter((entry) => entry.paths.length === 0);
483
+ const evidencePaths = [...matchedPaths].sort();
484
+ const scopedSuppressedPaths = evidencePaths.filter((path) => ruleExceptions.some((entry) => entry.paths.length > 0 && pathMatches(entry.paths, path)));
485
+ const scopedExceptionIds = ruleExceptions
486
+ .filter((entry) => entry.paths.length > 0 && scopedSuppressedPaths.some((path) => pathMatches(entry.paths, path)))
487
+ .map((entry) => entry.id);
488
+ const fullyScopedSuppressed = evidencePaths.length > 0 && scopedSuppressedPaths.length === evidencePaths.length;
489
+ const common: GovernanceRuleActivationEvidence = {
490
+ id: rule.id,
491
+ title: rule.title,
492
+ level: rule.level,
493
+ severity: rule.severity,
494
+ invariant: rule.invariant,
495
+ ...(rule.owner ? { owner: rule.owner } : {}),
496
+ ...(rule.version ? { version: rule.version } : {}),
497
+ matchedPaths: evidencePaths.filter((path) => !scopedSuppressedPaths.includes(path)),
498
+ matchedSignals: [...new Set(matchedSignals)].sort(),
499
+ checkIds: rule.validation.checkIds,
500
+ partialExceptionIds: fullyScopedSuppressed || globalExceptions.length > 0 ? [] : [...new Set(scopedExceptionIds)].sort(),
501
+ suppressedPaths: scopedSuppressedPaths,
502
+ };
503
+ if (globalExceptions.length > 0 || fullyScopedSuppressed) {
504
+ suppressedRules.push({
505
+ ...common,
506
+ exceptionIds: [...new Set([...globalExceptions.map((entry) => entry.id), ...scopedExceptionIds])].sort(),
507
+ });
508
+ continue;
509
+ }
510
+ activeRules.push(common);
511
+ }
512
+
513
+ const recommendedCheckIds = [...new Set(activeRules.flatMap((rule) => rule.checkIds))].sort();
514
+ const uniqueGaps = [...new Set(coverageGaps)].slice(0, 80);
515
+ return {
516
+ ...base,
517
+ status: uniqueGaps.length > 0 ? 'degraded' : 'ready',
518
+ activeRules: activeRules.sort((a, b) => a.id.localeCompare(b.id)),
519
+ suppressedRules: suppressedRules.sort((a, b) => a.id.localeCompare(b.id)),
520
+ recommendedCheckIds,
521
+ expiredExceptionIds,
522
+ coverageGaps: uniqueGaps,
523
+ metrics: {
524
+ rulesLoaded: rules.length,
525
+ rulesEvaluated,
526
+ exceptionsLoaded: exceptions.length,
527
+ filesScanned,
528
+ bytesScanned,
529
+ elapsedMs: Math.round((performance.now() - startedAt) * 100) / 100,
530
+ },
531
+ };
532
+ }