@moretea-labs/forge 1.5.0-rc.1 → 1.5.1

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 (264) hide show
  1. package/AGENTS.md +9 -11
  2. package/CHANGELOG.md +27 -1
  3. package/CLAUDE.md +8 -10
  4. package/DESIGN.md +278 -0
  5. package/README.md +70 -90
  6. package/README.zh-CN.md +52 -55
  7. package/SKILL.md +8 -11
  8. package/THIRD_PARTY_NOTICES.md +9 -3
  9. package/assets/branding/README.md +4 -2
  10. package/assets/hooks/lib/workflow-state.sh +19 -18
  11. package/assets/hooks/post-edit-guard.sh +6 -75
  12. package/assets/hooks/prompt-guard.sh +24 -12
  13. package/assets/hooks/session-start-context.sh +17 -17
  14. package/assets/hooks/stop-orchestrator.sh +2 -2
  15. package/assets/partials/04-project-structure.partial.md +6 -6
  16. package/assets/partials/05-workflow.partial.md +6 -6
  17. package/assets/partials/07-footer.partial.md +1 -1
  18. package/assets/partials/08-orchestration.partial.md +2 -2
  19. package/assets/partials-agents/02-operating-mode.partial.md +5 -5
  20. package/assets/partials-agents/03-orchestration.partial.md +3 -3
  21. package/assets/partials-agents/04-task-protocol.partial.md +3 -3
  22. package/assets/partials-agents/08-deep-docs.partial.md +1 -1
  23. package/assets/reference-configs/agentic-development-flow.md +49 -86
  24. package/assets/reference-configs/external-tooling.md +21 -61
  25. package/assets/reference-configs/handoff-protocol.md +25 -20
  26. package/assets/reference-configs/harness-overview.md +8 -8
  27. package/assets/skill-version.json +2 -2
  28. package/assets/templates/helpers/archive-workflow.sh +0 -1
  29. package/assets/templates/helpers/capture-plan.sh +3 -3
  30. package/assets/templates/helpers/check-task-workflow.sh +2 -2
  31. package/assets/templates/helpers/codex-handoff-resume.sh +5 -5
  32. package/assets/templates/helpers/ensure-task-workflow.sh +37 -35
  33. package/assets/templates/helpers/new-plan.sh +1 -1
  34. package/assets/templates/helpers/plan-to-todo.sh +2 -2
  35. package/assets/templates/helpers/prepare-codex-handoff.sh +2 -2
  36. package/assets/templates/helpers/prepare-handoff.sh +9 -9
  37. package/assets/templates/helpers/refresh-current-status.sh +2 -2
  38. package/assets/templates/helpers/ship-worktrees.sh +1 -1
  39. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  40. package/assets/templates/helpers/verify-sprint.sh +7 -7
  41. package/assets/templates/helpers/workstream-sync.sh +1 -1
  42. package/assets/templates/plan.template.md +1 -1
  43. package/assets/workflow-contract.v1.json +6 -3
  44. package/docs/architecture/current/README.md +10 -9
  45. package/docs/architecture/current/approved-target-architecture.zh-CN.md +2 -2
  46. package/docs/architecture/current/architecture-invariants.md +2 -2
  47. package/docs/architecture/current/automation-and-schedule-engine.md +15 -15
  48. package/docs/architecture/current/codegraph-context-provider.md +2 -2
  49. package/docs/architecture/current/control-plane-authority-inventory.md +12 -0
  50. package/docs/architecture/current/dispatch-and-agent-strategy.md +11 -11
  51. package/docs/architecture/current/entity-model.md +0 -27
  52. package/docs/architecture/current/failure-recovery.md +0 -24
  53. package/docs/architecture/current/governance.md +1 -1
  54. package/docs/architecture/current/implementation-status.md +1 -1
  55. package/docs/architecture/current/legacy-governance.md +58 -0
  56. package/docs/architecture/current/multi-repository-execution.md +1 -1
  57. package/docs/architecture/current/peer-supercontroller-runtime.md +1 -1
  58. package/docs/architecture/current/personal-assistant-plugin-baseline.md +4 -5
  59. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -10
  60. package/docs/architecture/current/target-requirements-traceability.md +2 -2
  61. package/docs/architecture/current/thin-harness-governance-policy.md +1 -1
  62. package/docs/architecture/current/thin-harness-v1.md +9 -11
  63. package/docs/architecture/domains/runtime-harness.md +1 -1
  64. package/docs/forge-autonomous-goal-loop.md +3 -1
  65. package/docs/forge-chatgpt-mcp-setup.md +67 -176
  66. package/docs/images/forge-banner-cn.svg +26 -1
  67. package/docs/images/forge-banner.svg +26 -1
  68. package/docs/operations/features.md +1 -1
  69. package/docs/operations/homebrew.md +3 -3
  70. package/docs/operations/mcp-tool-exposure.md +5 -5
  71. package/docs/operations/platform-support.md +45 -37
  72. package/docs/operations/platform-support.zh-CN.md +40 -38
  73. package/docs/operations/releasing.md +3 -3
  74. package/docs/operations/releasing.zh-CN.md +3 -3
  75. package/docs/operations/troubleshooting.md +11 -6
  76. package/docs/operations/troubleshooting.zh-CN.md +11 -6
  77. package/docs/public-usage-guide.md +28 -21
  78. package/docs/public-usage-guide.zh-CN.md +28 -21
  79. package/docs/tutorials/01-install-and-start.md +58 -35
  80. package/docs/tutorials/01-install-and-start.zh-CN.md +58 -35
  81. package/docs/tutorials/02-connect-chatgpt.md +118 -46
  82. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +119 -45
  83. package/docs/wiki/Architecture.md +1 -1
  84. package/docs/wiki/Installation.md +2 -2
  85. package/docs/wiki/Quick-Start.md +1 -1
  86. package/docs/wiki/Work-Lifecycle.md +1 -1
  87. package/install.ps1 +4 -5
  88. package/install.sh +4 -6
  89. package/package.json +15 -5
  90. package/scripts/archive-workflow.sh +0 -1
  91. package/scripts/benchmark-thin-harness-mcp-e2e.ts +27 -14
  92. package/scripts/build-controller-ui.ts +28 -0
  93. package/scripts/capture-plan.sh +4 -4
  94. package/scripts/check-agent-tooling.sh +12 -1
  95. package/scripts/check-platform-support.mjs +2 -2
  96. package/scripts/check-public-docs.sh +16 -5
  97. package/scripts/check-release-readiness.sh +5 -11
  98. package/scripts/check-runtime-architecture.mjs +14 -22
  99. package/scripts/check-tarball-install-smoke.sh +3 -2
  100. package/scripts/check-task-workflow.sh +2 -2
  101. package/scripts/codex-handoff-resume.sh +5 -5
  102. package/scripts/create-project-dirs.sh +3 -3
  103. package/scripts/ensure-task-workflow.sh +45 -35
  104. package/scripts/init-project.sh +4 -4
  105. package/scripts/install-desktop-operator-registration.ts +2 -2
  106. package/scripts/lib/project-init-lib.sh +46 -44
  107. package/scripts/migrate-project-template.sh +7 -4
  108. package/scripts/new-plan.sh +1 -1
  109. package/scripts/plan-to-todo.sh +2 -2
  110. package/scripts/prepare-codex-handoff.sh +2 -2
  111. package/scripts/prepare-handoff.sh +9 -9
  112. package/scripts/refresh-current-status.sh +2 -2
  113. package/scripts/setup-plugins.sh +3 -3
  114. package/scripts/ship-worktrees.sh +1 -1
  115. package/scripts/sprint-backlog.sh +5 -5
  116. package/scripts/sync-codex-installed-copies.sh +2 -3
  117. package/scripts/verify-sprint.sh +7 -7
  118. package/scripts/workstream-sync.sh +1 -1
  119. package/src/cli/chatgpt-browser/bridge-extension.ts +1 -1
  120. package/src/cli/chatgpt-browser/bridge-provider.ts +29 -0
  121. package/src/cli/chatgpt-browser/file-policy.ts +3 -1
  122. package/src/cli/commands/chatgpt.ts +51 -0
  123. package/src/cli/commands/doctor.ts +1 -1
  124. package/src/cli/commands/init-hook.ts +213 -70
  125. package/src/cli/commands/init.ts +5 -6
  126. package/src/cli/commands/mcp.ts +31 -5
  127. package/src/cli/commands/runtime.ts +61 -8
  128. package/src/cli/commands/setup-profile.ts +472 -0
  129. package/src/cli/controller/check-runner.ts +20 -3
  130. package/src/cli/controller/codex-continuation.ts +4 -7
  131. package/src/cli/controller/context-pack.ts +118 -14
  132. package/src/cli/controller/operational-plan.ts +4 -4
  133. package/src/cli/controller/task-ledger.ts +10 -10
  134. package/src/cli/controller/work-mode.ts +6 -13
  135. package/src/cli/index.ts +14 -35
  136. package/src/cli/local-bridge/connector-freshness.ts +1 -1
  137. package/src/cli/local-bridge/console-assets.ts +17 -0
  138. package/src/cli/local-bridge/console-automations.ts +160 -0
  139. package/src/cli/local-bridge/dashboard.ts +8 -1303
  140. package/src/cli/local-bridge/facade-api.ts +16 -7
  141. package/src/cli/local-bridge/frontend/src/App.tsx +10 -0
  142. package/src/cli/local-bridge/frontend/src/components/Button.tsx +1 -0
  143. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +3 -0
  144. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +1 -0
  145. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +1 -0
  146. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +1 -0
  147. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +3 -0
  148. package/src/cli/local-bridge/frontend/src/design/icons.tsx +11 -0
  149. package/src/cli/local-bridge/frontend/src/design/tokens.css +8 -0
  150. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +3 -0
  151. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +4 -0
  152. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +3 -0
  153. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +2 -0
  154. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +2 -0
  155. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +2 -0
  156. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +3 -0
  157. package/src/cli/local-bridge/frontend/src/lib/api.ts +23 -0
  158. package/src/cli/local-bridge/frontend/src/lib/format.ts +4 -0
  159. package/src/cli/local-bridge/frontend/src/lib/routes.ts +7 -0
  160. package/src/cli/local-bridge/frontend/src/main.tsx +1 -0
  161. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +1 -0
  162. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +2 -0
  163. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +3 -0
  164. package/src/cli/local-bridge/frontend/src/styles.css +12 -0
  165. package/src/cli/local-bridge/frontend/src/types.ts +14 -0
  166. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +1 -0
  167. package/src/cli/local-bridge/frontend/vite.config.ts +27 -0
  168. package/src/cli/local-bridge/server.ts +45 -0
  169. package/src/cli/local-bridge/ui-dist/app.css +1 -0
  170. package/src/cli/local-bridge/ui-dist/app.js +11094 -0
  171. package/src/cli/mcp/auth.ts +2 -0
  172. package/src/cli/mcp/instructions.ts +3 -3
  173. package/src/cli/mcp/legacy-tool-service.ts +27 -23
  174. package/src/cli/mcp/multi-repository.ts +2 -0
  175. package/src/cli/mcp/policy.ts +7 -5
  176. package/src/cli/mcp/repository-tools.ts +53 -14
  177. package/src/cli/mcp/server.ts +5 -10
  178. package/src/cli/mcp/setup.ts +220 -276
  179. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  180. package/src/cli/mcp/toolset-names.ts +1 -1
  181. package/src/cli/mcp/toolset.ts +0 -1
  182. package/src/cli/mcp/transports/http.ts +51 -4
  183. package/src/cli/repositories/branch-name-policy.ts +0 -7
  184. package/src/cli/repositories/controller-home.ts +110 -3
  185. package/src/cli/repositories/safe-patch.ts +1 -1
  186. package/src/cli/repositories/selected-path-actions.ts +26 -24
  187. package/src/core/adoption/gitignore-plan.ts +2 -3
  188. package/src/core/adoption/plan.ts +4 -1
  189. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +556 -0
  190. package/src/runtime/control-plane/execution/work-handle-store.ts +128 -1
  191. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +277 -0
  192. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +169 -0
  193. package/src/runtime/control-plane/facade/capability-registry.ts +0 -10
  194. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +44 -25
  195. package/src/runtime/control-plane/facade/suggested-actions.ts +7 -1
  196. package/src/runtime/control-plane/facade/types.ts +0 -1
  197. package/src/runtime/control-plane/global-scheduler/scheduler.ts +30 -8
  198. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +114 -0
  199. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +175 -0
  200. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +161 -0
  201. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +64 -0
  202. package/src/runtime/control-plane/launcher/thin-launcher.ts +117 -24
  203. package/src/runtime/control-plane/routing/route-policy.ts +14 -14
  204. package/src/runtime/control-plane/runtime-cleanup.ts +0 -1
  205. package/src/runtime/evidence/event-ledger.ts +1 -1
  206. package/src/runtime/{workflow/campaigns/workspace.ts → execution/managed-workspace.ts} +41 -43
  207. package/src/runtime/execution/process-runtime/check-facade.ts +6 -0
  208. package/src/runtime/execution/process-runtime/check-result.ts +45 -0
  209. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +42 -16
  210. package/src/runtime/execution/process-runtime/command-facade.ts +4 -2
  211. package/src/runtime/execution/process-runtime/gc.ts +1 -1
  212. package/src/runtime/execution/process-runtime/index.ts +3 -0
  213. package/src/runtime/execution/process-runtime/interactive-admission.ts +14 -5
  214. package/src/runtime/execution/process-runtime/persisted-check.ts +285 -0
  215. package/src/runtime/execution/process-runtime/resource-claims.ts +30 -0
  216. package/src/runtime/execution/process-runtime/runtime.ts +18 -1
  217. package/src/runtime/execution/process-runtime/store.ts +1 -19
  218. package/src/runtime/execution/process-runtime/types.ts +4 -0
  219. package/src/runtime/execution/thin-harness/execution-router.ts +0 -1
  220. package/src/runtime/execution/thin-harness/fast-executor.ts +1 -1
  221. package/src/runtime/execution/thin-harness/lightweight-lanes.ts +1 -2
  222. package/src/runtime/execution/thin-harness/types.ts +0 -1
  223. package/src/runtime/execution/workers/executor.ts +1 -1
  224. package/src/runtime/gateway/mcp/execution-tools.ts +1 -1
  225. package/src/runtime/gateway/mcp/persisted-check-process.ts +3 -255
  226. package/src/runtime/gateway/mcp/process-tools.ts +16 -3
  227. package/src/runtime/gateway/mcp/resource-policy.ts +0 -9
  228. package/src/runtime/gateway/mcp/router.ts +117 -182
  229. package/src/runtime/gateway/mcp/runtime-tools.ts +331 -409
  230. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +1 -219
  231. package/src/runtime/plugins/browser-adapter.ts +5 -0
  232. package/src/runtime/plugins/desktop-operator-registration.ts +1 -1
  233. package/src/runtime/plugins/store.ts +14 -6
  234. package/src/runtime/plugins/types.ts +1 -1
  235. package/src/runtime/projections/materialized-view.ts +1 -23
  236. package/src/runtime/recovery/maintenance-executor.ts +9 -0
  237. package/src/runtime/resources/ownership.ts +0 -1
  238. package/src/runtime/root/gateway-adapter.ts +2 -0
  239. package/src/runtime/root/mcp-transport.ts +10 -2
  240. package/src/runtime/root/package-connector-service.ts +136 -0
  241. package/src/runtime/root/package-runtime-release.ts +161 -0
  242. package/src/runtime/root/package-runtime-service.ts +185 -0
  243. package/src/runtime/root/release-materialize.ts +19 -0
  244. package/src/runtime/root/runtime.ts +2 -1
  245. package/src/runtime/root/scheduler.ts +5 -0
  246. package/src/runtime/root/service.ts +18 -5
  247. package/src/runtime/standalone-recovery/core.ts +65 -16
  248. package/src/runtime/workflow/findings/store.ts +145 -106
  249. package/src/runtime/workflow/findings/types.ts +22 -0
  250. package/src/runtime/workflow/schedules/engine.ts +132 -2
  251. package/src/runtime/workflow/schedules/pending-activation.ts +139 -0
  252. package/src/runtime/workflow/schedules/work-continuation.ts +218 -0
  253. package/docs/CHANGELOG.md +0 -978
  254. package/docs/README.zh-CN.md +0 -58
  255. package/scripts/smoke-chatgpt-supervised-automation.ts +0 -208
  256. package/src/cli/commands/controller.ts +0 -680
  257. package/src/runtime/workflow/campaigns/cleanup.ts +0 -263
  258. package/src/runtime/workflow/campaigns/engine.ts +0 -112
  259. package/src/runtime/workflow/campaigns/normalize.ts +0 -100
  260. package/src/runtime/workflow/campaigns/review.ts +0 -315
  261. package/src/runtime/workflow/campaigns/store.ts +0 -526
  262. package/src/runtime/workflow/campaigns/supervisor.ts +0 -112
  263. package/src/runtime/workflow/campaigns/types.ts +0 -290
  264. /package/src/runtime/{workflow/campaigns → execution}/workspace-agent.ts +0 -0
