@katyella/legio 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +422 -0
- package/LICENSE +21 -0
- package/README.md +555 -0
- package/agents/builder.md +141 -0
- package/agents/coordinator.md +351 -0
- package/agents/cto.md +196 -0
- package/agents/gateway.md +276 -0
- package/agents/lead.md +281 -0
- package/agents/merger.md +156 -0
- package/agents/monitor.md +212 -0
- package/agents/reviewer.md +142 -0
- package/agents/scout.md +131 -0
- package/agents/supervisor.md +416 -0
- package/bin/legio.mjs +38 -0
- package/package.json +77 -0
- package/src/agents/checkpoint.test.ts +88 -0
- package/src/agents/checkpoint.ts +102 -0
- package/src/agents/hooks-deployer.test.ts +1820 -0
- package/src/agents/hooks-deployer.ts +574 -0
- package/src/agents/identity.test.ts +614 -0
- package/src/agents/identity.ts +385 -0
- package/src/agents/lifecycle.test.ts +202 -0
- package/src/agents/lifecycle.ts +184 -0
- package/src/agents/manifest.test.ts +558 -0
- package/src/agents/manifest.ts +297 -0
- package/src/agents/overlay.test.ts +592 -0
- package/src/agents/overlay.ts +316 -0
- package/src/beads/client.test.ts +210 -0
- package/src/beads/client.ts +227 -0
- package/src/beads/molecules.test.ts +320 -0
- package/src/beads/molecules.ts +209 -0
- package/src/commands/agents.test.ts +325 -0
- package/src/commands/agents.ts +286 -0
- package/src/commands/clean.test.ts +730 -0
- package/src/commands/clean.ts +653 -0
- package/src/commands/completions.test.ts +346 -0
- package/src/commands/completions.ts +950 -0
- package/src/commands/coordinator.test.ts +1524 -0
- package/src/commands/coordinator.ts +880 -0
- package/src/commands/costs.test.ts +1015 -0
- package/src/commands/costs.ts +473 -0
- package/src/commands/dashboard.test.ts +94 -0
- package/src/commands/dashboard.ts +607 -0
- package/src/commands/doctor.test.ts +295 -0
- package/src/commands/doctor.ts +213 -0
- package/src/commands/down.test.ts +308 -0
- package/src/commands/down.ts +124 -0
- package/src/commands/errors.test.ts +648 -0
- package/src/commands/errors.ts +255 -0
- package/src/commands/feed.test.ts +579 -0
- package/src/commands/feed.ts +368 -0
- package/src/commands/gateway.test.ts +698 -0
- package/src/commands/gateway.ts +419 -0
- package/src/commands/group.test.ts +262 -0
- package/src/commands/group.ts +539 -0
- package/src/commands/hooks.test.ts +292 -0
- package/src/commands/hooks.ts +210 -0
- package/src/commands/init.test.ts +211 -0
- package/src/commands/init.ts +622 -0
- package/src/commands/inspect.test.ts +670 -0
- package/src/commands/inspect.ts +455 -0
- package/src/commands/log.test.ts +1556 -0
- package/src/commands/log.ts +752 -0
- package/src/commands/logs.test.ts +379 -0
- package/src/commands/logs.ts +544 -0
- package/src/commands/mail.test.ts +1726 -0
- package/src/commands/mail.ts +926 -0
- package/src/commands/merge.test.ts +676 -0
- package/src/commands/merge.ts +374 -0
- package/src/commands/metrics.test.ts +444 -0
- package/src/commands/metrics.ts +150 -0
- package/src/commands/monitor.test.ts +151 -0
- package/src/commands/monitor.ts +394 -0
- package/src/commands/nudge.test.ts +230 -0
- package/src/commands/nudge.ts +373 -0
- package/src/commands/prime.test.ts +467 -0
- package/src/commands/prime.ts +386 -0
- package/src/commands/replay.test.ts +742 -0
- package/src/commands/replay.ts +367 -0
- package/src/commands/run.test.ts +443 -0
- package/src/commands/run.ts +365 -0
- package/src/commands/server.test.ts +626 -0
- package/src/commands/server.ts +298 -0
- package/src/commands/sling.test.ts +810 -0
- package/src/commands/sling.ts +700 -0
- package/src/commands/spec.test.ts +206 -0
- package/src/commands/spec.ts +171 -0
- package/src/commands/status.test.ts +276 -0
- package/src/commands/status.ts +339 -0
- package/src/commands/stop.test.ts +357 -0
- package/src/commands/stop.ts +119 -0
- package/src/commands/supervisor.test.ts +186 -0
- package/src/commands/supervisor.ts +544 -0
- package/src/commands/trace.test.ts +746 -0
- package/src/commands/trace.ts +332 -0
- package/src/commands/up.test.ts +597 -0
- package/src/commands/up.ts +275 -0
- package/src/commands/watch.test.ts +152 -0
- package/src/commands/watch.ts +238 -0
- package/src/commands/worktree.test.ts +648 -0
- package/src/commands/worktree.ts +266 -0
- package/src/config.test.ts +496 -0
- package/src/config.ts +616 -0
- package/src/doctor/agents.test.ts +448 -0
- package/src/doctor/agents.ts +396 -0
- package/src/doctor/config-check.test.ts +184 -0
- package/src/doctor/config-check.ts +185 -0
- package/src/doctor/consistency.test.ts +645 -0
- package/src/doctor/consistency.ts +294 -0
- package/src/doctor/databases.test.ts +284 -0
- package/src/doctor/databases.ts +211 -0
- package/src/doctor/dependencies.test.ts +150 -0
- package/src/doctor/dependencies.ts +179 -0
- package/src/doctor/logs.test.ts +244 -0
- package/src/doctor/logs.ts +295 -0
- package/src/doctor/merge-queue.test.ts +210 -0
- package/src/doctor/merge-queue.ts +144 -0
- package/src/doctor/structure.test.ts +285 -0
- package/src/doctor/structure.ts +195 -0
- package/src/doctor/types.ts +37 -0
- package/src/doctor/version.test.ts +130 -0
- package/src/doctor/version.ts +131 -0
- package/src/e2e/chat-flow.test.ts +346 -0
- package/src/e2e/init-sling-lifecycle.test.ts +288 -0
- package/src/errors.test.ts +21 -0
- package/src/errors.ts +246 -0
- package/src/events/store.test.ts +660 -0
- package/src/events/store.ts +344 -0
- package/src/events/tool-filter.test.ts +330 -0
- package/src/events/tool-filter.ts +126 -0
- package/src/global-setup.ts +14 -0
- package/src/index.ts +339 -0
- package/src/insights/analyzer.test.ts +466 -0
- package/src/insights/analyzer.ts +203 -0
- package/src/logging/color.test.ts +118 -0
- package/src/logging/color.ts +71 -0
- package/src/logging/logger.test.ts +812 -0
- package/src/logging/logger.ts +266 -0
- package/src/logging/reporter.test.ts +258 -0
- package/src/logging/reporter.ts +109 -0
- package/src/logging/sanitizer.test.ts +190 -0
- package/src/logging/sanitizer.ts +57 -0
- package/src/mail/broadcast.test.ts +203 -0
- package/src/mail/broadcast.ts +92 -0
- package/src/mail/client.test.ts +873 -0
- package/src/mail/client.ts +236 -0
- package/src/mail/store.test.ts +815 -0
- package/src/mail/store.ts +402 -0
- package/src/merge/queue.test.ts +449 -0
- package/src/merge/queue.ts +262 -0
- package/src/merge/resolver.test.ts +1453 -0
- package/src/merge/resolver.ts +759 -0
- package/src/metrics/store.test.ts +1167 -0
- package/src/metrics/store.ts +511 -0
- package/src/metrics/summary.test.ts +397 -0
- package/src/metrics/summary.ts +178 -0
- package/src/metrics/transcript.test.ts +643 -0
- package/src/metrics/transcript.ts +351 -0
- package/src/mulch/client.test.ts +547 -0
- package/src/mulch/client.ts +416 -0
- package/src/server/audit-store.test.ts +384 -0
- package/src/server/audit-store.ts +257 -0
- package/src/server/headless.test.ts +180 -0
- package/src/server/headless.ts +151 -0
- package/src/server/index.test.ts +241 -0
- package/src/server/index.ts +317 -0
- package/src/server/public/app.js +187 -0
- package/src/server/public/apple-touch-icon.png +0 -0
- package/src/server/public/components/agent-badge.js +37 -0
- package/src/server/public/components/data-table.js +114 -0
- package/src/server/public/components/gateway-chat.js +256 -0
- package/src/server/public/components/issue-card.js +96 -0
- package/src/server/public/components/layout.js +88 -0
- package/src/server/public/components/message-bubble.js +120 -0
- package/src/server/public/components/stat-card.js +26 -0
- package/src/server/public/components/terminal-panel.js +140 -0
- package/src/server/public/favicon-16.png +0 -0
- package/src/server/public/favicon-32.png +0 -0
- package/src/server/public/favicon.ico +0 -0
- package/src/server/public/favicon.png +0 -0
- package/src/server/public/index.html +64 -0
- package/src/server/public/lib/api.js +35 -0
- package/src/server/public/lib/markdown.js +8 -0
- package/src/server/public/lib/preact-setup.js +8 -0
- package/src/server/public/lib/state.js +99 -0
- package/src/server/public/lib/utils.js +309 -0
- package/src/server/public/lib/ws.js +79 -0
- package/src/server/public/views/chat.js +983 -0
- package/src/server/public/views/costs.js +692 -0
- package/src/server/public/views/dashboard.js +781 -0
- package/src/server/public/views/gateway-chat.js +622 -0
- package/src/server/public/views/inspect.js +399 -0
- package/src/server/public/views/issues.js +470 -0
- package/src/server/public/views/setup.js +94 -0
- package/src/server/public/views/task-detail.js +422 -0
- package/src/server/routes.test.ts +3816 -0
- package/src/server/routes.ts +1964 -0
- package/src/server/websocket.test.ts +288 -0
- package/src/server/websocket.ts +196 -0
- package/src/sessions/compat.test.ts +109 -0
- package/src/sessions/compat.ts +17 -0
- package/src/sessions/store.test.ts +969 -0
- package/src/sessions/store.ts +480 -0
- package/src/test-helpers.test.ts +97 -0
- package/src/test-helpers.ts +143 -0
- package/src/types.ts +708 -0
- package/src/watchdog/daemon.test.ts +1233 -0
- package/src/watchdog/daemon.ts +533 -0
- package/src/watchdog/health.test.ts +371 -0
- package/src/watchdog/health.ts +248 -0
- package/src/watchdog/triage.test.ts +162 -0
- package/src/watchdog/triage.ts +193 -0
- package/src/worktree/manager.test.ts +444 -0
- package/src/worktree/manager.ts +224 -0
- package/src/worktree/tmux.test.ts +1238 -0
- package/src/worktree/tmux.ts +644 -0
- package/templates/CLAUDE.md.tmpl +89 -0
- package/templates/hooks.json.tmpl +132 -0
- package/templates/overlay.md.tmpl +79 -0
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for mulch CLI client.
|
|
3
|
+
*
|
|
4
|
+
* Uses real mulch CLI when available (preferred).
|
|
5
|
+
* All tests are skipped if mulch is not installed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
13
|
+
import { AgentError } from "../errors.ts";
|
|
14
|
+
import { createMulchClient, inferDomainsFromFiles } from "./client.ts";
|
|
15
|
+
|
|
16
|
+
// Check if mulch is available
|
|
17
|
+
let hasMulch = false;
|
|
18
|
+
try {
|
|
19
|
+
const result = spawnSync("which", ["mulch"], { stdio: "pipe" });
|
|
20
|
+
hasMulch = result.status === 0;
|
|
21
|
+
} catch {
|
|
22
|
+
hasMulch = false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("createMulchClient", () => {
|
|
26
|
+
let tempDir: string;
|
|
27
|
+
|
|
28
|
+
beforeEach(async () => {
|
|
29
|
+
tempDir = await mkdtemp(join(tmpdir(), "mulch-test-"));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(async () => {
|
|
33
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Helper to initialize git repo in tempDir.
|
|
38
|
+
* Some mulch commands (diff, learn) require a git repository.
|
|
39
|
+
*/
|
|
40
|
+
function initGit(): void {
|
|
41
|
+
spawnSync("git", ["init"], { cwd: tempDir, stdio: "pipe" });
|
|
42
|
+
spawnSync("git", ["config", "user.name", "Test User"], { cwd: tempDir, stdio: "pipe" });
|
|
43
|
+
spawnSync("git", ["config", "user.email", "test@example.com"], { cwd: tempDir, stdio: "pipe" });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Helper to initialize mulch in tempDir.
|
|
48
|
+
* Creates .mulch/ directory and initial structure.
|
|
49
|
+
*/
|
|
50
|
+
function initMulch(): void {
|
|
51
|
+
if (!hasMulch) return;
|
|
52
|
+
spawnSync("mulch", ["init"], { cwd: tempDir, stdio: "pipe" });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe("prime", () => {
|
|
56
|
+
test.skipIf(!hasMulch)("returns non-empty string", async () => {
|
|
57
|
+
initMulch();
|
|
58
|
+
const client = createMulchClient(tempDir);
|
|
59
|
+
const result = await client.prime();
|
|
60
|
+
expect(result).toBeTruthy();
|
|
61
|
+
expect(typeof result).toBe("string");
|
|
62
|
+
expect(result.length).toBeGreaterThan(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test.skipIf(!hasMulch)("passes domain args when provided", async () => {
|
|
66
|
+
initMulch();
|
|
67
|
+
// Add a domain first so we can prime it
|
|
68
|
+
spawnSync("mulch", ["add", "architecture"], { cwd: tempDir, stdio: "pipe" });
|
|
69
|
+
|
|
70
|
+
const client = createMulchClient(tempDir);
|
|
71
|
+
const result = await client.prime(["architecture"]);
|
|
72
|
+
expect(typeof result).toBe("string");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test.skipIf(!hasMulch)("passes --format flag", async () => {
|
|
76
|
+
initMulch();
|
|
77
|
+
const client = createMulchClient(tempDir);
|
|
78
|
+
const result = await client.prime([], "markdown");
|
|
79
|
+
expect(typeof result).toBe("string");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test.skipIf(!hasMulch)("passes both domains and format", async () => {
|
|
83
|
+
initMulch();
|
|
84
|
+
spawnSync("mulch", ["add", "architecture"], { cwd: tempDir, stdio: "pipe" });
|
|
85
|
+
|
|
86
|
+
const client = createMulchClient(tempDir);
|
|
87
|
+
const result = await client.prime(["architecture"], "xml");
|
|
88
|
+
expect(typeof result).toBe("string");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test.skipIf(!hasMulch)("passes --files flag", async () => {
|
|
92
|
+
initMulch();
|
|
93
|
+
const client = createMulchClient(tempDir);
|
|
94
|
+
const result = await client.prime([], "markdown", {
|
|
95
|
+
files: ["src/config.ts", "src/types.ts"],
|
|
96
|
+
});
|
|
97
|
+
expect(typeof result).toBe("string");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test.skipIf(!hasMulch)("passes --exclude-domain flag", async () => {
|
|
101
|
+
initMulch();
|
|
102
|
+
spawnSync("mulch", ["add", "architecture"], { cwd: tempDir, stdio: "pipe" });
|
|
103
|
+
|
|
104
|
+
const client = createMulchClient(tempDir);
|
|
105
|
+
const result = await client.prime([], "markdown", {
|
|
106
|
+
excludeDomain: ["architecture"],
|
|
107
|
+
});
|
|
108
|
+
expect(typeof result).toBe("string");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test.skipIf(!hasMulch)("passes both --files and --exclude-domain", async () => {
|
|
112
|
+
initMulch();
|
|
113
|
+
// Add a domain to exclude
|
|
114
|
+
spawnSync("mulch", ["add", "internal"], { cwd: tempDir, stdio: "pipe" });
|
|
115
|
+
|
|
116
|
+
const client = createMulchClient(tempDir);
|
|
117
|
+
const result = await client.prime([], "markdown", {
|
|
118
|
+
files: ["src/config.ts"],
|
|
119
|
+
excludeDomain: ["internal"],
|
|
120
|
+
});
|
|
121
|
+
expect(typeof result).toBe("string");
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe("status", () => {
|
|
126
|
+
test.skipIf(!hasMulch)("returns MulchStatus shape", async () => {
|
|
127
|
+
initMulch();
|
|
128
|
+
const client = createMulchClient(tempDir);
|
|
129
|
+
const result = await client.status();
|
|
130
|
+
expect(result).toHaveProperty("domains");
|
|
131
|
+
expect(Array.isArray(result.domains)).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test.skipIf(!hasMulch)("with no domains returns empty array", async () => {
|
|
135
|
+
initMulch();
|
|
136
|
+
const client = createMulchClient(tempDir);
|
|
137
|
+
const result = await client.status();
|
|
138
|
+
expect(result.domains).toEqual([]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test.skipIf(!hasMulch)("includes domain data when domains exist", async () => {
|
|
142
|
+
initMulch();
|
|
143
|
+
// Add a domain
|
|
144
|
+
spawnSync("mulch", ["add", "architecture"], { cwd: tempDir, stdio: "pipe" });
|
|
145
|
+
|
|
146
|
+
const client = createMulchClient(tempDir);
|
|
147
|
+
const result = await client.status();
|
|
148
|
+
expect(result.domains.length).toBeGreaterThan(0);
|
|
149
|
+
// Just verify we got an array with entries, don't check specific structure
|
|
150
|
+
// as mulch CLI output format may vary
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("record", () => {
|
|
155
|
+
test.skipIf(!hasMulch)("with required args succeeds", async () => {
|
|
156
|
+
initMulch();
|
|
157
|
+
// Add domain first
|
|
158
|
+
spawnSync("mulch", ["add", "architecture"], { cwd: tempDir, stdio: "pipe" });
|
|
159
|
+
|
|
160
|
+
const client = createMulchClient(tempDir);
|
|
161
|
+
await expect(
|
|
162
|
+
client.record("architecture", {
|
|
163
|
+
type: "convention",
|
|
164
|
+
description: "test convention",
|
|
165
|
+
}),
|
|
166
|
+
).resolves.toBeUndefined();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test.skipIf(!hasMulch)("with optional args succeeds", async () => {
|
|
170
|
+
initMulch();
|
|
171
|
+
spawnSync("mulch", ["add", "architecture"], { cwd: tempDir, stdio: "pipe" });
|
|
172
|
+
|
|
173
|
+
const client = createMulchClient(tempDir);
|
|
174
|
+
await expect(
|
|
175
|
+
client.record("architecture", {
|
|
176
|
+
type: "pattern",
|
|
177
|
+
name: "test-pattern",
|
|
178
|
+
description: "test description",
|
|
179
|
+
title: "Test Pattern",
|
|
180
|
+
rationale: "testing all options",
|
|
181
|
+
tags: ["testing", "example"],
|
|
182
|
+
}),
|
|
183
|
+
).resolves.toBeUndefined();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test.skipIf(!hasMulch)("with multiple tags", async () => {
|
|
187
|
+
initMulch();
|
|
188
|
+
spawnSync("mulch", ["add", "typescript"], { cwd: tempDir, stdio: "pipe" });
|
|
189
|
+
|
|
190
|
+
const client = createMulchClient(tempDir);
|
|
191
|
+
await expect(
|
|
192
|
+
client.record("typescript", {
|
|
193
|
+
type: "convention",
|
|
194
|
+
description: "multi-tag test",
|
|
195
|
+
tags: ["tag1", "tag2", "tag3"],
|
|
196
|
+
}),
|
|
197
|
+
).resolves.toBeUndefined();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test.skipIf(!hasMulch)("with --stdin flag passes flag to CLI", async () => {
|
|
201
|
+
initMulch();
|
|
202
|
+
spawnSync("mulch", ["add", "testing"], { cwd: tempDir, stdio: "pipe" });
|
|
203
|
+
|
|
204
|
+
const client = createMulchClient(tempDir);
|
|
205
|
+
// --stdin expects JSON input, which we're not providing, so this will fail
|
|
206
|
+
// but we're testing that the flag is passed correctly
|
|
207
|
+
await expect(
|
|
208
|
+
client.record("testing", {
|
|
209
|
+
type: "convention",
|
|
210
|
+
description: "stdin test",
|
|
211
|
+
stdin: true,
|
|
212
|
+
}),
|
|
213
|
+
).rejects.toThrow(AgentError);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test.skipIf(!hasMulch)("with --evidence-bead flag passes flag to CLI", async () => {
|
|
217
|
+
initMulch();
|
|
218
|
+
spawnSync("mulch", ["add", "testing"], { cwd: tempDir, stdio: "pipe" });
|
|
219
|
+
|
|
220
|
+
const client = createMulchClient(tempDir);
|
|
221
|
+
// The flag is passed correctly, but may fail if the bead ID is invalid
|
|
222
|
+
// or if other required fields are missing. This test documents that the
|
|
223
|
+
// flag is properly passed to the CLI.
|
|
224
|
+
try {
|
|
225
|
+
await client.record("testing", {
|
|
226
|
+
type: "decision",
|
|
227
|
+
description: "bead evidence test",
|
|
228
|
+
evidenceBead: "beads-abc123",
|
|
229
|
+
});
|
|
230
|
+
// If it succeeds, great!
|
|
231
|
+
expect(true).toBe(true);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
// If it fails, verify it's an AgentError (not a type error or similar)
|
|
234
|
+
// which proves the command was executed with the flag
|
|
235
|
+
expect(error).toBeInstanceOf(AgentError);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe("query", () => {
|
|
241
|
+
test.skipIf(!hasMulch)("passes domain arg when provided", async () => {
|
|
242
|
+
initMulch();
|
|
243
|
+
spawnSync("mulch", ["add", "architecture"], { cwd: tempDir, stdio: "pipe" });
|
|
244
|
+
|
|
245
|
+
const client = createMulchClient(tempDir);
|
|
246
|
+
const result = await client.query("architecture");
|
|
247
|
+
expect(typeof result).toBe("string");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test.skipIf(!hasMulch)("query without domain requires --all flag", async () => {
|
|
251
|
+
initMulch();
|
|
252
|
+
const client = createMulchClient(tempDir);
|
|
253
|
+
// Current implementation doesn't pass --all, so this will fail
|
|
254
|
+
// This documents the current behavior
|
|
255
|
+
await expect(client.query()).rejects.toThrow(AgentError);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe("search", () => {
|
|
260
|
+
test.skipIf(!hasMulch)("returns string output", async () => {
|
|
261
|
+
initMulch();
|
|
262
|
+
const client = createMulchClient(tempDir);
|
|
263
|
+
const result = await client.search("test");
|
|
264
|
+
expect(typeof result).toBe("string");
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test.skipIf(!hasMulch)("searches across domains", async () => {
|
|
268
|
+
initMulch();
|
|
269
|
+
// Add a domain and record
|
|
270
|
+
spawnSync("mulch", ["add", "testing"], { cwd: tempDir, stdio: "pipe" });
|
|
271
|
+
|
|
272
|
+
const client = createMulchClient(tempDir);
|
|
273
|
+
await client.record("testing", {
|
|
274
|
+
type: "convention",
|
|
275
|
+
description: "searchable keyword here",
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const result = await client.search("searchable");
|
|
279
|
+
expect(typeof result).toBe("string");
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
describe("diff", () => {
|
|
284
|
+
test.skipIf(!hasMulch)("shows expertise changes", async () => {
|
|
285
|
+
initGit();
|
|
286
|
+
initMulch();
|
|
287
|
+
const client = createMulchClient(tempDir);
|
|
288
|
+
const result = await client.diff();
|
|
289
|
+
expect(result).toHaveProperty("success");
|
|
290
|
+
expect(result).toHaveProperty("command");
|
|
291
|
+
expect(result).toHaveProperty("domains");
|
|
292
|
+
expect(Array.isArray(result.domains)).toBe(true);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test.skipIf(!hasMulch)("passes --since flag", async () => {
|
|
296
|
+
initGit();
|
|
297
|
+
initMulch();
|
|
298
|
+
const client = createMulchClient(tempDir);
|
|
299
|
+
const result = await client.diff({ since: "HEAD~5" });
|
|
300
|
+
expect(result).toHaveProperty("success");
|
|
301
|
+
expect(result).toHaveProperty("since");
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
describe("learn", () => {
|
|
306
|
+
test.skipIf(!hasMulch)("suggests domains for learnings", async () => {
|
|
307
|
+
initGit();
|
|
308
|
+
initMulch();
|
|
309
|
+
const client = createMulchClient(tempDir);
|
|
310
|
+
const result = await client.learn();
|
|
311
|
+
expect(result).toHaveProperty("success");
|
|
312
|
+
expect(result).toHaveProperty("command");
|
|
313
|
+
expect(result).toHaveProperty("changedFiles");
|
|
314
|
+
expect(Array.isArray(result.changedFiles)).toBe(true);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test.skipIf(!hasMulch)("passes --since flag", async () => {
|
|
318
|
+
initGit();
|
|
319
|
+
initMulch();
|
|
320
|
+
const client = createMulchClient(tempDir);
|
|
321
|
+
const result = await client.learn({ since: "HEAD~3" });
|
|
322
|
+
expect(result).toHaveProperty("success");
|
|
323
|
+
expect(result).toHaveProperty("changedFiles");
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe("prune", () => {
|
|
328
|
+
test.skipIf(!hasMulch)("prunes records", async () => {
|
|
329
|
+
initMulch();
|
|
330
|
+
const client = createMulchClient(tempDir);
|
|
331
|
+
const result = await client.prune();
|
|
332
|
+
expect(result).toHaveProperty("success");
|
|
333
|
+
expect(result).toHaveProperty("command");
|
|
334
|
+
expect(result).toHaveProperty("totalPruned");
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test.skipIf(!hasMulch)("supports --dry-run flag", async () => {
|
|
338
|
+
initMulch();
|
|
339
|
+
const client = createMulchClient(tempDir);
|
|
340
|
+
const result = await client.prune({ dryRun: true });
|
|
341
|
+
expect(result).toHaveProperty("success");
|
|
342
|
+
expect(result).toHaveProperty("dryRun");
|
|
343
|
+
expect(result.dryRun).toBe(true);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
describe("doctor", () => {
|
|
348
|
+
test.skipIf(!hasMulch)("runs health checks", async () => {
|
|
349
|
+
initMulch();
|
|
350
|
+
const client = createMulchClient(tempDir);
|
|
351
|
+
const result = await client.doctor();
|
|
352
|
+
expect(result).toHaveProperty("success");
|
|
353
|
+
expect(result).toHaveProperty("command");
|
|
354
|
+
expect(result).toHaveProperty("checks");
|
|
355
|
+
expect(Array.isArray(result.checks)).toBe(true);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test.skipIf(!hasMulch)("passes --fix flag", async () => {
|
|
359
|
+
initMulch();
|
|
360
|
+
const client = createMulchClient(tempDir);
|
|
361
|
+
const result = await client.doctor({ fix: true });
|
|
362
|
+
expect(result).toHaveProperty("success");
|
|
363
|
+
expect(result).toHaveProperty("checks");
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe("ready", () => {
|
|
368
|
+
test.skipIf(!hasMulch)("shows recently updated records", async () => {
|
|
369
|
+
initMulch();
|
|
370
|
+
const client = createMulchClient(tempDir);
|
|
371
|
+
const result = await client.ready();
|
|
372
|
+
expect(result).toHaveProperty("success");
|
|
373
|
+
expect(result).toHaveProperty("command");
|
|
374
|
+
expect(result).toHaveProperty("entries");
|
|
375
|
+
expect(Array.isArray(result.entries)).toBe(true);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
test.skipIf(!hasMulch)("passes --limit flag", async () => {
|
|
379
|
+
initMulch();
|
|
380
|
+
const client = createMulchClient(tempDir);
|
|
381
|
+
const result = await client.ready({ limit: 5 });
|
|
382
|
+
expect(result).toHaveProperty("success");
|
|
383
|
+
expect(result).toHaveProperty("count");
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test.skipIf(!hasMulch)("passes --domain flag", async () => {
|
|
387
|
+
initMulch();
|
|
388
|
+
spawnSync("mulch", ["add", "testing"], { cwd: tempDir, stdio: "pipe" });
|
|
389
|
+
|
|
390
|
+
const client = createMulchClient(tempDir);
|
|
391
|
+
const result = await client.ready({ domain: "testing" });
|
|
392
|
+
expect(result).toHaveProperty("success");
|
|
393
|
+
expect(result).toHaveProperty("entries");
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
test.skipIf(!hasMulch)("passes --since flag", async () => {
|
|
397
|
+
initMulch();
|
|
398
|
+
const client = createMulchClient(tempDir);
|
|
399
|
+
const result = await client.ready({ since: "7d" });
|
|
400
|
+
expect(result).toHaveProperty("success");
|
|
401
|
+
expect(result).toHaveProperty("entries");
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
describe("compact", () => {
|
|
406
|
+
test.skipIf(!hasMulch)("runs with --analyze flag", async () => {
|
|
407
|
+
initMulch();
|
|
408
|
+
const client = createMulchClient(tempDir);
|
|
409
|
+
const result = await client.compact(undefined, { analyze: true });
|
|
410
|
+
expect(result).toHaveProperty("success");
|
|
411
|
+
expect(result).toHaveProperty("command");
|
|
412
|
+
expect(result).toHaveProperty("action");
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test.skipIf(!hasMulch)("compacts specific domain with --analyze", async () => {
|
|
416
|
+
initMulch();
|
|
417
|
+
spawnSync("mulch", ["add", "large"], { cwd: tempDir, stdio: "pipe" });
|
|
418
|
+
|
|
419
|
+
const client = createMulchClient(tempDir);
|
|
420
|
+
const result = await client.compact("large", { analyze: true });
|
|
421
|
+
expect(result).toHaveProperty("success");
|
|
422
|
+
expect(result).toHaveProperty("action");
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test.skipIf(!hasMulch)("passes --auto with --dry-run flags", async () => {
|
|
426
|
+
initMulch();
|
|
427
|
+
const client = createMulchClient(tempDir);
|
|
428
|
+
const result = await client.compact(undefined, { auto: true, dryRun: true });
|
|
429
|
+
expect(result).toHaveProperty("success");
|
|
430
|
+
expect(result).toHaveProperty("command");
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test.skipIf(!hasMulch)("passes multiple options", async () => {
|
|
434
|
+
initMulch();
|
|
435
|
+
const client = createMulchClient(tempDir);
|
|
436
|
+
const result = await client.compact(undefined, {
|
|
437
|
+
auto: true,
|
|
438
|
+
dryRun: true,
|
|
439
|
+
minGroup: 3,
|
|
440
|
+
maxRecords: 20,
|
|
441
|
+
});
|
|
442
|
+
expect(result).toHaveProperty("success");
|
|
443
|
+
expect(result).toHaveProperty("command");
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
describe("error handling", () => {
|
|
448
|
+
test.skipIf(!hasMulch)("throws AgentError when mulch command fails", async () => {
|
|
449
|
+
// Don't init mulch - operations will fail with "not initialized" error
|
|
450
|
+
const client = createMulchClient(tempDir);
|
|
451
|
+
await expect(client.status()).rejects.toThrow(AgentError);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test.skipIf(!hasMulch)("AgentError message contains exit code", async () => {
|
|
455
|
+
const client = createMulchClient(tempDir);
|
|
456
|
+
try {
|
|
457
|
+
await client.status();
|
|
458
|
+
expect.unreachable("Should have thrown AgentError");
|
|
459
|
+
} catch (error) {
|
|
460
|
+
expect(error).toBeInstanceOf(AgentError);
|
|
461
|
+
const agentError = error as AgentError;
|
|
462
|
+
expect(agentError.message).toContain("exit");
|
|
463
|
+
expect(agentError.message).toContain("status");
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
test.skipIf(!hasMulch)("record fails with descriptive error for missing domain", async () => {
|
|
468
|
+
initMulch();
|
|
469
|
+
const client = createMulchClient(tempDir);
|
|
470
|
+
// Try to record to a domain that doesn't exist
|
|
471
|
+
await expect(
|
|
472
|
+
client.record("nonexistent-domain", {
|
|
473
|
+
type: "convention",
|
|
474
|
+
description: "test",
|
|
475
|
+
}),
|
|
476
|
+
).rejects.toThrow(AgentError);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
test.skipIf(!hasMulch)("handles empty status output correctly", async () => {
|
|
480
|
+
initMulch();
|
|
481
|
+
const client = createMulchClient(tempDir);
|
|
482
|
+
const result = await client.status();
|
|
483
|
+
// With no domains, should have empty array (not throw)
|
|
484
|
+
expect(result).toHaveProperty("domains");
|
|
485
|
+
expect(result.domains).toEqual([]);
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
describe("inferDomainsFromFiles", () => {
|
|
491
|
+
test("returns empty array for empty file list", () => {
|
|
492
|
+
expect(inferDomainsFromFiles([])).toEqual([]);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
test("maps src/commands/ files to cli domain", () => {
|
|
496
|
+
const result = inferDomainsFromFiles(["src/commands/init.ts"]);
|
|
497
|
+
expect(result).toContain("cli");
|
|
498
|
+
expect(result).not.toContain("server");
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test("maps src/worktree/ files to both swarm and merge domains", () => {
|
|
502
|
+
const result = inferDomainsFromFiles(["src/worktree/manager.ts"]);
|
|
503
|
+
expect(result).toContain("swarm");
|
|
504
|
+
expect(result).toContain("merge");
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test("maps test files to testing domain", () => {
|
|
508
|
+
const direct = inferDomainsFromFiles(["src/config.test.ts"]);
|
|
509
|
+
expect(direct).toContain("testing");
|
|
510
|
+
|
|
511
|
+
const nested = inferDomainsFromFiles(["src/mulch/client.test.ts"]);
|
|
512
|
+
expect(nested).toContain("testing");
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
test("deduplicates domains when multiple files match same domain", () => {
|
|
516
|
+
const result = inferDomainsFromFiles([
|
|
517
|
+
"src/commands/init.ts",
|
|
518
|
+
"src/commands/sling.ts",
|
|
519
|
+
"src/beads/client.ts",
|
|
520
|
+
]);
|
|
521
|
+
const cliCount = result.filter((d) => d === "cli").length;
|
|
522
|
+
expect(cliCount).toBe(1);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
test("accepts custom domainMap that overrides defaults", () => {
|
|
526
|
+
const customMap: Record<string, string[]> = {
|
|
527
|
+
"src/custom/**": ["myDomain"],
|
|
528
|
+
};
|
|
529
|
+
const result = inferDomainsFromFiles(["src/custom/foo.ts"], customMap);
|
|
530
|
+
expect(result).toEqual(["myDomain"]);
|
|
531
|
+
// Should NOT match DEFAULT_DOMAIN_MAP patterns
|
|
532
|
+
expect(result).not.toContain("cli");
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
test("returns empty array when no patterns match", () => {
|
|
536
|
+
const result = inferDomainsFromFiles(["some/random/file.txt"]);
|
|
537
|
+
expect(result).toEqual([]);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test("maps agents/ and templates/ to swarm domain", () => {
|
|
541
|
+
const agents = inferDomainsFromFiles(["agents/builder.md"]);
|
|
542
|
+
expect(agents).toContain("swarm");
|
|
543
|
+
|
|
544
|
+
const templates = inferDomainsFromFiles(["templates/overlay.md.tmpl"]);
|
|
545
|
+
expect(templates).toContain("swarm");
|
|
546
|
+
});
|
|
547
|
+
});
|