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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/AGENTS.md +9 -11
  2. package/CHANGELOG.md +19 -0
  3. package/CLAUDE.md +8 -10
  4. package/DESIGN.md +278 -0
  5. package/README.md +70 -90
  6. package/README.zh-CN.md +50 -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/templates/helpers/archive-workflow.sh +0 -1
  28. package/assets/templates/helpers/capture-plan.sh +3 -3
  29. package/assets/templates/helpers/check-task-workflow.sh +2 -2
  30. package/assets/templates/helpers/codex-handoff-resume.sh +5 -5
  31. package/assets/templates/helpers/ensure-task-workflow.sh +37 -35
  32. package/assets/templates/helpers/new-plan.sh +1 -1
  33. package/assets/templates/helpers/plan-to-todo.sh +2 -2
  34. package/assets/templates/helpers/prepare-codex-handoff.sh +2 -2
  35. package/assets/templates/helpers/prepare-handoff.sh +9 -9
  36. package/assets/templates/helpers/refresh-current-status.sh +2 -2
  37. package/assets/templates/helpers/ship-worktrees.sh +1 -1
  38. package/assets/templates/helpers/sprint-backlog.sh +1 -1
  39. package/assets/templates/helpers/verify-sprint.sh +7 -7
  40. package/assets/templates/helpers/workstream-sync.sh +1 -1
  41. package/assets/templates/plan.template.md +1 -1
  42. package/assets/workflow-contract.v1.json +6 -3
  43. package/docs/architecture/current/README.md +10 -9
  44. package/docs/architecture/current/approved-target-architecture.zh-CN.md +2 -2
  45. package/docs/architecture/current/architecture-invariants.md +2 -2
  46. package/docs/architecture/current/automation-and-schedule-engine.md +15 -15
  47. package/docs/architecture/current/codegraph-context-provider.md +2 -2
  48. package/docs/architecture/current/control-plane-authority-inventory.md +12 -0
  49. package/docs/architecture/current/dispatch-and-agent-strategy.md +11 -11
  50. package/docs/architecture/current/entity-model.md +0 -27
  51. package/docs/architecture/current/failure-recovery.md +0 -24
  52. package/docs/architecture/current/governance.md +1 -1
  53. package/docs/architecture/current/implementation-status.md +1 -1
  54. package/docs/architecture/current/legacy-governance.md +58 -0
  55. package/docs/architecture/current/multi-repository-execution.md +1 -1
  56. package/docs/architecture/current/peer-supercontroller-runtime.md +1 -1
  57. package/docs/architecture/current/personal-assistant-plugin-baseline.md +4 -5
  58. package/docs/architecture/current/scheduler-and-resource-claims.md +0 -10
  59. package/docs/architecture/current/target-requirements-traceability.md +2 -2
  60. package/docs/architecture/current/thin-harness-governance-policy.md +1 -1
  61. package/docs/architecture/current/thin-harness-v1.md +9 -11
  62. package/docs/architecture/domains/runtime-harness.md +1 -1
  63. package/docs/forge-autonomous-goal-loop.md +3 -1
  64. package/docs/forge-chatgpt-mcp-setup.md +51 -184
  65. package/docs/images/forge-banner-cn.svg +26 -1
  66. package/docs/images/forge-banner.svg +26 -1
  67. package/docs/operations/features.md +1 -1
  68. package/docs/operations/homebrew.md +3 -3
  69. package/docs/operations/mcp-tool-exposure.md +5 -5
  70. package/docs/operations/platform-support.md +45 -37
  71. package/docs/operations/platform-support.zh-CN.md +40 -38
  72. package/docs/operations/releasing.md +3 -3
  73. package/docs/operations/releasing.zh-CN.md +3 -3
  74. package/docs/operations/troubleshooting.md +11 -6
  75. package/docs/operations/troubleshooting.zh-CN.md +11 -6
  76. package/docs/public-usage-guide.md +28 -21
  77. package/docs/public-usage-guide.zh-CN.md +28 -21
  78. package/docs/tutorials/01-install-and-start.md +56 -35
  79. package/docs/tutorials/01-install-and-start.zh-CN.md +56 -35
  80. package/docs/tutorials/02-connect-chatgpt.md +112 -46
  81. package/docs/tutorials/02-connect-chatgpt.zh-CN.md +113 -45
  82. package/docs/wiki/Architecture.md +1 -1
  83. package/docs/wiki/Installation.md +2 -2
  84. package/docs/wiki/Quick-Start.md +1 -1
  85. package/docs/wiki/Work-Lifecycle.md +1 -1
  86. package/install.ps1 +4 -5
  87. package/install.sh +4 -6
  88. package/package.json +15 -5
  89. package/scripts/archive-workflow.sh +0 -1
  90. package/scripts/benchmark-thin-harness-mcp-e2e.ts +27 -14
  91. package/scripts/build-controller-ui.ts +28 -0
  92. package/scripts/capture-plan.sh +4 -4
  93. package/scripts/check-agent-tooling.sh +12 -1
  94. package/scripts/check-platform-support.mjs +2 -2
  95. package/scripts/check-public-docs.sh +16 -5
  96. package/scripts/check-release-readiness.sh +5 -11
  97. package/scripts/check-runtime-architecture.mjs +14 -22
  98. package/scripts/check-tarball-install-smoke.sh +3 -2
  99. package/scripts/check-task-workflow.sh +2 -2
  100. package/scripts/codex-handoff-resume.sh +5 -5
  101. package/scripts/create-project-dirs.sh +3 -3
  102. package/scripts/ensure-task-workflow.sh +45 -35
  103. package/scripts/init-project.sh +4 -4
  104. package/scripts/install-desktop-operator-registration.ts +2 -2
  105. package/scripts/lib/project-init-lib.sh +46 -44
  106. package/scripts/migrate-project-template.sh +7 -4
  107. package/scripts/new-plan.sh +1 -1
  108. package/scripts/plan-to-todo.sh +2 -2
  109. package/scripts/prepare-codex-handoff.sh +2 -2
  110. package/scripts/prepare-handoff.sh +9 -9
  111. package/scripts/refresh-current-status.sh +2 -2
  112. package/scripts/setup-plugins.sh +3 -3
  113. package/scripts/ship-worktrees.sh +1 -1
  114. package/scripts/sprint-backlog.sh +5 -5
  115. package/scripts/sync-codex-installed-copies.sh +2 -3
  116. package/scripts/verify-sprint.sh +7 -7
  117. package/scripts/workstream-sync.sh +1 -1
  118. package/src/cli/chatgpt-browser/bridge-extension.ts +1 -1
  119. package/src/cli/chatgpt-browser/bridge-provider.ts +29 -0
  120. package/src/cli/chatgpt-browser/file-policy.ts +3 -1
  121. package/src/cli/commands/chatgpt.ts +51 -0
  122. package/src/cli/commands/doctor.ts +1 -1
  123. package/src/cli/commands/init-hook.ts +211 -70
  124. package/src/cli/commands/init.ts +5 -6
  125. package/src/cli/commands/mcp.ts +29 -5
  126. package/src/cli/commands/runtime.ts +60 -8
  127. package/src/cli/commands/setup-profile.ts +441 -0
  128. package/src/cli/controller/check-runner.ts +20 -3
  129. package/src/cli/controller/codex-continuation.ts +4 -7
  130. package/src/cli/controller/context-pack.ts +118 -14
  131. package/src/cli/controller/operational-plan.ts +4 -4
  132. package/src/cli/controller/task-ledger.ts +10 -10
  133. package/src/cli/controller/work-mode.ts +6 -13
  134. package/src/cli/index.ts +14 -35
  135. package/src/cli/local-bridge/connector-freshness.ts +1 -1
  136. package/src/cli/local-bridge/console-automations.ts +160 -0
  137. package/src/cli/local-bridge/dashboard.ts +8 -1303
  138. package/src/cli/local-bridge/facade-api.ts +16 -7
  139. package/src/cli/local-bridge/frontend/src/App.tsx +10 -0
  140. package/src/cli/local-bridge/frontend/src/components/Button.tsx +1 -0
  141. package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +3 -0
  142. package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +1 -0
  143. package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +1 -0
  144. package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +1 -0
  145. package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +3 -0
  146. package/src/cli/local-bridge/frontend/src/design/icons.tsx +11 -0
  147. package/src/cli/local-bridge/frontend/src/design/tokens.css +8 -0
  148. package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +3 -0
  149. package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +4 -0
  150. package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +3 -0
  151. package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +2 -0
  152. package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +2 -0
  153. package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +2 -0
  154. package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +3 -0
  155. package/src/cli/local-bridge/frontend/src/lib/api.ts +23 -0
  156. package/src/cli/local-bridge/frontend/src/lib/format.ts +4 -0
  157. package/src/cli/local-bridge/frontend/src/lib/routes.ts +7 -0
  158. package/src/cli/local-bridge/frontend/src/main.tsx +1 -0
  159. package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +1 -0
  160. package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +2 -0
  161. package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +3 -0
  162. package/src/cli/local-bridge/frontend/src/styles.css +12 -0
  163. package/src/cli/local-bridge/frontend/src/types.ts +14 -0
  164. package/src/cli/local-bridge/frontend/src/vite-env.d.ts +1 -0
  165. package/src/cli/local-bridge/frontend/vite.config.ts +27 -0
  166. package/src/cli/local-bridge/server.ts +44 -0
  167. package/src/cli/local-bridge/ui-dist/app.css +1 -0
  168. package/src/cli/local-bridge/ui-dist/app.js +11094 -0
  169. package/src/cli/mcp/instructions.ts +3 -3
  170. package/src/cli/mcp/legacy-tool-service.ts +27 -23
  171. package/src/cli/mcp/multi-repository.ts +2 -0
  172. package/src/cli/mcp/policy.ts +7 -5
  173. package/src/cli/mcp/repository-tools.ts +53 -14
  174. package/src/cli/mcp/server.ts +5 -10
  175. package/src/cli/mcp/setup.ts +211 -275
  176. package/src/cli/mcp/tool-capability-cache.ts +1 -1
  177. package/src/cli/mcp/toolset-names.ts +1 -1
  178. package/src/cli/mcp/toolset.ts +0 -1
  179. package/src/cli/mcp/transports/http.ts +24 -4
  180. package/src/cli/repositories/branch-name-policy.ts +0 -7
  181. package/src/cli/repositories/controller-home.ts +110 -3
  182. package/src/cli/repositories/safe-patch.ts +1 -1
  183. package/src/cli/repositories/selected-path-actions.ts +26 -24
  184. package/src/core/adoption/gitignore-plan.ts +2 -3
  185. package/src/core/adoption/plan.ts +4 -1
  186. package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +556 -0
  187. package/src/runtime/control-plane/execution/work-handle-store.ts +128 -1
  188. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +277 -0
  189. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +169 -0
  190. package/src/runtime/control-plane/facade/capability-registry.ts +0 -10
  191. package/src/runtime/control-plane/facade/handoff-inbox-store.ts +44 -25
  192. package/src/runtime/control-plane/facade/suggested-actions.ts +7 -1
  193. package/src/runtime/control-plane/facade/types.ts +0 -1
  194. package/src/runtime/control-plane/global-scheduler/scheduler.ts +30 -8
  195. package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +114 -0
  196. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +175 -0
  197. package/src/runtime/control-plane/launcher/launch-reservation-store.ts +161 -0
  198. package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +64 -0
  199. package/src/runtime/control-plane/launcher/thin-launcher.ts +117 -24
  200. package/src/runtime/control-plane/routing/route-policy.ts +14 -14
  201. package/src/runtime/control-plane/runtime-cleanup.ts +0 -1
  202. package/src/runtime/evidence/event-ledger.ts +1 -1
  203. package/src/runtime/{workflow/campaigns/workspace.ts → execution/managed-workspace.ts} +41 -43
  204. package/src/runtime/execution/process-runtime/check-facade.ts +6 -0
  205. package/src/runtime/execution/process-runtime/check-result.ts +45 -0
  206. package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +42 -16
  207. package/src/runtime/execution/process-runtime/command-facade.ts +4 -2
  208. package/src/runtime/execution/process-runtime/gc.ts +1 -1
  209. package/src/runtime/execution/process-runtime/index.ts +3 -0
  210. package/src/runtime/execution/process-runtime/interactive-admission.ts +14 -5
  211. package/src/runtime/execution/process-runtime/persisted-check.ts +285 -0
  212. package/src/runtime/execution/process-runtime/resource-claims.ts +30 -0
  213. package/src/runtime/execution/process-runtime/runtime.ts +18 -1
  214. package/src/runtime/execution/process-runtime/store.ts +1 -19
  215. package/src/runtime/execution/process-runtime/types.ts +4 -0
  216. package/src/runtime/execution/thin-harness/execution-router.ts +0 -1
  217. package/src/runtime/execution/thin-harness/fast-executor.ts +1 -1
  218. package/src/runtime/execution/thin-harness/lightweight-lanes.ts +1 -2
  219. package/src/runtime/execution/thin-harness/types.ts +0 -1
  220. package/src/runtime/execution/workers/executor.ts +1 -1
  221. package/src/runtime/gateway/mcp/execution-tools.ts +1 -1
  222. package/src/runtime/gateway/mcp/persisted-check-process.ts +3 -255
  223. package/src/runtime/gateway/mcp/process-tools.ts +16 -3
  224. package/src/runtime/gateway/mcp/resource-policy.ts +0 -9
  225. package/src/runtime/gateway/mcp/router.ts +117 -182
  226. package/src/runtime/gateway/mcp/runtime-tools.ts +331 -409
  227. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +1 -219
  228. package/src/runtime/plugins/browser-adapter.ts +5 -0
  229. package/src/runtime/plugins/desktop-operator-registration.ts +1 -1
  230. package/src/runtime/plugins/store.ts +14 -6
  231. package/src/runtime/plugins/types.ts +1 -1
  232. package/src/runtime/projections/materialized-view.ts +1 -23
  233. package/src/runtime/recovery/maintenance-executor.ts +9 -0
  234. package/src/runtime/resources/ownership.ts +0 -1
  235. package/src/runtime/root/gateway-adapter.ts +2 -0
  236. package/src/runtime/root/mcp-transport.ts +10 -2
  237. package/src/runtime/root/package-runtime-release.ts +161 -0
  238. package/src/runtime/root/package-runtime-service.ts +172 -0
  239. package/src/runtime/root/runtime.ts +2 -1
  240. package/src/runtime/root/scheduler.ts +5 -0
  241. package/src/runtime/root/service.ts +18 -5
  242. package/src/runtime/standalone-recovery/core.ts +65 -16
  243. package/src/runtime/workflow/findings/store.ts +145 -106
  244. package/src/runtime/workflow/findings/types.ts +22 -0
  245. package/src/runtime/workflow/schedules/engine.ts +132 -2
  246. package/src/runtime/workflow/schedules/pending-activation.ts +139 -0
  247. package/src/runtime/workflow/schedules/work-continuation.ts +218 -0
  248. package/docs/CHANGELOG.md +0 -978
  249. package/docs/README.zh-CN.md +0 -58
  250. package/scripts/smoke-chatgpt-supervised-automation.ts +0 -208
  251. package/src/cli/commands/controller.ts +0 -680
  252. package/src/runtime/workflow/campaigns/cleanup.ts +0 -263
  253. package/src/runtime/workflow/campaigns/engine.ts +0 -112
  254. package/src/runtime/workflow/campaigns/normalize.ts +0 -100
  255. package/src/runtime/workflow/campaigns/review.ts +0 -315
  256. package/src/runtime/workflow/campaigns/store.ts +0 -526
  257. package/src/runtime/workflow/campaigns/supervisor.ts +0 -112
  258. package/src/runtime/workflow/campaigns/types.ts +0 -290
  259. /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';
