macro-agent 0.1.7 → 0.1.10

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/CLAUDE.md +179 -38
  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 +155 -6
  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/agent/agent-manager-v2.d.ts +21 -0
  14. package/dist/agent/agent-manager-v2.d.ts.map +1 -1
  15. package/dist/agent/agent-manager-v2.js +234 -71
  16. package/dist/agent/agent-manager-v2.js.map +1 -1
  17. package/dist/agent/agent-manager.d.ts +12 -0
  18. package/dist/agent/agent-manager.d.ts.map +1 -1
  19. package/dist/agent/agent-manager.js.map +1 -1
  20. package/dist/agent/types.d.ts +15 -2
  21. package/dist/agent/types.d.ts.map +1 -1
  22. package/dist/agent/types.js.map +1 -1
  23. package/dist/boot-v2.d.ts +41 -0
  24. package/dist/boot-v2.d.ts.map +1 -1
  25. package/dist/boot-v2.js +34 -37
  26. package/dist/boot-v2.js.map +1 -1
  27. package/dist/cli/index.js +56 -0
  28. package/dist/cli/index.js.map +1 -1
  29. package/dist/cognitive/macro-agent-backend.d.ts.map +1 -1
  30. package/dist/cognitive/macro-agent-backend.js +40 -22
  31. package/dist/cognitive/macro-agent-backend.js.map +1 -1
  32. package/dist/integrations/skilltree.d.ts.map +1 -1
  33. package/dist/integrations/skilltree.js +1 -0
  34. package/dist/integrations/skilltree.js.map +1 -1
  35. package/dist/lifecycle/cleanup.d.ts +33 -2
  36. package/dist/lifecycle/cleanup.d.ts.map +1 -1
  37. package/dist/lifecycle/cleanup.js +28 -6
  38. package/dist/lifecycle/cleanup.js.map +1 -1
  39. package/dist/lifecycle/handlers-v2.d.ts +7 -0
  40. package/dist/lifecycle/handlers-v2.d.ts.map +1 -1
  41. package/dist/lifecycle/handlers-v2.js +28 -2
  42. package/dist/lifecycle/handlers-v2.js.map +1 -1
  43. package/dist/lifecycle/types.d.ts +11 -0
  44. package/dist/lifecycle/types.d.ts.map +1 -1
  45. package/dist/lifecycle/types.js.map +1 -1
  46. package/dist/map/acp-bridge.d.ts +9 -0
  47. package/dist/map/acp-bridge.d.ts.map +1 -1
  48. package/dist/map/acp-bridge.js +15 -2
  49. package/dist/map/acp-bridge.js.map +1 -1
  50. package/dist/map/cascade-bridge.d.ts +44 -0
  51. package/dist/map/cascade-bridge.d.ts.map +1 -0
  52. package/dist/map/cascade-bridge.js +257 -0
  53. package/dist/map/cascade-bridge.js.map +1 -0
  54. package/dist/map/lifecycle-bridge.d.ts +1 -8
  55. package/dist/map/lifecycle-bridge.d.ts.map +1 -1
  56. package/dist/map/lifecycle-bridge.js +76 -22
  57. package/dist/map/lifecycle-bridge.js.map +1 -1
  58. package/dist/map/server.d.ts.map +1 -1
  59. package/dist/map/server.js +47 -6
  60. package/dist/map/server.js.map +1 -1
  61. package/dist/map/sidecar.d.ts.map +1 -1
  62. package/dist/map/sidecar.js +33 -4
  63. package/dist/map/sidecar.js.map +1 -1
  64. package/dist/map/types.d.ts +20 -0
  65. package/dist/map/types.d.ts.map +1 -1
  66. package/dist/mcp/tools/done-v2.d.ts.map +1 -1
  67. package/dist/mcp/tools/done-v2.js +8 -0
  68. package/dist/mcp/tools/done-v2.js.map +1 -1
  69. package/dist/teams/team-manager-v2.d.ts.map +1 -1
  70. package/dist/teams/team-manager-v2.js +26 -0
  71. package/dist/teams/team-manager-v2.js.map +1 -1
  72. package/dist/teams/team-runtime-v2.d.ts.map +1 -1
  73. package/dist/teams/team-runtime-v2.js +16 -3
  74. package/dist/teams/team-runtime-v2.js.map +1 -1
  75. package/dist/workspace/config.d.ts +10 -10
  76. package/dist/workspace/config.d.ts.map +1 -1
  77. package/dist/workspace/config.js +4 -4
  78. package/dist/workspace/config.js.map +1 -1
  79. package/dist/workspace/git-cascade-adapter.d.ts +510 -0
  80. package/dist/workspace/git-cascade-adapter.d.ts.map +1 -0
  81. package/dist/workspace/git-cascade-adapter.js +908 -0
  82. package/dist/workspace/git-cascade-adapter.js.map +1 -0
  83. package/dist/workspace/index.d.ts +3 -3
  84. package/dist/workspace/index.d.ts.map +1 -1
  85. package/dist/workspace/index.js +4 -4
  86. package/dist/workspace/index.js.map +1 -1
  87. package/dist/workspace/landing/direct-push.d.ts +20 -0
  88. package/dist/workspace/landing/direct-push.d.ts.map +1 -0
  89. package/dist/workspace/landing/direct-push.js +74 -0
  90. package/dist/workspace/landing/direct-push.js.map +1 -0
  91. package/dist/workspace/landing/index.d.ts +29 -0
  92. package/dist/workspace/landing/index.d.ts.map +1 -0
  93. package/dist/workspace/landing/index.js +37 -0
  94. package/dist/workspace/landing/index.js.map +1 -0
  95. package/dist/workspace/landing/merge-to-parent.d.ts +41 -0
  96. package/dist/workspace/landing/merge-to-parent.d.ts.map +1 -0
  97. package/dist/workspace/landing/merge-to-parent.js +185 -0
  98. package/dist/workspace/landing/merge-to-parent.js.map +1 -0
  99. package/dist/workspace/landing/optimistic-push.d.ts +16 -0
  100. package/dist/workspace/landing/optimistic-push.d.ts.map +1 -0
  101. package/dist/workspace/landing/optimistic-push.js +27 -0
  102. package/dist/workspace/landing/optimistic-push.js.map +1 -0
  103. package/dist/workspace/landing/queue-to-branch.d.ts +24 -0
  104. package/dist/workspace/landing/queue-to-branch.d.ts.map +1 -0
  105. package/dist/workspace/landing/queue-to-branch.js +79 -0
  106. package/dist/workspace/landing/queue-to-branch.js.map +1 -0
  107. package/dist/workspace/merge-queue/merge-queue.d.ts +10 -0
  108. package/dist/workspace/merge-queue/merge-queue.d.ts.map +1 -1
  109. package/dist/workspace/merge-queue/merge-queue.js +10 -0
  110. package/dist/workspace/merge-queue/merge-queue.js.map +1 -1
  111. package/dist/workspace/merge-queue/types.d.ts +16 -2
  112. package/dist/workspace/merge-queue/types.d.ts.map +1 -1
  113. package/dist/workspace/merge-queue/types.js +9 -0
  114. package/dist/workspace/merge-queue/types.js.map +1 -1
  115. package/dist/workspace/pool/types.d.ts +1 -0
  116. package/dist/workspace/pool/types.d.ts.map +1 -1
  117. package/dist/workspace/pool/worktree-pool.d.ts.map +1 -1
  118. package/dist/workspace/pool/worktree-pool.js +1 -0
  119. package/dist/workspace/pool/worktree-pool.js.map +1 -1
  120. package/dist/workspace/recovery/abandon.d.ts +15 -0
  121. package/dist/workspace/recovery/abandon.d.ts.map +1 -0
  122. package/dist/workspace/recovery/abandon.js +45 -0
  123. package/dist/workspace/recovery/abandon.js.map +1 -0
  124. package/dist/workspace/recovery/auto-resolve.d.ts +27 -0
  125. package/dist/workspace/recovery/auto-resolve.d.ts.map +1 -0
  126. package/dist/workspace/recovery/auto-resolve.js +99 -0
  127. package/dist/workspace/recovery/auto-resolve.js.map +1 -0
  128. package/dist/workspace/recovery/defer.d.ts +15 -0
  129. package/dist/workspace/recovery/defer.d.ts.map +1 -0
  130. package/dist/workspace/recovery/defer.js +16 -0
  131. package/dist/workspace/recovery/defer.js.map +1 -0
  132. package/dist/workspace/recovery/escalate.d.ts +16 -0
  133. package/dist/workspace/recovery/escalate.d.ts.map +1 -0
  134. package/dist/workspace/recovery/escalate.js +24 -0
  135. package/dist/workspace/recovery/escalate.js.map +1 -0
  136. package/dist/workspace/recovery/index.d.ts +32 -0
  137. package/dist/workspace/recovery/index.d.ts.map +1 -0
  138. package/dist/workspace/recovery/index.js +45 -0
  139. package/dist/workspace/recovery/index.js.map +1 -0
  140. package/dist/workspace/recovery/spawn-resolver.d.ts +45 -0
  141. package/dist/workspace/recovery/spawn-resolver.d.ts.map +1 -0
  142. package/dist/workspace/recovery/spawn-resolver.js +111 -0
  143. package/dist/workspace/recovery/spawn-resolver.js.map +1 -0
  144. package/dist/workspace/recovery/types.d.ts +63 -0
  145. package/dist/workspace/recovery/types.d.ts.map +1 -0
  146. package/dist/workspace/recovery/types.js +12 -0
  147. package/dist/workspace/recovery/types.js.map +1 -0
  148. package/dist/workspace/topology/index.d.ts +9 -0
  149. package/dist/workspace/topology/index.d.ts.map +1 -0
  150. package/dist/workspace/topology/index.js +8 -0
  151. package/dist/workspace/topology/index.js.map +1 -0
  152. package/dist/workspace/topology/no-workspace.d.ts +18 -0
  153. package/dist/workspace/topology/no-workspace.d.ts.map +1 -0
  154. package/dist/workspace/topology/no-workspace.js +25 -0
  155. package/dist/workspace/topology/no-workspace.js.map +1 -0
  156. package/dist/workspace/topology/types.d.ts +97 -0
  157. package/dist/workspace/topology/types.d.ts.map +1 -0
  158. package/dist/workspace/topology/types.js +20 -0
  159. package/dist/workspace/topology/types.js.map +1 -0
  160. package/dist/workspace/topology/yaml-driven.d.ts +69 -0
  161. package/dist/workspace/topology/yaml-driven.d.ts.map +1 -0
  162. package/dist/workspace/topology/yaml-driven.js +273 -0
  163. package/dist/workspace/topology/yaml-driven.js.map +1 -0
  164. package/dist/workspace/types-v3.d.ts +110 -0
  165. package/dist/workspace/types-v3.d.ts.map +1 -0
  166. package/dist/workspace/types-v3.js +20 -0
  167. package/dist/workspace/types-v3.js.map +1 -0
  168. package/dist/workspace/types.d.ts +145 -17
  169. package/dist/workspace/types.d.ts.map +1 -1
  170. package/dist/workspace/workspace-manager.d.ts +92 -13
  171. package/dist/workspace/workspace-manager.d.ts.map +1 -1
  172. package/dist/workspace/workspace-manager.js +373 -13
  173. package/dist/workspace/workspace-manager.js.map +1 -1
  174. package/dist/workspace/yaml-schema.d.ts +254 -0
  175. package/dist/workspace/yaml-schema.d.ts.map +1 -0
  176. package/dist/workspace/yaml-schema.js +170 -0
  177. package/dist/workspace/yaml-schema.js.map +1 -0
  178. package/docs/conflict-recovery.md +472 -0
  179. package/docs/git-cascade-integration-gaps.md +678 -0
  180. package/docs/workspace-interfaces.md +731 -0
  181. package/docs/workspace-redesign-plan.md +302 -0
  182. package/package.json +4 -4
  183. package/src/__tests__/e2e/auto-sync.e2e.test.ts +257 -0
  184. package/src/__tests__/e2e/cascade-rebase.e2e.test.ts +254 -0
  185. package/src/__tests__/e2e/cli-run.e2e.test.ts +167 -0
  186. package/src/__tests__/e2e/self-driving-v3.e2e.test.ts +197 -0
  187. package/src/__tests__/e2e/spawn-resolver.e2e.test.ts +200 -0
  188. package/src/__tests__/e2e/workspace-lifecycle.e2e.test.ts +30 -22
  189. package/src/__tests__/e2e/workspace-v3.e2e.test.ts +413 -0
  190. package/src/acp/__tests__/claude-code-replay.test.ts +225 -0
  191. package/src/acp/__tests__/macro-agent.test.ts +39 -1
  192. package/src/acp/claude-code-replay.ts +208 -0
  193. package/src/acp/macro-agent.ts +167 -9
  194. package/src/acp/types.ts +10 -0
  195. package/src/agent/__tests__/agent-manager-topology.test.ts +73 -0
  196. package/src/agent/__tests__/agent-manager-v2.test.ts +71 -11
  197. package/src/agent/__tests__/task-ref-resolution.test.ts +231 -0
  198. package/src/agent/agent-manager-v2.ts +293 -77
  199. package/src/agent/agent-manager.ts +14 -0
  200. package/src/agent/types.ts +16 -2
  201. package/src/boot-v2.ts +87 -36
  202. package/src/cli/index.ts +61 -0
  203. package/src/cognitive/__tests__/macro-agent-backend.test.ts +47 -5
  204. package/src/cognitive/macro-agent-backend.ts +45 -29
  205. package/src/integrations/skilltree.ts +1 -0
  206. package/src/lifecycle/cleanup.ts +52 -3
  207. package/src/lifecycle/handlers-v2.ts +40 -3
  208. package/src/lifecycle/types.ts +12 -0
  209. package/src/map/__tests__/cascade-bridge.test.ts +229 -0
  210. package/src/map/__tests__/lifecycle-bridge.test.ts +165 -22
  211. package/src/map/acp-bridge.ts +26 -3
  212. package/src/map/cascade-bridge.ts +301 -0
  213. package/src/map/lifecycle-bridge.ts +77 -27
  214. package/src/map/server.ts +47 -6
  215. package/src/map/sidecar.ts +31 -3
  216. package/src/map/types.ts +20 -0
  217. package/src/mcp/tools/done-v2.ts +9 -0
  218. package/src/teams/team-manager-v2.ts +37 -0
  219. package/src/teams/team-runtime-v2.ts +23 -3
  220. package/src/workspace/__tests__/{dataplane-adapter.test.ts → git-cascade-adapter.test.ts} +209 -14
  221. package/src/workspace/__tests__/self-driving-yaml.test.ts +114 -0
  222. package/src/workspace/__tests__/shared-worktree-refcount.test.ts +154 -0
  223. package/src/workspace/__tests__/standalone-mode.test.ts +118 -0
  224. package/src/workspace/__tests__/workspace-manager-v3.test.ts +245 -0
  225. package/src/workspace/__tests__/yaml-schema.test.ts +210 -0
  226. package/src/workspace/config.ts +11 -11
  227. package/src/workspace/git-cascade-adapter.ts +1186 -0
  228. package/src/workspace/index.ts +11 -11
  229. package/src/workspace/landing/__tests__/strategies.test.ts +142 -0
  230. package/src/workspace/landing/direct-push.ts +91 -0
  231. package/src/workspace/landing/index.ts +40 -0
  232. package/src/workspace/landing/merge-to-parent.ts +228 -0
  233. package/src/workspace/landing/optimistic-push.ts +36 -0
  234. package/src/workspace/landing/queue-to-branch.ts +108 -0
  235. package/src/workspace/merge-queue/merge-queue.ts +10 -0
  236. package/src/workspace/merge-queue/types.ts +16 -2
  237. package/src/workspace/pool/__tests__/worktree-pool.integration.test.ts +5 -5
  238. package/src/workspace/pool/types.ts +1 -0
  239. package/src/workspace/pool/worktree-pool.ts +1 -0
  240. package/src/workspace/recovery/__tests__/auto-resolve-integration.test.ts +127 -0
  241. package/src/workspace/recovery/__tests__/spawn-resolver.test.ts +139 -0
  242. package/src/workspace/recovery/__tests__/strategies.test.ts +145 -0
  243. package/src/workspace/recovery/abandon.ts +51 -0
  244. package/src/workspace/recovery/auto-resolve.ts +119 -0
  245. package/src/workspace/recovery/defer.ts +23 -0
  246. package/src/workspace/recovery/escalate.ts +30 -0
  247. package/src/workspace/recovery/index.ts +58 -0
  248. package/src/workspace/recovery/spawn-resolver.ts +145 -0
  249. package/src/workspace/recovery/types.ts +54 -0
  250. package/src/workspace/topology/__tests__/yaml-driven.test.ts +345 -0
  251. package/src/workspace/topology/index.ts +18 -0
  252. package/src/workspace/topology/no-workspace.ts +39 -0
  253. package/src/workspace/topology/types.ts +116 -0
  254. package/src/workspace/topology/yaml-driven.ts +316 -0
  255. package/src/workspace/types-v3.ts +155 -0
  256. package/src/workspace/types.ts +191 -20
  257. package/src/workspace/workspace-manager.ts +474 -19
  258. package/src/workspace/yaml-schema.ts +216 -0
  259. package/src/workspace/dataplane-adapter.ts +0 -546
