@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
@@ -0,0 +1,174 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from 'fs';
2
+ import { tmpdir } from 'os';
3
+ import { join } from 'path';
4
+ import { listRepositories } from '../repositories/registry';
5
+ import { resolveRepoPreferredControllerHome } from '../repositories/controller-home';
6
+ import { getWorkContract, listWorkContracts, type WorkContract } from '../../../packages/kernel/work/api/index';
7
+ import { getPlanContract, planSemanticView } from '../../runtime/control-plane/facade/plan-contract-store';
8
+ import { readRequirement } from '../../runtime/control-plane/persistence/requirement-store';
9
+ import type { GitHubIssueLink } from '../controller/types';
10
+ import { gh, createGitHubIssue, updateGitHubIssue, addProjectItem, resolveGitHubRepository } from './github';
11
+
12
+ interface RemoteIssue { number: number; url: string; title: string; body: string }
13
+ export interface SemanticReleaseProjectionInput {
14
+ releaseWorkId: string;
15
+ repository?: string;
16
+ projectOwner?: string;
17
+ projectNumber?: number;
18
+ }
19
+ export interface SemanticReleaseProjectionResult {
20
+ releaseIssue: string;
21
+ children: Array<{ workId: string; issue: string }>;
22
+ created: number;
23
+ updated: number;
24
+ unchanged: number;
25
+ }
26
+
27
+ function safeIdentity(value: string): string {
28
+ if (!/^[A-Za-z0-9_.:-]{1,200}$/.test(value)) throw new Error('GITHUB_SEMANTIC_ID_INVALID');
29
+ return value;
30
+ }
31
+
32
+ function marker(workId: string): string {
33
+ return '<!-- forge-semantic-work:' + safeIdentity(workId) + ' -->';
34
+ }
35
+
36
+ function lines(values: readonly string[] | undefined, prefix = '- '): string {
37
+ return values?.length ? values.map((line) => prefix + line).join('\n') : '- 尚无记录';
38
+ }
39
+
40
+ function workDisplayTitle(work: WorkContract, home: string): string {
41
+ const requirement = work.requirementId
42
+ ? readRequirement({ controllerHome: home }, work.requirementId)?.value
43
+ : undefined;
44
+ return (requirement?.title || work.objective).replace(/\s+/g, ' ').slice(0, 96);
45
+ }
46
+
47
+ function semanticWorkText(work: WorkContract, home: string): string {
48
+ const requirement = work.requirementId
49
+ ? readRequirement({ controllerHome: home }, work.requirementId)?.value
50
+ : undefined;
51
+ const plan = work.planId
52
+ ? getPlanContract({ controllerHome: home, repoId: work.repoId }, work.planId)
53
+ : undefined;
54
+ const planView = plan ? planSemanticView(plan) : undefined;
55
+ const text = [
56
+ marker(work.workId),
57
+ '> Forge 生成视图(单向投影)。Requirement / Plan / Work 的正式状态只由 Forge 持有。GitHub 上的勾选或评论不改变执行授权。',
58
+ '',
59
+ '## 工作目标', work.objective,
60
+ '',
61
+ '## Forge 当前事实',
62
+ '- Work: `' + work.workId + '`',
63
+ '- 语义状态: `' + work.semanticState + '`',
64
+ '- Requirement: `' + (work.requirementId || '未绑定') + '`',
65
+ '- Plan: `' + (work.planId || '未绑定') + '`',
66
+ '- 父 Work: `' + (work.semanticParentWorkId || '无') + '`',
67
+ '- 最近语义更新: ' + (work.semanticUpdatedAt || work.updatedAt),
68
+ '',
69
+ '## 已选择的交付结果引用',
70
+ lines(work.semanticResultRefs),
71
+ ];
72
+ if (requirement) {
73
+ text.push('', '## 原始需求:' + requirement.title, requirement.outcomeStatement, '',
74
+ '### 验收标准', lines(requirement.acceptanceCriteria));
75
+ if (requirement.needsAttention && requirement.attentionSummary) {
76
+ text.push('', '### Forge 记录的需关注事项', requirement.attentionSummary);
77
+ }
78
+ }
79
+ if (planView) {
80
+ text.push('', '## 当前方案', planView.goal, '',
81
+ '### 方案决定', lines(planView.resolvedDecisions), '',
82
+ '### 方案项(描述性工作记忆,不是自动审批状态)',
83
+ lines(planView.items.map((item) => item.id + ' — ' + item.objective)));
84
+ }
85
+ text.push('', '> 未明确记录的用户产品审批、测试、发布均不能推断为通过。');
86
+ return text.join('\n') + '\n';
87
+ }
88
+
89
+ export function syncGitHubSemanticRelease(
90
+ repoRoot: string,
91
+ input: SemanticReleaseProjectionInput,
92
+ ): SemanticReleaseProjectionResult {
93
+ const home = resolveRepoPreferredControllerHome(repoRoot);
94
+ const repositoryRecord = listRepositories(home).find((record) =>
95
+ record.canonicalRoot === repoRoot || record.localRoot === repoRoot ||
96
+ record.checkouts.some((checkout) => checkout.canonicalRoot === repoRoot || checkout.localRoot === repoRoot));
97
+ if (!repositoryRecord) throw new Error('GITHUB_SEMANTIC_REPOSITORY_NOT_REGISTERED');
98
+ const repoId = repositoryRecord.repoId;
99
+ const store = { controllerHome: home, repoId };
100
+ const releaseWorkId = safeIdentity(input.releaseWorkId);
101
+ const parent = getWorkContract(store, releaseWorkId);
102
+ if (!parent || parent.lifecycleRole === 'execution_child') throw new Error('GITHUB_SEMANTIC_RELEASE_WORK_NOT_FOUND');
103
+ if (parent.semanticParentWorkId) throw new Error('GITHUB_SEMANTIC_RELEASE_MUST_BE_ROOT');
104
+ const all: WorkContract[] = [];
105
+ for (let offset = 0; offset < 10_000; offset += 100) {
106
+ const page = listWorkContracts({ ...store, state: 'all', limit: 100, offset });
107
+ all.push(...page);
108
+ if (page.length < 100) break;
109
+ if (offset + 100 >= 10_000) throw new Error('GITHUB_SEMANTIC_WORK_INVENTORY_TOO_LARGE');
110
+ }
111
+ const children = all.filter((work) => work.semanticParentWorkId === releaseWorkId &&
112
+ work.lifecycleRole !== 'execution_child').sort((a, b) => a.workId.localeCompare(b.workId));
113
+
114
+ // The GitHub Issue is a projection; never import remote status back into Work.
115
+ if (Boolean(input.projectOwner) !== Boolean(input.projectNumber)) throw new Error('GITHUB_SEMANTIC_PROJECT_CONFIG_INCOMPLETE');
116
+ const repo = resolveGitHubRepository(repoRoot, input.repository);
117
+ const inventory = gh(repoRoot,
118
+ ['issue', 'list', '--repo', repo.nameWithOwner, '--state', 'all', '--limit', '1000',
119
+ '--json', 'number,url,title,body'], undefined, 4 * 1024 * 1024);
120
+ if (!inventory.ok) throw new Error('GITHUB_SEMANTIC_ISSUE_INVENTORY_FAILED: ' + (inventory.error || inventory.stderr));
121
+ const remote = JSON.parse(inventory.stdout) as RemoteIssue[];
122
+ if (!Array.isArray(remote) || remote.length === 1000) {
123
+ throw new Error('GITHUB_SEMANTIC_ISSUE_INVENTORY_UNBOUNDED');
124
+ }
125
+ let created = 0;
126
+ let updated = 0;
127
+ let unchanged = 0;
128
+ const tmp = mkdtempSync(join(tmpdir(), 'forge-semantic-github-'));
129
+ function ensureIssue(workId: string, title: string, body: string): string {
130
+ const matches = remote.filter((issue) => issue.body?.includes(marker(workId)));
131
+ if (matches.length > 1) throw new Error('GITHUB_SEMANTIC_DUPLICATE_REMOTE_IDENTITY: ' + workId);
132
+ const file = join(tmp, 'issue.md');
133
+ writeFileSync(file, body, 'utf8');
134
+ let link: GitHubIssueLink;
135
+ if (matches.length === 1) {
136
+ const current = matches[0]!;
137
+ link = { owner: repo.owner, repo: repo.repo, number: current.number, url: current.url, syncedAt: '' };
138
+ if (current.title === title && current.body === body) unchanged++;
139
+ else { link = updateGitHubIssue(repoRoot, repo, link, title, file); updated++; }
140
+ } else {
141
+ link = createGitHubIssue(repoRoot, repo, title, file, []);
142
+ remote.push({ number: link.number, title, body, url: link.url });
143
+ created++;
144
+ }
145
+ if (input.projectOwner && input.projectNumber) {
146
+ addProjectItem(repoRoot, link, input.projectOwner, input.projectNumber);
147
+ }
148
+ return link.url;
149
+ }
150
+ try {
151
+ const linked = children.map((work) => ({
152
+ workId: work.workId,
153
+ title: workDisplayTitle(work, home),
154
+ issue: ensureIssue(work.workId, workDisplayTitle(work, home),
155
+ semanticWorkText(work, home)),
156
+ }));
157
+ const counts = {
158
+ open: children.filter((work) => work.semanticState === 'open').length,
159
+ completed: children.filter((work) => work.semanticState === 'completed').length,
160
+ cancelled: children.filter((work) => work.semanticState === 'cancelled').length,
161
+ };
162
+ const releaseBody = semanticWorkText(parent, home) + '\n## 版本关联工作\n' +
163
+ (linked.length
164
+ ? linked.map((child) => '- [' + child.title + '](' + child.issue + ') — \`' + child.workId + '\`').join('\n')
165
+ : '- 暂无已关联子 Work') +
166
+ '\n\n## 版本进度(真实 Work 事实,不是提交/发布证明)\n' +
167
+ '- 关联:' + linked.length + '\n- Open:' + counts.open +
168
+ '\n- Completed:' + counts.completed + '\n- Cancelled:' + counts.cancelled + '\n';
169
+ const releaseIssue = ensureIssue(releaseWorkId, workDisplayTitle(parent, home), releaseBody);
170
+ return { releaseIssue, children: linked, created, updated, unchanged };
171
+ } finally {
172
+ rmSync(tmp, { recursive: true, force: true });
173
+ }
174
+ }
@@ -465,15 +465,6 @@ export function isAgenticPackagingIntent(ctx: PromptIntentContext): boolean {
465
465
  return AGENTIC_PACKAGING.test(ctx.text);
466
466
  }
