@os-eco/overstory-cli 0.6.1 → 0.6.5
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/README.md +8 -7
- package/package.json +12 -4
- package/src/agents/checkpoint.test.ts +2 -2
- package/src/agents/hooks-deployer.test.ts +131 -16
- package/src/agents/hooks-deployer.ts +33 -1
- package/src/agents/identity.test.ts +27 -27
- package/src/agents/identity.ts +10 -10
- package/src/agents/lifecycle.test.ts +6 -6
- package/src/agents/lifecycle.ts +2 -2
- package/src/agents/manifest.test.ts +86 -0
- package/src/agents/overlay.test.ts +9 -9
- package/src/agents/overlay.ts +4 -4
- package/src/commands/agents.test.ts +8 -8
- package/src/commands/agents.ts +62 -91
- package/src/commands/clean.test.ts +36 -51
- package/src/commands/clean.ts +28 -49
- package/src/commands/completions.ts +14 -0
- package/src/commands/coordinator.test.ts +133 -26
- package/src/commands/coordinator.ts +101 -64
- package/src/commands/costs.test.ts +47 -47
- package/src/commands/costs.ts +96 -75
- package/src/commands/dashboard.test.ts +2 -2
- package/src/commands/dashboard.ts +75 -95
- package/src/commands/doctor.test.ts +2 -2
- package/src/commands/doctor.ts +92 -79
- package/src/commands/errors.test.ts +2 -2
- package/src/commands/errors.ts +56 -50
- package/src/commands/feed.test.ts +2 -2
- package/src/commands/feed.ts +86 -83
- package/src/commands/group.ts +167 -177
- package/src/commands/hooks.test.ts +2 -2
- package/src/commands/hooks.ts +52 -42
- package/src/commands/init.test.ts +19 -19
- package/src/commands/init.ts +7 -16
- package/src/commands/inspect.test.ts +18 -18
- package/src/commands/inspect.ts +55 -58
- package/src/commands/log.test.ts +26 -31
- package/src/commands/log.ts +97 -91
- package/src/commands/logs.test.ts +1 -1
- package/src/commands/logs.ts +101 -104
- package/src/commands/mail.test.ts +5 -5
- package/src/commands/mail.ts +157 -169
- package/src/commands/merge.test.ts +28 -66
- package/src/commands/merge.ts +21 -51
- package/src/commands/metrics.test.ts +8 -8
- package/src/commands/metrics.ts +34 -35
- package/src/commands/monitor.test.ts +3 -3
- package/src/commands/monitor.ts +57 -62
- package/src/commands/nudge.test.ts +1 -1
- package/src/commands/nudge.ts +41 -89
- package/src/commands/prime.test.ts +19 -51
- package/src/commands/prime.ts +13 -50
- package/src/commands/replay.test.ts +2 -2
- package/src/commands/replay.ts +79 -86
- package/src/commands/run.test.ts +1 -1
- package/src/commands/run.ts +97 -77
- package/src/commands/sling.test.ts +201 -5
- package/src/commands/sling.ts +37 -64
- package/src/commands/spec.test.ts +14 -40
- package/src/commands/spec.ts +32 -101
- package/src/commands/status.test.ts +97 -1
- package/src/commands/status.ts +63 -58
- package/src/commands/stop.test.ts +22 -40
- package/src/commands/stop.ts +18 -33
- package/src/commands/supervisor.test.ts +12 -14
- package/src/commands/supervisor.ts +144 -165
- package/src/commands/trace.test.ts +15 -15
- package/src/commands/trace.ts +59 -82
- package/src/commands/watch.test.ts +2 -2
- package/src/commands/watch.ts +38 -45
- package/src/commands/worktree.test.ts +213 -37
- package/src/commands/worktree.ts +110 -55
- package/src/config.test.ts +96 -0
- package/src/doctor/consistency.test.ts +14 -14
- package/src/doctor/databases.test.ts +22 -2
- package/src/doctor/databases.ts +16 -0
- package/src/doctor/dependencies.test.ts +55 -1
- package/src/doctor/dependencies.ts +113 -18
- package/src/doctor/merge-queue.test.ts +4 -4
- package/src/e2e/init-sling-lifecycle.test.ts +8 -8
- package/src/errors.ts +1 -1
- package/src/index.ts +223 -213
- package/src/logging/color.test.ts +74 -91
- package/src/logging/color.ts +52 -46
- package/src/logging/reporter.test.ts +10 -10
- package/src/logging/reporter.ts +6 -5
- package/src/mail/broadcast.test.ts +1 -1
- package/src/mail/client.test.ts +6 -6
- package/src/mail/store.test.ts +3 -3
- package/src/merge/queue.test.ts +73 -7
- package/src/merge/queue.ts +17 -2
- package/src/merge/resolver.test.ts +159 -7
- package/src/merge/resolver.ts +46 -2
- package/src/metrics/store.test.ts +44 -44
- package/src/metrics/store.ts +2 -2
- package/src/metrics/summary.test.ts +35 -35
- package/src/mulch/client.test.ts +1 -1
- package/src/schema-consistency.test.ts +239 -0
- package/src/sessions/compat.test.ts +3 -3
- package/src/sessions/compat.ts +2 -2
- package/src/sessions/store.test.ts +41 -4
- package/src/sessions/store.ts +13 -2
- package/src/types.ts +14 -14
- package/src/watchdog/daemon.test.ts +10 -10
- package/src/watchdog/daemon.ts +1 -1
- package/src/watchdog/health.test.ts +1 -1
- package/src/worktree/manager.test.ts +20 -20
- package/src/worktree/manager.ts +120 -4
- package/src/worktree/tmux.test.ts +98 -9
- package/src/worktree/tmux.ts +18 -0
|
@@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { existsSync, realpathSync } from "node:fs";
|
|
3
3
|
import { mkdir } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { ValidationError } from "../errors.ts";
|
|
5
6
|
import { createSessionStore } from "../sessions/store.ts";
|
|
6
7
|
import { cleanupTempDir, commitFile, createTempGitRepo, runGitInDir } from "../test-helpers.ts";
|
|
7
8
|
import type { AgentSession } from "../types.ts";
|
|
@@ -67,7 +68,7 @@ describe("worktreeCommand", () => {
|
|
|
67
68
|
capability: "builder",
|
|
68
69
|
worktreePath: join(tempDir, ".overstory", "worktrees", "test-agent"),
|
|
69
70
|
branchName: "overstory/test-agent/task-1",
|
|
70
|
-
|
|
71
|
+
taskId: "task-1",
|
|
71
72
|
tmuxSession: "overstory-test-agent-fake", // FAKE tmux session name
|
|
72
73
|
state: "working",
|
|
73
74
|
pid: 12345,
|
|
@@ -99,30 +100,29 @@ describe("worktreeCommand", () => {
|
|
|
99
100
|
await worktreeCommand(["--help"]);
|
|
100
101
|
const out = output();
|
|
101
102
|
|
|
102
|
-
expect(out).toContain("
|
|
103
|
+
expect(out).toContain("worktree");
|
|
103
104
|
expect(out).toContain("list");
|
|
104
105
|
expect(out).toContain("clean");
|
|
105
|
-
expect(out).toContain("--json");
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
test("-h shows help text", async () => {
|
|
109
109
|
await worktreeCommand(["-h"]);
|
|
110
110
|
const out = output();
|
|
111
111
|
|
|
112
|
-
expect(out).toContain("
|
|
112
|
+
expect(out).toContain("worktree");
|
|
113
113
|
expect(out).toContain("list");
|
|
114
114
|
});
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
describe("validation", () => {
|
|
118
118
|
test("unknown subcommand throws ValidationError", async () => {
|
|
119
|
-
await expect(worktreeCommand(["unknown"])).rejects.toThrow(
|
|
120
|
-
"Unknown worktree subcommand: unknown",
|
|
121
|
-
);
|
|
119
|
+
await expect(worktreeCommand(["unknown"])).rejects.toThrow(ValidationError);
|
|
122
120
|
});
|
|
123
121
|
|
|
124
|
-
test("
|
|
125
|
-
await
|
|
122
|
+
test("empty args shows help text", async () => {
|
|
123
|
+
await worktreeCommand([]);
|
|
124
|
+
const out = output();
|
|
125
|
+
expect(out).toContain("worktree");
|
|
126
126
|
});
|
|
127
127
|
});
|
|
128
128
|
|
|
@@ -156,7 +156,7 @@ describe("worktreeCommand", () => {
|
|
|
156
156
|
capability: "builder",
|
|
157
157
|
worktreePath,
|
|
158
158
|
branchName: "overstory/test-agent/task-1",
|
|
159
|
-
|
|
159
|
+
taskId: "task-1",
|
|
160
160
|
tmuxSession: "overstory-test-agent",
|
|
161
161
|
state: "working",
|
|
162
162
|
pid: 12345,
|
|
@@ -203,7 +203,7 @@ describe("worktreeCommand", () => {
|
|
|
203
203
|
capability: "builder",
|
|
204
204
|
worktreePath,
|
|
205
205
|
branchName: "overstory/test-agent/task-1",
|
|
206
|
-
|
|
206
|
+
taskId: "task-1",
|
|
207
207
|
tmuxSession: "overstory-test-agent",
|
|
208
208
|
state: "working",
|
|
209
209
|
pid: 12345,
|
|
@@ -226,7 +226,7 @@ describe("worktreeCommand", () => {
|
|
|
226
226
|
head: string;
|
|
227
227
|
agentName: string | null;
|
|
228
228
|
state: string | null;
|
|
229
|
-
|
|
229
|
+
taskId: string | null;
|
|
230
230
|
}>;
|
|
231
231
|
|
|
232
232
|
expect(parsed).toHaveLength(1);
|
|
@@ -234,7 +234,7 @@ describe("worktreeCommand", () => {
|
|
|
234
234
|
expect(parsed[0]?.branch).toBe("overstory/test-agent/task-1");
|
|
235
235
|
expect(parsed[0]?.agentName).toBe("test-agent");
|
|
236
236
|
expect(parsed[0]?.state).toBe("working");
|
|
237
|
-
expect(parsed[0]?.
|
|
237
|
+
expect(parsed[0]?.taskId).toBe("task-1");
|
|
238
238
|
});
|
|
239
239
|
|
|
240
240
|
test("worktrees without sessions show unknown state", async () => {
|
|
@@ -291,7 +291,7 @@ describe("worktreeCommand", () => {
|
|
|
291
291
|
capability: "builder",
|
|
292
292
|
worktreePath,
|
|
293
293
|
branchName: "overstory/completed-agent/task-done",
|
|
294
|
-
|
|
294
|
+
taskId: "task-done",
|
|
295
295
|
tmuxSession: "overstory-completed-agent",
|
|
296
296
|
state: "completed",
|
|
297
297
|
pid: 12345,
|
|
@@ -345,7 +345,7 @@ describe("worktreeCommand", () => {
|
|
|
345
345
|
capability: "builder",
|
|
346
346
|
worktreePath,
|
|
347
347
|
branchName: "overstory/done-agent/task-x",
|
|
348
|
-
|
|
348
|
+
taskId: "task-x",
|
|
349
349
|
tmuxSession: "overstory-done-agent",
|
|
350
350
|
state: "completed",
|
|
351
351
|
pid: 12345,
|
|
@@ -383,7 +383,7 @@ describe("worktreeCommand", () => {
|
|
|
383
383
|
capability: "builder",
|
|
384
384
|
worktreePath: nonExistentPath,
|
|
385
385
|
branchName: "overstory/ghost-agent/task-ghost",
|
|
386
|
-
|
|
386
|
+
taskId: "task-ghost",
|
|
387
387
|
tmuxSession: "overstory-ghost-agent",
|
|
388
388
|
state: "zombie",
|
|
389
389
|
pid: null,
|
|
@@ -437,7 +437,7 @@ describe("worktreeCommand", () => {
|
|
|
437
437
|
capability: "builder",
|
|
438
438
|
worktreePath,
|
|
439
439
|
branchName: "overstory/stalled-agent/task-stuck",
|
|
440
|
-
|
|
440
|
+
taskId: "task-stuck",
|
|
441
441
|
tmuxSession: "overstory-stalled-agent",
|
|
442
442
|
state: "stalled",
|
|
443
443
|
pid: 12345,
|
|
@@ -468,7 +468,7 @@ describe("worktreeCommand", () => {
|
|
|
468
468
|
baseDir: worktreesDir,
|
|
469
469
|
agentName: "completed-agent",
|
|
470
470
|
baseBranch: "main",
|
|
471
|
-
|
|
471
|
+
taskId: "task-done",
|
|
472
472
|
});
|
|
473
473
|
|
|
474
474
|
const { path: workingPath } = await createWorktree({
|
|
@@ -476,7 +476,7 @@ describe("worktreeCommand", () => {
|
|
|
476
476
|
baseDir: worktreesDir,
|
|
477
477
|
agentName: "working-agent",
|
|
478
478
|
baseBranch: "main",
|
|
479
|
-
|
|
479
|
+
taskId: "task-wip",
|
|
480
480
|
});
|
|
481
481
|
|
|
482
482
|
// Write sessions.db with both agents
|
|
@@ -486,7 +486,7 @@ describe("worktreeCommand", () => {
|
|
|
486
486
|
agentName: "completed-agent",
|
|
487
487
|
worktreePath: completedPath,
|
|
488
488
|
branchName: "overstory/completed-agent/task-done",
|
|
489
|
-
|
|
489
|
+
taskId: "task-done",
|
|
490
490
|
tmuxSession: "overstory-completed-agent-fake",
|
|
491
491
|
state: "completed",
|
|
492
492
|
}),
|
|
@@ -495,7 +495,7 @@ describe("worktreeCommand", () => {
|
|
|
495
495
|
agentName: "working-agent",
|
|
496
496
|
worktreePath: workingPath,
|
|
497
497
|
branchName: "overstory/working-agent/task-wip",
|
|
498
|
-
|
|
498
|
+
taskId: "task-wip",
|
|
499
499
|
tmuxSession: "overstory-working-agent-fake",
|
|
500
500
|
state: "working",
|
|
501
501
|
pid: 12346,
|
|
@@ -522,7 +522,7 @@ describe("worktreeCommand", () => {
|
|
|
522
522
|
baseDir: worktreesDir,
|
|
523
523
|
agentName: "completed-agent",
|
|
524
524
|
baseBranch: "main",
|
|
525
|
-
|
|
525
|
+
taskId: "task-done",
|
|
526
526
|
});
|
|
527
527
|
|
|
528
528
|
const { path: workingPath } = await createWorktree({
|
|
@@ -530,7 +530,7 @@ describe("worktreeCommand", () => {
|
|
|
530
530
|
baseDir: worktreesDir,
|
|
531
531
|
agentName: "working-agent",
|
|
532
532
|
baseBranch: "main",
|
|
533
|
-
|
|
533
|
+
taskId: "task-wip",
|
|
534
534
|
});
|
|
535
535
|
|
|
536
536
|
const { path: stalledPath } = await createWorktree({
|
|
@@ -538,7 +538,7 @@ describe("worktreeCommand", () => {
|
|
|
538
538
|
baseDir: worktreesDir,
|
|
539
539
|
agentName: "stalled-agent",
|
|
540
540
|
baseBranch: "main",
|
|
541
|
-
|
|
541
|
+
taskId: "task-stuck",
|
|
542
542
|
});
|
|
543
543
|
|
|
544
544
|
// Write sessions with different states
|
|
@@ -548,7 +548,7 @@ describe("worktreeCommand", () => {
|
|
|
548
548
|
agentName: "completed-agent",
|
|
549
549
|
worktreePath: completedPath,
|
|
550
550
|
branchName: "overstory/completed-agent/task-done",
|
|
551
|
-
|
|
551
|
+
taskId: "task-done",
|
|
552
552
|
state: "completed",
|
|
553
553
|
}),
|
|
554
554
|
makeSession({
|
|
@@ -556,7 +556,7 @@ describe("worktreeCommand", () => {
|
|
|
556
556
|
agentName: "working-agent",
|
|
557
557
|
worktreePath: workingPath,
|
|
558
558
|
branchName: "overstory/working-agent/task-wip",
|
|
559
|
-
|
|
559
|
+
taskId: "task-wip",
|
|
560
560
|
state: "working",
|
|
561
561
|
}),
|
|
562
562
|
makeSession({
|
|
@@ -564,7 +564,7 @@ describe("worktreeCommand", () => {
|
|
|
564
564
|
agentName: "stalled-agent",
|
|
565
565
|
worktreePath: stalledPath,
|
|
566
566
|
branchName: "overstory/stalled-agent/task-stuck",
|
|
567
|
-
|
|
567
|
+
taskId: "task-stuck",
|
|
568
568
|
state: "stalled",
|
|
569
569
|
}),
|
|
570
570
|
]);
|
|
@@ -598,7 +598,7 @@ describe("worktreeCommand", () => {
|
|
|
598
598
|
capability: "builder",
|
|
599
599
|
worktreePath: path1,
|
|
600
600
|
branchName: "overstory/agent-1/task-1",
|
|
601
|
-
|
|
601
|
+
taskId: "task-1",
|
|
602
602
|
tmuxSession: "overstory-agent-1",
|
|
603
603
|
state: "completed",
|
|
604
604
|
pid: 12345,
|
|
@@ -616,7 +616,7 @@ describe("worktreeCommand", () => {
|
|
|
616
616
|
capability: "builder",
|
|
617
617
|
worktreePath: path2,
|
|
618
618
|
branchName: "overstory/agent-2/task-2",
|
|
619
|
-
|
|
619
|
+
taskId: "task-2",
|
|
620
620
|
tmuxSession: "overstory-agent-2",
|
|
621
621
|
state: "completed",
|
|
622
622
|
pid: 12346,
|
|
@@ -645,7 +645,7 @@ describe("worktreeCommand", () => {
|
|
|
645
645
|
baseDir: worktreesDir,
|
|
646
646
|
agentName: "unmerged-agent",
|
|
647
647
|
baseBranch: "main",
|
|
648
|
-
|
|
648
|
+
taskId: "task-unmerged",
|
|
649
649
|
});
|
|
650
650
|
|
|
651
651
|
// Add an unmerged commit
|
|
@@ -657,7 +657,7 @@ describe("worktreeCommand", () => {
|
|
|
657
657
|
agentName: "unmerged-agent",
|
|
658
658
|
worktreePath: wtPath,
|
|
659
659
|
branchName: "overstory/unmerged-agent/task-unmerged",
|
|
660
|
-
|
|
660
|
+
taskId: "task-unmerged",
|
|
661
661
|
state: "completed",
|
|
662
662
|
}),
|
|
663
663
|
]);
|
|
@@ -682,7 +682,7 @@ describe("worktreeCommand", () => {
|
|
|
682
682
|
baseDir: worktreesDir,
|
|
683
683
|
agentName: "unmerged-agent",
|
|
684
684
|
baseBranch: "main",
|
|
685
|
-
|
|
685
|
+
taskId: "task-force",
|
|
686
686
|
});
|
|
687
687
|
|
|
688
688
|
// Add an unmerged commit
|
|
@@ -694,7 +694,7 @@ describe("worktreeCommand", () => {
|
|
|
694
694
|
agentName: "unmerged-agent",
|
|
695
695
|
worktreePath: wtPath,
|
|
696
696
|
branchName: "overstory/unmerged-agent/task-force",
|
|
697
|
-
|
|
697
|
+
taskId: "task-force",
|
|
698
698
|
state: "completed",
|
|
699
699
|
}),
|
|
700
700
|
]);
|
|
@@ -716,7 +716,7 @@ describe("worktreeCommand", () => {
|
|
|
716
716
|
baseDir: worktreesDir,
|
|
717
717
|
agentName: "merged-agent",
|
|
718
718
|
baseBranch: "main",
|
|
719
|
-
|
|
719
|
+
taskId: "task-merged",
|
|
720
720
|
});
|
|
721
721
|
|
|
722
722
|
// Add a commit and merge it into main
|
|
@@ -729,7 +729,7 @@ describe("worktreeCommand", () => {
|
|
|
729
729
|
agentName: "merged-agent",
|
|
730
730
|
worktreePath: wtPath,
|
|
731
731
|
branchName: branch,
|
|
732
|
-
|
|
732
|
+
taskId: "task-merged",
|
|
733
733
|
state: "completed",
|
|
734
734
|
}),
|
|
735
735
|
]);
|
|
@@ -751,7 +751,7 @@ describe("worktreeCommand", () => {
|
|
|
751
751
|
baseDir: worktreesDir,
|
|
752
752
|
agentName: "unmerged-json-agent",
|
|
753
753
|
baseBranch: "main",
|
|
754
|
-
|
|
754
|
+
taskId: "task-json",
|
|
755
755
|
});
|
|
756
756
|
|
|
757
757
|
// Add an unmerged commit
|
|
@@ -763,7 +763,7 @@ describe("worktreeCommand", () => {
|
|
|
763
763
|
agentName: "unmerged-json-agent",
|
|
764
764
|
worktreePath: wtPath,
|
|
765
765
|
branchName: "overstory/unmerged-json-agent/task-json",
|
|
766
|
-
|
|
766
|
+
taskId: "task-json",
|
|
767
767
|
state: "completed",
|
|
768
768
|
}),
|
|
769
769
|
]);
|
|
@@ -782,5 +782,181 @@ describe("worktreeCommand", () => {
|
|
|
782
782
|
expect(parsed.cleaned).toEqual([]);
|
|
783
783
|
expect(parsed.skipped).toEqual(["overstory/unmerged-json-agent/task-json"]);
|
|
784
784
|
});
|
|
785
|
+
|
|
786
|
+
test("lead worktree with .seeds/ changes preserves them to canonical before cleanup", async () => {
|
|
787
|
+
const worktreesDir = join(tempDir, ".overstory", "worktrees");
|
|
788
|
+
await mkdir(worktreesDir, { recursive: true });
|
|
789
|
+
|
|
790
|
+
const { path: wtPath, branch } = await createWorktree({
|
|
791
|
+
repoRoot: tempDir,
|
|
792
|
+
baseDir: worktreesDir,
|
|
793
|
+
agentName: "lead-with-seeds",
|
|
794
|
+
baseBranch: "main",
|
|
795
|
+
taskId: "task-lead-seeds",
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
// Commit a .seeds/ file in the lead worktree
|
|
799
|
+
await commitFile(
|
|
800
|
+
wtPath,
|
|
801
|
+
".seeds/issues/test-issue.yaml",
|
|
802
|
+
"id: test-issue\ntitle: Test Issue\nstatus: open\n",
|
|
803
|
+
"seeds: add test issue",
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
writeSessionsToStore([
|
|
807
|
+
makeSession({
|
|
808
|
+
id: "session-lead-seeds",
|
|
809
|
+
agentName: "lead-with-seeds",
|
|
810
|
+
capability: "lead",
|
|
811
|
+
worktreePath: wtPath,
|
|
812
|
+
branchName: branch,
|
|
813
|
+
taskId: "task-lead-seeds",
|
|
814
|
+
state: "completed",
|
|
815
|
+
}),
|
|
816
|
+
]);
|
|
817
|
+
|
|
818
|
+
await worktreeCommand(["clean"]);
|
|
819
|
+
const out = output();
|
|
820
|
+
|
|
821
|
+
// The worktree should be removed
|
|
822
|
+
expect(existsSync(wtPath)).toBe(false);
|
|
823
|
+
|
|
824
|
+
// The .seeds/ changes should have been preserved to main
|
|
825
|
+
const showProc = Bun.spawn(["git", "show", "main:.seeds/issues/test-issue.yaml"], {
|
|
826
|
+
cwd: tempDir,
|
|
827
|
+
stdout: "pipe",
|
|
828
|
+
stderr: "pipe",
|
|
829
|
+
});
|
|
830
|
+
const showOut = await new Response(showProc.stdout).text();
|
|
831
|
+
const showExit = await showProc.exited;
|
|
832
|
+
expect(showExit).toBe(0);
|
|
833
|
+
expect(showOut).toContain("test-issue");
|
|
834
|
+
|
|
835
|
+
// Output should mention preservation
|
|
836
|
+
expect(out).toContain("Preserved .seeds/");
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
test("lead worktree without .seeds/ changes cleans normally", async () => {
|
|
840
|
+
const worktreesDir = join(tempDir, ".overstory", "worktrees");
|
|
841
|
+
await mkdir(worktreesDir, { recursive: true });
|
|
842
|
+
|
|
843
|
+
const { path: wtPath, branch } = await createWorktree({
|
|
844
|
+
repoRoot: tempDir,
|
|
845
|
+
baseDir: worktreesDir,
|
|
846
|
+
agentName: "lead-no-seeds",
|
|
847
|
+
baseBranch: "main",
|
|
848
|
+
taskId: "task-lead-no-seeds",
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
// Commit a non-.seeds/ file
|
|
852
|
+
await commitFile(wtPath, "src/work.ts", "export const x = 1;", "non-seeds work");
|
|
853
|
+
|
|
854
|
+
writeSessionsToStore([
|
|
855
|
+
makeSession({
|
|
856
|
+
id: "session-lead-no-seeds",
|
|
857
|
+
agentName: "lead-no-seeds",
|
|
858
|
+
capability: "lead",
|
|
859
|
+
worktreePath: wtPath,
|
|
860
|
+
branchName: branch,
|
|
861
|
+
taskId: "task-lead-no-seeds",
|
|
862
|
+
state: "completed",
|
|
863
|
+
}),
|
|
864
|
+
]);
|
|
865
|
+
|
|
866
|
+
await worktreeCommand(["clean"]);
|
|
867
|
+
const out = output();
|
|
868
|
+
|
|
869
|
+
// Worktree should be removed
|
|
870
|
+
expect(existsSync(wtPath)).toBe(false);
|
|
871
|
+
// Output should NOT mention .seeds/ preservation
|
|
872
|
+
expect(out).not.toContain("Preserved .seeds/");
|
|
873
|
+
// Should still report as cleaned
|
|
874
|
+
expect(out).toContain("Cleaned 1 worktree");
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
test("lead worktrees are cleaned without --force even with unmerged non-seeds changes", async () => {
|
|
878
|
+
const worktreesDir = join(tempDir, ".overstory", "worktrees");
|
|
879
|
+
await mkdir(worktreesDir, { recursive: true });
|
|
880
|
+
|
|
881
|
+
const { path: wtPath, branch } = await createWorktree({
|
|
882
|
+
repoRoot: tempDir,
|
|
883
|
+
baseDir: worktreesDir,
|
|
884
|
+
agentName: "lead-unmerged",
|
|
885
|
+
baseBranch: "main",
|
|
886
|
+
taskId: "task-lead-unmerged",
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
// Add unmerged non-.seeds/ commit
|
|
890
|
+
await commitFile(wtPath, "src/lead-work.ts", "export const y = 2;", "unmerged lead work");
|
|
891
|
+
|
|
892
|
+
writeSessionsToStore([
|
|
893
|
+
makeSession({
|
|
894
|
+
id: "session-lead-unmerged",
|
|
895
|
+
agentName: "lead-unmerged",
|
|
896
|
+
capability: "lead",
|
|
897
|
+
worktreePath: wtPath,
|
|
898
|
+
branchName: branch,
|
|
899
|
+
taskId: "task-lead-unmerged",
|
|
900
|
+
state: "completed",
|
|
901
|
+
}),
|
|
902
|
+
]);
|
|
903
|
+
|
|
904
|
+
// Run clean WITHOUT --force — leads bypass merge check
|
|
905
|
+
await worktreeCommand(["clean"]);
|
|
906
|
+
const out = output();
|
|
907
|
+
|
|
908
|
+
// Lead worktree SHOULD be removed (not skipped)
|
|
909
|
+
expect(existsSync(wtPath)).toBe(false);
|
|
910
|
+
expect(out).toContain("Cleaned 1 worktree");
|
|
911
|
+
expect(out).not.toContain("Skipped");
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
test("--json output includes seedsPreserved array", async () => {
|
|
915
|
+
const worktreesDir = join(tempDir, ".overstory", "worktrees");
|
|
916
|
+
await mkdir(worktreesDir, { recursive: true });
|
|
917
|
+
|
|
918
|
+
const { path: wtPath, branch } = await createWorktree({
|
|
919
|
+
repoRoot: tempDir,
|
|
920
|
+
baseDir: worktreesDir,
|
|
921
|
+
agentName: "lead-seeds-json",
|
|
922
|
+
baseBranch: "main",
|
|
923
|
+
taskId: "task-seeds-json",
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
// Commit a .seeds/ file in the lead worktree
|
|
927
|
+
await commitFile(
|
|
928
|
+
wtPath,
|
|
929
|
+
".seeds/issues/json-issue.yaml",
|
|
930
|
+
"id: json-issue\ntitle: JSON Issue\nstatus: open\n",
|
|
931
|
+
"seeds: add json issue",
|
|
932
|
+
);
|
|
933
|
+
|
|
934
|
+
writeSessionsToStore([
|
|
935
|
+
makeSession({
|
|
936
|
+
id: "session-lead-json",
|
|
937
|
+
agentName: "lead-seeds-json",
|
|
938
|
+
capability: "lead",
|
|
939
|
+
worktreePath: wtPath,
|
|
940
|
+
branchName: branch,
|
|
941
|
+
taskId: "task-seeds-json",
|
|
942
|
+
state: "completed",
|
|
943
|
+
}),
|
|
944
|
+
]);
|
|
945
|
+
|
|
946
|
+
await worktreeCommand(["clean", "--json"]);
|
|
947
|
+
const out = output();
|
|
948
|
+
|
|
949
|
+
const parsed = JSON.parse(out.trim()) as {
|
|
950
|
+
cleaned: string[];
|
|
951
|
+
failed: string[];
|
|
952
|
+
skipped: string[];
|
|
953
|
+
pruned: number;
|
|
954
|
+
mailPurged: number;
|
|
955
|
+
seedsPreserved: string[];
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
expect(parsed.cleaned).toContain(branch);
|
|
959
|
+
expect(parsed.seedsPreserved).toContain(branch);
|
|
960
|
+
});
|
|
785
961
|
});
|
|
786
962
|
});
|