@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,266 @@
1
+ /**
2
+ * CLI command: legio worktree list | clean [--completed] [--all]
3
+ *
4
+ * List shows worktrees with agent status.
5
+ * Clean removes worktree dirs, branch refs (if merged), and tmux sessions.
6
+ * Logs are never auto-deleted.
7
+ */
8
+
9
+ import { access } from "node:fs/promises";
10
+ import { join } from "node:path";
11
+ import { loadConfig } from "../config.ts";
12
+ import { ValidationError } from "../errors.ts";
13
+ import { createMailStore } from "../mail/store.ts";
14
+ import { openSessionStore } from "../sessions/compat.ts";
15
+ import type { AgentSession } from "../types.ts";
16
+ import { listWorktrees, removeWorktree } from "../worktree/manager.ts";
17
+ import { isSessionAlive, killSession } from "../worktree/tmux.ts";
18
+
19
+ function hasFlag(args: string[], flag: string): boolean {
20
+ return args.includes(flag);
21
+ }
22
+
23
+ /**
24
+ * Handle `legio worktree list`.
25
+ */
26
+ async function handleList(root: string, json: boolean): Promise<void> {
27
+ const worktrees = await listWorktrees(root);
28
+ const legioDir = join(root, ".legio");
29
+ const { store } = openSessionStore(legioDir);
30
+ let sessions: AgentSession[];
31
+ try {
32
+ sessions = store.getAll();
33
+ } finally {
34
+ store.close();
35
+ }
36
+
37
+ const legioWts = worktrees.filter((wt) => wt.branch.startsWith("legio/"));
38
+
39
+ if (json) {
40
+ const entries = legioWts.map((wt) => {
41
+ const session = sessions.find((s) => s.worktreePath === wt.path);
42
+ return {
43
+ path: wt.path,
44
+ branch: wt.branch,
45
+ head: wt.head,
46
+ agentName: session?.agentName ?? null,
47
+ state: session?.state ?? null,
48
+ beadId: session?.beadId ?? null,
49
+ };
50
+ });
51
+ process.stdout.write(`${JSON.stringify(entries, null, "\t")}\n`);
52
+ return;
53
+ }
54
+
55
+ if (legioWts.length === 0) {
56
+ process.stdout.write("No agent worktrees found.\n");
57
+ return;
58
+ }
59
+
60
+ process.stdout.write(`🌳 Agent worktrees: ${legioWts.length}\n\n`);
61
+ for (const wt of legioWts) {
62
+ const session = sessions.find((s) => s.worktreePath === wt.path);
63
+ const state = session?.state ?? "unknown";
64
+ const agent = session?.agentName ?? "?";
65
+ const bead = session?.beadId ?? "?";
66
+ process.stdout.write(` ${wt.branch}\n`);
67
+ process.stdout.write(` Agent: ${agent} | State: ${state} | Task: ${bead}\n`);
68
+ process.stdout.write(` Path: ${wt.path}\n\n`);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Handle `legio worktree clean [--completed] [--all]`.
74
+ */
75
+ async function handleClean(args: string[], root: string, json: boolean): Promise<void> {
76
+ const all = hasFlag(args, "--all");
77
+ const completedOnly = hasFlag(args, "--completed") || !all;
78
+
79
+ const worktrees = await listWorktrees(root);
80
+ const legioDir = join(root, ".legio");
81
+ const { store } = openSessionStore(legioDir);
82
+
83
+ let sessions: AgentSession[];
84
+ try {
85
+ sessions = store.getAll();
86
+ } catch {
87
+ store.close();
88
+ return;
89
+ }
90
+
91
+ const legioWts = worktrees.filter((wt) => wt.branch.startsWith("legio/"));
92
+ const cleaned: string[] = [];
93
+ const failed: string[] = [];
94
+
95
+ try {
96
+ for (const wt of legioWts) {
97
+ const session = sessions.find((s) => s.worktreePath === wt.path);
98
+
99
+ // If --completed (default), only clean worktrees whose agent is done/zombie
100
+ if (completedOnly && session && session.state !== "completed" && session.state !== "zombie") {
101
+ continue;
102
+ }
103
+
104
+ // If --all, clean everything
105
+ // Kill tmux session if still alive
106
+ if (session?.tmuxSession) {
107
+ const alive = await isSessionAlive(session.tmuxSession);
108
+ if (alive) {
109
+ try {
110
+ await killSession(session.tmuxSession);
111
+ } catch {
112
+ // Best effort
113
+ }
114
+ }
115
+ }
116
+
117
+ // Remove worktree and its branch.
118
+ // Always force worktree removal since deployed .claude/ files create untracked
119
+ // files that cause non-forced removal to fail.
120
+ // Always force-delete the branch since we're cleaning up finished/zombie agents
121
+ // whose branches are typically unmerged.
122
+ try {
123
+ await removeWorktree(root, wt.path, { force: true, forceBranch: true });
124
+ cleaned.push(wt.branch);
125
+
126
+ if (!json) {
127
+ process.stdout.write(`🗑️ Removed: ${wt.branch}\n`);
128
+ }
129
+ } catch (err) {
130
+ failed.push(wt.branch);
131
+ if (!json) {
132
+ const msg = err instanceof Error ? err.message : String(err);
133
+ process.stderr.write(`⚠️ Failed to remove ${wt.branch}: ${msg}\n`);
134
+ }
135
+ }
136
+ }
137
+
138
+ // Purge mail for cleaned agents
139
+ let mailPurged = 0;
140
+ if (cleaned.length > 0) {
141
+ const mailDbPath = join(root, ".legio", "mail.db");
142
+ let mailDbFileExists = false;
143
+ try {
144
+ await access(mailDbPath);
145
+ mailDbFileExists = true;
146
+ } catch {
147
+ /* not found */
148
+ }
149
+ if (mailDbFileExists) {
150
+ const mailStore = createMailStore(mailDbPath);
151
+ try {
152
+ for (const branch of cleaned) {
153
+ const session = sessions.find((s) => s.branchName === branch);
154
+ if (session) {
155
+ mailPurged += mailStore.purge({ agent: session.agentName });
156
+ }
157
+ }
158
+ } finally {
159
+ mailStore.close();
160
+ }
161
+ }
162
+ }
163
+
164
+ // Mark cleaned sessions as zombie in the SessionStore
165
+ for (const branch of cleaned) {
166
+ const session = sessions.find((s) => s.branchName === branch);
167
+ if (session) {
168
+ store.updateState(session.agentName, "zombie");
169
+ }
170
+ }
171
+
172
+ // Prune zombie entries whose worktree paths no longer exist on disk.
173
+ // This prevents the session store from growing unbounded with stale entries.
174
+ const remainingWorktrees = await listWorktrees(root);
175
+ const worktreePaths = new Set(remainingWorktrees.map((wt) => wt.path));
176
+ let pruneCount = 0;
177
+
178
+ // Re-read sessions after state updates to get current zombie list
179
+ const currentSessions = store.getAll();
180
+ for (const session of currentSessions) {
181
+ if (session.state === "zombie" && !worktreePaths.has(session.worktreePath)) {
182
+ store.remove(session.agentName);
183
+ pruneCount++;
184
+ }
185
+ }
186
+
187
+ if (json) {
188
+ process.stdout.write(
189
+ `${JSON.stringify({ cleaned, failed, pruned: pruneCount, mailPurged })}\n`,
190
+ );
191
+ } else if (cleaned.length === 0 && pruneCount === 0 && failed.length === 0) {
192
+ process.stdout.write("No worktrees to clean.\n");
193
+ } else {
194
+ if (cleaned.length > 0) {
195
+ process.stdout.write(
196
+ `\nCleaned ${cleaned.length} worktree${cleaned.length === 1 ? "" : "s"}.\n`,
197
+ );
198
+ }
199
+ if (failed.length > 0) {
200
+ process.stdout.write(
201
+ `Failed to clean ${failed.length} worktree${failed.length === 1 ? "" : "s"}.\n`,
202
+ );
203
+ }
204
+ if (mailPurged > 0) {
205
+ process.stdout.write(
206
+ `Purged ${mailPurged} mail message${mailPurged === 1 ? "" : "s"} from cleaned agents.\n`,
207
+ );
208
+ }
209
+ if (pruneCount > 0) {
210
+ process.stdout.write(
211
+ `Pruned ${pruneCount} zombie session${pruneCount === 1 ? "" : "s"} from store.\n`,
212
+ );
213
+ }
214
+ }
215
+ } finally {
216
+ store.close();
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Entry point for `legio worktree <subcommand> [flags]`.
222
+ *
223
+ * Subcommands: list, clean.
224
+ */
225
+ const WORKTREE_HELP = `legio worktree — Manage agent worktrees
226
+
227
+ Usage: legio worktree <subcommand> [flags]
228
+
229
+ Subcommands:
230
+ list List worktrees with agent status
231
+ clean Remove completed worktrees
232
+ [--completed] Only finished agents (default)
233
+ [--all] Force remove all
234
+
235
+ Options:
236
+ --json Output as JSON
237
+ --help, -h Show this help`;
238
+
239
+ export async function worktreeCommand(args: string[]): Promise<void> {
240
+ if (args.includes("--help") || args.includes("-h")) {
241
+ process.stdout.write(`${WORKTREE_HELP}\n`);
242
+ return;
243
+ }
244
+
245
+ const subcommand = args[0];
246
+ const subArgs = args.slice(1);
247
+ const jsonFlag = hasFlag(args, "--json");
248
+
249
+ const cwd = process.cwd();
250
+ const config = await loadConfig(cwd);
251
+ const root = config.project.root;
252
+
253
+ switch (subcommand) {
254
+ case "list":
255
+ await handleList(root, jsonFlag);
256
+ break;
257
+ case "clean":
258
+ await handleClean(subArgs, root, jsonFlag);
259
+ break;
260
+ default:
261
+ throw new ValidationError(
262
+ `Unknown worktree subcommand: ${subcommand ?? "(none)"}. Use: list, clean`,
263
+ { field: "subcommand" },
264
+ );
265
+ }
266
+ }