macro-agent 0.1.8 → 0.1.11

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 (306) hide show
  1. package/CLAUDE.md +263 -33
  2. package/README.md +781 -131
  3. package/dist/acp/claude-code-replay.d.ts +11 -0
  4. package/dist/acp/claude-code-replay.d.ts.map +1 -0
  5. package/dist/acp/claude-code-replay.js +190 -0
  6. package/dist/acp/claude-code-replay.js.map +1 -0
  7. package/dist/acp/macro-agent.d.ts.map +1 -1
  8. package/dist/acp/macro-agent.js +192 -7
  9. package/dist/acp/macro-agent.js.map +1 -1
  10. package/dist/acp/types.d.ts +9 -0
  11. package/dist/acp/types.d.ts.map +1 -1
  12. package/dist/acp/types.js.map +1 -1
  13. package/dist/adapters/tasks-adapter.d.ts.map +1 -1
  14. package/dist/adapters/tasks-adapter.js +3 -0
  15. package/dist/adapters/tasks-adapter.js.map +1 -1
  16. package/dist/adapters/types.d.ts +1 -0
  17. package/dist/adapters/types.d.ts.map +1 -1
  18. package/dist/agent/agent-manager-v2.d.ts +21 -0
  19. package/dist/agent/agent-manager-v2.d.ts.map +1 -1
  20. package/dist/agent/agent-manager-v2.js +308 -54
  21. package/dist/agent/agent-manager-v2.js.map +1 -1
  22. package/dist/agent/agent-manager.d.ts +12 -0
  23. package/dist/agent/agent-manager.d.ts.map +1 -1
  24. package/dist/agent/agent-manager.js.map +1 -1
  25. package/dist/agent/agent-store.d.ts +10 -0
  26. package/dist/agent/agent-store.d.ts.map +1 -1
  27. package/dist/agent/agent-store.js +22 -0
  28. package/dist/agent/agent-store.js.map +1 -1
  29. package/dist/agent/types.d.ts +15 -2
  30. package/dist/agent/types.d.ts.map +1 -1
  31. package/dist/agent/types.js.map +1 -1
  32. package/dist/boot-v2.d.ts +129 -1
  33. package/dist/boot-v2.d.ts.map +1 -1
  34. package/dist/boot-v2.js +359 -8
  35. package/dist/boot-v2.js.map +1 -1
  36. package/dist/cli/acp.js +4 -0
  37. package/dist/cli/acp.js.map +1 -1
  38. package/dist/cli/index.js +56 -0
  39. package/dist/cli/index.js.map +1 -1
  40. package/dist/cognitive/macro-agent-backend.d.ts.map +1 -1
  41. package/dist/cognitive/macro-agent-backend.js +40 -22
  42. package/dist/cognitive/macro-agent-backend.js.map +1 -1
  43. package/dist/integrations/skilltree.d.ts.map +1 -1
  44. package/dist/integrations/skilltree.js +1 -0
  45. package/dist/integrations/skilltree.js.map +1 -1
  46. package/dist/lifecycle/cascade.d.ts +25 -2
  47. package/dist/lifecycle/cascade.d.ts.map +1 -1
  48. package/dist/lifecycle/cascade.js +70 -2
  49. package/dist/lifecycle/cascade.js.map +1 -1
  50. package/dist/lifecycle/cleanup.d.ts +33 -2
  51. package/dist/lifecycle/cleanup.d.ts.map +1 -1
  52. package/dist/lifecycle/cleanup.js +28 -6
  53. package/dist/lifecycle/cleanup.js.map +1 -1
  54. package/dist/lifecycle/handlers-v2.d.ts +7 -0
  55. package/dist/lifecycle/handlers-v2.d.ts.map +1 -1
  56. package/dist/lifecycle/handlers-v2.js +28 -2
  57. package/dist/lifecycle/handlers-v2.js.map +1 -1
  58. package/dist/lifecycle/types.d.ts +11 -0
  59. package/dist/lifecycle/types.d.ts.map +1 -1
  60. package/dist/lifecycle/types.js.map +1 -1
  61. package/dist/map/acp-bridge.d.ts +9 -0
  62. package/dist/map/acp-bridge.d.ts.map +1 -1
  63. package/dist/map/acp-bridge.js +15 -2
  64. package/dist/map/acp-bridge.js.map +1 -1
  65. package/dist/map/cascade-action-handler.d.ts +24 -0
  66. package/dist/map/cascade-action-handler.d.ts.map +1 -0
  67. package/dist/map/cascade-action-handler.js +170 -0
  68. package/dist/map/cascade-action-handler.js.map +1 -0
  69. package/dist/map/cascade-bridge.d.ts +44 -0
  70. package/dist/map/cascade-bridge.d.ts.map +1 -0
  71. package/dist/map/cascade-bridge.js +294 -0
  72. package/dist/map/cascade-bridge.js.map +1 -0
  73. package/dist/map/coordination-handler.d.ts.map +1 -1
  74. package/dist/map/coordination-handler.js +12 -1
  75. package/dist/map/coordination-handler.js.map +1 -1
  76. package/dist/map/lifecycle-bridge.d.ts +1 -1
  77. package/dist/map/lifecycle-bridge.d.ts.map +1 -1
  78. package/dist/map/lifecycle-bridge.js +58 -23
  79. package/dist/map/lifecycle-bridge.js.map +1 -1
  80. package/dist/map/server.d.ts.map +1 -1
  81. package/dist/map/server.js +219 -7
  82. package/dist/map/server.js.map +1 -1
  83. package/dist/map/sidecar.d.ts.map +1 -1
  84. package/dist/map/sidecar.js +49 -2
  85. package/dist/map/sidecar.js.map +1 -1
  86. package/dist/map/types.d.ts +22 -0
  87. package/dist/map/types.d.ts.map +1 -1
  88. package/dist/mcp/tools/done-v2.d.ts.map +1 -1
  89. package/dist/mcp/tools/done-v2.js +8 -0
  90. package/dist/mcp/tools/done-v2.js.map +1 -1
  91. package/dist/teams/team-manager-v2.d.ts.map +1 -1
  92. package/dist/teams/team-manager-v2.js +26 -0
  93. package/dist/teams/team-manager-v2.js.map +1 -1
  94. package/dist/teams/team-runtime-v2.d.ts.map +1 -1
  95. package/dist/teams/team-runtime-v2.js +16 -3
  96. package/dist/teams/team-runtime-v2.js.map +1 -1
  97. package/dist/workspace/config.d.ts +10 -10
  98. package/dist/workspace/config.d.ts.map +1 -1
  99. package/dist/workspace/config.js +4 -4
  100. package/dist/workspace/config.js.map +1 -1
  101. package/dist/workspace/git-cascade-adapter.d.ts +510 -0
  102. package/dist/workspace/git-cascade-adapter.d.ts.map +1 -0
  103. package/dist/workspace/git-cascade-adapter.js +934 -0
  104. package/dist/workspace/git-cascade-adapter.js.map +1 -0
  105. package/dist/workspace/index.d.ts +3 -3
  106. package/dist/workspace/index.d.ts.map +1 -1
  107. package/dist/workspace/index.js +4 -4
  108. package/dist/workspace/index.js.map +1 -1
  109. package/dist/workspace/landing/direct-push.d.ts +20 -0
  110. package/dist/workspace/landing/direct-push.d.ts.map +1 -0
  111. package/dist/workspace/landing/direct-push.js +74 -0
  112. package/dist/workspace/landing/direct-push.js.map +1 -0
  113. package/dist/workspace/landing/index.d.ts +29 -0
  114. package/dist/workspace/landing/index.d.ts.map +1 -0
  115. package/dist/workspace/landing/index.js +37 -0
  116. package/dist/workspace/landing/index.js.map +1 -0
  117. package/dist/workspace/landing/merge-to-parent.d.ts +41 -0
  118. package/dist/workspace/landing/merge-to-parent.d.ts.map +1 -0
  119. package/dist/workspace/landing/merge-to-parent.js +186 -0
  120. package/dist/workspace/landing/merge-to-parent.js.map +1 -0
  121. package/dist/workspace/landing/optimistic-push.d.ts +16 -0
  122. package/dist/workspace/landing/optimistic-push.d.ts.map +1 -0
  123. package/dist/workspace/landing/optimistic-push.js +27 -0
  124. package/dist/workspace/landing/optimistic-push.js.map +1 -0
  125. package/dist/workspace/landing/queue-to-branch.d.ts +24 -0
  126. package/dist/workspace/landing/queue-to-branch.d.ts.map +1 -0
  127. package/dist/workspace/landing/queue-to-branch.js +79 -0
  128. package/dist/workspace/landing/queue-to-branch.js.map +1 -0
  129. package/dist/workspace/merge-queue/merge-queue.d.ts +10 -0
  130. package/dist/workspace/merge-queue/merge-queue.d.ts.map +1 -1
  131. package/dist/workspace/merge-queue/merge-queue.js +10 -0
  132. package/dist/workspace/merge-queue/merge-queue.js.map +1 -1
  133. package/dist/workspace/merge-queue/types.d.ts +16 -2
  134. package/dist/workspace/merge-queue/types.d.ts.map +1 -1
  135. package/dist/workspace/merge-queue/types.js +9 -0
  136. package/dist/workspace/merge-queue/types.js.map +1 -1
  137. package/dist/workspace/pool/types.d.ts +1 -0
  138. package/dist/workspace/pool/types.d.ts.map +1 -1
  139. package/dist/workspace/pool/worktree-pool.d.ts.map +1 -1
  140. package/dist/workspace/pool/worktree-pool.js +1 -0
  141. package/dist/workspace/pool/worktree-pool.js.map +1 -1
  142. package/dist/workspace/recovery/abandon.d.ts +15 -0
  143. package/dist/workspace/recovery/abandon.d.ts.map +1 -0
  144. package/dist/workspace/recovery/abandon.js +45 -0
  145. package/dist/workspace/recovery/abandon.js.map +1 -0
  146. package/dist/workspace/recovery/auto-resolve.d.ts +27 -0
  147. package/dist/workspace/recovery/auto-resolve.d.ts.map +1 -0
  148. package/dist/workspace/recovery/auto-resolve.js +99 -0
  149. package/dist/workspace/recovery/auto-resolve.js.map +1 -0
  150. package/dist/workspace/recovery/defer.d.ts +15 -0
  151. package/dist/workspace/recovery/defer.d.ts.map +1 -0
  152. package/dist/workspace/recovery/defer.js +16 -0
  153. package/dist/workspace/recovery/defer.js.map +1 -0
  154. package/dist/workspace/recovery/escalate.d.ts +16 -0
  155. package/dist/workspace/recovery/escalate.d.ts.map +1 -0
  156. package/dist/workspace/recovery/escalate.js +24 -0
  157. package/dist/workspace/recovery/escalate.js.map +1 -0
  158. package/dist/workspace/recovery/index.d.ts +32 -0
  159. package/dist/workspace/recovery/index.d.ts.map +1 -0
  160. package/dist/workspace/recovery/index.js +45 -0
  161. package/dist/workspace/recovery/index.js.map +1 -0
  162. package/dist/workspace/recovery/spawn-resolver.d.ts +45 -0
  163. package/dist/workspace/recovery/spawn-resolver.d.ts.map +1 -0
  164. package/dist/workspace/recovery/spawn-resolver.js +118 -0
  165. package/dist/workspace/recovery/spawn-resolver.js.map +1 -0
  166. package/dist/workspace/recovery/types.d.ts +63 -0
  167. package/dist/workspace/recovery/types.d.ts.map +1 -0
  168. package/dist/workspace/recovery/types.js +12 -0
  169. package/dist/workspace/recovery/types.js.map +1 -0
  170. package/dist/workspace/topology/index.d.ts +9 -0
  171. package/dist/workspace/topology/index.d.ts.map +1 -0
  172. package/dist/workspace/topology/index.js +8 -0
  173. package/dist/workspace/topology/index.js.map +1 -0
  174. package/dist/workspace/topology/no-workspace.d.ts +18 -0
  175. package/dist/workspace/topology/no-workspace.d.ts.map +1 -0
  176. package/dist/workspace/topology/no-workspace.js +25 -0
  177. package/dist/workspace/topology/no-workspace.js.map +1 -0
  178. package/dist/workspace/topology/types.d.ts +97 -0
  179. package/dist/workspace/topology/types.d.ts.map +1 -0
  180. package/dist/workspace/topology/types.js +20 -0
  181. package/dist/workspace/topology/types.js.map +1 -0
  182. package/dist/workspace/topology/yaml-driven.d.ts +69 -0
  183. package/dist/workspace/topology/yaml-driven.d.ts.map +1 -0
  184. package/dist/workspace/topology/yaml-driven.js +273 -0
  185. package/dist/workspace/topology/yaml-driven.js.map +1 -0
  186. package/dist/workspace/types-v3.d.ts +117 -0
  187. package/dist/workspace/types-v3.d.ts.map +1 -0
  188. package/dist/workspace/types-v3.js +20 -0
  189. package/dist/workspace/types-v3.js.map +1 -0
  190. package/dist/workspace/types.d.ts +162 -17
  191. package/dist/workspace/types.d.ts.map +1 -1
  192. package/dist/workspace/workspace-manager.d.ts +101 -13
  193. package/dist/workspace/workspace-manager.d.ts.map +1 -1
  194. package/dist/workspace/workspace-manager.js +416 -13
  195. package/dist/workspace/workspace-manager.js.map +1 -1
  196. package/dist/workspace/yaml-schema.d.ts +254 -0
  197. package/dist/workspace/yaml-schema.d.ts.map +1 -0
  198. package/dist/workspace/yaml-schema.js +170 -0
  199. package/dist/workspace/yaml-schema.js.map +1 -0
  200. package/docs/conflict-recovery.md +472 -0
  201. package/docs/design/task-dispatcher.md +880 -0
  202. package/docs/git-cascade-integration-gaps.md +678 -0
  203. package/docs/workspace-interfaces.md +731 -0
  204. package/docs/workspace-redesign-plan.md +302 -0
  205. package/package.json +6 -5
  206. package/src/__tests__/boot-v2.test.ts +435 -0
  207. package/src/__tests__/e2e/acp-over-map.e2e.test.ts +92 -0
  208. package/src/__tests__/e2e/auto-sync.e2e.test.ts +257 -0
  209. package/src/__tests__/e2e/bootstrap.e2e.test.ts +319 -0
  210. package/src/__tests__/e2e/cascade-rebase.e2e.test.ts +254 -0
  211. package/src/__tests__/e2e/cli-run.e2e.test.ts +167 -0
  212. package/src/__tests__/e2e/dispatch-coordination.e2e.test.ts +495 -0
  213. package/src/__tests__/e2e/dispatch-live.e2e.test.ts +564 -0
  214. package/src/__tests__/e2e/dispatch-opentasks.e2e.test.ts +496 -0
  215. package/src/__tests__/e2e/dispatch-phase2-live.e2e.test.ts +456 -0
  216. package/src/__tests__/e2e/dispatch-phase2.e2e.test.ts +386 -0
  217. package/src/__tests__/e2e/dispatch.e2e.test.ts +376 -0
  218. package/src/__tests__/e2e/self-driving-v3.e2e.test.ts +197 -0
  219. package/src/__tests__/e2e/spawn-resolver.e2e.test.ts +200 -0
  220. package/src/__tests__/e2e/workspace-lifecycle.e2e.test.ts +30 -22
  221. package/src/__tests__/e2e/workspace-v3.e2e.test.ts +413 -0
  222. package/src/acp/__tests__/claude-code-replay.test.ts +225 -0
  223. package/src/acp/__tests__/macro-agent.test.ts +39 -1
  224. package/src/acp/claude-code-replay.ts +208 -0
  225. package/src/acp/macro-agent.ts +203 -10
  226. package/src/acp/types.ts +10 -0
  227. package/src/adapters/__tests__/tasks-adapter.test.ts +1 -0
  228. package/src/adapters/tasks-adapter.ts +3 -0
  229. package/src/adapters/types.ts +1 -0
  230. package/src/agent/__tests__/agent-manager-topology.test.ts +73 -0
  231. package/src/agent/__tests__/agent-manager-v2.test.ts +66 -0
  232. package/src/agent/__tests__/agent-store.test.ts +52 -0
  233. package/src/agent/__tests__/task-ref-resolution.test.ts +231 -0
  234. package/src/agent/agent-manager-v2.ts +372 -59
  235. package/src/agent/agent-manager.ts +14 -0
  236. package/src/agent/agent-store.ts +24 -0
  237. package/src/agent/types.ts +16 -2
  238. package/src/boot-v2.ts +589 -35
  239. package/src/cli/acp.ts +4 -0
  240. package/src/cli/index.ts +61 -0
  241. package/src/cognitive/macro-agent-backend.ts +45 -29
  242. package/src/integrations/skilltree.ts +1 -0
  243. package/src/lifecycle/__tests__/cascade-consolidation.test.ts +240 -0
  244. package/src/lifecycle/cascade.ts +77 -2
  245. package/src/lifecycle/cleanup.ts +52 -3
  246. package/src/lifecycle/handlers-v2.ts +40 -3
  247. package/src/lifecycle/types.ts +12 -0
  248. package/src/map/__tests__/cascade-bridge.test.ts +229 -0
  249. package/src/map/__tests__/emit-event.test.ts +71 -0
  250. package/src/map/__tests__/lifecycle-bridge.test.ts +86 -10
  251. package/src/map/acp-bridge.ts +26 -3
  252. package/src/map/cascade-action-handler.ts +205 -0
  253. package/src/map/cascade-bridge.ts +339 -0
  254. package/src/map/coordination-handler.ts +13 -1
  255. package/src/map/lifecycle-bridge.ts +52 -17
  256. package/src/map/server.ts +225 -7
  257. package/src/map/sidecar.ts +48 -1
  258. package/src/map/types.ts +23 -0
  259. package/src/mcp/tools/done-v2.ts +9 -0
  260. package/src/teams/team-manager-v2.ts +37 -0
  261. package/src/teams/team-runtime-v2.ts +23 -3
  262. package/src/workspace/__tests__/{dataplane-adapter.test.ts → git-cascade-adapter.test.ts} +209 -14
  263. package/src/workspace/__tests__/land-dispatch.test.ts +214 -0
  264. package/src/workspace/__tests__/self-driving-yaml.test.ts +114 -0
  265. package/src/workspace/__tests__/shared-worktree-refcount.test.ts +154 -0
  266. package/src/workspace/__tests__/standalone-mode.test.ts +118 -0
  267. package/src/workspace/__tests__/workspace-manager-v3.test.ts +245 -0
  268. package/src/workspace/__tests__/yaml-schema.test.ts +210 -0
  269. package/src/workspace/config.ts +11 -11
  270. package/src/workspace/git-cascade-adapter.ts +1213 -0
  271. package/src/workspace/index.ts +11 -11
  272. package/src/workspace/landing/__tests__/strategies.test.ts +184 -0
  273. package/src/workspace/landing/direct-push.ts +91 -0
  274. package/src/workspace/landing/index.ts +40 -0
  275. package/src/workspace/landing/merge-to-parent.ts +229 -0
  276. package/src/workspace/landing/optimistic-push.ts +36 -0
  277. package/src/workspace/landing/queue-to-branch.ts +108 -0
  278. package/src/workspace/merge-queue/merge-queue.ts +10 -0
  279. package/src/workspace/merge-queue/types.ts +16 -2
  280. package/src/workspace/pool/__tests__/worktree-pool.integration.test.ts +5 -5
  281. package/src/workspace/pool/types.ts +1 -0
  282. package/src/workspace/pool/worktree-pool.ts +1 -0
  283. package/src/workspace/recovery/__tests__/auto-resolve-integration.test.ts +127 -0
  284. package/src/workspace/recovery/__tests__/spawn-resolver.test.ts +139 -0
  285. package/src/workspace/recovery/__tests__/strategies.test.ts +145 -0
  286. package/src/workspace/recovery/abandon.ts +51 -0
  287. package/src/workspace/recovery/auto-resolve.ts +119 -0
  288. package/src/workspace/recovery/defer.ts +23 -0
  289. package/src/workspace/recovery/escalate.ts +30 -0
  290. package/src/workspace/recovery/index.ts +58 -0
  291. package/src/workspace/recovery/spawn-resolver.ts +152 -0
  292. package/src/workspace/recovery/types.ts +54 -0
  293. package/src/workspace/topology/__tests__/yaml-driven.test.ts +345 -0
  294. package/src/workspace/topology/index.ts +18 -0
  295. package/src/workspace/topology/no-workspace.ts +39 -0
  296. package/src/workspace/topology/types.ts +116 -0
  297. package/src/workspace/topology/yaml-driven.ts +316 -0
  298. package/src/workspace/types-v3.ts +162 -0
  299. package/src/workspace/types.ts +211 -20
  300. package/src/workspace/workspace-manager.ts +533 -19
  301. package/src/workspace/yaml-schema.ts +216 -0
  302. package/dist/workspace/dataplane-adapter.d.ts +0 -260
  303. package/dist/workspace/dataplane-adapter.d.ts.map +0 -1
  304. package/dist/workspace/dataplane-adapter.js +0 -416
  305. package/dist/workspace/dataplane-adapter.js.map +0 -1
  306. package/src/workspace/dataplane-adapter.ts +0 -546
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Shared worktree ref-counting tests (Gap 3 fix).
3
+ *
4
+ * Verifies the fixed deallocateWorkspace behavior for shared worktrees:
5
+ * - Sharer deallocation decrements ref-count without tearing down the worktree.
6
+ * - Owner deallocation with active sharers defers teardown (ownerDeparted flag).
7
+ * - Teardown fires when the last sharer leaves after owner departed.
8
+ * - Normal (non-shared) teardown is unchanged.
9
+ */
10
+
11
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+ import * as os from 'os';
15
+ import { execSync } from 'child_process';
16
+ import { createGitCascadeAdapter, type GitCascadeAdapter } from '../git-cascade-adapter.js';
17
+ import {
18
+ DefaultWorkspaceManager,
19
+ createWorkspaceManagerWithAdapter,
20
+ } from '../workspace-manager.js';
21
+
22
+ describe('shared worktree ref-counting', () => {
23
+ let tempDir: string;
24
+ let repoPath: string;
25
+ let adapter: GitCascadeAdapter;
26
+ let manager: DefaultWorkspaceManager;
27
+
28
+ beforeEach(() => {
29
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'refcount-'));
30
+ repoPath = path.join(tempDir, 'repo');
31
+ fs.mkdirSync(repoPath);
32
+
33
+ execSync('git init -b main', { cwd: repoPath, stdio: 'pipe' });
34
+ execSync('git config user.email "t@t.com"', { cwd: repoPath, stdio: 'pipe' });
35
+ execSync('git config user.name "T"', { cwd: repoPath, stdio: 'pipe' });
36
+ fs.writeFileSync(path.join(repoPath, 'README.md'), '# Test');
37
+ execSync('git add .', { cwd: repoPath, stdio: 'pipe' });
38
+ execSync('git commit -m "init"', { cwd: repoPath, stdio: 'pipe' });
39
+
40
+ adapter = createGitCascadeAdapter({
41
+ enabled: true,
42
+ repoPath,
43
+ dbPath: path.join(tempDir, 'db.sqlite'),
44
+ skipRecovery: true,
45
+ });
46
+ manager = createWorkspaceManagerWithAdapter(adapter, {
47
+ worktreeBaseDir: path.join(tempDir, 'worktrees'),
48
+ }) as DefaultWorkspaceManager;
49
+ });
50
+
51
+ afterEach(() => {
52
+ manager.close();
53
+ adapter.close();
54
+ if (fs.existsSync(tempDir)) fs.rmSync(tempDir, { recursive: true, force: true });
55
+ });
56
+
57
+ it('sharer deallocation decrements refs without touching the worktree', () => {
58
+ const streamId = manager.createStreamV3({ name: 's1', ownerId: 'owner-A' });
59
+ const owner = manager.allocateWorktree({ agentId: 'owner-A', streamId });
60
+ manager.allocateWorktree({ agentId: 'sharer-B', sharedWithAgent: 'owner-A' });
61
+ manager.allocateWorktree({ agentId: 'sharer-C', sharedWithAgent: 'owner-A' });
62
+
63
+ // Owner's worktree exists on disk
64
+ expect(fs.existsSync(owner.path)).toBe(true);
65
+
66
+ // Deallocate one sharer
67
+ manager.deallocateWorkspace('sharer-B');
68
+ expect(fs.existsSync(owner.path)).toBe(true);
69
+
70
+ // Owner's adapter record untouched
71
+ expect(adapter.getWorktree('owner-A')).not.toBeNull();
72
+ });
73
+
74
+ it('owner deallocation with active sharers defers teardown', () => {
75
+ const streamId = manager.createStreamV3({ name: 's1', ownerId: 'owner-A' });
76
+ const owner = manager.allocateWorktree({ agentId: 'owner-A', streamId });
77
+ manager.allocateWorktree({ agentId: 'sharer-B', sharedWithAgent: 'owner-A' });
78
+
79
+ const events: Array<{ type: string }> = [];
80
+ manager.onEvent((e) => events.push(e));
81
+
82
+ manager.deallocateWorkspace('owner-A');
83
+
84
+ // Path still exists — teardown deferred
85
+ expect(fs.existsSync(owner.path)).toBe(true);
86
+ // git-cascade still thinks owner-A has a worktree (deferred state)
87
+ expect(adapter.getWorktree('owner-A')).not.toBeNull();
88
+
89
+ // Released event fired with kind=owner-departed
90
+ const released = events.find((e) => e.type === 'worktree:released');
91
+ expect(released).toBeDefined();
92
+ expect((released as { data?: { kind?: string } }).data?.kind).toBe('owner-departed');
93
+ });
94
+
95
+ it('last sharer leaving after owner departed finalizes teardown', () => {
96
+ const streamId = manager.createStreamV3({ name: 's1', ownerId: 'owner-A' });
97
+ const owner = manager.allocateWorktree({ agentId: 'owner-A', streamId });
98
+ manager.allocateWorktree({ agentId: 'sharer-B', sharedWithAgent: 'owner-A' });
99
+
100
+ manager.deallocateWorkspace('owner-A');
101
+ // Still deferred
102
+ expect(fs.existsSync(owner.path)).toBe(true);
103
+
104
+ manager.deallocateWorkspace('sharer-B');
105
+ // Now actually torn down
106
+ expect(fs.existsSync(owner.path)).toBe(false);
107
+ expect(adapter.getWorktree('owner-A')).toBeNull();
108
+ });
109
+
110
+ it('rejects allocation after owner departed with no sharers', () => {
111
+ const streamId = manager.createStreamV3({ name: 's1', ownerId: 'owner-A' });
112
+ manager.allocateWorktree({ agentId: 'owner-A', streamId });
113
+ manager.allocateWorktree({ agentId: 'sharer-B', sharedWithAgent: 'owner-A' });
114
+
115
+ manager.deallocateWorkspace('owner-A'); // owner departs (sharer-B still alive)
116
+ manager.deallocateWorkspace('sharer-B'); // last sharer leaves — torn down
117
+
118
+ // Now try to share again — owner is gone
119
+ expect(() =>
120
+ manager.allocateWorktree({ agentId: 'late-C', sharedWithAgent: 'owner-A' })
121
+ ).toThrow();
122
+ });
123
+
124
+ it('non-shared worktree teardown is unchanged', () => {
125
+ const streamId = manager.createStreamV3({ name: 's1', ownerId: 'lone-A' });
126
+ const wt = manager.allocateWorktree({ agentId: 'lone-A', streamId });
127
+ expect(fs.existsSync(wt.path)).toBe(true);
128
+
129
+ manager.deallocateWorkspace('lone-A');
130
+ expect(fs.existsSync(wt.path)).toBe(false);
131
+ expect(adapter.getWorktree('lone-A')).toBeNull();
132
+ });
133
+
134
+ it('multiple sharers coexist; dealloc order does not matter', () => {
135
+ const streamId = manager.createStreamV3({ name: 's1', ownerId: 'owner-A' });
136
+ const owner = manager.allocateWorktree({ agentId: 'owner-A', streamId });
137
+ manager.allocateWorktree({ agentId: 'sharer-B', sharedWithAgent: 'owner-A' });
138
+ manager.allocateWorktree({ agentId: 'sharer-C', sharedWithAgent: 'owner-A' });
139
+ manager.allocateWorktree({ agentId: 'sharer-D', sharedWithAgent: 'owner-A' });
140
+
141
+ // Dealloc in a scrambled order: owner first, then sharers
142
+ manager.deallocateWorkspace('owner-A');
143
+ expect(fs.existsSync(owner.path)).toBe(true);
144
+
145
+ manager.deallocateWorkspace('sharer-C');
146
+ expect(fs.existsSync(owner.path)).toBe(true);
147
+
148
+ manager.deallocateWorkspace('sharer-D');
149
+ expect(fs.existsSync(owner.path)).toBe(true);
150
+
151
+ manager.deallocateWorkspace('sharer-B'); // last sharer
152
+ expect(fs.existsSync(owner.path)).toBe(false);
153
+ });
154
+ });
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Standalone-mode regression test (G11).
3
+ *
4
+ * Asserts that GitCascadeAdapter (and the cascade event flow it drives)
5
+ * works correctly when no MAP sidecar / cascade bridge is configured —
6
+ * i.e., macro-agent runs without an OpenHive hub.
7
+ *
8
+ * The contract: cascade emits its own structured event stream regardless,
9
+ * and operations succeed without any hub roundtrip. If a future change
10
+ * accidentally introduces a hard dependency on the bridge or sidecar
11
+ * (e.g., awaiting a hub call inside cascade), this test catches it.
12
+ */
13
+
14
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
15
+ import * as fs from "fs";
16
+ import * as path from "path";
17
+ import * as os from "os";
18
+ import { execSync } from "child_process";
19
+ import { GitCascadeAdapter } from "../git-cascade-adapter.js";
20
+ import type { GitCascadeEvent } from "../git-cascade-adapter.js";
21
+
22
+ function mkTempRepo(): string {
23
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "macro-standalone-"));
24
+ execSync("git init", { cwd: dir, stdio: "pipe" });
25
+ execSync('git config user.email "test@test.com"', { cwd: dir, stdio: "pipe" });
26
+ execSync('git config user.name "Test"', { cwd: dir, stdio: "pipe" });
27
+ fs.writeFileSync(path.join(dir, ".gitignore"), ".git-cascade/\n");
28
+ fs.writeFileSync(path.join(dir, "README.md"), "# test\n");
29
+ execSync("git add .", { cwd: dir, stdio: "pipe" });
30
+ execSync('git commit -m "init"', { cwd: dir, stdio: "pipe" });
31
+ fs.mkdirSync(path.join(dir, ".git-cascade"), { recursive: true });
32
+ return dir;
33
+ }
34
+
35
+ describe("standalone mode: GitCascadeAdapter without sidecar/bridge", () => {
36
+ let repoPath: string;
37
+ let adapter: GitCascadeAdapter;
38
+
39
+ beforeEach(() => {
40
+ repoPath = mkTempRepo();
41
+ // Construct the adapter with no MAP sidecar — same shape as boot-v2 when
42
+ // `config.map?.enabled !== true`. No CascadeBridge is ever wired.
43
+ adapter = new GitCascadeAdapter({ repoPath });
44
+ });
45
+
46
+ afterEach(() => {
47
+ fs.rmSync(repoPath, { recursive: true, force: true });
48
+ });
49
+
50
+ it("runs full stream lifecycle without errors and produces local events", () => {
51
+ const events: GitCascadeEvent[] = [];
52
+ adapter.onEvent((e) => events.push(e));
53
+
54
+ // Open a stream
55
+ const streamId = adapter.createStream({
56
+ name: "standalone-feat",
57
+ agentId: "a1",
58
+ });
59
+ expect(streamId).toBeTruthy();
60
+
61
+ // Create worktree + commit through tracker
62
+ const wt = path.join(repoPath, ".worktrees", "a1");
63
+ adapter.createWorktree({
64
+ agentId: "a1",
65
+ path: wt,
66
+ branch: `stream/${streamId}`,
67
+ });
68
+ fs.writeFileSync(path.join(wt, "x.txt"), "hi\n");
69
+ execSync("git add .", { cwd: wt, stdio: "pipe" });
70
+ const result = adapter.commitChanges({
71
+ streamId,
72
+ agentId: "a1",
73
+ worktree: wt,
74
+ message: "feat: add x",
75
+ metadata: { task_ref: { resource_id: "r", node_id: "n" } },
76
+ });
77
+ expect(result.commit).toBeTruthy();
78
+ expect(result.changeId).toBeTruthy();
79
+
80
+ // Abandon
81
+ adapter.abandonStream(streamId, { reason: "test" });
82
+
83
+ // Verify expected events fired locally (no hub involved)
84
+ const types = events.map((e) => e.type);
85
+ expect(types).toContain("stream:created");
86
+ expect(types).toContain("stream:committed");
87
+ expect(types).toContain("stream:abandoned");
88
+ });
89
+
90
+ it("does not throw when no event listener is attached", () => {
91
+ // No onEvent subscribers — adapter must not assume any.
92
+ const streamId = adapter.createStream({ name: "lonely", agentId: "a" });
93
+ expect(streamId).toBeTruthy();
94
+ adapter.abandonStream(streamId, { reason: "test" });
95
+ });
96
+
97
+ it("preserves Change-Id even with no hub forwarding", () => {
98
+ // Confirms the tracker's emit hook works locally even when nothing
99
+ // forwards events — the Change-Id is computed by git-cascade itself, not
100
+ // by the bridge.
101
+ const streamId = adapter.createStream({ name: "chgid", agentId: "a" });
102
+ const wt = path.join(repoPath, ".worktrees", "a");
103
+ adapter.createWorktree({
104
+ agentId: "a",
105
+ path: wt,
106
+ branch: `stream/${streamId}`,
107
+ });
108
+ fs.writeFileSync(path.join(wt, "y.txt"), "y\n");
109
+ execSync("git add .", { cwd: wt, stdio: "pipe" });
110
+ const { changeId } = adapter.commitChanges({
111
+ streamId,
112
+ agentId: "a",
113
+ worktree: wt,
114
+ message: "test",
115
+ });
116
+ expect(changeId).toMatch(/^c-/);
117
+ });
118
+ });
@@ -0,0 +1,245 @@
1
+ /**
2
+ * WorkspaceManager V3 surface tests (Phase 1).
3
+ *
4
+ * Covers the stream-first methods added alongside the legacy role-shaped API.
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import * as os from 'os';
11
+ import { execSync } from 'child_process';
12
+ import { createGitCascadeAdapter, type GitCascadeAdapter } from '../git-cascade-adapter.js';
13
+ import {
14
+ DefaultWorkspaceManager,
15
+ createWorkspaceManagerWithAdapter,
16
+ } from '../workspace-manager.js';
17
+
18
+ describe('WorkspaceManager V3 surface', () => {
19
+ let tempDir: string;
20
+ let repoPath: string;
21
+ let dbPath: string;
22
+ let adapter: GitCascadeAdapter;
23
+ let manager: DefaultWorkspaceManager;
24
+
25
+ beforeEach(() => {
26
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-v3-test-'));
27
+ repoPath = path.join(tempDir, 'repo');
28
+ dbPath = path.join(tempDir, 'test.db');
29
+ fs.mkdirSync(repoPath);
30
+
31
+ execSync('git init -b main', { cwd: repoPath, stdio: 'pipe' });
32
+ execSync('git config user.email "test@test.com"', { cwd: repoPath, stdio: 'pipe' });
33
+ execSync('git config user.name "Test User"', { cwd: repoPath, stdio: 'pipe' });
34
+ fs.writeFileSync(path.join(repoPath, 'README.md'), '# Test');
35
+ execSync('git add .', { cwd: repoPath, stdio: 'pipe' });
36
+ execSync('git commit -m "init"', { cwd: repoPath, stdio: 'pipe' });
37
+
38
+ adapter = createGitCascadeAdapter({
39
+ enabled: true,
40
+ repoPath,
41
+ dbPath,
42
+ skipRecovery: true,
43
+ });
44
+ manager = createWorkspaceManagerWithAdapter(adapter, {
45
+ worktreeBaseDir: path.join(tempDir, 'worktrees'),
46
+ }) as DefaultWorkspaceManager;
47
+ });
48
+
49
+ afterEach(() => {
50
+ manager.close();
51
+ adapter.close();
52
+ if (tempDir && fs.existsSync(tempDir)) {
53
+ fs.rmSync(tempDir, { recursive: true, force: true });
54
+ }
55
+ });
56
+
57
+ describe('streams', () => {
58
+ it('creates a stream with a pseudo-principal owner', () => {
59
+ const streamId = manager.createStreamV3({
60
+ name: 'team-root',
61
+ ownerId: 'team:peer-swarm',
62
+ forkFrom: 'main',
63
+ });
64
+ expect(streamId).toBeDefined();
65
+ });
66
+
67
+ it('forks a child stream off a parent', () => {
68
+ const parent = manager.createStreamV3({
69
+ name: 'parent',
70
+ ownerId: 'team:solo-stack',
71
+ });
72
+ const child = manager.forkStream({
73
+ parentStreamId: parent,
74
+ name: 'child',
75
+ ownerId: 'agent-1',
76
+ });
77
+ const childStream = adapter.getStream(child);
78
+ expect(childStream?.parentStream).toBe(parent);
79
+ });
80
+
81
+ it('emits stream:created and stream:forked events', () => {
82
+ const events: string[] = [];
83
+ manager.onEvent((e) => events.push(e.type));
84
+
85
+ const parent = manager.createStreamV3({ name: 'parent', ownerId: 'team:x' });
86
+ manager.forkStream({ parentStreamId: parent, name: 'child', ownerId: 'agent-1' });
87
+
88
+ expect(events).toContain('stream:created');
89
+ expect(events).toContain('stream:forked');
90
+ });
91
+
92
+ it('pauses and resumes a stream with events', () => {
93
+ const streamId = manager.createStreamV3({ name: 'x', ownerId: 'agent-1' });
94
+ const events: string[] = [];
95
+ manager.onEvent((e) => events.push(e.type));
96
+
97
+ manager.pauseStream(streamId, 'manual');
98
+ expect(adapter.getStream(streamId)?.status).toBe('paused');
99
+ expect(events).toContain('stream:paused');
100
+
101
+ manager.resumeStream(streamId);
102
+ expect(adapter.getStream(streamId)?.status).toBe('active');
103
+ expect(events).toContain('stream:resumed');
104
+ });
105
+
106
+ it('lists streams by owner', () => {
107
+ manager.createStreamV3({ name: 'a', ownerId: 'agent-1' });
108
+ manager.createStreamV3({ name: 'b', ownerId: 'agent-2' });
109
+ const owned = manager.listStreams({ ownerId: 'agent-1' });
110
+ expect(owned.length).toBe(1);
111
+ expect(owned[0].name).toBe('a');
112
+ });
113
+ });
114
+
115
+ describe('worktree allocation', () => {
116
+ it('allocates a worktree attached to a stream', () => {
117
+ const streamId = manager.createStreamV3({ name: 'feat', ownerId: 'agent-1' });
118
+ const wt = manager.allocateWorktree({
119
+ agentId: 'agent-1',
120
+ streamId,
121
+ });
122
+ expect(wt.path).toContain('agent-1');
123
+ expect(wt.agentId).toBe('agent-1');
124
+ });
125
+
126
+ it('shares a worktree between two agents (ref-counted)', () => {
127
+ const streamId = manager.createStreamV3({ name: 'feat', ownerId: 'agent-1' });
128
+ const primary = manager.allocateWorktree({ agentId: 'agent-1', streamId });
129
+
130
+ const shared = manager.allocateWorktree({
131
+ agentId: 'agent-2',
132
+ sharedWithAgent: 'agent-1',
133
+ });
134
+
135
+ expect(shared.path).toBe(primary.path);
136
+ });
137
+
138
+ it('throws when sharing from an unallocated agent', () => {
139
+ expect(() =>
140
+ manager.allocateWorktree({ agentId: 'agent-x', sharedWithAgent: 'nonexistent' })
141
+ ).toThrow(/has no allocated worktree/);
142
+ });
143
+
144
+ it('emits worktree:allocated and worktree:shared events', () => {
145
+ const streamId = manager.createStreamV3({ name: 'feat', ownerId: 'agent-1' });
146
+ const events: string[] = [];
147
+ manager.onEvent((e) => events.push(e.type));
148
+
149
+ manager.allocateWorktree({ agentId: 'agent-1', streamId });
150
+ manager.allocateWorktree({ agentId: 'agent-2', sharedWithAgent: 'agent-1' });
151
+
152
+ expect(events).toContain('worktree:allocated');
153
+ expect(events).toContain('worktree:shared');
154
+ });
155
+ });
156
+
157
+ describe('changes (Change-Id tracking)', () => {
158
+ it('commits via commitChanges and assigns a Change-Id', () => {
159
+ const streamId = manager.createStreamV3({ name: 'feat', ownerId: 'agent-1' });
160
+ const wt = manager.allocateWorktree({ agentId: 'agent-1', streamId });
161
+
162
+ fs.writeFileSync(path.join(wt.path, 'hello.txt'), 'hi');
163
+
164
+ const result = manager.commitChanges({
165
+ agentId: 'agent-1',
166
+ streamId,
167
+ worktree: wt.path,
168
+ message: 'add hello',
169
+ });
170
+
171
+ expect(result.commit).toBeDefined();
172
+ expect(result.changeId).toBeDefined();
173
+ expect(result.changeId.startsWith('c-')).toBe(true);
174
+
175
+ const change = manager.getChangeByCommit(result.commit);
176
+ expect(change?.id).toBe(result.changeId);
177
+ });
178
+
179
+ it('emits stream:committed event on commit', () => {
180
+ const streamId = manager.createStreamV3({ name: 'feat', ownerId: 'agent-1' });
181
+ const wt = manager.allocateWorktree({ agentId: 'agent-1', streamId });
182
+
183
+ fs.writeFileSync(path.join(wt.path, 'x.txt'), 'x');
184
+
185
+ const events: Array<{ type: string; data: Record<string, unknown> }> = [];
186
+ manager.onEvent((e) => events.push(e));
187
+
188
+ manager.commitChanges({
189
+ agentId: 'agent-1',
190
+ streamId,
191
+ worktree: wt.path,
192
+ message: 'x',
193
+ });
194
+
195
+ const committed = events.find((e) => e.type === 'stream:committed');
196
+ expect(committed).toBeDefined();
197
+ expect(committed?.data.streamId).toBe(streamId);
198
+ });
199
+
200
+ it('marks changes merged and emits events', () => {
201
+ const streamId = manager.createStreamV3({ name: 'feat', ownerId: 'agent-1' });
202
+ const wt = manager.allocateWorktree({ agentId: 'agent-1', streamId });
203
+ fs.writeFileSync(path.join(wt.path, 'x.txt'), 'x');
204
+ const { changeId } = manager.commitChanges({
205
+ agentId: 'agent-1',
206
+ streamId,
207
+ worktree: wt.path,
208
+ message: 'x',
209
+ });
210
+
211
+ const events: string[] = [];
212
+ manager.onEvent((e) => events.push(e.type));
213
+
214
+ manager.markChangesMerged([changeId]);
215
+ expect(events).toContain('change:merged');
216
+ });
217
+ });
218
+
219
+ describe('landing strategy registry', () => {
220
+ it('registers landing strategies', () => {
221
+ const mockStrategy = {
222
+ name: 'test-strategy',
223
+ async land() {
224
+ return { success: true };
225
+ },
226
+ };
227
+ manager.registerLandingStrategy(mockStrategy);
228
+ // Registration is internal; successful if no throw. Full integration
229
+ // covered in Phase 5.
230
+ expect(true).toBe(true);
231
+ });
232
+ });
233
+
234
+ describe('reconcileV3', () => {
235
+ it('returns a MacroReconcileResult with zero issues on a clean db', () => {
236
+ manager.createStreamV3({ name: 'a', ownerId: 'agent-1' });
237
+ const result = manager.reconcileV3();
238
+
239
+ expect(result).toBeDefined();
240
+ expect(result.errors.length).toBe(0);
241
+ // Fresh streams are in sync → no fixes needed
242
+ expect(result.worktreesOrphaned).toBe(0);
243
+ });
244
+ });
245
+ });