@@ -162,6 +162,18 @@ export function mcpSessionToolSurfaceFingerprintIsCurrent(
162
162
  return !sessionFingerprint || !currentFingerprint || sessionFingerprint === currentFingerprint;
163
163
  }
164
164
 
165
+ export async function resolveMcpSessionCurrentFingerprint(
166
+ publishedFingerprint: string | undefined,
167
+ loadRuntimeFingerprint?: () => Promise<string | undefined>,
168
+ ): Promise<string | undefined> {
169
+ // Runtime status publishes the exact schema fingerprint observed at session
170
+ // initialization/cutover. Prefer that O(1) fence on the hot path. If the
171
+ // publication is temporarily unavailable, retain the previous fail-safe
172
+ // behavior by asking the Canonical Runtime directly.
173
+ if (publishedFingerprint) return publishedFingerprint;
174
+ return await loadRuntimeFingerprint?.();
175
+ }
176
+
165
177
  function toolCallOutsideSessionSchema(body: unknown, toolNames: readonly string[] | undefined): boolean {
166
178
  if (!toolNames) return false;
167
179
  const messages = Array.isArray(body) ? body : [body];
@@ -744,8 +756,12 @@ async function handleMcpPost(
744
756
  registry.beginPost(sessionId);
745
757
  stats.activePosts += 1;
746
758
  try {
747
- const runtimeSchema = await resolveRuntimeSchema?.(managed.toolContext);
748
- const currentFingerprint = runtimeSchema?.fingerprint ?? currentToolSurfaceFingerprint();
759
+ const currentFingerprint = await resolveMcpSessionCurrentFingerprint(
760
+ currentToolSurfaceFingerprint(),
761
+ resolveRuntimeSchema
762
+ ? async () => (await resolveRuntimeSchema(managed.toolContext))?.fingerprint
763
+ : undefined,
764
+ );
749
765
  if (!mcpSessionToolSurfaceFingerprintIsCurrent(managed.toolSurfaceFingerprint, currentFingerprint)) {
750
766
  // Keep the transport/session alive long enough for the host to observe
751
767
  // the recoverable reset and issue a replacement initialize request.
@@ -795,8 +811,12 @@ async function handleMcpGet(
795
811
  req.once('aborted', releaseStream);
796
812
  res.once('close', releaseStream);
797
813
  try {
798
- const runtimeSchema = await resolveRuntimeSchema?.(managed.toolContext);
799
- const currentFingerprint = runtimeSchema?.fingerprint ?? currentToolSurfaceFingerprint();
814
+ const currentFingerprint = await resolveMcpSessionCurrentFingerprint(
815
+ currentToolSurfaceFingerprint(),
816
+ resolveRuntimeSchema
817
+ ? async () => (await resolveRuntimeSchema(managed.toolContext))?.fingerprint
818
+ : undefined,
819
+ );
800
820
  if (!mcpSessionToolSurfaceFingerprintIsCurrent(managed.toolSurfaceFingerprint, currentFingerprint)) {
801
821
  // Preserve the existing SSE transport while asking the host to
802
822
  // 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 = [