@@ -22,7 +22,7 @@ export function controllerCapabilityGroupForTool(name: string): ControllerCapabi
22
22
  if (name.startsWith('plugin_') || name === 'list_plugins') return 'plugins';
23
23
  if (name.startsWith('runtime_') || name.startsWith('controller_')) return 'runtime';
24
24
  if (name.startsWith('work_') || name.startsWith('session_') || name.startsWith('approval_') || name.startsWith('result_')) return 'workflow';
25
- if (name.startsWith('admin_') || name.startsWith('campaign_')) return 'admin';
25
+ if (name.startsWith('admin_')) return 'admin';
26
26
  return 'legacy';
27
27
  }
28
28
 
@@ -6,7 +6,7 @@ export const PREFERRED_FACADE_TOOL_NAMES = [...FACADE_TOOLS] as const;
6
6
  /**
7
7
  * Default ChatGPT-facing MCP surface. The Runtime keeps every internal atomic
8
8
  * handler and the full compatibility profile; ChatGPT only needs this bounded
9
- * execution surface. Atomic Git / issue-task / campaign / edit-session / plugin
9
+ * execution surface. Atomic Git / issue-task / edit-session / plugin
10
10
  * tools remain registered and reachable through `toolset=full`.
11
11
  */
12
12
  export const DEFAULT_CONTROLLER_TOOL_NAMES = [
@@ -213,7 +213,6 @@ function staticControllerExposureSnapshot(
213
213
  function capabilityForToolName(name: string): string {
214
214
  if (name.startsWith('rh_')) return 'facade';
215
215
  if (name.startsWith('git_') || name.startsWith('repository_git_')) return 'git';
216
- if (name.includes('campaign')) return 'campaign';
217
216
  if (/(^|_)(issue|task|project|worklog)(_|$)/.test(name)) return 'issue-task';
218
217
  if (name.startsWith('repository_') || name.startsWith('search_') || name.startsWith('read_')) return 'repository';
219
218
  if (name.startsWith('ios_')) return 'ios';
@@ -95,6 +95,24 @@ function isInitializeRequest(body: unknown): boolean {
95
95
  return typeof body === 'object' && body !== null && (body as Record<string, unknown>).method === 'initialize';
96
96
  }
97
97
 
98
+ function isServerDiscoverRequest(body: unknown): boolean {
99
+ return typeof body === 'object' && body !== null && (body as Record<string, unknown>).method === 'server/discover';
100
+ }
101
+
102
+ function sendLegacyServerDiscoverUnsupported(res: Response, body: unknown): void {
103
+ const record = typeof body === 'object' && body !== null ? body as Record<string, unknown> : {};
104
+ const id = typeof record.id === 'string' || typeof record.id === 'number' || record.id === null ? record.id : null;
105
+ res.setHeader('Cache-Control', 'no-store');
106
+ res.status(404).json({
107
+ jsonrpc: '2.0',
108
+ id,
109
+ error: {
110
+ code: -32601,
111
+ message: 'Method not found',
112
+ },
113
+ });
114
+ }
115
+
98
116
  function initializeClientIdentity(req: Request, body: unknown, route: McpSessionRoute, principalId: string): string {
99
117
  const params = typeof body === 'object' && body !== null
100
118
  ? (body as { params?: { clientInfo?: { name?: unknown; version?: unknown } } }).params
@@ -162,6 +180,18 @@ export function mcpSessionToolSurfaceFingerprintIsCurrent(
162
180
  return !sessionFingerprint || !currentFingerprint || sessionFingerprint === currentFingerprint;
163
181
  }
164
182
 
183
+ export async function resolveMcpSessionCurrentFingerprint(
184
+ publishedFingerprint: string | undefined,
185
+ loadRuntimeFingerprint?: () => Promise<string | undefined>,
186
+ ): Promise<string | undefined> {
187
+ // Runtime status publishes the exact schema fingerprint observed at session
188
+ // initialization/cutover. Prefer that O(1) fence on the hot path. If the
189
+ // publication is temporarily unavailable, retain the previous fail-safe
190
+ // behavior by asking the Canonical Runtime directly.
191
+ if (publishedFingerprint) return publishedFingerprint;
192
+ return await loadRuntimeFingerprint?.();
193
+ }
194
+
165
195
  function toolCallOutsideSessionSchema(body: unknown, toolNames: readonly string[] | undefined): boolean {
166
196
  if (!toolNames) return false;
167
197
  const messages = Array.isArray(body) ? body : [body];
@@ -664,6 +694,15 @@ async function handleMcpPost(
664
694
  return;
665
695
  }
666
696
  const sessionId = req.headers['mcp-session-id'] as string | undefined;
697
+ // MCP 2026-07-28 clients probe legacy servers with server/discover before
698
+ // falling back to the initialize-era protocol. Forge still serves the
699
+ // legacy stateful transport, so reject the unsupported modern RPC with the
700
+ // protocol-prescribed Method not found / HTTP 404 response instead of
701
+ // misclassifying it as a missing-session HTTP 400.
702
+ if (isServerDiscoverRequest(body)) {
703
+ sendLegacyServerDiscoverUnsupported(res, body);
704
+ return;
705
+ }
667
706
  if (isInitializeRequest(body)) {
668
707
  if (sessionId) {
669
708
  res.setHeader('Mcp-Session-Reset', 'reinitialized');
@@ -744,8 +783,12 @@ async function handleMcpPost(
744
783
  registry.beginPost(sessionId);
745
784
  stats.activePosts += 1;
746
785
  try {
747
- const runtimeSchema = await resolveRuntimeSchema?.(managed.toolContext);
748
- const currentFingerprint = runtimeSchema?.fingerprint ?? currentToolSurfaceFingerprint();
786
+ const currentFingerprint = await resolveMcpSessionCurrentFingerprint(
787
+ currentToolSurfaceFingerprint(),
788
+ resolveRuntimeSchema
789
+ ? async () => (await resolveRuntimeSchema(managed.toolContext))?.fingerprint
790
+ : undefined,
791
+ );
749
792
  if (!mcpSessionToolSurfaceFingerprintIsCurrent(managed.toolSurfaceFingerprint, currentFingerprint)) {
750
793
  // Keep the transport/session alive long enough for the host to observe
751
794
  // the recoverable reset and issue a replacement initialize request.
@@ -795,8 +838,12 @@ async function handleMcpGet(
795
838
  req.once('aborted', releaseStream);
796
839
  res.once('close', releaseStream);
797
840
  try {
798
- const runtimeSchema = await resolveRuntimeSchema?.(managed.toolContext);
799
- const currentFingerprint = runtimeSchema?.fingerprint ?? currentToolSurfaceFingerprint();
841
+ const currentFingerprint = await resolveMcpSessionCurrentFingerprint(
842
+ currentToolSurfaceFingerprint(),
843
+ resolveRuntimeSchema
844
+ ? async () => (await resolveRuntimeSchema(managed.toolContext))?.fingerprint
845
+ : undefined,
846
+ );
800
847
  if (!mcpSessionToolSurfaceFingerprintIsCurrent(managed.toolSurfaceFingerprint, currentFingerprint)) {
801
848
  // Preserve the existing SSE transport while asking the host to
802
849
  // reinitialize. The replacement initialize owns supersession/cleanup.
@@ -48,10 +48,3 @@ export function validateBranchName(raw: unknown, options: BranchNamePolicyOption
48
48
  export function branchRef(branch: unknown, options: BranchNamePolicyOptions = {}): string {
49
49
  return `refs/heads/${validateBranchName(branch, options)}`;
50
50
  }
51
-
52
- export function campaignBranchName(input: { readonly title: string; readonly identity: string }): string {
53
- return validateBranchName(
54
- `campaign/${branchSlugSegment(input.title)}-${input.identity}`,
55
- { purpose: 'CAMPAIGN_WORKSPACE_BRANCH' },
56
- );
57
- }
@@ -1,6 +1,6 @@
1
- import { existsSync, mkdirSync } from 'fs';
1
+ import { existsSync, lstatSync, mkdirSync, readlinkSync, renameSync, rmSync, symlinkSync } from 'fs';
2
2
  import { homedir } from 'os';
3
- import { join, resolve } from 'path';
3
+ import { basename, dirname, join, relative, resolve } from 'path';
4
4
 
5
5
  export function resolveControllerHome(explicit?: string): string {
6
6
  const configured = explicit?.trim()
@@ -29,8 +29,115 @@ export function resolveRepoPreferredControllerHome(repoRoot?: string, explicit?:
29
29
  return resolveControllerHome();
30
30
  }
31
31
 
32
+ export function repoLocalNoIndexControllerHome(
33
+ controllerHome: string,
34
+ platform: NodeJS.Platform = process.platform,
35
+ ): string | undefined {
36
+ const home = resolve(controllerHome);
37
+ if (platform !== 'darwin' || basename(home) !== 'controller-home' || basename(dirname(home)) !== '_ops') return undefined;
38
+ return `${home}.noindex`;
39
+ }
40
+
41
+ export function ensureControllerHomeStorage(
42
+ controllerHome: string,
43
+ platform: NodeJS.Platform = process.platform,
44
+ ): string {
45
+ const home = resolve(controllerHome);
46
+ const physical = repoLocalNoIndexControllerHome(home, platform);
47
+ if (!physical || existsSync(home)) return home;
48
+
49
+ let logicalEntryExists = false;
50
+ try { logicalEntryExists = lstatSync(home).isSymbolicLink(); } catch { /* absent */ }
51
+ mkdirSync(physical, { recursive: true });
52
+ if (!logicalEntryExists) {
53
+ const target = relative(dirname(home), physical) || basename(physical);
54
+ try {
55
+ symlinkSync(target, home, 'dir');
56
+ } catch (error) {
57
+ if (!existsSync(home)) throw error;
58
+ }
59
+ }
60
+ return home;
61
+ }
62
+
63
+ export interface ControllerHomeStorageMigration {
64
+ migrated: boolean;
65
+ logicalHome: string;
66
+ physicalHome?: string;
67
+ }
68
+
69
+ function symlinkTargetPath(path: string): string {
70
+ return resolve(dirname(path), readlinkSync(path));
71
+ }
72
+
73
+ export function repoLocalControllerHomeStorageNeedsMigration(
74
+ controllerHome: string,
75
+ platform: NodeJS.Platform = process.platform,
76
+ ): boolean {
77
+ const logicalHome = resolve(controllerHome);
78
+ const physicalHome = repoLocalNoIndexControllerHome(logicalHome, platform);
79
+ if (!physicalHome) return false;
80
+ let stat;
81
+ try {
82
+ stat = lstatSync(logicalHome);
83
+ } catch (error) {
84
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false;
85
+ throw error;
86
+ }
87
+ if (stat.isSymbolicLink()) {
88
+ if (symlinkTargetPath(logicalHome) !== physicalHome) {
89
+ throw new Error(`CONTROLLER_HOME_NOINDEX_SYMLINK_CONFLICT: ${logicalHome}`);
90
+ }
91
+ if (!existsSync(physicalHome)) {
92
+ throw new Error(`CONTROLLER_HOME_NOINDEX_TARGET_MISSING: ${physicalHome}`);
93
+ }
94
+ return false;
95
+ }
96
+ if (!stat.isDirectory()) throw new Error(`CONTROLLER_HOME_STORAGE_UNSUPPORTED: ${logicalHome}`);
97
+ if (existsSync(physicalHome)) throw new Error(`CONTROLLER_HOME_NOINDEX_TARGET_EXISTS: ${physicalHome}`);
98
+ return true;
99
+ }
100
+
101
+ /** Caller must prove the Canonical Runtime is fully stopped before invoking. */
102
+ export function migrateStoppedRepoLocalControllerHomeStorage(
103
+ controllerHome: string,
104
+ platform: NodeJS.Platform = process.platform,
105
+ ): ControllerHomeStorageMigration {
106
+ const logicalHome = resolve(controllerHome);
107
+ const physicalHome = repoLocalNoIndexControllerHome(logicalHome, platform);
108
+ if (!physicalHome || !repoLocalControllerHomeStorageNeedsMigration(logicalHome, platform)) {
109
+ return { migrated: false, logicalHome, physicalHome };
110
+ }
111
+ renameSync(logicalHome, physicalHome);
112
+ try {
113
+ const target = relative(dirname(logicalHome), physicalHome) || basename(physicalHome);
114
+ symlinkSync(target, logicalHome, 'dir');
115
+ if (!lstatSync(logicalHome).isSymbolicLink() || symlinkTargetPath(logicalHome) !== physicalHome || !existsSync(physicalHome)) {
116
+ throw new Error(`CONTROLLER_HOME_NOINDEX_POSTCHECK_FAILED: ${logicalHome}`);
117
+ }
118
+ return { migrated: true, logicalHome, physicalHome };
119
+ } catch (error) {
120
+ try { rmSync(logicalHome, { force: true }); } catch { /* best effort */ }
121
+ if (!existsSync(logicalHome) && existsSync(physicalHome)) renameSync(physicalHome, logicalHome);
122
+ throw error;
123
+ }
124
+ }
125
+
126
+ /** Caller must prove the Canonical Runtime is fully stopped before invoking. */
127
+ export function rollbackStoppedRepoLocalControllerHomeStorage(migration: ControllerHomeStorageMigration): void {
128
+ if (!migration.migrated || !migration.physicalHome) return;
129
+ const { logicalHome, physicalHome } = migration;
130
+ const stat = lstatSync(logicalHome);
131
+ if (!stat.isSymbolicLink() || symlinkTargetPath(logicalHome) !== physicalHome) {
132
+ throw new Error(`CONTROLLER_HOME_NOINDEX_ROLLBACK_CONFLICT: ${logicalHome}`);
133
+ }
134
+ if (!existsSync(physicalHome)) throw new Error(`CONTROLLER_HOME_NOINDEX_ROLLBACK_TARGET_MISSING: ${physicalHome}`);
135
+ rmSync(logicalHome, { force: true });
136
+ renameSync(physicalHome, logicalHome);
137
+ }
138
+
32
139
  export function ensureControllerHome(explicit?: string): string {
33
- const home = resolveControllerHome(explicit);
140
+ const home = ensureControllerHomeStorage(resolveControllerHome(explicit));
34
141
  for (const child of ['', 'repositories', 'system', 'locks', 'indexes', 'audit', 'mcp', 'sessions', 'work-handles']) {
35
142
  mkdirSync(join(home, child), { recursive: true });
36
143
  }
@@ -415,7 +415,7 @@ export function applySafePatch(repository: RepositoryRecord, input: {
415
415
  failures,
416
416
  plan,
417
417
  next: status === 'applied'
418
- ? 'Run repository_git_diff or repository_git_status, then repository_git_commit when ready.'
418
+ ? 'Review the returned edit-session diff evidence. Run focused validation only after the coherent edit batch is stable, then commit when acceptance evidence is sufficient.'
419
419
  : 'Inspect failures[].context, refresh the operation text, and re-run only the failed chunk or remaining operations.',
420
420
  };
421
421
  }
@@ -31,13 +31,13 @@ export interface SelectedPathCommitResult {
31
31
  error?: { code: string; message: string };
32
32
  }
33
33
 
34
- export interface HandoffArtifactPreview {
34
+ export interface TransferArtifactPreview {
35
35
  path: string;
36
36
  exists: boolean;
37
37
  preview?: string;
38
38
  }
39
39
 
40
- export interface PreparedHandoffArtifacts {
40
+ export interface PreparedTransferArtifacts {
41
41
  reason: string;
42
42
  usedScript: boolean;
43
43
  fallbackUsed: boolean;
@@ -45,7 +45,7 @@ export interface PreparedHandoffArtifacts {
45
45
  exitCode?: number;
46
46
  stdout?: string;
47
47
  stderr?: string;
48
- artifacts: HandoffArtifactPreview[];
48
+ artifacts: TransferArtifactPreview[];
49
49
  }
50
50
 
51
51
  function boundedOutputBytes(value: number | undefined): number {
@@ -129,7 +129,7 @@ function stagedPaths(repository: RepositoryRecord, paths: string[]): string[] {
129
129
  .filter(Boolean);
130
130
  }
131
131
 
132
- function artifactPreview(repository: RepositoryRecord, path: string): HandoffArtifactPreview {
132
+ function artifactPreview(repository: RepositoryRecord, path: string): TransferArtifactPreview {
133
133
  const absolute = join(repository.canonicalRoot, path);
134
134
  if (!existsSync(absolute)) return { path, exists: false };
135
135
  const content = redactProcessOutput(readFileSync(absolute, 'utf-8'));
@@ -147,7 +147,7 @@ function repositoryChangedPaths(repository: RepositoryRecord): string[] {
147
147
  return [...new Set([tracked, staged, untracked].join('\n').split(/\r?\n/).map((entry) => entry.trim()).filter(Boolean))].sort();
148
148
  }
149
149
 
150
- function writePatchHandoffArtifact(repository: RepositoryRecord, handoffDir: string, reason: string): HandoffArtifactPreview {
150
+ function writePatchTransferArtifact(repository: RepositoryRecord, transferDir: string, reason: string): TransferArtifactPreview {
151
151
  const diff = runGit(repository, ['diff', '--binary'], 512 * 1024).stdout;
152
152
  const stagedDiff = runGit(repository, ['diff', '--cached', '--binary'], 512 * 1024).stdout;
153
153
  const touchedPaths = repositoryChangedPaths(repository);
@@ -158,27 +158,29 @@ function writePatchHandoffArtifact(repository: RepositoryRecord, handoffDir: str
158
158
  touchedPaths,
159
159
  checks: [],
160
160
  actor: 'forge',
161
- source: `fallback-handoff:${reason}`,
161
+ source: `patch-transfer:${reason}`,
162
162
  notes: [
163
163
  'Integration must use selected-path review gates.',
164
164
  'Do not overwrite unrelated dirty files; rerun conflict detection before applying this patch.',
165
165
  ],
166
166
  });
167
- const patchPath = join(handoffDir, 'patch.json');
167
+ const patchPath = join(transferDir, 'patch.json');
168
168
  writeFileSync(patchPath, `${JSON.stringify(artifact, null, 2)}\n`, 'utf-8');
169
- return artifactPreview(repository, '.ai/harness/handoff/patch.json');
169
+ return artifactPreview(repository, '.ai/harness/transfers/patch.json');
170
170
  }
171
171
 
172
- function ensureFallbackArtifact(repository: RepositoryRecord, reason: string): HandoffArtifactPreview {
173
- const handoffDir = join(repository.canonicalRoot, '.ai', 'harness', 'handoff');
174
- mkdirSync(handoffDir, { recursive: true });
175
- const patchArtifact = writePatchHandoffArtifact(repository, handoffDir, reason);
172
+ function ensureFallbackArtifact(repository: RepositoryRecord, reason: string): TransferArtifactPreview {
173
+ const transferDir = join(repository.canonicalRoot, '.ai', 'harness', 'transfers');
174
+ const sessionDir = join(repository.canonicalRoot, '.ai', 'harness', 'session');
175
+ mkdirSync(transferDir, { recursive: true });
176
+ mkdirSync(sessionDir, { recursive: true });
177
+ const patchArtifact = writePatchTransferArtifact(repository, transferDir, reason);
176
178
  const timestamp = new Date().toISOString();
177
- const currentPath = join(handoffDir, 'current.md');
178
- const resumePath = join(handoffDir, 'resume.md');
179
+ const currentPath = join(sessionDir, 'continuation.md');
180
+ const resumePath = join(sessionDir, 'resume.md');
179
181
  if (!existsSync(currentPath)) {
180
182
  writeFileSync(currentPath, [
181
- '# Harness Handoff',
183
+ '# Forge Session Continuation Snapshot',
182
184
  '',
183
185
  `> **Generated**: ${timestamp}`,
184
186
  `> **Reason**: ${reason}`,
@@ -187,7 +189,7 @@ function ensureFallbackArtifact(repository: RepositoryRecord, reason: string): H
187
189
  '## Exact Next Step',
188
190
  '',
189
191
  '- Inspect the selected-path Git diff and continue from this repository state.',
190
- '- Review `.ai/harness/handoff/patch.json` before any integration attempt.',
192
+ '- Review `.ai/harness/transfers/patch.json` before any integration attempt.',
191
193
  '',
192
194
  ].join('\n'), 'utf-8');
193
195
  }
@@ -197,8 +199,8 @@ function ensureFallbackArtifact(repository: RepositoryRecord, reason: string): H
197
199
  '',
198
200
  `- Repository: \`${repository.canonicalRoot}\``,
199
201
  `- Reason: \`${reason}\``,
200
- '- Patch artifact: `.ai/harness/handoff/patch.json`',
201
- '- Next: open `.ai/harness/handoff/current.md` and continue from the recorded state.',
202
+ '- Patch artifact: `.ai/harness/transfers/patch.json`',
203
+ '- Next: open `.ai/harness/session/continuation.md` and continue from the recorded state.',
202
204
  '',
203
205
  ].join('\n'), 'utf-8');
204
206
  }
@@ -290,10 +292,10 @@ export function commitSelectedPaths(
290
292
  };
291
293
  }
292
294
 
293
- export function prepareFallbackHandoffArtifacts(
295
+ export function prepareTransferArtifacts(
294
296
  repository: RepositoryRecord,
295
297
  input: { reason?: unknown },
296
- ): PreparedHandoffArtifacts {
298
+ ): PreparedTransferArtifacts {
297
299
  const reason = String(input.reason ?? 'manual').trim() || 'manual';
298
300
  const script = join(repository.canonicalRoot, 'scripts', 'prepare-handoff.sh');
299
301
  const usedScript = existsSync(script);
@@ -321,8 +323,8 @@ export function prepareFallbackHandoffArtifacts(
321
323
  ok = exitCode === 0 && !result.error;
322
324
  }
323
325
 
324
- const beforeCurrent = existsSync(join(repository.canonicalRoot, '.ai', 'harness', 'handoff', 'current.md'));
325
- const beforeResume = existsSync(join(repository.canonicalRoot, '.ai', 'harness', 'handoff', 'resume.md'));
326
+ const beforeCurrent = existsSync(join(repository.canonicalRoot, '.ai', 'harness', 'session', 'continuation.md'));
327
+ const beforeResume = existsSync(join(repository.canonicalRoot, '.ai', 'harness', 'session', 'resume.md'));
326
328
  const patchArtifact = ensureFallbackArtifact(repository, reason);
327
329
  const fallbackUsed = !usedScript || !ok || !beforeCurrent || !beforeResume;
328
330
 
@@ -333,8 +335,8 @@ export function prepareFallbackHandoffArtifacts(
333
335
  ok: usedScript ? ok : true,
334
336
  ...(usedScript ? { exitCode, stdout, stderr } : {}),
335
337
  artifacts: [
336
- artifactPreview(repository, '.ai/harness/handoff/current.md'),
337
- artifactPreview(repository, '.ai/harness/handoff/resume.md'),
338
+ artifactPreview(repository, '.ai/harness/session/continuation.md'),
339
+ artifactPreview(repository, '.ai/harness/session/resume.md'),
338
340
  patchArtifact,
339
341
  ],
340
342
  };
@@ -39,7 +39,6 @@ export const GITIGNORE_MANAGED_BLOCK_CONTENT = [
39
39
  ".claude/.trace.jsonl",
40
40
  ".claude/.session-handoff.md",
41
41
  ".claude/.task-state.json",
42
- ".claude/.task-handoff.md",
43
42
  ".claude/.codegraph-state/",
44
43
  ".claude/*.tmp",
45
44
  ".claude/*.bak",
@@ -52,8 +51,8 @@ export const GITIGNORE_MANAGED_BLOCK_CONTENT = [
52
51
  ".ai/harness/archive/",
53
52
  ".ai/harness/backups/",
54
53
  ".ai/harness/failures/latest.jsonl",
55
- ".ai/harness/handoff/current.md",
56
- ".ai/harness/handoff/resume.md",
54
+ ".ai/harness/session/continuation.md",
55
+ ".ai/harness/session/resume.md",
57
56
  ".ai/harness/capability-context/",
58
57
  ".ai/harness/security/*",
59
58
  "!.ai/harness/security/.gitkeep",
@@ -26,7 +26,10 @@ const MINIMAL_DIRS = [
26
26
  "tasks/notes",
27
27
  "docs",
28
28
  ".ai/harness/checks",
29
- ".ai/harness/handoff",
29
+ ".ai/harness/session",
30
+ ".ai/harness/controller/packets",
31
+ ".ai/harness/projections",
32
+ ".ai/harness/transfers",
30
33
  ] as const;
31
34
 
32
35
  const STANDARD_EXTRA_DIRS = [