467
467
 
468
- const CODEGRAPH_ROUTE = re(
469
- String.raw`(who calls|what calls|callers|callees|impact|impact radius|trace\s+(flow|path|call)|where\s.*(defined|definition)|definition of|symbol named|调用关系|谁调用|调用了谁|哪里定义|定义在哪|影响面|调用链|追踪(路径|调用|链)|从.*到.*怎么走)`,
470
- );
471
-
472
- export function isCodegraphRouteIntent(ctx: PromptIntentContext): boolean {
473
- if (isTriggerQuestionPrompt(ctx)) return false;
474
- return CODEGRAPH_ROUTE.test(ctx.text);
475
- }
476
-
477
468
  const GIT_STATUS_LINE = re(
478
469
  String.raw`^${SP}*(git\s+(status|log|show|diff|push|pull|commit)|status|commit|push|merge|提交|推送|合并|看状态|看看状态)(${SP}|$)`,
479
470
  );
package/src/cli/index.ts CHANGED
@@ -15,7 +15,6 @@ import { CLI_VERSION, formatStatus, runStatus } from './commands/status';
15
15
  import { formatDoctor, runDoctor } from './commands/doctor';
16
16
  import { buildSetupCommand, formatInitHook, runInitHook } from './commands/init-hook';
17
17
  import { formatMigratePlan, runMigrate } from './commands/migrate';
