@moretea-labs/forge 1.8.1 → 1.8.2

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 (328) hide show
  1. package/AGENTS.md +5 -2
  2. package/CHANGELOG.md +20 -0
  3. package/README.en.md +24 -3
  4. package/README.md +10 -6
  5. package/README.zh-CN.md +5 -3
  6. package/THIRD_PARTY_NOTICES.md +1 -2
  7. package/adapters/chatgpt/browser-delivery-runtime.ts +5 -3
  8. package/adapters/chatgpt/provider-delivery.ts +1 -1
  9. package/adapters/chatgpt/work-conversation-binding-store.ts +20 -1
  10. package/adapters/computer/chatgpt-extension-target.ts +295 -50
  11. package/adapters/computer/chrome-extension/background.js +227 -71
  12. package/adapters/computer/chrome-extension/content.js +181 -157
  13. package/adapters/computer/chrome-extension/manifest.json +3 -1
  14. package/adapters/computer/desktop-operator-provider.ts +18 -6
  15. package/adapters/computer/interaction-target-authority.ts +39 -9
  16. package/adapters/mcp/access-mode.ts +18 -39
  17. package/adapters/mcp/auth.ts +2 -6
  18. package/adapters/mcp/instructions.ts +2 -2
  19. package/adapters/mcp/multi-repository.ts +7 -28
  20. package/adapters/mcp/runtime-gateway/context-adapter.ts +79 -40
  21. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +7 -15
  22. package/adapters/mcp/runtime-gateway/execution-tools.ts +7 -13
  23. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +16 -3
  24. package/adapters/mcp/runtime-gateway/process-tools.ts +3 -21
  25. package/adapters/mcp/runtime-gateway/router.ts +3 -5
  26. package/adapters/mcp/runtime-gateway/routing-policy.ts +17 -10
  27. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1 -1
  28. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +14 -9
  29. package/adapters/mcp/runtime-gateway/runtime-tools.ts +1 -5
  30. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +36 -1
  31. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +58 -4
  32. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +7 -3
  33. package/adapters/mcp/runtime-gateway/work-adapter.ts +16 -16
  34. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +0 -17
  35. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +69 -12
  36. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +26 -2
  37. package/adapters/mcp/server.ts +24 -3
  38. package/adapters/mcp/setup.ts +4 -10
  39. package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
  40. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +672 -2935
  41. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +1 -1
  42. package/adapters/mcp/tool-mapping/repository-tools.ts +52 -86
  43. package/adapters/mcp/toolset-names.ts +11 -42
  44. package/adapters/mcp/toolset.ts +16 -90
  45. package/{src/cli → adapters}/mcp/transports/AGENTS.md +5 -5
  46. package/{src/cli → adapters}/mcp/transports/CLAUDE.md +5 -5
  47. package/adapters/mcp/transports/http-observation.ts +18 -6
  48. package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
  49. package/adapters/mcp/transports/http.ts +54 -14
  50. package/adapters/mcp/types.ts +0 -6
  51. package/assets/hooks/lib/session-state.sh +0 -38
  52. package/assets/hooks/post-bash.sh +1 -1
  53. package/assets/hooks/post-tool-observer.sh +0 -5
  54. package/assets/hooks/prompt-guard.sh +0 -55
  55. package/assets/partials/04-project-structure.partial.md +1 -1
  56. package/assets/partials/07-footer.partial.md +1 -1
  57. package/assets/partials-agents/02-operating-mode.partial.md +1 -1
  58. package/assets/partials-agents/08-deep-docs.partial.md +1 -1
  59. package/assets/reference-configs/agentic-development-flow.md +1 -1
  60. package/assets/reference-configs/external-tooling.md +0 -76
  61. package/assets/reference-configs/release-deploy.md +1 -1
  62. package/assets/templates/helpers/architecture-queue.sh +1 -1
  63. package/assets/templates/helpers/check-agent-tooling.sh +1 -327
  64. package/assets/templates/helpers/check-task-workflow.sh +1 -1
  65. package/assets/templates/helpers/ensure-task-workflow.sh +0 -14
  66. package/assets/templates/helpers/migrate-project-template.sh +2 -2
  67. package/assets/workflow-contract.v1.json +0 -2
  68. package/docs/README.md +5 -2
  69. package/docs/architecture/CURRENT.md +114 -30
  70. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +42 -0
  71. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +15 -2
  72. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +4 -2
  73. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +109 -4
  74. package/docs/architecture/decisions/20261009-exact-code-context-discovery.md +19 -0
  75. package/docs/architecture/decisions/20261009-generated-xcode-cache-retention.md +37 -0
  76. package/docs/architecture/decisions/20261011-autonomous-continuation-convergence.md +597 -0
  77. package/docs/architecture/decisions/20261017-edit-session-finalization-reconciliation.md +17 -0
  78. package/docs/architecture/decisions/20261019-retire-host-skills.md +16 -0
  79. package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +1 -1
  80. package/docs/architecture/domains/public-surface.md +3 -5
  81. package/docs/architecture/domains/verification.md +5 -5
  82. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +2 -2
  83. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +4 -4
  84. package/docs/architecture/reviews/20261015-code-search-runtime-acceptance.md +75 -0
  85. package/docs/forge-plugin-management.md +14 -2
  86. package/docs/operations/controller-browser-plugin.md +23 -0
  87. package/docs/operations/features.md +6 -7
  88. package/docs/operations/features.zh-CN.md +1 -1
  89. package/docs/operations/github-repository.md +4 -4
  90. package/docs/operations/github-semantic-release-projection.md +20 -0
  91. package/docs/operations/homebrew.md +2 -2
  92. package/docs/operations/mcp-tool-exposure.md +13 -54
  93. package/docs/operations/runtime-performance-diagnostics.md +8 -0
  94. package/docs/operations/standalone-disaster-recovery.md +1 -1
  95. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  96. package/docs/tutorials/03-first-repository-task.md +2 -0
  97. package/docs/wiki/Computer-and-Plugins.md +78 -0
  98. package/docs/wiki/Computer-and-Plugins.zh-CN.md +88 -0
  99. package/docs/wiki/Home.md +16 -2
  100. package/docs/wiki/Implementation.md +24 -12
  101. package/docs/wiki/Installation.md +28 -32
  102. package/docs/wiki/Integrations.md +4 -0
  103. package/docs/wiki/Quick-Start.md +35 -22
  104. package/docs/wiki/Releases-and-Upgrades.md +15 -25
  105. package/docs/wiki/_Sidebar.md +5 -1
  106. package/package.json +1 -12
  107. package/packages/kernel/controller/infrastructure/controller-session-store.ts +63 -38
  108. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +10 -0
  109. package/packages/kernel/scheduler/application/eligibility.ts +4 -1
  110. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +13 -10
  111. package/packages/kernel/work/application/work-service.ts +1 -0
  112. package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
  113. package/packages/kernel/work/domain/state-machine.ts +9 -2
  114. package/packages/kernel/work/infrastructure/work-contract-store.ts +28 -1
  115. package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
  116. package/packages/plugin-runtime/computer/conversation-target.ts +17 -7
  117. package/scripts/assemble-template.ts +11 -72
  118. package/scripts/check-mcp-compatibility.ts +55 -74
  119. package/scripts/check-package-identity.mjs +0 -5
  120. package/scripts/check-runtime-architecture.mjs +305 -28
  121. package/scripts/init-project.sh +0 -46
  122. package/scripts/install-ios-hid-toolchain.sh +60 -0
  123. package/scripts/install-standalone-recovery.ts +1 -1
  124. package/scripts/lib/project-init-lib.sh +3 -3
  125. package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
  126. package/scripts/open-source-audit-allowlist.txt +0 -1
  127. package/scripts/run-governed-gate.ts +2 -0
  128. package/scripts/smoke-ios-physical-device.ts +38 -10
  129. package/scripts/smoke-mcp-http-runtime.ts +7 -8
  130. package/scripts/smoke-mcp-tool-surface.ts +27 -41
  131. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +6 -3
  132. package/scripts/sync-generated-authority.mjs +0 -1
  133. package/src/cli/agent-jobs/executor-health.ts +1 -1
  134. package/src/cli/agent-jobs/integration.ts +2 -2
  135. package/src/cli/chatgpt-browser/file-policy.ts +2 -2
  136. package/src/cli/commands/doctor.ts +1 -143
  137. package/src/cli/commands/global-runtime.ts +3 -196
  138. package/src/cli/commands/init-hook.ts +6 -23
  139. package/src/cli/commands/init.ts +3 -252
  140. package/src/cli/commands/mcp.ts +0 -5
  141. package/src/cli/commands/prompt-guard-decision.ts +0 -2
  142. package/src/cli/commands/recovery.ts +2 -14
  143. package/src/cli/commands/repository.ts +17 -1
  144. package/src/cli/commands/setup-execution.ts +41 -0
  145. package/src/cli/controller/completion-orchestrator.ts +1 -1
  146. package/src/cli/controller/composite-operations.ts +1 -1
  147. package/src/cli/controller/context/instruction-context.ts +1 -1
  148. package/src/cli/controller/context/known-paths.ts +2 -2
  149. package/src/cli/controller/context/query-planning.ts +11 -4
  150. package/src/cli/controller/context/rule-resolution.ts +2 -2
  151. package/src/cli/controller/context/source-materializer.ts +2 -2
  152. package/src/cli/controller/context/structural-provider.ts +84 -0
  153. package/src/cli/controller/context/structural.ts +6 -6
  154. package/src/cli/controller/context/types.ts +8 -9
  155. package/src/cli/controller/context-pack.ts +97 -76
  156. package/src/cli/controller/operational-plan.ts +1 -1
  157. package/src/cli/controller/systemd-user.ts +4 -10
  158. package/src/cli/editing/edit-session.ts +17 -5
  159. package/src/cli/github/github.ts +4 -4
  160. package/src/cli/github/semantic-projection.ts +174 -0
  161. package/src/cli/hook/prompt-intents.ts +0 -9
  162. package/src/cli/index.ts +1 -43
  163. package/src/cli/local-bridge/connector-freshness.ts +1 -12
  164. package/src/cli/local-bridge/console-automations.ts +1 -1
  165. package/src/cli/local-bridge/console-view-models.ts +1 -1
  166. package/src/cli/local-bridge/facade-api.ts +0 -2
  167. package/src/cli/repositories/command-classifier.ts +0 -9
  168. package/src/cli/repositories/command-scope.ts +2 -2
  169. package/src/cli/repositories/locks.ts +20 -0
  170. package/src/cli/repositories/registry.ts +2 -1
  171. package/src/cli/repositories/safe-patch.ts +2 -12
  172. package/src/cli/repositories/structured-git.ts +39 -3
  173. package/src/cli/repository/inspector.ts +185 -343
  174. package/src/effects/process-runner.ts +17 -1
  175. package/src/runtime/context/generic-lsp-provider.ts +1 -1
  176. package/src/runtime/context/semantic-navigation-contract.ts +7 -0
  177. package/src/runtime/context/semantic-navigation.ts +1 -1
  178. package/src/runtime/control-plane/cleanup-artifact-retention.ts +4 -4
  179. package/src/runtime/control-plane/execution/work-delivery-service.ts +150 -31
  180. package/src/runtime/control-plane/execution/work-handle-authority.ts +12 -4
  181. package/src/runtime/control-plane/execution/work-handle-store.ts +53 -1
  182. package/src/runtime/control-plane/execution/work-operation-service.ts +20 -10
  183. package/src/runtime/control-plane/execution/work-preparation-service.ts +108 -4
  184. package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
  185. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +28 -13
  186. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +5 -1
  187. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +1 -1
  188. package/src/runtime/control-plane/facade/capability-registry.ts +2 -0
  189. package/src/runtime/control-plane/facade/work-continuation.ts +1 -1
  190. package/src/runtime/control-plane/generated-cache-retention.ts +114 -63
  191. package/src/runtime/control-plane/global-scheduler/maintenance.ts +20 -8
  192. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -11
  193. package/src/runtime/control-plane/governance/external-effects.ts +1 -1
  194. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +1 -0
  195. package/src/runtime/control-plane/runtime-cleanup.ts +12 -93
  196. package/src/runtime/evidence/sensitive-output.ts +22 -2
  197. package/src/runtime/execution/jobs/child-reference.ts +11 -0
  198. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -11
  199. package/src/runtime/execution/process-runtime/command-facade.ts +3 -3
  200. package/src/runtime/execution/process-runtime/runtime.ts +20 -16
  201. package/src/runtime/execution/thin-harness/execution-router.ts +16 -16
  202. package/src/runtime/execution/thin-harness/fast-executor.ts +2 -2
  203. package/src/runtime/execution/thin-harness/search-worker.ts +2 -2
  204. package/src/runtime/execution/workers/executor.ts +1 -2
  205. package/src/runtime/maintenance/cleanup.ts +41 -5
  206. package/src/runtime/plugins/action-application.ts +12 -1
  207. package/src/runtime/plugins/app-store-connect-adapter.ts +252 -4
  208. package/src/runtime/plugins/browser-adapter.ts +381 -313
  209. package/src/runtime/plugins/browser-macos-bridge.ts +114 -112
  210. package/src/runtime/plugins/browser-manifest-surface.ts +2 -1
  211. package/src/runtime/plugins/browser-registration.ts +2 -0
  212. package/src/runtime/plugins/browser-runtime.ts +26 -35
  213. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +11 -2
  214. package/src/runtime/plugins/computer-registration.ts +76 -9
  215. package/src/runtime/plugins/github-adapter.ts +30 -3
  216. package/src/runtime/plugins/gmail-adapter.ts +166 -1
  217. package/src/runtime/plugins/google-shared.ts +10 -5
  218. package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
  219. package/src/runtime/plugins/ios-adapter.ts +45 -40
  220. package/src/runtime/plugins/ios-agent-device.ts +54 -4
  221. package/src/runtime/plugins/ios-device-ownership.ts +103 -0
  222. package/src/runtime/plugins/ios-physical-device.ts +497 -79
  223. package/src/runtime/plugins/local-system-adapter.ts +1 -99
  224. package/src/runtime/plugins/store.ts +80 -7
  225. package/src/runtime/plugins/types.ts +4 -0
  226. package/src/runtime/projections/git-status-sampler.ts +2 -19
  227. package/src/runtime/recovery/actions.ts +1 -1
  228. package/src/runtime/recovery/lifecycle-inventory.ts +1 -11
  229. package/src/runtime/recovery/probe.ts +2 -2
  230. package/src/runtime/release/release-coordinator.ts +20 -4
  231. package/src/runtime/root/controller-progression-composition.ts +67 -5
  232. package/src/runtime/root/package-connector-service.ts +123 -11
  233. package/src/runtime/root/package-runtime-release.ts +6 -18
  234. package/src/runtime/root/package-runtime-service.ts +7 -13
  235. package/src/runtime/root/release-execution-canary.ts +18 -5
  236. package/src/runtime/root/release-manifest.ts +0 -34
  237. package/src/runtime/root/release-materialize.ts +0 -78
  238. package/src/runtime/root/runtime.ts +1 -2
  239. package/src/runtime/root/scheduled-controller-composition.ts +2 -1
  240. package/src/runtime/root/service.ts +90 -11
  241. package/src/runtime/root/types.ts +0 -6
  242. package/src/runtime/root/workflow-supervisor-composition.ts +207 -19
  243. package/src/runtime/root/workflow-supervisor-runtime.ts +365 -10
  244. package/src/runtime/safe-tooling/google-credential-store.ts +1 -11
  245. package/src/runtime/safe-tooling/ios-development.ts +22 -8
  246. package/src/runtime/safe-tooling/job-summary.ts +1 -1
  247. package/src/runtime/safe-tooling/workspace-auth.ts +4 -1
  248. package/src/runtime/shared/atomic-text-file.ts +21 -0
  249. package/src/runtime/shared/json-files.ts +8 -3
  250. package/src/runtime/standalone-recovery/core.ts +189 -174
  251. package/src/runtime/standalone-recovery/entry.ts +62 -30
  252. package/src/runtime/standalone-recovery/operation-lock.ts +11 -1
  253. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -0
  254. package/src/runtime/workflow/schedules/engine.ts +36 -14
  255. package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
  256. package/src/runtime/workflow/schedules/work-continuation.ts +51 -6
  257. package/supervisor/client.ts +31 -14
  258. package/supervisor/control-plane.ts +210 -115
  259. package/supervisor/forge-validators.ts +88 -21
  260. package/supervisor/native-browser-adapter.ts +393 -28
  261. package/supervisor/native-messaging/host.ts +21 -2
  262. package/supervisor/protocol.ts +74 -26
  263. package/supervisor/server.ts +16 -8
  264. package/supervisor/store.ts +476 -57
  265. package/supervisor/types.ts +19 -13
  266. package/SKILL.md +0 -427
  267. package/adapters/computer/chatgpt-target-router.ts +0 -103
  268. package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +0 -87
  269. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +0 -1
  270. package/adapters/mcp/toolset-selection.ts +0 -41
  271. package/agents/openai.yaml +0 -4
  272. package/assets/skill-hooks.json +0 -41
  273. package/assets/skill-version.json +0 -189
  274. package/assets/skills/claude-review/SKILL.md +0 -231
  275. package/assets/skills/codex-review/SKILL.md +0 -103
  276. package/assets/templates/helpers/check-skill-version.ts +0 -193
  277. package/scripts/check-skill-version.ts +0 -5
  278. package/scripts/ensure-codegraph.sh +0 -16
  279. package/scripts/run-skill-hook.ts +0 -244
  280. package/scripts/sync-codex-installed-copies.sh +0 -182
  281. package/src/cli/commands/tools.ts +0 -167
  282. package/src/cli/mcp/access-mode.ts +0 -2
  283. package/src/cli/mcp/access-tools.ts +0 -2
  284. package/src/cli/mcp/audit.ts +0 -2
  285. package/src/cli/mcp/auth.ts +0 -2
  286. package/src/cli/mcp/instructions.ts +0 -2
  287. package/src/cli/mcp/legacy-context.ts +0 -2
  288. package/src/cli/mcp/legacy-tool-service.ts +0 -2
  289. package/src/cli/mcp/multi-repository.ts +0 -2
  290. package/src/cli/mcp/oauth.ts +0 -2
  291. package/src/cli/mcp/openai-secure-tunnel.ts +0 -2
  292. package/src/cli/mcp/paths.ts +0 -2
  293. package/src/cli/mcp/policy.ts +0 -2
  294. package/src/cli/mcp/post-finalize-work-attribution.ts +0 -2
  295. package/src/cli/mcp/proxy-env.ts +0 -2
  296. package/src/cli/mcp/readiness-projection.ts +0 -2
  297. package/src/cli/mcp/redaction.ts +0 -2
  298. package/src/cli/mcp/repo.ts +0 -2
  299. package/src/cli/mcp/repository-tools.ts +0 -2
  300. package/src/cli/mcp/server.ts +0 -2
  301. package/src/cli/mcp/setup.ts +0 -2
  302. package/src/cli/mcp/tool-capability-cache.ts +0 -2
  303. package/src/cli/mcp/tool-contract.ts +0 -2
  304. package/src/cli/mcp/tools.ts +0 -2
  305. package/src/cli/mcp/toolset-names.ts +0 -2
  306. package/src/cli/mcp/toolset-selection.ts +0 -2
  307. package/src/cli/mcp/toolset.ts +0 -2
  308. package/src/cli/mcp/transports/http.ts +0 -2
  309. package/src/cli/mcp/transports/session-registry.ts +0 -2
  310. package/src/cli/mcp/transports/stdio.ts +0 -2
  311. package/src/cli/mcp/types.ts +0 -2
  312. package/src/cli/mcp/umbrella-tools.ts +0 -2
  313. package/src/cli/tools/codegraph.ts +0 -695
  314. package/src/core/adoption/helper-wrapper-plan.ts +0 -159
  315. package/src/runtime/context/codegraph-cache-boundary.ts +0 -255
  316. package/src/runtime/context/codegraph-read-provider.ts +0 -611
  317. package/src/runtime/context/codegraph-sidecar.cjs +0 -248
  318. package/src/runtime/control-plane/codegraph-cache-retention.ts +0 -164
  319. package/src/runtime/gateway/mcp/execution-tools.ts +0 -2
  320. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +0 -2
  321. package/src/runtime/gateway/mcp/persisted-check-process.ts +0 -2
  322. package/src/runtime/gateway/mcp/process-tools.ts +0 -2
  323. package/src/runtime/gateway/mcp/router.ts +0 -2
  324. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +0 -2
  325. package/src/runtime/gateway/mcp/runtime-tools.ts +0 -2
  326. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +0 -2
  327. package/src/runtime/plugins/browser-provider-registry.ts +0 -2
  328. package/src/runtime/plugins/computer-chatgpt-macos-target.ts +0 -891