@@ -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
+ });
@@ -0,0 +1,210 @@
1
+ /**
2
+ * YAML schema validation tests (Phase 2).
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+ import {
7
+ parseTeamWorkspaceConfig,
8
+ extractWorkspaceConfig,
9
+ TeamWorkspaceConfigSchema,
10
+ RoleWorkspaceConfigSchema,
11
+ } from '../yaml-schema.js';
12
+
13
+ describe('YAML schema', () => {
14
+ describe('parseTeamWorkspaceConfig', () => {
15
+ it('returns null when input is undefined', () => {
16
+ expect(parseTeamWorkspaceConfig(undefined)).toBeNull();
17
+ expect(parseTeamWorkspaceConfig(null)).toBeNull();
18
+ });
19
+
20
+ it('accepts a minimal peer-swarm-style config', () => {
21
+ const config = parseTeamWorkspaceConfig({
22
+ roles: {
23
+ peer: {
24
+ workspace: 'new_stream',
25
+ stream_lineage: 'fork_from_team_root',
26
+ landing: 'merge_to_parent_stream',
27
+ },
28
+ },
29
+ });
30
+ expect(config).not.toBeNull();
31
+ expect(config!.on_team_complete).toBe('keep'); // default
32
+ expect(config!.roles.peer.workspace).toBe('new_stream');
33
+ });
34
+
35
+ it('applies defaults for default_stream', () => {
36
+ const config = parseTeamWorkspaceConfig({
37
+ default_stream: {},
38
+ roles: { x: { workspace: 'none' } },
39
+ });
40
+ expect(config!.default_stream?.fork_from).toBe('main');
41
+ expect(config!.default_stream?.change_id_tracking).toBe(true);
42
+ });
43
+
44
+ it('requires share_with when workspace is share_with_agent', () => {
45
+ expect(() =>
46
+ parseTeamWorkspaceConfig({
47
+ roles: {
48
+ reviewer: { workspace: 'share_with_agent' },
49
+ },
50
+ })
51
+ ).toThrow(/share_with is required/);
52
+ });
53
+
54
+ it('requires track_branch when stream_lineage is track_existing_branch', () => {
55
+ expect(() =>
56
+ parseTeamWorkspaceConfig({
57
+ roles: {
58
+ resolver: {
59
+ workspace: 'new_stream',
60
+ stream_lineage: 'track_existing_branch',
61
+ },
62
+ },
63
+ })
64
+ ).toThrow(/track_branch is required/);
65
+ });
66
+
67
+ it('requires stream_lineage when workspace is new_stream', () => {
68
+ expect(() =>
69
+ parseTeamWorkspaceConfig({
70
+ roles: {
71
+ x: { workspace: 'new_stream' },
72
+ },
73
+ })
74
+ ).toThrow(/stream_lineage is required/);
75
+ });
76
+
77
+ it('accepts triad shape with queue_to_branch + merge_queue.drain', () => {
78
+ const config = parseTeamWorkspaceConfig({
79
+ on_team_complete: 'keep',
80
+ roles: {
81
+ coordinator: { workspace: 'attach_to_team_root' },
82
+ worker: {
83
+ workspace: 'new_stream',
84
+ stream_lineage: 'fork_from_parent',
85
+ landing: 'queue_to_branch',
86
+ landing_config: { target: 'team_root' },
87
+ capabilities: ['workspace.commit', 'workspace.land'],
88
+ },
89
+ integrator: {
90
+ workspace: 'attach_to_team_root',
91
+ capabilities: ['workspace.merge', 'merge_queue.drain'],
92
+ },
93
+ },
94
+ });
95
+ expect(config).not.toBeNull();
96
+ expect(config!.roles.worker.landing).toBe('queue_to_branch');
97
+ expect(config!.roles.integrator.capabilities).toContain('merge_queue.drain');
98
+ });
99
+
100
+ it('accepts pipeline shape with share_with_agent', () => {
101
+ const config = parseTeamWorkspaceConfig({
102
+ roles: {
103
+ planner: { workspace: 'none' },
104
+ coder: {
105
+ workspace: 'new_stream',
106
+ stream_lineage: 'fork_from_team_root',
107
+ landing: 'queue_to_branch',
108
+ },
109
+ reviewer: {
110
+ workspace: 'share_with_agent',
111
+ share_with: 'coder',
112
+ capabilities: ['workspace.read'],
113
+ },
114
+ },
115
+ });
116
+ expect(config!.roles.reviewer.share_with).toBe('coder');
117
+ });
118
+
119
+ it('accepts long-lived feature config with cascade + auto-sync', () => {
120
+ const config = parseTeamWorkspaceConfig({
121
+ roles: {
122
+ feature_owner: {
123
+ workspace: 'new_stream',
124
+ stream_lineage: 'fork_from_team_root',
125
+ landing: 'merge_to_parent_stream',
126
+ cascade_on_parent_update: true,
127
+ on_parent_advanced: 'sync_with_parent',
128
+ },
129
+ subtask: {
130
+ workspace: 'new_stream',
131
+ stream_lineage: 'fork_from_parent',
132
+ landing: 'merge_to_parent_stream',
133
+ },
134
+ },
135
+ });
136
+ expect(config!.roles.feature_owner.cascade_on_parent_update).toBe(true);
137
+ expect(config!.roles.feature_owner.on_parent_advanced).toBe('sync_with_parent');
138
+ });
139
+
140
+ it('accepts conflict_recovery team defaults', () => {
141
+ const config = parseTeamWorkspaceConfig({
142
+ roles: { peer: { workspace: 'none' } },
143
+ conflict_recovery: {
144
+ default_strategy: 'spawn-resolver',
145
+ default_config: { role: 'resolver', timeout_ms: 1200000 },
146
+ max_recovery_depth: 5,
147
+ },
148
+ });
149
+ expect(config!.conflict_recovery?.default_strategy).toBe('spawn-resolver');
150
+ expect(config!.conflict_recovery?.max_recovery_depth).toBe(5);
151
+ });
152
+
153
+ it('rejects unknown workspace kinds', () => {
154
+ expect(() =>
155
+ parseTeamWorkspaceConfig({
156
+ roles: { x: { workspace: 'made-up' } },
157
+ })
158
+ ).toThrow();
159
+ });
160
+
161
+ it('rejects unknown landing strategy names', () => {
162
+ expect(() =>
163
+ parseTeamWorkspaceConfig({
164
+ roles: {
165
+ x: {
166
+ workspace: 'new_stream',
167
+ stream_lineage: 'fork_from_team_root',
168
+ landing: 'unknown-strategy',
169
+ },
170
+ },
171
+ })
172
+ ).toThrow();
173
+ });
174
+ });
175
+
176
+ describe('extractWorkspaceConfig', () => {
177
+ it('extracts workspace block from a manifest with macro_agent.workspace', () => {
178
+ const manifest = {
179
+ macro_agent: {
180
+ workspace: {
181
+ roles: { x: { workspace: 'none' } },
182
+ },
183
+ },
184
+ };
185
+ const config = extractWorkspaceConfig(manifest);
186
+ expect(config).not.toBeNull();
187
+ });
188
+
189
+ it('returns null when macro_agent.workspace is absent', () => {
190
+ const manifest = { macro_agent: { integration: { strategy: 'trunk' } } };
191
+ expect(extractWorkspaceConfig(manifest)).toBeNull();
192
+ });
193
+
194
+ it('returns null when macro_agent is absent', () => {
195
+ expect(extractWorkspaceConfig({})).toBeNull();
196
+ });
197
+ });
198
+
199
+ describe('schema shape', () => {
200
+ it('TeamWorkspaceConfigSchema is a zod object', () => {
201
+ expect(TeamWorkspaceConfigSchema).toBeDefined();
202
+ expect(typeof TeamWorkspaceConfigSchema.safeParse).toBe('function');
203
+ });
204
+
205
+ it('RoleWorkspaceConfigSchema is a zod schema', () => {
206
+ expect(RoleWorkspaceConfigSchema).toBeDefined();
207
+ expect(typeof RoleWorkspaceConfigSchema.safeParse).toBe('function');
208
+ });
209
+ });
210
+ });
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Workspace Configuration Types
3
3
  *
4
- * Configuration for dataplane integration and workspace management.
4
+ * Configuration for git-cascade integration and workspace management.
5
5
  *
6
6
  * @module workspace/config
7
7
  */