18
- import { buildToolsCommand } from './commands/tools';
19
18
  import { buildPluginCommand } from './commands/plugin';
20
19
  import { buildComputerCommand } from './commands/computer';
21
20
  import { buildBrainCommand } from './commands/brain';
@@ -73,10 +72,7 @@ const VALID_LOCATIONS: readonly Location[] = ['global', 'local'];
73
72
  interface GlobalRuntimeCommandOptions {
74
73
  target: string;
75
74
  cli?: boolean;
76
- syncSkill?: boolean;
77
75
  hooks?: string | false;
78
- withExternalSkills?: boolean;
79
- codegraph?: boolean;
80
76
  brainRoot?: string;
81
77
  json?: boolean;
82
78
  }
@@ -91,10 +87,7 @@ function runGlobalRuntimeBootstrap(commandName: 'init' | 'install', rawOpts: Glo
91
87
  const result = runGlobalRuntimeSetup({
92
88
  target: rawOpts.target as InstallTargetSpec,
93
89
  installCli: rawOpts.cli !== false,
94
- syncSkill: rawOpts.syncSkill !== false,
95
90
  hostAdapters: rawOpts.hooks !== false,
96
- externalSkills: rawOpts.withExternalSkills === true,
97
- codegraph: rawOpts.codegraph !== false,
98
91
  brainRoot: rawOpts.brainRoot,
99
92
  });
100
93
  if (rawOpts.json === true) {
@@ -117,19 +110,14 @@ export function buildProgram(): Command {
117
110
  program
118
111
  .command('update')
119
112
  .description('Update the global Forge CLI and user-level managed runtime')
120
- .option('--target <target>', `Host target for adapters and runtime skills: ${VALID_TARGETS.join('|')}`, 'both')
113
+ .option('--target <target>', `Host target for adapters: ${VALID_TARGETS.join('|')}`, 'both')
121
114
  .option('--version <version>', 'Install a specific Forge package version')
122
115
  .option('--channel <channel>', 'Install package channel: latest|next')
123
116
  .option('--check', 'Run the read-only setup check without refreshing runtime')
124
117
  .option('--check-updates', 'Include network-backed version update advisories in setup check output')
125
118
  .option('--no-runtime-refresh', 'Skip runtime refresh and run the read-only setup check only')
126
119
  .option('--no-cli', 'Skip installing the Forge CLI globally')
127
- .option('--no-sync-skill', 'Skip refreshing Forge host skill aliases')
128
120
  .option('--no-hooks', 'Skip global hook adapter installation')
129
- .option('--with-external-skills', 'Also bootstrap third-party Waza, Mermaid, and cross-review skills')
130
- .option('--no-external-skills', 'Compatibility no-op; update no longer bootstraps third-party skills by default')
131
- .option('--configure-codegraph', 'Also configure CodeGraph CLI/MCP during runtime refresh')
132
- .option('--no-codegraph', 'Compatibility no-op; update no longer configures CodeGraph by default')
133
121
  .option('--brain-root <path>', 'Brain vault root for manifest sync')
134
122
  .option('--repo <path>', 'Deprecated: use forge adopt --repo <path>')
135
123
  .option('--dry-run', 'Deprecated: use forge adopt --dry-run for repo-level planning')
@@ -145,11 +133,7 @@ export function buildProgram(): Command {
145
133
  checkUpdates?: boolean;
146
134
  runtimeRefresh?: boolean;
147
135
  cli?: boolean;
148
- syncSkill?: boolean;
149
136
  hooks?: string | false;
150
- withExternalSkills?: boolean;
151
- codegraph?: boolean;
152
- configureCodegraph?: boolean;
153
137
  brainRoot?: string;
154
138
  interactive?: boolean;
155
139
  json?: boolean;
@@ -187,10 +171,7 @@ export function buildProgram(): Command {
187
171
  target: rawOpts.target as InstallTargetSpec,
188
172
  installCli: rawOpts.cli !== false,
189
173
  installSpec,
190
- syncSkill: rawOpts.syncSkill !== false,
191
174
  hostAdapters: rawOpts.hooks !== false,
192
- externalSkills: rawOpts.withExternalSkills === true,
193
- codegraph: rawOpts.configureCodegraph === true,
194
175
  brainRoot: rawOpts.brainRoot,
195
176
  });
196
177
  if (rawOpts.json === true) {
@@ -210,16 +191,11 @@ export function buildProgram(): Command {
210
191
  .option('--transaction <path>', 'Adoption transaction manifest to restore when action is rollback')
211
192
  .option('--dry-run', 'Plan Forge changes without applying them')
212
193
  .option('--target <target>', `Host target for readiness checks and optional global bootstrap: ${VALID_TARGETS.join('|')}`, 'both')
213
- .option('--no-sync-skill', 'Compatibility no-op; adopt never refreshes user-level skill aliases')
214
194
  .option('--no-host-adapters', 'Compatibility no-op; adopt never writes global Codex/Claude hook adapters')
215
- .option('--no-external-skills', 'Compatibility no-op; adopt never bootstraps user-level external skills')
216
195
  .option('--no-verify', 'Skip repo workflow verification after apply')
217
- .option('--no-codegraph', 'Skip building the CodeGraph index and MCP readiness check')
218
196
  .option('--reclaim-runtime', 'Reclaim generated repo-local hook/helper runtime copies after replacement paths verify')
219
197
  .option('--compact', 'Compact repo surface; includes --reclaim-runtime plus package script rewrite')
220
198
  .option('--mode <mode>', 'Adoption mode: minimal|standard|self-host', 'standard')
221
- .option('--configure-codegraph', 'Deprecated: user-level MCP config belongs to Forge update/setup')
222
- .option('--sync-codegraph', 'Sync the CodeGraph index after ensure')
223
199
  .option('--brain-root <path>', 'Deprecated: user-level brain config belongs to Forge update/setup')
224
200
  .option('--brain-mode <mode>', 'Deprecated: adopt does not perform user-level brain sync', 'skip')
225
201
  .option('--interactive', 'Run the numbered interactive install planner')
@@ -231,16 +207,11 @@ export function buildProgram(): Command {
231
207
  transaction?: string;
232
208
  dryRun?: boolean;
233
209
  target: string;
234
- syncSkill?: boolean;
235
210
  hostAdapters?: boolean;
236
- withExternalSkills?: boolean;
237
211
  verify?: boolean;
238
- codegraph?: boolean;
239
212
  reclaimRuntime?: boolean;
240
213
  compact?: boolean;
241
214
  mode?: string;
242
- configureCodegraph?: boolean;
243
- syncCodegraph?: boolean;
244
215
  brainRoot?: string;
245
216
  brainMode?: string;
246
217
  interactive?: boolean;
@@ -294,10 +265,6 @@ export function buildProgram(): Command {
294
265
  console.error('forge adopt: invalid --mode (expected: minimal, standard, self-host)');
295
266
  process.exit(2);
296
267
  }
297
- if (rawOpts.configureCodegraph === true) {
298
- console.error('forge adopt: --configure-codegraph writes user-level MCP config; run forge update instead');
299
- process.exit(2);
300
- }
301
268
  if (rawOpts.brainRoot || rawOpts.brainMode !== 'skip') {
302
269
  console.error('forge adopt: brain configuration writes user-level state; run forge update instead');
303
270
  process.exit(2);
@@ -347,13 +314,8 @@ export function buildProgram(): Command {
347
314
  repo: rawOpts.repo,
348
315
  apply: rawOpts.dryRun !== true,
349
316
  target: rawOpts.target as InstallTargetSpec,
350
- syncSkill: false,
351
317
  hostAdapters: false,
352
- externalSkills: false,
353
318
  verify: rawOpts.verify !== false,
354
- codegraph: rawOpts.codegraph !== false,
355
- configureCodegraphMcp: false,
356
- syncCodegraph: rawOpts.syncCodegraph === true,
357
319
  brainRoot: rawOpts.brainRoot,
358
320
  brainMode: rawOpts.brainMode as InitBrainMode,
359
321
  };
@@ -392,10 +354,7 @@ export function buildProgram(): Command {
392
354
  .option('--target <target>', `Target host: ${VALID_TARGETS.join('|')}`, 'both')
393
355
  .option('--location <location>', `Adapter-only install location: ${VALID_LOCATIONS.join('|')}`)
394
356
  .option('--no-cli', 'Skip installing the Forge CLI globally')
395
- .option('--no-sync-skill', 'Skip refreshing Forge host skill aliases')
396
357
  .option('--no-hooks', 'Skip global hook adapter installation during full runtime install')
397
- .option('--with-external-skills', 'Also install optional third-party Waza/Mermaid skills and Forge cross-review helpers')
398
- .option('--no-codegraph', 'Skip CodeGraph CLI/MCP configuration')
399
358
  .option('--brain-root <path>', 'Brain vault root to persist for Forge brain commands')
400
359
  .option('--json', 'Output JSON instead of human-readable text')
401
360
  .action((rawOpts: GlobalRuntimeCommandOptions & { location?: string }) => {
@@ -509,7 +468,6 @@ export function buildProgram(): Command {
509
468
  process.exit(rawOpts.strict === true && strictFailure ? 1 : 0);
510
469
  });
511
470
 
512
- program.addCommand(buildToolsCommand());
513
471
  program.addCommand(buildPluginCommand());
514
472
  program.addCommand(buildComputerCommand());
515
473
  program.addCommand(buildBrainCommand(), { hidden: true });
@@ -19,7 +19,7 @@ import {
19
19
  loadMcpLocalConfig,
20
20
  loadMcpServiceLocalConfig,
21
21
  } from '../../../adapters/mcp/auth';
22
- import { PREFERRED_FACADE_TOOL_NAMES, DEFAULT_CONTROLLER_TOOL_NAMES } from '../../../adapters/mcp/toolset-names';
22
+ import { PREFERRED_FACADE_TOOL_NAMES } from '../../../adapters/mcp/toolset-names';
23
23
  import { resolveRepoPreferredControllerHome } from '../repositories/controller-home';
24
24
  import type { PlainStatusTone } from './console-view-models';
25
25
 
@@ -37,8 +37,6 @@ export const OPTIONAL_INTERACTIVE_DEVELOPMENT_TOOLS = [
37
37
  'process_wait',
38
38
  ] as const;
39
39
 
40
- /** @deprecated Prefer ADVANCED_CONTROLLER_TOOL_NAMES; kept for import stability. */
41
- export const CORE_SURFACE_INTERACTIVE_TOOLS = OPTIONAL_INTERACTIVE_DEVELOPMENT_TOOLS;
42
40
 
43
41
  export type ConnectorFreshnessStatus =
44
42
  | 'local_mcp_updated'
@@ -479,15 +477,6 @@ function finalize(
479
477
  return report;
480
478
  }
481
479
 
482
- /**
483
- * Build local tool names from the in-process controller registry (not ChatGPT).
484
- * Uses expectedTools when provided; otherwise falls back to the stable default exposure.
485
- */
486
- export function localControllerToolNames(expectedTools?: readonly string[]): string[] {
487
- if (expectedTools && expectedTools.length > 0) return uniqueSorted(expectedTools);
488
- return uniqueSorted([...DEFAULT_CONTROLLER_TOOL_NAMES]);
489
- }
490
-
491
480
  export function buildLocalConnectorStatus(input: {
492
481
  expectedTools: readonly string[];
493
482
  connectorToolNames?: readonly string[] | null;
@@ -138,7 +138,7 @@ function occurrenceHistory(occurrences: ScheduleOccurrence[]): ConsoleAutomation
138
138
  }
139
139
 
140
140
  function scheduleMode(schedule: RepositorySchedule): ConsoleAutomationMode {
141
- if (schedule.action.operation === 'external_controller_wake') return 'continuation';
141
+ if (schedule.action.operation === 'external_controller_wake' || schedule.action.operation === 'controller_progression') return 'continuation';
142
142
  if (schedule.action.operation === 'browser_probe') return schedule.action.arguments?.keepalive_only === true ? 'browser_keepalive' : 'browser_watch';
143
143
  return 'schedule';
144
144
  }
@@ -243,7 +243,7 @@ export interface AccessStateViewModel {
243
243
  configuredAccessModeLabel: string;
244
244
  effectiveAccessMode: 'request' | 'full_access';
245
245
  effectiveAccessModeLabel: string;
246
- effectiveToolset: 'facade' | 'core' | 'advanced' | 'full';
246
+ effectiveToolset: 'full';
247
247
  exposureRevision: number;
248
248
  lastAppliedAt?: string;
249
249
  source: string;
@@ -598,8 +598,6 @@ function controllerAccessStateView(ctx: ConsoleFacadeContext): AccessStateViewMo
598
598
  const effective = resolveControllerAccessState({
599
599
  controllerHome: ctx.controllerHome,
600
600
  repoRoot: ctx.repository.canonicalRoot,
601
- toolsetOverride: toolCtx.toolset,
602
- toolsetLocked: toolCtx.toolsetLocked,
603
601
  });
604
602
  const repositoryPolicy = readRepositoryAccessPolicy(ctx.controllerHome, ctx.repository.repoId);
605
603
  const runtimeGeneration = readRuntimeGeneration(ctx.controllerHome);
@@ -255,11 +255,6 @@ function readOnlyGitSegment(words: string[], _segment: string): boolean {
255
255
  return isReadOnlyGitCommand(words);
256
256
  }
257
257
 
258
- function isReadOnlyCodegraphCommand(words: string[]): boolean {
259
- const program = words[0]?.split(/[\\/]/).at(-1)?.toLowerCase();
260
- return program === 'codegraph' && words[1]?.toLowerCase() === 'status';
261
- }
262
-
263
258
  function githubCliApiMethod(words: string[]): string | undefined {
264
259
  for (let index = 2; index < words.length; index += 1) {
265
260
  const word = words[index]?.toLowerCase();
@@ -315,7 +310,6 @@ function isReadOnlySegment(segment: string): boolean {
315
310
  const program = words[0]?.toLowerCase();
316
311
  if (!program) return false;
317
312
  if (program === 'git') return readOnlyGitSegment(words, segment);
318
- if (isReadOnlyCodegraphCommand(words)) return true;
319
313
  if (program === 'curl') return isReadOnlyLoopbackCurl(words);
320
314
  if (program === 'sqlite3') return isSafeReadOnlySqliteSegment(segment);
321
315
  if (program === 'find') return !/(?:-delete|-exec|-execdir|-ok|-okdir)\b/.test(segment);
@@ -621,9 +615,6 @@ function classifyArgvCommand(
621
615
  if (program === 'git' && isReadOnlyGitCommand(argv)) {
622
616
  return { risk: 'readonly', confirmation: 'none', reasons: ['the argv command is a recognized repository-local read operation'] };
623
617
  }
624
- if (isReadOnlyCodegraphCommand(argv)) {
625
- return { risk: 'readonly', confirmation: 'none', reasons: ['CodeGraph status only observes the local semantic index'] };
626
- }
627
618
  if (program === 'curl' && isReadOnlyLoopbackCurl(argv)) {
628
619
  return { risk: 'readonly', confirmation: 'none', reasons: ['curl performs a read-only loopback HTTP observation without writing response data to disk'] };
629
620
  }
@@ -184,13 +184,13 @@ function assertNoUnmanagedControllerWorktreeLifecycle(command: CanonicalReposito
184
184
  };
185
185
  if (command.kind === 'argv') {
186
186
  if (isGitWorktreeAdd([command.executable!, ...(command.args ?? [])])) {
187
- throw new Error('MANAGED_WORKSPACE_REQUIRED: repository_command_execute must not create an unmanaged temporary git worktree; use rh_work so Forge persists checkout/worktree ownership and terminal cleanup authority');
187
+ throw new Error('MANAGED_WORKSPACE_REQUIRED: execute_command must not create an unmanaged temporary git worktree; use rh_work so Forge persists checkout/worktree ownership and terminal cleanup authority');
188
188
  }
189
189
  return;
190
190
  }
191
191
  for (const segment of shellSegments(command.shellCommand ?? '')) {
192
192
  if (isGitWorktreeAdd(shellWords(segment))) {
193
- throw new Error('MANAGED_WORKSPACE_REQUIRED: repository_command_execute must not create an unmanaged temporary git worktree; use rh_work so Forge persists checkout/worktree ownership and terminal cleanup authority');
193
+ throw new Error('MANAGED_WORKSPACE_REQUIRED: execute_command must not create an unmanaged temporary git worktree; use rh_work so Forge persists checkout/worktree ownership and terminal cleanup authority');
194
194
  }
195
195
  }
196
196
  }
@@ -150,6 +150,26 @@ export function readControllerLock(controllerHome: string, key: ControllerLockKe
150
150
  return record;
151
151
  }
152
152
 
153
+ export function renewControllerLock(
154
+ controllerHome: string,
155
+ key: ControllerLockKey,
156
+ lockId: string,
157
+ ttlMs: number,
158
+ ): ControllerLockRecord {
159
+ const current = readControllerLock(controllerHome, key);
160
+ if (!current) throw new Error(`LOCK_NOT_FOUND: ${lockId}`);
161
+ if (current.lockId !== lockId) throw new Error(`LOCK_OWNERSHIP_MISMATCH: ${current.lockId}`);
162
+ const normalizedTtlMs = Math.max(5_000, Math.trunc(ttlMs));
163
+ const heartbeatAt = new Date().toISOString();
164
+ const next: ControllerLockRecord = {
165
+ ...current,
166
+ heartbeatAt,
167
+ expiresAt: new Date(Date.now() + normalizedTtlMs).toISOString(),
168
+ };
169
+ writeFileSync(current.path, `${JSON.stringify(next, null, 2)}\n`, 'utf-8');
170
+ return next;
171
+ }
172
+
153
173
  function metric(key: ControllerLockKey, waitedMs: number, outcome: 'success' | 'contention' | 'timeout'): void {
154
174
  recordGatewayLatency({
155
175
  repoId: key.repoId,
@@ -93,7 +93,8 @@ function now(): string {
93
93
 
94
94
  function comparablePath(value: string): string {
95
95
  const resolved = resolve(value);
96
- return process.platform === 'darwin' && resolved.startsWith('/var/')
96
+ // macOS /tmp and /var are symlinks into /private; compare both aliases canonically.
97
+ return process.platform === 'darwin' && (/^\/(?:tmp|var)(?:\/|$)/.test(resolved))
97
98
  ? `/private${resolved}`
98
99
  : resolved;
99
100
  }
@@ -4,6 +4,7 @@ import { join, relative, resolve } from 'path';
4
4
  import {
5
5
  adoptEditSessionSuccessorHead,
6
6
  applyEditOperations,
7
+ findOccurrence,
7
8
  beginEditSession,
8
9
  getEditSession,
9
10
  type EditSession,
@@ -11,7 +12,7 @@ import {
11
12
  type EditSessionPatchError,
12
13
  } from '../editing/edit-session';
13
14
  import { normalizeEditOperation, type EditOperation } from '../editing/edit-operation-contract';
14
- import { getMcpPolicy } from '../mcp/policy';
15
+ import { getMcpPolicy } from '../../../adapters/mcp/policy';
15
16
  import type { RepositoryRecord } from './types';
16
17
  import { ensureRepositoryRuntimeStorageBinding } from './runtime-storage';
17
18
 
@@ -189,17 +190,6 @@ function snippetAround(content: string, needle: string): SafePatchFailureContext
189
190
  return { line: lineIndex + 1, before, focus, after };
190
191
  }
191
192
 
192
- function findOccurrence(value: string, anchor: string, occurrence: number): number {
193
- let from = 0;
194
- for (let current = 1; current <= occurrence; current += 1) {
195
- const index = value.indexOf(anchor, from);
196
- if (index < 0) return -1;
197
- if (current === occurrence) return index;
198
- from = index + anchor.length;
199
- }
200
- return -1;
201
- }
202
-
203
193
  function applyTextPreview(operation: Exclude<EditOperation, { type: 'create' | 'write' | 'delete' }>, before: string): string {
204
194
  if (operation.type === 'replace') {
205
195
  let after = before;
@@ -154,7 +154,7 @@ export function resolveRepositoryGitCommitScope(
154
154
  return { source: 'staged_index', paths: repositoryGitStagedPaths(repository) };
155
155
  }
156
156
 
157
- function splitStatus(porcelain: string): { staged: string[]; unstaged: string[]; untracked: string[] } {
157
+ export function splitRepositoryGitStatus(porcelain: string): { staged: string[]; unstaged: string[]; untracked: string[] } {
158
158
  const staged: string[] = [];
159
159
  const unstaged: string[] = [];
160
160
  const untracked: string[] = [];
@@ -174,7 +174,7 @@ function splitStatus(porcelain: string): { staged: string[]; unstaged: string[];
174
174
  export function repositoryGitStatus(repository: RepositoryRecord): RepositoryGitStatusSnapshot {
175
175
  const observedAt = new Date().toISOString();
176
176
  const porcelain = runGit(repository, ['status', '--porcelain=v1', '--branch', '--untracked-files=all', '--', '.', ':(exclude).ai/harness/**']).stdout;
177
- const split = splitStatus(porcelain);
177
+ const split = splitRepositoryGitStatus(porcelain);
178
178
  return {
179
179
  repoId: repository.repoId,
180
180
  checkoutId: repository.activeCheckoutId,
@@ -185,7 +185,9 @@ export function repositoryGitStatus(repository: RepositoryRecord): RepositoryGit
185
185
  head: gitText(repository, ['rev-parse', '--verify', 'HEAD']),
186
186
  upstream: gitText(repository, ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}']),
187
187
  porcelain,
188
- shortStatus: runGit(repository, ['status', '--short', '--branch', '--untracked-files=all', '--', '.', ':(exclude).ai/harness/**']).stdout,
188
+ // Git porcelain v1 with --branch uses the same short-status layout. Reuse
189
+ // the authoritative status snapshot instead of spawning Git a second time.
190
+ shortStatus: porcelain,
189
191
  ...split,
190
192
  clean: split.staged.length === 0 && split.unstaged.length === 0 && split.untracked.length === 0,
191
193
  };
@@ -214,6 +216,40 @@ export function repositoryGitDiff(repository: RepositoryRecord, input: { staged?
214
216
  };
215
217
  }
216
218
 
219
+ /**
220
+ * One Git-index/worktree candidate view for Work inspection and check selection.
221
+ * Untracked paths are explicit evidence; Git cannot provide their patch before add.
222
+ * Keep both index slices visible while bounding the aggregate patch size.
223
+ */
224
+ export function repositoryGitWorkspaceChanges(
225
+ repository: RepositoryRecord,
226
+ maxBytes = 64 * 1024,
227
+ ): {
228
+ status: RepositoryGitStatusSnapshot;
229
+ staged: RepositoryGitDiffResult;
230
+ unstaged: RepositoryGitDiffResult;
231
+ changedPaths: string[];
232
+ diff: { nameOnly: string[]; stat: string; patch: string; truncated: boolean };
233
+ } {
234
+ const status = repositoryGitStatus(repository);
235
+ const sliceBytes = Math.max(1024, Math.floor(boundedOutputBytes(maxBytes) / 2));
236
+ const staged = repositoryGitDiff(repository, { staged: true, maxBytes: sliceBytes });
237
+ const unstaged = repositoryGitDiff(repository, { maxBytes: sliceBytes });
238
+ const changedPaths = [...new Set([...staged.nameOnly, ...unstaged.nameOnly, ...status.untracked])].sort();
239
+ return {
240
+ status,
241
+ staged,
242
+ unstaged,
243
+ changedPaths,
244
+ diff: {
245
+ nameOnly: changedPaths,
246
+ stat: [staged.stat, unstaged.stat].filter(Boolean).join('\n'),
247
+ patch: [staged.patch, unstaged.patch].filter(Boolean).join('\n'),
248
+ truncated: staged.truncated || unstaged.truncated,
249
+ },
250
+ };
251
+ }
252
+
217
253
  export function repositoryGitCreateBranch(controllerHome: string, repository: RepositoryRecord, input: { branch: unknown; startPoint?: unknown; switchTo?: unknown }): { branch: string; execution: RepositoryGitExecution } {
218
254
  const branch = assertSafeBranchName(input.branch);
219
255
  const startPoint = typeof input.startPoint === 'string' && input.startPoint.trim() ? input.startPoint.trim() : undefined;