@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.
Files changed (219) hide show
  1. package/CHANGELOG.md +422 -0
  2. package/LICENSE +21 -0
  3. package/README.md +555 -0
  4. package/agents/builder.md +141 -0
  5. package/agents/coordinator.md +351 -0
  6. package/agents/cto.md +196 -0
  7. package/agents/gateway.md +276 -0
  8. package/agents/lead.md +281 -0
  9. package/agents/merger.md +156 -0
  10. package/agents/monitor.md +212 -0
  11. package/agents/reviewer.md +142 -0
  12. package/agents/scout.md +131 -0
  13. package/agents/supervisor.md +416 -0
  14. package/bin/legio.mjs +38 -0
  15. package/package.json +77 -0
  16. package/src/agents/checkpoint.test.ts +88 -0
  17. package/src/agents/checkpoint.ts +102 -0
  18. package/src/agents/hooks-deployer.test.ts +1820 -0
  19. package/src/agents/hooks-deployer.ts +574 -0
  20. package/src/agents/identity.test.ts +614 -0
  21. package/src/agents/identity.ts +385 -0
  22. package/src/agents/lifecycle.test.ts +202 -0
  23. package/src/agents/lifecycle.ts +184 -0
  24. package/src/agents/manifest.test.ts +558 -0
  25. package/src/agents/manifest.ts +297 -0
  26. package/src/agents/overlay.test.ts +592 -0
  27. package/src/agents/overlay.ts +316 -0
  28. package/src/beads/client.test.ts +210 -0
  29. package/src/beads/client.ts +227 -0
  30. package/src/beads/molecules.test.ts +320 -0
  31. package/src/beads/molecules.ts +209 -0
  32. package/src/commands/agents.test.ts +325 -0
  33. package/src/commands/agents.ts +286 -0
  34. package/src/commands/clean.test.ts +730 -0
  35. package/src/commands/clean.ts +653 -0
  36. package/src/commands/completions.test.ts +346 -0
  37. package/src/commands/completions.ts +950 -0
  38. package/src/commands/coordinator.test.ts +1524 -0
  39. package/src/commands/coordinator.ts +880 -0
  40. package/src/commands/costs.test.ts +1015 -0
  41. package/src/commands/costs.ts +473 -0
  42. package/src/commands/dashboard.test.ts +94 -0
  43. package/src/commands/dashboard.ts +607 -0
  44. package/src/commands/doctor.test.ts +295 -0
  45. package/src/commands/doctor.ts +213 -0
  46. package/src/commands/down.test.ts +308 -0
  47. package/src/commands/down.ts +124 -0
  48. package/src/commands/errors.test.ts +648 -0
  49. package/src/commands/errors.ts +255 -0
  50. package/src/commands/feed.test.ts +579 -0
  51. package/src/commands/feed.ts +368 -0
  52. package/src/commands/gateway.test.ts +698 -0
  53. package/src/commands/gateway.ts +419 -0
  54. package/src/commands/group.test.ts +262 -0
  55. package/src/commands/group.ts +539 -0
  56. package/src/commands/hooks.test.ts +292 -0
  57. package/src/commands/hooks.ts +210 -0
  58. package/src/commands/init.test.ts +211 -0
  59. package/src/commands/init.ts +622 -0
  60. package/src/commands/inspect.test.ts +670 -0
  61. package/src/commands/inspect.ts +455 -0
  62. package/src/commands/log.test.ts +1556 -0
  63. package/src/commands/log.ts +752 -0
  64. package/src/commands/logs.test.ts +379 -0
  65. package/src/commands/logs.ts +544 -0
  66. package/src/commands/mail.test.ts +1726 -0
  67. package/src/commands/mail.ts +926 -0
  68. package/src/commands/merge.test.ts +676 -0
  69. package/src/commands/merge.ts +374 -0
  70. package/src/commands/metrics.test.ts +444 -0
  71. package/src/commands/metrics.ts +150 -0
  72. package/src/commands/monitor.test.ts +151 -0
  73. package/src/commands/monitor.ts +394 -0
  74. package/src/commands/nudge.test.ts +230 -0
  75. package/src/commands/nudge.ts +373 -0
  76. package/src/commands/prime.test.ts +467 -0
  77. package/src/commands/prime.ts +386 -0
  78. package/src/commands/replay.test.ts +742 -0
  79. package/src/commands/replay.ts +367 -0
  80. package/src/commands/run.test.ts +443 -0
  81. package/src/commands/run.ts +365 -0
  82. package/src/commands/server.test.ts +626 -0
  83. package/src/commands/server.ts +298 -0
  84. package/src/commands/sling.test.ts +810 -0
  85. package/src/commands/sling.ts +700 -0
  86. package/src/commands/spec.test.ts +206 -0
  87. package/src/commands/spec.ts +171 -0
  88. package/src/commands/status.test.ts +276 -0
  89. package/src/commands/status.ts +339 -0
  90. package/src/commands/stop.test.ts +357 -0
  91. package/src/commands/stop.ts +119 -0
  92. package/src/commands/supervisor.test.ts +186 -0
  93. package/src/commands/supervisor.ts +544 -0
  94. package/src/commands/trace.test.ts +746 -0
  95. package/src/commands/trace.ts +332 -0
  96. package/src/commands/up.test.ts +597 -0
  97. package/src/commands/up.ts +275 -0
  98. package/src/commands/watch.test.ts +152 -0
  99. package/src/commands/watch.ts +238 -0
  100. package/src/commands/worktree.test.ts +648 -0
  101. package/src/commands/worktree.ts +266 -0
  102. package/src/config.test.ts +496 -0
  103. package/src/config.ts +616 -0
  104. package/src/doctor/agents.test.ts +448 -0
  105. package/src/doctor/agents.ts +396 -0
  106. package/src/doctor/config-check.test.ts +184 -0
  107. package/src/doctor/config-check.ts +185 -0
  108. package/src/doctor/consistency.test.ts +645 -0
  109. package/src/doctor/consistency.ts +294 -0
  110. package/src/doctor/databases.test.ts +284 -0
  111. package/src/doctor/databases.ts +211 -0
  112. package/src/doctor/dependencies.test.ts +150 -0
  113. package/src/doctor/dependencies.ts +179 -0
  114. package/src/doctor/logs.test.ts +244 -0
  115. package/src/doctor/logs.ts +295 -0
  116. package/src/doctor/merge-queue.test.ts +210 -0
  117. package/src/doctor/merge-queue.ts +144 -0
  118. package/src/doctor/structure.test.ts +285 -0
  119. package/src/doctor/structure.ts +195 -0
  120. package/src/doctor/types.ts +37 -0
  121. package/src/doctor/version.test.ts +130 -0
  122. package/src/doctor/version.ts +131 -0
  123. package/src/e2e/chat-flow.test.ts +346 -0
  124. package/src/e2e/init-sling-lifecycle.test.ts +288 -0
  125. package/src/errors.test.ts +21 -0
  126. package/src/errors.ts +246 -0
  127. package/src/events/store.test.ts +660 -0
  128. package/src/events/store.ts +344 -0
  129. package/src/events/tool-filter.test.ts +330 -0
  130. package/src/events/tool-filter.ts +126 -0
  131. package/src/global-setup.ts +14 -0
  132. package/src/index.ts +339 -0
  133. package/src/insights/analyzer.test.ts +466 -0
  134. package/src/insights/analyzer.ts +203 -0
  135. package/src/logging/color.test.ts +118 -0
  136. package/src/logging/color.ts +71 -0
  137. package/src/logging/logger.test.ts +812 -0
  138. package/src/logging/logger.ts +266 -0
  139. package/src/logging/reporter.test.ts +258 -0
  140. package/src/logging/reporter.ts +109 -0
  141. package/src/logging/sanitizer.test.ts +190 -0
  142. package/src/logging/sanitizer.ts +57 -0
  143. package/src/mail/broadcast.test.ts +203 -0
  144. package/src/mail/broadcast.ts +92 -0
  145. package/src/mail/client.test.ts +873 -0
  146. package/src/mail/client.ts +236 -0
  147. package/src/mail/store.test.ts +815 -0
  148. package/src/mail/store.ts +402 -0
  149. package/src/merge/queue.test.ts +449 -0
  150. package/src/merge/queue.ts +262 -0
  151. package/src/merge/resolver.test.ts +1453 -0
  152. package/src/merge/resolver.ts +759 -0
  153. package/src/metrics/store.test.ts +1167 -0
  154. package/src/metrics/store.ts +511 -0
  155. package/src/metrics/summary.test.ts +397 -0
  156. package/src/metrics/summary.ts +178 -0
  157. package/src/metrics/transcript.test.ts +643 -0
  158. package/src/metrics/transcript.ts +351 -0
  159. package/src/mulch/client.test.ts +547 -0
  160. package/src/mulch/client.ts +416 -0
  161. package/src/server/audit-store.test.ts +384 -0
  162. package/src/server/audit-store.ts +257 -0
  163. package/src/server/headless.test.ts +180 -0
  164. package/src/server/headless.ts +151 -0
  165. package/src/server/index.test.ts +241 -0
  166. package/src/server/index.ts +317 -0
  167. package/src/server/public/app.js +187 -0
  168. package/src/server/public/apple-touch-icon.png +0 -0
  169. package/src/server/public/components/agent-badge.js +37 -0
  170. package/src/server/public/components/data-table.js +114 -0
  171. package/src/server/public/components/gateway-chat.js +256 -0
  172. package/src/server/public/components/issue-card.js +96 -0
  173. package/src/server/public/components/layout.js +88 -0
  174. package/src/server/public/components/message-bubble.js +120 -0
  175. package/src/server/public/components/stat-card.js +26 -0
  176. package/src/server/public/components/terminal-panel.js +140 -0
  177. package/src/server/public/favicon-16.png +0 -0
  178. package/src/server/public/favicon-32.png +0 -0
  179. package/src/server/public/favicon.ico +0 -0
  180. package/src/server/public/favicon.png +0 -0
  181. package/src/server/public/index.html +64 -0
  182. package/src/server/public/lib/api.js +35 -0
  183. package/src/server/public/lib/markdown.js +8 -0
  184. package/src/server/public/lib/preact-setup.js +8 -0
  185. package/src/server/public/lib/state.js +99 -0
  186. package/src/server/public/lib/utils.js +309 -0
  187. package/src/server/public/lib/ws.js +79 -0
  188. package/src/server/public/views/chat.js +983 -0
  189. package/src/server/public/views/costs.js +692 -0
  190. package/src/server/public/views/dashboard.js +781 -0
  191. package/src/server/public/views/gateway-chat.js +622 -0
  192. package/src/server/public/views/inspect.js +399 -0
  193. package/src/server/public/views/issues.js +470 -0
  194. package/src/server/public/views/setup.js +94 -0
  195. package/src/server/public/views/task-detail.js +422 -0
  196. package/src/server/routes.test.ts +3816 -0
  197. package/src/server/routes.ts +1964 -0
  198. package/src/server/websocket.test.ts +288 -0
  199. package/src/server/websocket.ts +196 -0
  200. package/src/sessions/compat.test.ts +109 -0
  201. package/src/sessions/compat.ts +17 -0
  202. package/src/sessions/store.test.ts +969 -0
  203. package/src/sessions/store.ts +480 -0
  204. package/src/test-helpers.test.ts +97 -0
  205. package/src/test-helpers.ts +143 -0
  206. package/src/types.ts +708 -0
  207. package/src/watchdog/daemon.test.ts +1233 -0
  208. package/src/watchdog/daemon.ts +533 -0
  209. package/src/watchdog/health.test.ts +371 -0
  210. package/src/watchdog/health.ts +248 -0
  211. package/src/watchdog/triage.test.ts +162 -0
  212. package/src/watchdog/triage.ts +193 -0
  213. package/src/worktree/manager.test.ts +444 -0
  214. package/src/worktree/manager.ts +224 -0
  215. package/src/worktree/tmux.test.ts +1238 -0
  216. package/src/worktree/tmux.ts +644 -0
  217. package/templates/CLAUDE.md.tmpl +89 -0
  218. package/templates/hooks.json.tmpl +132 -0
  219. package/templates/overlay.md.tmpl +79 -0