@@ -9,11 +9,11 @@
9
9
  import type Database from 'better-sqlite3';
10
10
 
11
11
  /**
12
- * Configuration for dataplane integration
12
+ * Configuration for git-cascade integration
13
13
  */
14
- export interface DataplaneConfig {
14
+ export interface GitCascadeConfig {
15
15
  /**
16
- * Whether dataplane is enabled.
16
+ * Whether the git-cascade adapter is enabled.
17
17
  * When disabled, workspace operations are no-ops.
18
18
  */
19
19
  enabled: boolean;
@@ -25,21 +25,21 @@ export interface DataplaneConfig {
25
25
  repoPath?: string;
26
26
 
27
27
  /**
28
- * Table prefix for dataplane tables in shared SQLite database.
29
- * Defaults to 'dataplane_'.
28
+ * Table prefix for git-cascade tables in shared SQLite database.
29
+ * Defaults to 'git_cascade_'.
30
30
  */
31
31
  tablePrefix?: string;
32
32
 
33
33
  /**
34
34
  * Path to SQLite database file.
35
35
  * If not provided and db is not provided, creates database at
36
- * `<repoPath>/.dataplane/tracker.db`.
36
+ * `<repoPath>/.git-cascade/tracker.db`.
37
37
  */
38
38
  dbPath?: string;
39
39
 
40
40
  /**
41
41
  * Existing database connection to share.
42
- * If provided, dataplane will use this connection instead of creating its own.
42
+ * If provided, the adapter will use this connection instead of creating its own.
43
43
  * The caller is responsible for closing this connection.
44
44
  */
45
45
  db?: Database.Database;
@@ -57,11 +57,11 @@ export interface DataplaneConfig {
57
57
  }
58
58
 
59
59
  /**
60
- * Default dataplane configuration
60
+ * Default git-cascade configuration
61
61
  */
62
- export const DEFAULT_DATAPLANE_CONFIG: Partial<DataplaneConfig> = {
62
+ export const DEFAULT_GIT_CASCADE_CONFIG: Partial<GitCascadeConfig> = {
63
63
  enabled: true,
64
- tablePrefix: 'dataplane_',
64
+ tablePrefix: 'git_cascade_',
65
65
  verbose: false,
66
66
  skipRecovery: false,
67
67
  };