@@ -5,19 +5,13 @@ import {
5
5
  searchRepositoryManyAsync,
6
6
  searchRepositoryManyCacheIdentity,
7
7
  } from "../repository/inspector";
8
- import { globMatches } from "../mcp/paths";
8
+ import { globMatches } from "../../../adapters/mcp/paths";
9
9
  import { posix } from "path";
10
- import type { McpPolicy } from "../mcp/types";
10
+ import type { McpPolicy } from "../../../adapters/mcp/types";
11
11
  import { materializeSource } from "./context/source-materializer";
12
12
  import { projectBoard } from "./issue-store";
13
13
  import { buildControllerTaskLedgerProjection } from "./task-ledger";
14
14
  import { legacyIssueAuthorityRetired } from "./legacy-issue-cutover";
15
- import {
16
- queryCodeGraphReadProvider,
17
- queryCodeGraphReadProviderAsync,
18
- type CodeGraphNodeSummary,
19
- type CodeGraphReadProviderResponse,
20
- } from "../../runtime/context/codegraph-read-provider";
21
15
  import {
22
16
  CONTEXT_PACK_SCHEMA_VERSION,
23
17
  CONTROLLER_CONTEXT_IMPACT_DOMAINS,
@@ -38,7 +32,7 @@ import {
38
32
  clamp,
39
33
  cleanList,
40
34
  gitStatusChangedPaths,
41
- isCodeShapedTerm,
35
+ explicitCodeQueries,
42
36
  pathNoisePenalty,
43
37
  structuralIntentQuery,
44
38
  textTokens,
@@ -46,6 +40,11 @@ import {
46
40
  import { issueTaskFocus, ledgerTask } from './context/focus';
47
41
  import { addReason, coveredByGlob, expandKnownPath, looksLikeGlob, readableFile } from './context/known-paths';
48
42
  import { structuralResult } from './context/structural';
43
+ import {
44
+ unavailableStructuralProvider,
45
+ unavailableStructuralProviderAsync,
46
+ type StructuralReadProviderResponse,
47
+ } from './context/structural-provider';
49
48
  import { buildRepositoryInstructionContext } from './context/instruction-context';
50
49
  import { resolveRepositoryGovernance } from './context/rule-resolution';
51
50
  import { controllerCheckSelection, listControllerChecks } from './check-runner';
@@ -100,7 +99,7 @@ function resolveSourceReference(repoRoot: string, policy: McpPolicy, sourcePath:
100
99
  function contextPathPriority(path: string, reasons: string[]): number {
101
100
  const testBonus = /(^|\/)(__tests__|tests?|spec)(\/|$)|\.(test|spec)\.[^.]+$/i.test(path) ? 6 : 0;
102
101
  const sourceBonus = reasons.some((reason) => reason.startsWith('source-reference:')) ? 5 : 0;
103
- const structuralBonus = reasons.some((reason) => reason.startsWith('codegraph:')) ? 3 : 0;
102
+ const structuralBonus = reasons.some((reason) => reason.startsWith('structural:')) ? 3 : 0;
104
103
  return testBonus + sourceBonus + structuralBonus;
105
104
  }
106
105
 
@@ -148,7 +147,8 @@ export function buildControllerContextPack(
148
147
  focus.task?.objective,
149
148
  ].filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0);
150
149
  const goal = goalParts.join("\n").trim();
151
- const terms = cleanList([...(options.searchTerms ?? []), ...textTokens(goal)]).slice(0, 14);
150
+ const requiredSearchQueries = explicitCodeQueries(options.searchTerms);
151
+ const terms = cleanList([...requiredSearchQueries, ...(options.searchTerms ?? []), ...(options.searchTerms ?? []).flatMap(textTokens), ...textTokens(goal)]).slice(0, 14);
152
152
  const impactDomains = cleanList(options.impactDomains)
153
153
  .filter((domain): domain is ControllerContextImpactDomain => CONTROLLER_CONTEXT_IMPACT_DOMAINS.includes(domain as ControllerContextImpactDomain))
154
154
  .slice(0, 6);
@@ -168,9 +168,6 @@ export function buildControllerContextPack(
168
168
  ]).slice(0, 32);
169
169
  // Preserve the guarantee for explicit code symbols while broad natural-
170
170
  // language terms remain eligible for early discovery completion.
171
- const requiredSearchQueries = cleanList(options.searchTerms)
172
- .filter(isCodeShapedTerm)
173
- .slice(0, 2);
174
171
  const impactCandidatePaths = new Map<ControllerContextImpactDomain, Set<string>>(impactDomains.map((domain) => [domain, new Set<string>()]));
175
172
  const candidates = new Map<string, { reasons: Set<string>; lines: Set<number> }>();
176
173
  const deniedPaths: Array<{ path: string; reason: string }> = [];
@@ -192,7 +189,7 @@ export function buildControllerContextPack(
192
189
  const baselineRevisionMatches = indexSource === "current_checkout" || gitSnapshot(structuralIndexRoot).head === git.head;
193
190
  const graphImpactFiles = new Set<string>();
194
191
  let structuralContext: ControllerContextPackProjection["structuralContext"] = {
195
- provider: "codegraph",
192
+ provider: "none",
196
193
  requestedMode: structuralMode,
197
194
  status: "disabled",
198
195
  requiredSatisfied: structuralMode !== "required",
@@ -204,17 +201,17 @@ export function buildControllerContextPack(
204
201
  truncated: false,
205
202
  };
206
203
  const repositorySessionCache = getRepositoryReadSessionCache(repoRoot, options.session);
207
- const queryCodeGraph = dependencies.queryCodeGraph ?? queryCodeGraphReadProvider;
208
- const queryStructural = (queryRoot: string, request: Parameters<typeof queryCodeGraphReadProvider>[1]): CodeGraphReadProviderResponse => {
204
+ const structuralProvider = dependencies.queryStructural ?? unavailableStructuralProvider;
205
+ const queryStructural = (queryRoot: string, request: Parameters<typeof unavailableStructuralProvider>[1]): StructuralReadProviderResponse => {
209
206
  const key = JSON.stringify({ queryRoot, request });
210
207
  const cached = repositorySessionCache?.getStructural(key);
211
208
  if (cached && typeof cached === "object") {
212
209
  structuralCacheHits += 1;
213
- return cached as CodeGraphReadProviderResponse;
210
+ return cached as StructuralReadProviderResponse;
214
211
  }
215
212
  structuralCacheMisses += 1;
216
- const response = queryCodeGraph(queryRoot, request);
217
- if (response.error?.code !== CODEGRAPH_PREFETCH_DEFERRED_CODE) {
213
+ const response = structuralProvider(queryRoot, request);
214
+ if (response.error?.code !== STRUCTURAL_PREFETCH_DEFERRED_CODE) {
218
215
  repositorySessionCache?.putStructural(key, response);
219
216
  }
220
217
  return response;
@@ -262,7 +259,7 @@ export function buildControllerContextPack(
262
259
  ? "stale"
263
260
  : structural.status;
264
261
  structuralContext = {
265
- provider: "codegraph",
262
+ provider: "none",
266
263
  requestedMode: structuralMode,
267
264
  status: structuralStatus,
268
265
  requiredSatisfied: structuralMode !== "required" || structuralEvidenceCurrent,
@@ -304,12 +301,12 @@ export function buildControllerContextPack(
304
301
  // must never steer which raw source GPT sees first.
305
302
  if (structuralEvidenceCurrent) {
306
303
  for (const node of graph.nodes.slice(0, maxFiles * 6)) {
307
- addStructuralPath(node.filePath, `codegraph:context:${node.kind}:${node.name}`, node.startLine);
304
+ addStructuralPath(node.filePath, `structural:context:${node.kind}:${node.name}`, node.startLine);
308
305
  }
309
306
  for (const node of graph.entryPoints) {
310
- addStructuralPath(node.filePath, `codegraph:entry:${node.name}`, node.startLine);
307
+ addStructuralPath(node.filePath, `structural:entry:${node.name}`, node.startLine);
311
308
  }
312
- for (const path of graph.relatedFiles.slice(0, maxFiles * 6)) addStructuralPath(path, "codegraph:related");
309
+ for (const path of graph.relatedFiles.slice(0, maxFiles * 6)) addStructuralPath(path, "structural:related");
313
310
  // Context search identifies the structural entry points. For a bounded
314
311
  // number of those files, enrich the same Context Plane call with direct
315
312
  // file dependencies/dependents. This is impact evidence, not a second
@@ -326,18 +323,18 @@ export function buildControllerContextPack(
326
323
  : [];
327
324
  for (const path of dependencies.slice(0, 40)) {
328
325
  graphImpactFiles.add(path);
329
- addStructuralPath(path, `codegraph:dependency:${filePath}`);
326
+ addStructuralPath(path, `structural:dependency:${filePath}`);
330
327
  }
331
328
  for (const path of dependents.slice(0, 40)) {
332
329
  graphImpactFiles.add(path);
333
- addStructuralPath(path, `codegraph:dependent:${filePath}`);
330
+ addStructuralPath(path, `structural:dependent:${filePath}`);
334
331
  }
335
332
  }
336
333
  }
337
- for (const path of metadata?.changedFiles.removed ?? []) omitted.push({ path, reason: "codegraph reports file removed since index" });
334
+ for (const path of metadata?.changedFiles.removed ?? []) omitted.push({ path, reason: "structural provider reports file removed since index" });
338
335
  // A repository-baseline graph deliberately runs against another checkout.
339
336
  // Current worktree changes are still authoritative raw-source evidence,
340
- // but they come from Git rather than stale CodeGraph metadata.
337
+ // but they come from Git rather than stale structural metadata.
341
338
  if (indexSource === "repository_baseline") {
342
339
  for (const path of gitStatusChangedPaths(git.status)) addStructuralPath(path, "worktree:changed-file", 1);
343
340
  }
@@ -349,16 +346,17 @@ export function buildControllerContextPack(
349
346
  let exactKnownFileScope = explicitKnownPaths.length > 0;
350
347
  for (const path of explicitKnownPaths) {
351
348
  const expanded = expandKnownPath(repoRoot, policy, path, Math.max(maxFiles * 4, 40));
352
- for (const file of expanded.files) {
349
+ const allowedFiles = expanded.files.filter((file) => !excludeGlobs.some((glob) => globMatches(glob, file)));
350
+ for (const file of allowedFiles) {
353
351
  addReason(candidates, file, expanded.directory ? `explicit-known-directory:${expanded.directory}` : "explicit-known-path");
354
352
  }
355
- if (!expanded.directory && expanded.files.length === 1 && expanded.denied.length === 0 && !expanded.truncated) {
356
- exactKnownFiles.push(expanded.files[0]!);
353
+ if (!expanded.directory && allowedFiles.length === 1 && expanded.denied.length === 0 && !expanded.truncated) {
354
+ exactKnownFiles.push(allowedFiles[0]!);
357
355
  } else {
358
356
  exactKnownFileScope = false;
359
357
  }
360
358
  deniedPaths.push(...expanded.denied);
361
- scannedFiles += expanded.files.length;
359
+ scannedFiles += allowedFiles.length;
362
360
  searchTruncated = searchTruncated || expanded.truncated;
363
361
  }
364
362
  // Exact caller-selected files own a reserved file slot. The inferred-file
@@ -407,6 +405,14 @@ export function buildControllerContextPack(
407
405
  searchTruncated = searchTruncated || search.truncated;
408
406
  for (const hit of search.results) {
409
407
  if (!coveredByGlob(hit.path, searchIncludeGlobs) || excludeGlobs.some((glob) => globMatches(glob, hit.path))) continue;
408
+ if (candidates.size >= maxFiles * 3 && !candidates.has(hit.path) && !requiredSearchQueries.includes(hit.query)) continue;
409
+ if (requiredSearchQueries.includes(hit.query)) {
410
+ const source = hit.text.trimStart();
411
+ const declared = /^(?:(?:export|declare|default|async)\s+)*(?:function|class|interface|type|const|let|enum)\s+([\w$]+)/.exec(source)?.[1];
412
+ if (declared?.toLowerCase() === hit.query.toLowerCase()) {
413
+ addReason(candidates, hit.path, `${source.startsWith('export ') ? 'exported' : 'local'}-definition:${hit.query}`, hit.line);
414
+ }
415
+ }
410
416
  if (terms.includes(hit.query)) {
411
417
  addReason(candidates, hit.path, `search:${hit.query}`, hit.line);
412
418
  } else {
@@ -417,12 +423,14 @@ export function buildControllerContextPack(
417
423
  addReason(candidates, hit.path, `impact:${domain}:${hit.query}`, hit.line);
418
424
  }
419
425
  }
420
- if (candidates.size >= maxFiles * 3) break;
421
426
  }
422
427
  }
423
428
 
424
429
  timingsMs.lexical = Math.round((performance.now() - lexicalStartedAt) * 100) / 100;
425
430
  const primarySearchReason = terms.length > 0 ? `search:${terms[0]}` : undefined;
431
+ const definitionPriority = (reasons: readonly string[]): number =>
432
+ reasons.some((reason) => reason.startsWith('exported-definition:')) ? 2
433
+ : reasons.some((reason) => reason.startsWith('local-definition:')) ? 1 : 0;
426
434
  const rankedCandidates = Array.from(candidates.entries())
427
435
  .map(([path, entry]) => ({ path, reasons: Array.from(entry.reasons), lines: Array.from(entry.lines) }))
428
436
  .sort((left, right) => {
@@ -433,6 +441,8 @@ export function buildControllerContextPack(
433
441
  ? 2
434
442
  : right.reasons.some((reason) => reason.startsWith("explicit-known-directory:")) ? 1 : 0;
435
443
  if (leftExplicit !== rightExplicit) return rightExplicit - leftExplicit;
444
+ const definitionDelta = definitionPriority(right.reasons) - definitionPriority(left.reasons);
445
+ if (definitionDelta !== 0) return definitionDelta;
436
446
  const leftPrimary = primarySearchReason && left.reasons.includes(primarySearchReason) ? 1 : 0;
437
447
  const rightPrimary = primarySearchReason && right.reasons.includes(primarySearchReason) ? 1 : 0;
438
448
  if (leftPrimary !== rightPrimary) return rightPrimary - leftPrimary;
@@ -453,12 +463,19 @@ export function buildControllerContextPack(
453
463
  || structuralMode !== "off"
454
464
  || impactDomains.length > 0;
455
465
  timingsMs.expansionBudgetMax = multiWaveEligible ? Math.min(6, Math.max(2, Math.ceil(maxFiles / 2))) : 0;
456
- const rankedExactKnownCount = rankedCandidates.filter(isExactKnownCandidate).length;
466
+ // Reserve one best file per explicit symbol before broad hits can consume
467
+ // the result window. Caller-selected exact paths retain first priority.
468
+ const prioritizedCandidates = Array.from(new Map([
469
+ ...rankedCandidates.filter(isExactKnownCandidate),
470
+ ...requiredSearchQueries.flatMap((query) => rankedCandidates.filter((entry) => entry.reasons.includes(`search:${query}`)).slice(0, 1)),
471
+ ...rankedCandidates,
472
+ ].map((entry) => [entry.path, entry] as const)).values());
473
+ const rankedExactKnownCount = prioritizedCandidates.filter(isExactKnownCandidate).length;
457
474
  const initialCandidateLimit = multiWaveEligible
458
475
  ? Math.min(maxFiles, Math.max(rankedExactKnownCount, Math.max(1, maxFiles - timingsMs.expansionBudgetMax)))
459
476
  : maxFiles;
460
- const selected = rankedCandidates.slice(0, initialCandidateLimit);
461
- const deferredSelected = rankedCandidates.slice(initialCandidateLimit, maxFiles);
477
+ const selected = prioritizedCandidates.slice(0, initialCandidateLimit);
478
+ const deferredSelected = prioritizedCandidates.slice(initialCandidateLimit, maxFiles);
462
479
  const selectedExactKnownCount = selected.filter(isExactKnownCandidate).length;
463
480
  // Reserve at least one materialization for every selected exact known file.
464
481
  // This may raise a caller's inferred snippet budget, but never beyond the
@@ -477,7 +494,7 @@ export function buildControllerContextPack(
477
494
  status: selectedFiles.length > 0 ? "selected" as const : matchedFiles.length > 0 ? "matched_not_selected" as const : "no_evidence" as const,
478
495
  };
479
496
  });
480
- for (const entry of rankedCandidates.slice(maxFiles)) omitted.push({ path: entry.path, reason: "max_files" });
497
+ for (const entry of prioritizedCandidates.slice(maxFiles)) omitted.push({ path: entry.path, reason: "max_files" });
481
498
 
482
499
  let remainingSnippets = maxSnippets;
483
500
  const files: ControllerContextPackFile[] = [];
@@ -498,9 +515,12 @@ export function buildControllerContextPack(
498
515
  const waveOnePerFileCap = multiWaveEligible
499
516
  ? Math.max(1, Math.ceil(maxSnippets / Math.max(1, initialCandidateLimit + timingsMs.expansionBudgetMax)))
500
517
  : remainingSnippets;
518
+ // A caller-increased max_snippets budget must not let the first broad
519
+ // candidate consume every snippet and erase other selected source files.
520
+ const inferredFairShare = Math.max(1, Math.ceil(remainingSnippets / (selected.length - selectedIndex)));
501
521
  const fileSnippetBudget = exactKnown
502
522
  ? Math.max(1, remainingSnippets - exactKnownAfter)
503
- : Math.min(remainingSnippets, waveOnePerFileCap);
523
+ : Math.min(remainingSnippets, waveOnePerFileCap, inferredFairShare);
504
524
  let snippets: ControllerContextPackSnippet[] = [];
505
525
  try {
506
526
  snippets = materializeSource({
@@ -509,8 +529,11 @@ export function buildControllerContextPack(
509
529
  path: entry.path,
510
530
  hitLines: entry.lines.length > 0 ? entry.lines : [1],
511
531
  reasons: entry.reasons,
512
- maxSnippets: Math.min(fileSnippetBudget, remainingSnippets),
513
- maxCharsPerSnippet,
532
+ // Discovery should cover multiple entry points rather than dump one huge file.
533
+ // Explicit known files retain their full evidence budget; only inferred
534
+ // candidates receive the cheap two-snippet discovery default.
535
+ maxSnippets: Math.min(options.maxSnippets === undefined && !exactKnown ? 2 : fileSnippetBudget, remainingSnippets),
536
+ maxCharsPerSnippet: exactKnown && options.maxCharsPerSnippet === undefined ? 8000 : maxCharsPerSnippet,
514
537
  session: options.session,
515
538
  });
516
539
  for (const snippet of snippets) {
@@ -534,7 +557,7 @@ export function buildControllerContextPack(
534
557
 
535
558
  // Evidence-closure eligible requests perform bounded internal waves after
536
559
  // Wave 1 raw source materialization. Each later wave derives concrete paths
537
- // from the source that was just read plus current CodeGraph adjacency, then
560
+ // from the source that was just read plus current structural adjacency, then
538
561
  // materializes those paths in the same context request.
539
562
  const materializedPathSet = new Set(files.map((file) => file.path));
540
563
  let frontier = files.slice();
@@ -572,11 +595,11 @@ export function buildControllerContextPack(
572
595
  : [];
573
596
  for (const path of dependencies.slice(0, 40)) {
574
597
  graphImpactFiles.add(path);
575
- addExpansionCandidate(path, `codegraph:dependency:${file.path}`);
598
+ addExpansionCandidate(path, `structural:dependency:${file.path}`);
576
599
  }
577
600
  for (const path of dependents.slice(0, 40)) {
578
601
  graphImpactFiles.add(path);
579
- addExpansionCandidate(path, `codegraph:dependent:${file.path}`);
602
+ addExpansionCandidate(path, `structural:dependent:${file.path}`);
580
603
  }
581
604
  }
582
605
 
@@ -752,7 +775,7 @@ export function buildControllerContextPack(
752
775
  architectureContracts,
753
776
  coverageGaps: Array.from(new Set(coverageGaps)).slice(0, 40),
754
777
  relationSources: [
755
- ...(structuralMode !== "off" ? ["codegraph" as const] : []),
778
+ ...(structuralMode !== "off" ? ["structural" as const] : []),
756
779
  ...(files.length > 0 ? ["lexical" as const] : []),
757
780
  ...(taskChecks.length > 0 ? ["forge_checks" as const] : []),
758
781
  ],
@@ -985,7 +1008,7 @@ export function buildControllerContextPack(
985
1008
  retrievalMode === "implementation"
986
1009
  ? "The pack contains policy-approved current raw source with file SHA identities, but returned snippets are bounded evidence rather than proof of semantic completeness. After reading this evidence, derive new exact paths, symbols, tests, or relationships from the source itself. A guessed lexical term with no result is not by itself a reason to keep scanning or repeat the same broad query."
987
1010
  : `Investigation mode performed ${timingsMs.waveCount} bounded evidence wave(s) inside this request; expansion budget ${timingsMs.expansionBudgetUsed}/${timingsMs.expansionBudgetMax}.`,
988
- "Search/CodeGraph ranking is discovery evidence, not a business-semantics authority. readiness and impactContext make the bounded evidence surface and unresolved gaps explicit; ChatGPT still decides semantic sufficiency.",
1011
+ "Search/structural ranking is discovery evidence, not a business-semantics authority. readiness and impactContext make the bounded evidence surface and unresolved gaps explicit; ChatGPT still decides semantic sufficiency.",
989
1012
  instructionContext.contracts.length > 0
990
1013
  ? `Applicable repository guidance was resolved hierarchically for selected source paths: ${instructionContext.contracts.map((entry) => entry.path).join(", ")}. These AGENTS.md/CLAUDE.md files are guidance-only evidence and do not define semantic scope.`
991
1014
  : "No applicable AGENTS.md/CLAUDE.md guidance file was found for the selected source paths.",
@@ -993,15 +1016,15 @@ export function buildControllerContextPack(
993
1016
  governanceContext.status === 'none'
994
1017
  ? "No repository governance rule registry is present; existing guidance and explicit Task checks behave as before."
995
1018
  : `Repository governance resolved ${governanceContext.activeRules.length} active rule(s), ${governanceContext.suppressedRules.length} suppressed rule(s), and ${governanceContext.recommendedCheckIds.length} recommended check(s) through bounded local matching. Recommendations are evidence only and never execute checks or create Work by themselves.`,
996
- "CodeGraph structural evidence is discovery evidence. Raw source access still passes through Forge repository policy and current-file reads.",
997
- structuralContext.status === "stale" ? "CodeGraph reports stale structural evidence. Treat graph relationships as hints and prefer the returned current raw source for changed files." : structuralContext.status === "unavailable" || structuralContext.status === "degraded" ? "Structural context was unavailable or degraded; bounded text search remains the fallback." : structuralContext.status === "ready" ? "CodeGraph structural context was queried read-only with index sync disabled." : "Structural context was not requested.",
1019
+ "Structural evidence is discovery evidence. Raw source access still passes through Forge repository policy and current-file reads.",
1020
+ structuralContext.status === "stale" ? "Structural provider reports stale evidence. Treat relationships as hints and prefer the returned current raw source for changed files." : structuralContext.status === "unavailable" || structuralContext.status === "degraded" ? "Structural context was unavailable or degraded; bounded text search remains the fallback." : structuralContext.status === "ready" ? "Structural context was queried read-only." : "Structural context was not requested.",
998
1021
  "Run focused validation after a coherent edit batch; expensive full-suite checks belong at candidate/release boundaries unless debugging specifically needs them earlier.",
999
1022
  taskChecks.length > 0 ? `Task checks advertised by the board: ${taskChecks.join(", ")}.` : "No task-specific checks were found in the compact ledger.",
1000
1023
  ],
1001
1024
  },
1002
1025
  next: [
1003
1026
  ...(structuralMode === "required" && !structuralContext.requiredSatisfied
1004
- ? ["Structural context was required but is not current/ready. Refresh CodeGraph explicitly or continue only with the degraded-plan warning visible."]
1027
+ ? ["Structural context was required but is not current/ready. Continue only with the degraded-plan warning visible or use current raw source / semantic navigation instead."]
1005
1028
  : []),
1006
1029
  files.length > 0
1007
1030
  ? retrievalMode === "implementation"
@@ -1016,26 +1039,26 @@ export function buildControllerContextPack(
1016
1039
 
1017
1040
  /**
1018
1041
  * Broad first-call fan-in for the canonical rh_context path. Lexical retrieval
1019
- * remains required evidence. Auto CodeGraph is opportunistic enrichment: it may
1042
+ * remains required evidence. Auto structural retrieval is opportunistic enrichment: it may
1020
1043
  * finish inside the first-call latency budget, otherwise the request returns
1021
1044
  * bounded lexical/raw evidence while the same source-bound structural request
1022
1045
  * continues warming the session cache. Required structural mode still waits.
1023
1046
  */
1024
1047
  export const AUTO_STRUCTURAL_PREFETCH_BUDGET_MS = 100;
1025
1048
  export const AUTO_STRUCTURAL_PREFETCH_TIMEOUT_MS = 1_000;
1026
- const CODEGRAPH_PREFETCH_DEFERRED_CODE = 'CODEGRAPH_PREFETCH_DEFERRED';
1027
- const structuralPrefetchInflight = new Map<string, Promise<CodeGraphReadProviderResponse>>();
1049
+ const STRUCTURAL_PREFETCH_DEFERRED_CODE = 'STRUCTURAL_PREFETCH_DEFERRED';
1050
+ const structuralPrefetchInflight = new Map<string, Promise<StructuralReadProviderResponse>>();
1028
1051
 
1029
- function codeGraphPrefetchDeferredResponse(elapsedMs: number): CodeGraphReadProviderResponse {
1052
+ function structuralPrefetchDeferredResponse(elapsedMs: number): StructuralReadProviderResponse {
1030
1053
  return {
1031
1054
  schemaVersion: 1,
1032
- provider: 'codegraph',
1055
+ provider: 'none',
1033
1056
  operation: 'context',
1034
1057
  ok: false,
1035
1058
  status: 'degraded',
1036
1059
  error: {
1037
- code: CODEGRAPH_PREFETCH_DEFERRED_CODE,
1038
- message: `Optional CodeGraph enrichment exceeded the ${AUTO_STRUCTURAL_PREFETCH_BUDGET_MS}ms first-call budget and continues warming the source-bound session cache.`,
1060
+ code: STRUCTURAL_PREFETCH_DEFERRED_CODE,
1061
+ message: `Optional structural enrichment exceeded the ${AUTO_STRUCTURAL_PREFETCH_BUDGET_MS}ms first-call budget and continues warming the source-bound session cache.`,
1039
1062
  },
1040
1063
  timingsMs: { total: Math.round(elapsedMs * 100) / 100 },
1041
1064
  };
@@ -1061,10 +1084,10 @@ function cacheDeferredStructuralResult(
1061
1084
  sessionCache: ReturnType<typeof getRepositoryReadSessionCache>,
1062
1085
  structuralCacheKey: string,
1063
1086
  sourceIdentity: string | undefined,
1064
- response: CodeGraphReadProviderResponse,
1087
+ response: StructuralReadProviderResponse,
1065
1088
  ): void {
1066
1089
  if (!sessionCache || !sourceIdentity) return;
1067
- if (response.error?.code === 'CODEGRAPH_TIMEOUT' || response.error?.code === CODEGRAPH_PREFETCH_DEFERRED_CODE) return;
1090
+ if (response.error?.code === 'STRUCTURAL_TIMEOUT' || response.error?.code === STRUCTURAL_PREFETCH_DEFERRED_CODE) return;
1068
1091
  if (JSON.stringify(sessionCache.currentIdentity()) !== sourceIdentity) return;
1069
1092
  sessionCache.putStructural(structuralCacheKey, response);
1070
1093
  }
@@ -1076,7 +1099,7 @@ export async function buildControllerContextPackAsync(
1076
1099
  dependencies: ControllerContextPackDependencies = {},
1077
1100
  ): Promise<ControllerContextPackProjection> {
1078
1101
  const structuralMode = options.structuralContext ?? 'off';
1079
- if ((dependencies.queryCodeGraph && !dependencies.queryCodeGraphAsync) || !options.session || options.issueId || options.taskId) {
1102
+ if ((dependencies.queryStructural && !dependencies.queryStructuralAsync) || !options.session || options.issueId || options.taskId) {
1080
1103
  return buildControllerContextPack(repoRoot, policy, options, dependencies);
1081
1104
  }
1082
1105
  const prefetchStartedAt = performance.now();
@@ -1087,7 +1110,8 @@ export async function buildControllerContextPackAsync(
1087
1110
  const explicitKnownPaths = knownPaths.filter((path) => !looksLikeGlob(path));
1088
1111
  const includeGlobs = cleanList([...(options.includeGlobs ?? []), ...knownGlobs]);
1089
1112
  const excludeGlobs = cleanList([...DEFAULT_SEARCH_EXCLUDE_GLOBS, ...(options.excludeGlobs ?? [])]);
1090
- const terms = cleanList([...(options.searchTerms ?? []), ...textTokens(options.description ?? '')]).slice(0, 14);
1113
+ const requiredSearchQueries = explicitCodeQueries(options.searchTerms);
1114
+ const terms = cleanList([...requiredSearchQueries, ...(options.searchTerms ?? []), ...(options.searchTerms ?? []).flatMap(textTokens), ...textTokens(options.description ?? '')]).slice(0, 14);
1091
1115
  const impactDomains = cleanList(options.impactDomains)
1092
1116
  .filter((domain): domain is ControllerContextImpactDomain => CONTROLLER_CONTEXT_IMPACT_DOMAINS.includes(domain as ControllerContextImpactDomain))
1093
1117
  .slice(0, 6);
@@ -1095,8 +1119,9 @@ export async function buildControllerContextPackAsync(
1095
1119
  let exactKnownFileScope = explicitKnownPaths.length > 0;
1096
1120
  for (const path of explicitKnownPaths) {
1097
1121
  const expanded = expandKnownPath(repoRoot, policy, path, Math.max(requestedMaxFiles * 4, 40));
1098
- if (!expanded.directory && expanded.files.length === 1 && expanded.denied.length === 0 && !expanded.truncated) {
1099
- exactKnownFiles.push(expanded.files[0]!);
1122
+ const allowedFiles = expanded.files.filter((file) => !excludeGlobs.some((glob) => globMatches(glob, file)));
1123
+ if (!expanded.directory && allowedFiles.length === 1 && expanded.denied.length === 0 && !expanded.truncated) {
1124
+ exactKnownFiles.push(allowedFiles[0]!);
1100
1125
  } else {
1101
1126
  exactKnownFileScope = false;
1102
1127
  }
@@ -1112,9 +1137,6 @@ export async function buildControllerContextPackAsync(
1112
1137
  ...impactDomains.flatMap((domain) => IMPACT_DOMAIN_TERMS[domain]),
1113
1138
  ...terms.slice(1),
1114
1139
  ]).slice(0, 32);
1115
- const requiredSearchQueries = cleanList(options.searchTerms)
1116
- .filter(isCodeShapedTerm)
1117
- .slice(0, 2);
1118
1140
  const structuralQuery = structuralMode === 'off'
1119
1141
  ? ''
1120
1142
  : structuralIntentQuery(options.description ?? '', terms, impactDomains);
@@ -1131,7 +1153,7 @@ export async function buildControllerContextPackAsync(
1131
1153
  const sessionCache = getRepositoryReadSessionCache(repoRoot, options.session);
1132
1154
  const structuralSourceIdentity = sessionCache ? JSON.stringify(sessionCache.currentIdentity()) : undefined;
1133
1155
  const cachedStructural = structuralQuery
1134
- ? sessionCache?.peekStructural(structuralCacheKey) as CodeGraphReadProviderResponse | undefined
1156
+ ? sessionCache?.peekStructural(structuralCacheKey) as StructuralReadProviderResponse | undefined
1135
1157
  : undefined;
1136
1158
  const lexicalOptions = searchQueries.length > 0
1137
1159
  ? (() => {
@@ -1168,18 +1190,18 @@ export async function buildControllerContextPackAsync(
1168
1190
  return pack;
1169
1191
  }
1170
1192
 
1171
- const noStructuralResponse: CodeGraphReadProviderResponse = {
1193
+ const noStructuralResponse: StructuralReadProviderResponse = {
1172
1194
  schemaVersion: 1,
1173
- provider: 'codegraph',
1195
+ provider: 'none',
1174
1196
  operation: 'context',
1175
1197
  ok: false,
1176
1198
  status: 'degraded',
1177
- error: { code: 'CODEGRAPH_QUERY_REQUIRED', message: 'No context query was provided.' },
1199
+ error: { code: 'STRUCTURAL_QUERY_REQUIRED', message: 'No context query was provided.' },
1178
1200
  timingsMs: { total: 0 },
1179
1201
  };
1180
1202
  let structuralSettled = Boolean(cachedStructural) || !structuralQuery;
1181
- let structuralSettledValue: CodeGraphReadProviderResponse | undefined = cachedStructural || (!structuralQuery ? noStructuralResponse : undefined);
1182
- const queryCodeGraphAsync = dependencies.queryCodeGraphAsync ?? queryCodeGraphReadProviderAsync;
1203
+ let structuralSettledValue: StructuralReadProviderResponse | undefined = cachedStructural || (!structuralQuery ? noStructuralResponse : undefined);
1204
+ const queryStructuralAsync = dependencies.queryStructuralAsync ?? unavailableStructuralProviderAsync;
1183
1205
  const structuralInflightKey = structuralQuery && structuralSourceIdentity
1184
1206
  ? JSON.stringify({ structuralCacheKey, sourceIdentity: structuralSourceIdentity, structuralMode })
1185
1207
  : undefined;
@@ -1188,10 +1210,9 @@ export async function buildControllerContextPackAsync(
1188
1210
  const launchedStructuralPromise = cachedStructural
1189
1211
  ? Promise.resolve(cachedStructural)
1190
1212
  : structuralQuery
1191
- ? existingStructuralInflight ?? queryCodeGraphAsync(
1213
+ ? existingStructuralInflight ?? queryStructuralAsync(
1192
1214
  structuralIndexRoot,
1193
1215
  structuralRequest,
1194
- structuralMode === 'auto' ? { timeoutMs: AUTO_STRUCTURAL_PREFETCH_TIMEOUT_MS } : {},
1195
1216
  )
1196
1217
  : Promise.resolve(noStructuralResponse);
1197
1218
  if (structuralInflightKey && structuralQuery && !cachedStructural && !existingStructuralInflight) {
@@ -1242,16 +1263,16 @@ export async function buildControllerContextPackAsync(
1242
1263
  prefetched = outcome.value;
1243
1264
  } else {
1244
1265
  structuralPrefetchDeferred = true;
1245
- prefetched = codeGraphPrefetchDeferredResponse(performance.now() - prefetchStartedAt);
1266
+ prefetched = structuralPrefetchDeferredResponse(performance.now() - prefetchStartedAt);
1246
1267
  }
1247
1268
  }
1248
1269
  }
1249
1270
 
1250
- const fallbackQueryCodeGraph = dependencies.queryCodeGraph ?? queryCodeGraphReadProvider;
1271
+ const fallbackQueryStructural = dependencies.queryStructural ?? unavailableStructuralProvider;
1251
1272
  const pack = buildControllerContextPack(repoRoot, policy, options, {
1252
- queryCodeGraph: (queryRoot, request) => request.operation === 'context' && queryRoot === structuralIndexRoot
1273
+ queryStructural: (queryRoot, request) => request.operation === 'context' && queryRoot === structuralIndexRoot
1253
1274
  ? prefetched
1254
- : fallbackQueryCodeGraph(queryRoot, request),
1275
+ : fallbackQueryStructural(queryRoot, request),
1255
1276
  });
1256
1277
  pack.timingsMs.parallelFirstPass = true;
1257
1278
  pack.timingsMs.parallelPrefetch = Math.round((performance.now() - prefetchStartedAt) * 100) / 100;
@@ -169,7 +169,7 @@ export function buildControllerOperationalPlan(repoRoot: string, ledger: TaskLed
169
169
  mcpToolSchemaConvergence: {
170
170
  source: "mcp-tool-schema-convergence",
171
171
  readModels: ["controller_context", "rh_context", "work_status_digest", "prepare_transfer_artifacts"],
172
- commandModels: ["rh_work", "run_check", "repository_command_execute", "plugin_action_execute", "repository_safe_patch_apply"],
172
+ commandModels: ["rh_work", "run_check", "execute_command", "plugin_action_execute", "repository_safe_patch_apply"],
173
173
  compatibilityRules: [
174
174
  "compact reads are default",
175
175
  "raw code and raw logs require explicit opt-in tools",
@@ -1,7 +1,8 @@
1
+ import { writeAtomicTextFile } from '../../runtime/shared/atomic-text-file';
1
2
  import { spawnSync, type SpawnSyncReturns } from 'child_process';
2
- import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'fs';
3
+ import { existsSync, rmSync } from 'fs';
3
4
  import { homedir } from 'os';
4
- import { dirname, join } from 'path';
5
+ import { join } from 'path';
5
6
 
6
7
  export interface SystemdUserUnitInput {
7
8
  description?: string;
@@ -12,13 +13,6 @@ export interface SystemdUserUnitInput {
12
13
  restartSec?: number;
13
14
  }
14
15
 
15
- function atomicWrite(path: string, content: string, mode = 0o644): void {
16
- mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
17
- const temporary = `${path}.${process.pid}.tmp`;
18
- writeFileSync(temporary, content, { encoding: 'utf8', mode });
19
- renameSync(temporary, path);
20
- }
21
-
22
16
  export function systemdEscape(value: string): string {
23
17
  return `"${value.replaceAll('%', '%%').replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
24
18
  }
@@ -79,7 +73,7 @@ export function writeSystemdUserUnit(
79
73
  env: NodeJS.ProcessEnv = process.env,
80
74
  ): string {
81
75
  const unitPath = systemdUserUnitPath(unitName, env);
82
- atomicWrite(unitPath, renderSystemdUserUnit(input), 0o644);
76
+ writeAtomicTextFile(unitPath, renderSystemdUserUnit(input), 0o644);
83
77
  return unitPath;
84
78
  }
85
79
 
@@ -9,8 +9,8 @@ import {
9
9
  type ControllerCheckResult,
10
10
  } from '../controller/check-runner';
11
11
  import { tryAppendControllerWorklogEvent } from '../controller/worklog';
12
- import { globMatches, resolveMcpPath } from '../mcp/paths';
13
- import type { McpPolicy } from '../mcp/types';
12
+ import { globMatches, resolveMcpPath } from '../../../adapters/mcp/paths';
13
+ import type { McpPolicy } from '../../../adapters/mcp/types';
14
14
  import type { ProcessCheckCompletionReceipt, ProcessCheckReceiptStatus } from '../../runtime/execution/process-runtime/check-receipt';
15
15
  import { invalidateRepositoryReadCaches } from '../repository/inspector';
16
16
  import type { EditOperation } from './edit-operation-contract';
@@ -312,6 +312,10 @@ function bindSessionControllerInstance(repoRoot: string, session: EditSession, b
312
312
  assertEditSessionDurableBinding(session, binding);
313
313
  const nextInstanceId = binding?.controllerInstanceId?.trim();
314
314
  if (!nextInstanceId || session.controllerInstanceId === nextInstanceId) return session;
315
+ // A non-Work Safe Patch session has no Controller instance owner to rebind.
316
+ // Its persisted repository identity is still checked above; never promote
317
+ // a caller-supplied transient Runtime identity into durable session authority.
318
+ if (!session.workId && !session.controllerInstanceId) return session;
315
319
  const durableIdentity = [
316
320
  [session.workId, binding?.workId],
317
321
  [session.repoId, binding?.repoId],
@@ -1144,7 +1148,7 @@ function sessionSummary(session: EditSession): EditSessionSummary {
1144
1148
  };
1145
1149
  }
1146
1150
 
1147
- function findOccurrence(value: string, anchor: string, occurrence: number): number {
1151
+ export function findOccurrence(value: string, anchor: string, occurrence: number): number {
1148
1152
  let from = 0;
1149
1153
  for (let current = 1; current <= occurrence; current += 1) {
1150
1154
  const index = value.indexOf(anchor, from);
@@ -1913,9 +1917,17 @@ export function finalizeEditSession(repoRoot: string, sessionId: string, input:
1913
1917
  } = {}): EditSession {
1914
1918
  let session = getEditSession(repoRoot, sessionId);
1915
1919
  session = bindSessionControllerInstance(repoRoot, session, input.binding);
1916
- if (session.baseRevision && gitRevision(repoRoot) !== session.baseRevision) throw new Error('EDIT_SESSION_BASE_REVISION_CHANGED');
1917
- if (session.workspaceFingerprint && workspaceFingerprint(repoRoot) !== session.workspaceFingerprint) throw new Error('EDIT_SESSION_WORKSPACE_FINGERPRINT_CHANGED');
1920
+ const baseRevisionChanged = Boolean(session.baseRevision && gitRevision(repoRoot) !== session.baseRevision);
1921
+ const workspaceFingerprintChanged = Boolean(session.workspaceFingerprint && workspaceFingerprint(repoRoot) !== session.workspaceFingerprint);
1918
1922
  if (session.assurance?.approvalRequired && input.approvalConfirmed !== true) throw new Error('EDIT_SESSION_APPROVAL_REQUIRED');
1923
+ if (baseRevisionChanged || workspaceFingerprintChanged) {
1924
+ // A commit changes HEAD and Git's workspace fingerprint even when the exact edit-session after-images are now integrated.
1925
+ // Reconcile through the canonical after-image + clean-tracked-path checks before rejecting the stale snapshot.
1926
+ const reconciled = reconcileEditSession(repoRoot, session.sessionId, input);
1927
+ if (reconciled.status === 'finalized' || reconciled.status === 'superseded') return reconciled;
1928
+ if (baseRevisionChanged) throw new Error('EDIT_SESSION_BASE_REVISION_CHANGED');
1929
+ throw new Error('EDIT_SESSION_WORKSPACE_FINGERPRINT_CHANGED');
1930
+ }
1919
1931
  const emptyOpenSession = session.status === 'open' && session.operations.length === 0;
1920
1932
  if (!emptyOpenSession && !['dirty', 'checked', 'check_failed'].includes(session.status)) {
1921
1933
  throw new Error(`edit session cannot be finalized from ${session.status}`);
@@ -121,7 +121,7 @@ export function buildGitHubWorkflowDispatchArgs(repository: string, input: GitHu
121
121
  return args;
122
122
  }
123
123
 
124
- function gh(repoRoot: string, args: string[], input?: string, maxOutputBytes = 512 * 1024) {
124
+ export function gh(repoRoot: string, args: string[], input?: string, maxOutputBytes = 512 * 1024) {
125
125
  return runProcess('gh', args, {
126
126
  cwd: repoRoot,
127
127
  timeoutMs: 120_000,
@@ -284,7 +284,7 @@ function parseIssueUrl(output: string): { url: string; number: number } {
284
284
  return { url, number };
285
285
  }
286
286
 
287
- function addProjectItem(repoRoot: string, link: GitHubIssueLink, projectOwner?: string, projectNumber?: number): GitHubIssueLink {
287
+ export function addProjectItem(repoRoot: string, link: GitHubIssueLink, projectOwner?: string, projectNumber?: number): GitHubIssueLink {
288
288
  if (!projectOwner || !projectNumber) return link;
289
289
  const result = gh(repoRoot, ['project', 'item-add', String(projectNumber), '--owner', projectOwner, '--url', link.url, '--format', 'json']);
290
290
  if (!result.ok) throw new Error(`failed to add ${link.url} to GitHub Project: ${result.error || result.stderr}`);
@@ -302,7 +302,7 @@ function withoutPairedOption(args: string[], option: string): string[] {
302
302
  return args.filter((entry, index, all) => entry !== option && all[index - 1] !== option);
303
303
  }
304
304
 
305
- function createGitHubIssue(repoRoot: string, repo: GitHubRepositoryInfo, title: string, bodyPath: string, labels: string[], parentNumber?: number): GitHubIssueLink {
305
+ export function createGitHubIssue(repoRoot: string, repo: GitHubRepositoryInfo, title: string, bodyPath: string, labels: string[], parentNumber?: number): GitHubIssueLink {
306
306
  const args = ['issue', 'create', '--repo', repo.nameWithOwner, '--title', title, '--body-file', bodyPath];
307
307
  for (const label of labels) args.push('--label', label);
308
308
  if (parentNumber) args.push('--parent', String(parentNumber));
@@ -323,7 +323,7 @@ function createGitHubIssue(repoRoot: string, repo: GitHubRepositoryInfo, title:
323
323
  return { owner: repo.owner, repo: repo.repo, number: parsed.number, url: parsed.url, syncedAt: new Date().toISOString() };
324
324
  }
325
325
 
326
- function updateGitHubIssue(repoRoot: string, repo: GitHubRepositoryInfo, link: GitHubIssueLink, title: string, bodyPath: string): GitHubIssueLink {
326
+ export function updateGitHubIssue(repoRoot: string, repo: GitHubRepositoryInfo, link: GitHubIssueLink, title: string, bodyPath: string): GitHubIssueLink {
327
327
  const result = gh(repoRoot, ['issue', 'edit', String(link.number), '--repo', repo.nameWithOwner, '--title', title, '--body-file', bodyPath]);
328
328
  if (!result.ok) throw new Error(`failed to update GitHub issue #${link.number}: ${result.error || result.stderr}`);
329
329
  return { ...link, owner: repo.owner, repo: repo.repo, syncedAt: new Date().toISOString() };