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.
- package/CLAUDE.md +263 -33
- package/README.md +781 -131
- package/dist/acp/claude-code-replay.d.ts +11 -0
- package/dist/acp/claude-code-replay.d.ts.map +1 -0
- package/dist/acp/claude-code-replay.js +190 -0
- package/dist/acp/claude-code-replay.js.map +1 -0
- package/dist/acp/macro-agent.d.ts.map +1 -1
- package/dist/acp/macro-agent.js +192 -7
- package/dist/acp/macro-agent.js.map +1 -1
- package/dist/acp/types.d.ts +9 -0
- package/dist/acp/types.d.ts.map +1 -1
- package/dist/acp/types.js.map +1 -1
- package/dist/adapters/tasks-adapter.d.ts.map +1 -1
- package/dist/adapters/tasks-adapter.js +3 -0
- package/dist/adapters/tasks-adapter.js.map +1 -1
- package/dist/adapters/types.d.ts +1 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/agent/agent-manager-v2.d.ts +21 -0
- package/dist/agent/agent-manager-v2.d.ts.map +1 -1
- package/dist/agent/agent-manager-v2.js +308 -54
- package/dist/agent/agent-manager-v2.js.map +1 -1
- package/dist/agent/agent-manager.d.ts +12 -0
- package/dist/agent/agent-manager.d.ts.map +1 -1
- package/dist/agent/agent-manager.js.map +1 -1
- package/dist/agent/agent-store.d.ts +10 -0
- package/dist/agent/agent-store.d.ts.map +1 -1
- package/dist/agent/agent-store.js +22 -0
- package/dist/agent/agent-store.js.map +1 -1
- package/dist/agent/types.d.ts +15 -2
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/boot-v2.d.ts +129 -1
- package/dist/boot-v2.d.ts.map +1 -1
- package/dist/boot-v2.js +359 -8
- package/dist/boot-v2.js.map +1 -1
- package/dist/cli/acp.js +4 -0
- package/dist/cli/acp.js.map +1 -1
- package/dist/cli/index.js +56 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cognitive/macro-agent-backend.d.ts.map +1 -1
- package/dist/cognitive/macro-agent-backend.js +40 -22
- package/dist/cognitive/macro-agent-backend.js.map +1 -1
- package/dist/integrations/skilltree.d.ts.map +1 -1
- package/dist/integrations/skilltree.js +1 -0
- package/dist/integrations/skilltree.js.map +1 -1
- package/dist/lifecycle/cascade.d.ts +25 -2
- package/dist/lifecycle/cascade.d.ts.map +1 -1
- package/dist/lifecycle/cascade.js +70 -2
- package/dist/lifecycle/cascade.js.map +1 -1
- package/dist/lifecycle/cleanup.d.ts +33 -2
- package/dist/lifecycle/cleanup.d.ts.map +1 -1
- package/dist/lifecycle/cleanup.js +28 -6
- package/dist/lifecycle/cleanup.js.map +1 -1
- package/dist/lifecycle/handlers-v2.d.ts +7 -0
- package/dist/lifecycle/handlers-v2.d.ts.map +1 -1
- package/dist/lifecycle/handlers-v2.js +28 -2
- package/dist/lifecycle/handlers-v2.js.map +1 -1
- package/dist/lifecycle/types.d.ts +11 -0
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js.map +1 -1
- package/dist/map/acp-bridge.d.ts +9 -0
- package/dist/map/acp-bridge.d.ts.map +1 -1
- package/dist/map/acp-bridge.js +15 -2
- package/dist/map/acp-bridge.js.map +1 -1
- package/dist/map/cascade-action-handler.d.ts +24 -0
- package/dist/map/cascade-action-handler.d.ts.map +1 -0
- package/dist/map/cascade-action-handler.js +170 -0
- package/dist/map/cascade-action-handler.js.map +1 -0
- package/dist/map/cascade-bridge.d.ts +44 -0
- package/dist/map/cascade-bridge.d.ts.map +1 -0
- package/dist/map/cascade-bridge.js +294 -0
- package/dist/map/cascade-bridge.js.map +1 -0
- package/dist/map/coordination-handler.d.ts.map +1 -1
- package/dist/map/coordination-handler.js +12 -1
- package/dist/map/coordination-handler.js.map +1 -1
- package/dist/map/lifecycle-bridge.d.ts +1 -1
- package/dist/map/lifecycle-bridge.d.ts.map +1 -1
- package/dist/map/lifecycle-bridge.js +58 -23
- package/dist/map/lifecycle-bridge.js.map +1 -1
- package/dist/map/server.d.ts.map +1 -1
- package/dist/map/server.js +219 -7
- package/dist/map/server.js.map +1 -1
- package/dist/map/sidecar.d.ts.map +1 -1
- package/dist/map/sidecar.js +49 -2
- package/dist/map/sidecar.js.map +1 -1
- package/dist/map/types.d.ts +22 -0
- package/dist/map/types.d.ts.map +1 -1
- package/dist/mcp/tools/done-v2.d.ts.map +1 -1
- package/dist/mcp/tools/done-v2.js +8 -0
- package/dist/mcp/tools/done-v2.js.map +1 -1
- package/dist/teams/team-manager-v2.d.ts.map +1 -1
- package/dist/teams/team-manager-v2.js +26 -0
- package/dist/teams/team-manager-v2.js.map +1 -1
- package/dist/teams/team-runtime-v2.d.ts.map +1 -1
- package/dist/teams/team-runtime-v2.js +16 -3
- package/dist/teams/team-runtime-v2.js.map +1 -1
- package/dist/workspace/config.d.ts +10 -10
- package/dist/workspace/config.d.ts.map +1 -1
- package/dist/workspace/config.js +4 -4
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/git-cascade-adapter.d.ts +510 -0
- package/dist/workspace/git-cascade-adapter.d.ts.map +1 -0
- package/dist/workspace/git-cascade-adapter.js +934 -0
- package/dist/workspace/git-cascade-adapter.js.map +1 -0
- package/dist/workspace/index.d.ts +3 -3
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js +4 -4
- package/dist/workspace/index.js.map +1 -1
- package/dist/workspace/landing/direct-push.d.ts +20 -0
- package/dist/workspace/landing/direct-push.d.ts.map +1 -0
- package/dist/workspace/landing/direct-push.js +74 -0
- package/dist/workspace/landing/direct-push.js.map +1 -0
- package/dist/workspace/landing/index.d.ts +29 -0
- package/dist/workspace/landing/index.d.ts.map +1 -0
- package/dist/workspace/landing/index.js +37 -0
- package/dist/workspace/landing/index.js.map +1 -0
- package/dist/workspace/landing/merge-to-parent.d.ts +41 -0
- package/dist/workspace/landing/merge-to-parent.d.ts.map +1 -0
- package/dist/workspace/landing/merge-to-parent.js +186 -0
- package/dist/workspace/landing/merge-to-parent.js.map +1 -0
- package/dist/workspace/landing/optimistic-push.d.ts +16 -0
- package/dist/workspace/landing/optimistic-push.d.ts.map +1 -0
- package/dist/workspace/landing/optimistic-push.js +27 -0
- package/dist/workspace/landing/optimistic-push.js.map +1 -0
- package/dist/workspace/landing/queue-to-branch.d.ts +24 -0
- package/dist/workspace/landing/queue-to-branch.d.ts.map +1 -0
- package/dist/workspace/landing/queue-to-branch.js +79 -0
- package/dist/workspace/landing/queue-to-branch.js.map +1 -0
- package/dist/workspace/merge-queue/merge-queue.d.ts +10 -0
- package/dist/workspace/merge-queue/merge-queue.d.ts.map +1 -1
- package/dist/workspace/merge-queue/merge-queue.js +10 -0
- package/dist/workspace/merge-queue/merge-queue.js.map +1 -1
- package/dist/workspace/merge-queue/types.d.ts +16 -2
- package/dist/workspace/merge-queue/types.d.ts.map +1 -1
- package/dist/workspace/merge-queue/types.js +9 -0
- package/dist/workspace/merge-queue/types.js.map +1 -1
- package/dist/workspace/pool/types.d.ts +1 -0
- package/dist/workspace/pool/types.d.ts.map +1 -1
- package/dist/workspace/pool/worktree-pool.d.ts.map +1 -1
- package/dist/workspace/pool/worktree-pool.js +1 -0
- package/dist/workspace/pool/worktree-pool.js.map +1 -1
- package/dist/workspace/recovery/abandon.d.ts +15 -0
- package/dist/workspace/recovery/abandon.d.ts.map +1 -0
- package/dist/workspace/recovery/abandon.js +45 -0
- package/dist/workspace/recovery/abandon.js.map +1 -0
- package/dist/workspace/recovery/auto-resolve.d.ts +27 -0
- package/dist/workspace/recovery/auto-resolve.d.ts.map +1 -0
- package/dist/workspace/recovery/auto-resolve.js +99 -0
- package/dist/workspace/recovery/auto-resolve.js.map +1 -0
- package/dist/workspace/recovery/defer.d.ts +15 -0
- package/dist/workspace/recovery/defer.d.ts.map +1 -0
- package/dist/workspace/recovery/defer.js +16 -0
- package/dist/workspace/recovery/defer.js.map +1 -0
- package/dist/workspace/recovery/escalate.d.ts +16 -0
- package/dist/workspace/recovery/escalate.d.ts.map +1 -0
- package/dist/workspace/recovery/escalate.js +24 -0
- package/dist/workspace/recovery/escalate.js.map +1 -0
- package/dist/workspace/recovery/index.d.ts +32 -0
- package/dist/workspace/recovery/index.d.ts.map +1 -0
- package/dist/workspace/recovery/index.js +45 -0
- package/dist/workspace/recovery/index.js.map +1 -0
- package/dist/workspace/recovery/spawn-resolver.d.ts +45 -0
- package/dist/workspace/recovery/spawn-resolver.d.ts.map +1 -0
- package/dist/workspace/recovery/spawn-resolver.js +118 -0
- package/dist/workspace/recovery/spawn-resolver.js.map +1 -0
- package/dist/workspace/recovery/types.d.ts +63 -0
- package/dist/workspace/recovery/types.d.ts.map +1 -0
- package/dist/workspace/recovery/types.js +12 -0
- package/dist/workspace/recovery/types.js.map +1 -0
- package/dist/workspace/topology/index.d.ts +9 -0
- package/dist/workspace/topology/index.d.ts.map +1 -0
- package/dist/workspace/topology/index.js +8 -0
- package/dist/workspace/topology/index.js.map +1 -0
- package/dist/workspace/topology/no-workspace.d.ts +18 -0
- package/dist/workspace/topology/no-workspace.d.ts.map +1 -0
- package/dist/workspace/topology/no-workspace.js +25 -0
- package/dist/workspace/topology/no-workspace.js.map +1 -0
- package/dist/workspace/topology/types.d.ts +97 -0
- package/dist/workspace/topology/types.d.ts.map +1 -0
- package/dist/workspace/topology/types.js +20 -0
- package/dist/workspace/topology/types.js.map +1 -0
- package/dist/workspace/topology/yaml-driven.d.ts +69 -0
- package/dist/workspace/topology/yaml-driven.d.ts.map +1 -0
- package/dist/workspace/topology/yaml-driven.js +273 -0
- package/dist/workspace/topology/yaml-driven.js.map +1 -0
- package/dist/workspace/types-v3.d.ts +117 -0
- package/dist/workspace/types-v3.d.ts.map +1 -0
- package/dist/workspace/types-v3.js +20 -0
- package/dist/workspace/types-v3.js.map +1 -0
- package/dist/workspace/types.d.ts +162 -17
- package/dist/workspace/types.d.ts.map +1 -1
- package/dist/workspace/workspace-manager.d.ts +101 -13
- package/dist/workspace/workspace-manager.d.ts.map +1 -1
- package/dist/workspace/workspace-manager.js +416 -13
- package/dist/workspace/workspace-manager.js.map +1 -1
- package/dist/workspace/yaml-schema.d.ts +254 -0
- package/dist/workspace/yaml-schema.d.ts.map +1 -0
- package/dist/workspace/yaml-schema.js +170 -0
- package/dist/workspace/yaml-schema.js.map +1 -0
- package/docs/conflict-recovery.md +472 -0
- package/docs/design/task-dispatcher.md +880 -0
- package/docs/git-cascade-integration-gaps.md +678 -0
- package/docs/workspace-interfaces.md +731 -0
- package/docs/workspace-redesign-plan.md +302 -0
- package/package.json +6 -5
- package/src/__tests__/boot-v2.test.ts +435 -0
- package/src/__tests__/e2e/acp-over-map.e2e.test.ts +92 -0
- package/src/__tests__/e2e/auto-sync.e2e.test.ts +257 -0
- package/src/__tests__/e2e/bootstrap.e2e.test.ts +319 -0
- package/src/__tests__/e2e/cascade-rebase.e2e.test.ts +254 -0
- package/src/__tests__/e2e/cli-run.e2e.test.ts +167 -0
- package/src/__tests__/e2e/dispatch-coordination.e2e.test.ts +495 -0
- package/src/__tests__/e2e/dispatch-live.e2e.test.ts +564 -0
- package/src/__tests__/e2e/dispatch-opentasks.e2e.test.ts +496 -0
- package/src/__tests__/e2e/dispatch-phase2-live.e2e.test.ts +456 -0
- package/src/__tests__/e2e/dispatch-phase2.e2e.test.ts +386 -0
- package/src/__tests__/e2e/dispatch.e2e.test.ts +376 -0
- package/src/__tests__/e2e/self-driving-v3.e2e.test.ts +197 -0
- package/src/__tests__/e2e/spawn-resolver.e2e.test.ts +200 -0
- package/src/__tests__/e2e/workspace-lifecycle.e2e.test.ts +30 -22
- package/src/__tests__/e2e/workspace-v3.e2e.test.ts +413 -0
- package/src/acp/__tests__/claude-code-replay.test.ts +225 -0
- package/src/acp/__tests__/macro-agent.test.ts +39 -1
- package/src/acp/claude-code-replay.ts +208 -0
- package/src/acp/macro-agent.ts +203 -10
- package/src/acp/types.ts +10 -0
- package/src/adapters/__tests__/tasks-adapter.test.ts +1 -0
- package/src/adapters/tasks-adapter.ts +3 -0
- package/src/adapters/types.ts +1 -0
- package/src/agent/__tests__/agent-manager-topology.test.ts +73 -0
- package/src/agent/__tests__/agent-manager-v2.test.ts +66 -0
- package/src/agent/__tests__/agent-store.test.ts +52 -0
- package/src/agent/__tests__/task-ref-resolution.test.ts +231 -0
- package/src/agent/agent-manager-v2.ts +372 -59
- package/src/agent/agent-manager.ts +14 -0
- package/src/agent/agent-store.ts +24 -0
- package/src/agent/types.ts +16 -2
- package/src/boot-v2.ts +589 -35
- package/src/cli/acp.ts +4 -0
- package/src/cli/index.ts +61 -0
- package/src/cognitive/macro-agent-backend.ts +45 -29
- package/src/integrations/skilltree.ts +1 -0
- package/src/lifecycle/__tests__/cascade-consolidation.test.ts +240 -0
- package/src/lifecycle/cascade.ts +77 -2
- package/src/lifecycle/cleanup.ts +52 -3
- package/src/lifecycle/handlers-v2.ts +40 -3
- package/src/lifecycle/types.ts +12 -0
- package/src/map/__tests__/cascade-bridge.test.ts +229 -0
- package/src/map/__tests__/emit-event.test.ts +71 -0
- package/src/map/__tests__/lifecycle-bridge.test.ts +86 -10
- package/src/map/acp-bridge.ts +26 -3
- package/src/map/cascade-action-handler.ts +205 -0
- package/src/map/cascade-bridge.ts +339 -0
- package/src/map/coordination-handler.ts +13 -1
- package/src/map/lifecycle-bridge.ts +52 -17
- package/src/map/server.ts +225 -7
- package/src/map/sidecar.ts +48 -1
- package/src/map/types.ts +23 -0
- package/src/mcp/tools/done-v2.ts +9 -0
- package/src/teams/team-manager-v2.ts +37 -0
- package/src/teams/team-runtime-v2.ts +23 -3
- package/src/workspace/__tests__/{dataplane-adapter.test.ts → git-cascade-adapter.test.ts} +209 -14
- package/src/workspace/__tests__/land-dispatch.test.ts +214 -0
- package/src/workspace/__tests__/self-driving-yaml.test.ts +114 -0
- package/src/workspace/__tests__/shared-worktree-refcount.test.ts +154 -0
- package/src/workspace/__tests__/standalone-mode.test.ts +118 -0
- package/src/workspace/__tests__/workspace-manager-v3.test.ts +245 -0
- package/src/workspace/__tests__/yaml-schema.test.ts +210 -0
- package/src/workspace/config.ts +11 -11
- package/src/workspace/git-cascade-adapter.ts +1213 -0
- package/src/workspace/index.ts +11 -11
- package/src/workspace/landing/__tests__/strategies.test.ts +184 -0
- package/src/workspace/landing/direct-push.ts +91 -0
- package/src/workspace/landing/index.ts +40 -0
- package/src/workspace/landing/merge-to-parent.ts +229 -0
- package/src/workspace/landing/optimistic-push.ts +36 -0
- package/src/workspace/landing/queue-to-branch.ts +108 -0
- package/src/workspace/merge-queue/merge-queue.ts +10 -0
- package/src/workspace/merge-queue/types.ts +16 -2
- package/src/workspace/pool/__tests__/worktree-pool.integration.test.ts +5 -5
- package/src/workspace/pool/types.ts +1 -0
- package/src/workspace/pool/worktree-pool.ts +1 -0
- package/src/workspace/recovery/__tests__/auto-resolve-integration.test.ts +127 -0
- package/src/workspace/recovery/__tests__/spawn-resolver.test.ts +139 -0
- package/src/workspace/recovery/__tests__/strategies.test.ts +145 -0
- package/src/workspace/recovery/abandon.ts +51 -0
- package/src/workspace/recovery/auto-resolve.ts +119 -0
- package/src/workspace/recovery/defer.ts +23 -0
- package/src/workspace/recovery/escalate.ts +30 -0
- package/src/workspace/recovery/index.ts +58 -0
- package/src/workspace/recovery/spawn-resolver.ts +152 -0
- package/src/workspace/recovery/types.ts +54 -0
- package/src/workspace/topology/__tests__/yaml-driven.test.ts +345 -0
- package/src/workspace/topology/index.ts +18 -0
- package/src/workspace/topology/no-workspace.ts +39 -0
- package/src/workspace/topology/types.ts +116 -0
- package/src/workspace/topology/yaml-driven.ts +316 -0
- package/src/workspace/types-v3.ts +162 -0
- package/src/workspace/types.ts +211 -20
- package/src/workspace/workspace-manager.ts +533 -19
- package/src/workspace/yaml-schema.ts +216 -0
- package/dist/workspace/dataplane-adapter.d.ts +0 -260
- package/dist/workspace/dataplane-adapter.d.ts.map +0 -1
- package/dist/workspace/dataplane-adapter.js +0 -416
- package/dist/workspace/dataplane-adapter.js.map +0 -1
- 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
|
+
});
|