@@ -0,0 +1,211 @@
1
+ import { access, readdir, readFile, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { afterEach, beforeEach, describe, expect, test } from "vitest";
4
+ import { cleanupTempDir, createTempGitRepo } from "../test-helpers.ts";
5
+ import { initCommand, LEGIO_GITIGNORE } from "./init.ts";
6
+
7
+ /**
8
+ * Tests for `legio init` -- agent definition deployment.
9
+ *
10
+ * Uses real temp git repos. Suppresses stdout to keep test output clean.
11
+ * process.cwd() is saved/restored because initCommand uses it to find the project root.
12
+ */
13
+
14
+ const AGENT_DEF_FILES = [
15
+ "scout.md",
16
+ "builder.md",
17
+ "reviewer.md",
18
+ "lead.md",
19
+ "merger.md",
20
+ "supervisor.md",
21
+ "coordinator.md",
22
+ "monitor.md",
23
+ "cto.md",
24
+ "gateway.md",
25
+ ];
26
+
27
+ /** Resolve the source agents directory (same logic as init.ts). */
28
+ const SOURCE_AGENTS_DIR = join(import.meta.dirname, "..", "..", "agents");
29
+
30
+ describe("initCommand: agent-defs deployment", () => {
31
+ let tempDir: string;
32
+ let originalCwd: string;
33
+ let originalWrite: typeof process.stdout.write;
34
+
35
+ beforeEach(async () => {
36
+ tempDir = await createTempGitRepo();
37
+ originalCwd = process.cwd();
38
+ process.chdir(tempDir);
39
+
40
+ // Suppress stdout noise from initCommand
41
+ originalWrite = process.stdout.write;
42
+ process.stdout.write = (() => true) as typeof process.stdout.write;
43
+ });
44
+
45
+ afterEach(async () => {
46
+ process.chdir(originalCwd);
47
+ process.stdout.write = originalWrite;
48
+ await cleanupTempDir(tempDir);
49
+ });
50
+
51
+ test("creates .legio/agent-defs/ with all 10 agent definition files", async () => {
52
+ await initCommand([]);
53
+
54
+ const agentDefsDir = join(tempDir, ".legio", "agent-defs");
55
+ const files = await readdir(agentDefsDir);
56
+ const mdFiles = files.filter((f) => f.endsWith(".md")).sort();
57
+
58
+ expect(mdFiles).toEqual(AGENT_DEF_FILES.slice().sort());
59
+ });
60
+
61
+ test("copied files match source content", async () => {
62
+ await initCommand([]);
63
+
64
+ for (const fileName of AGENT_DEF_FILES) {
65
+ const sourcePath = join(SOURCE_AGENTS_DIR, fileName);
66
+ const targetPath = join(tempDir, ".legio", "agent-defs", fileName);
67
+
68
+ const sourceContent = await readFile(sourcePath, "utf-8");
69
+ const targetContent = await readFile(targetPath, "utf-8");
70
+
71
+ expect(targetContent).toBe(sourceContent);
72
+ }
73
+ });
74
+
75
+ test("--force reinit overwrites existing agent def files", async () => {
76
+ // First init
77
+ await initCommand([]);
78
+
79
+ // Tamper with one of the deployed files
80
+ const tamperPath = join(tempDir, ".legio", "agent-defs", "scout.md");
81
+ await writeFile(tamperPath, "# tampered content\n");
82
+
83
+ // Verify tamper worked
84
+ const tampered = await readFile(tamperPath, "utf-8");
85
+ expect(tampered).toBe("# tampered content\n");
86
+
87
+ // Reinit with --force
88
+ await initCommand(["--force"]);
89
+
90
+ // Verify the file was overwritten with the original source
91
+ const sourceContent = await readFile(join(SOURCE_AGENTS_DIR, "scout.md"), "utf-8");
92
+ const restored = await readFile(tamperPath, "utf-8");
93
+ expect(restored).toBe(sourceContent);
94
+ });
95
+
96
+ test("Stop hook includes mulch learn command", async () => {
97
+ await initCommand([]);
98
+
99
+ const hooksPath = join(tempDir, ".legio", "hooks.json");
100
+ const content = await readFile(hooksPath, "utf-8");
101
+ const parsed = JSON.parse(content);
102
+ const stopHooks = parsed.hooks.Stop[0].hooks;
103
+
104
+ expect(stopHooks.length).toBe(2);
105
+ expect(stopHooks[0].command).toContain("legio log session-end");
106
+ expect(stopHooks[1].command).toBe("mulch learn");
107
+ });
108
+ });
109
+
110
+ describe("initCommand: .legio/.gitignore", () => {
111
+ let tempDir: string;
112
+ let originalCwd: string;
113
+ let originalWrite: typeof process.stdout.write;
114
+
115
+ beforeEach(async () => {
116
+ tempDir = await createTempGitRepo();
117
+ originalCwd = process.cwd();
118
+ process.chdir(tempDir);
119
+
120
+ // Suppress stdout noise from initCommand
121
+ originalWrite = process.stdout.write;
122
+ process.stdout.write = (() => true) as typeof process.stdout.write;
123
+ });
124
+
125
+ afterEach(async () => {
126
+ process.chdir(originalCwd);
127
+ process.stdout.write = originalWrite;
128
+ await cleanupTempDir(tempDir);
129
+ });
130
+
131
+ test("creates .legio/.gitignore with wildcard+whitelist model", async () => {
132
+ await initCommand([]);
133
+
134
+ const gitignorePath = join(tempDir, ".legio", ".gitignore");
135
+ const content = await readFile(gitignorePath, "utf-8");
136
+
137
+ // Verify wildcard+whitelist pattern
138
+ expect(content).toContain("*\n");
139
+ expect(content).toContain("!.gitignore\n");
140
+ expect(content).toContain("!config.yaml\n");
141
+ expect(content).toContain("!agent-manifest.json\n");
142
+ expect(content).toContain("!hooks.json\n");
143
+ expect(content).toContain("!groups.json\n");
144
+ expect(content).toContain("!agent-defs/\n");
145
+
146
+ // Verify it matches the exported constant
147
+ expect(content).toBe(LEGIO_GITIGNORE);
148
+ });
149
+
150
+ test("gitignore is always written when init completes", async () => {
151
+ // Init should write gitignore
152
+ await initCommand([]);
153
+
154
+ const gitignorePath = join(tempDir, ".legio", ".gitignore");
155
+ const content = await readFile(gitignorePath, "utf-8");
156
+
157
+ // Verify gitignore was written with correct content
158
+ expect(content).toBe(LEGIO_GITIGNORE);
159
+
160
+ // Verify the file exists
161
+ const exists = await access(gitignorePath)
162
+ .then(() => true)
163
+ .catch(() => false);
164
+ expect(exists).toBe(true);
165
+ });
166
+
167
+ test("--force reinit overwrites stale .legio/.gitignore", async () => {
168
+ // First init
169
+ await initCommand([]);
170
+
171
+ const gitignorePath = join(tempDir, ".legio", ".gitignore");
172
+
173
+ // Tamper with the gitignore file (simulate old deny-list format)
174
+ await writeFile(gitignorePath, "# old format\nworktrees/\nlogs/\nmail.db\n");
175
+
176
+ // Verify tamper worked
177
+ const tampered = await readFile(gitignorePath, "utf-8");
178
+ expect(tampered).not.toContain("*\n");
179
+ expect(tampered).not.toContain("!.gitignore\n");
180
+
181
+ // Reinit with --force
182
+ await initCommand(["--force"]);
183
+
184
+ // Verify the file was overwritten with the new wildcard+whitelist format
185
+ const restored = await readFile(gitignorePath, "utf-8");
186
+ expect(restored).toBe(LEGIO_GITIGNORE);
187
+ expect(restored).toContain("*\n");
188
+ expect(restored).toContain("!.gitignore\n");
189
+ });
190
+
191
+ test("subsequent init without --force does not overwrite gitignore", async () => {
192
+ // First init
193
+ await initCommand([]);
194
+
195
+ const gitignorePath = join(tempDir, ".legio", ".gitignore");
196
+
197
+ // Tamper with the gitignore file
198
+ await writeFile(gitignorePath, "# custom content\n");
199
+
200
+ // Verify tamper worked
201
+ const tampered = await readFile(gitignorePath, "utf-8");
202
+ expect(tampered).toBe("# custom content\n");
203
+
204
+ // Second init without --force should return early (not overwrite)
205
+ await initCommand([]);
206
+
207
+ // Verify the file was NOT overwritten (early return prevented it)
208
+ const afterSecondInit = await readFile(gitignorePath, "utf-8");
209
+ expect(afterSecondInit).toBe("# custom content\n");
210
+ });
211
+ });