@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,286 @@
1
+ /**
2
+ * CLI command: legio agents <sub> [--json]
3
+ *
4
+ * Discover and query agents by capability.
5
+ */
6
+
7
+ import { readFile } from "node:fs/promises";
8
+ import { join } from "node:path";
9
+ import { loadConfig } from "../config.ts";
10
+ import { ValidationError } from "../errors.ts";
11
+ import { openSessionStore } from "../sessions/compat.ts";
12
+ import { type AgentSession, SUPPORTED_CAPABILITIES } from "../types.ts";
13
+
14
+ /**
15
+ * Parse a named flag value from args.
16
+ */
17
+ function getFlag(args: string[], flag: string): string | undefined {
18
+ const idx = args.indexOf(flag);
19
+ if (idx === -1 || idx + 1 >= args.length) {
20
+ return undefined;
21
+ }
22
+ return args[idx + 1];
23
+ }
24
+
25
+ function hasFlag(args: string[], flag: string): boolean {
26
+ return args.includes(flag);
27
+ }
28
+
29
+ /**
30
+ * Discovered agent information including file scope.
31
+ */
32
+ export interface DiscoveredAgent {
33
+ agentName: string;
34
+ capability: string;
35
+ state: string;
36
+ beadId: string;
37
+ branchName: string;
38
+ parentAgent: string | null;
39
+ depth: number;
40
+ fileScope: string[];
41
+ startedAt: string;
42
+ lastActivity: string;
43
+ }
44
+
45
+ /**
46
+ * Extract file scope from an agent's overlay CLAUDE.md.
47
+ * Returns empty array if overlay doesn't exist, has no file scope restrictions,
48
+ * or can't be read.
49
+ *
50
+ * @param worktreePath - Absolute path to the agent's worktree
51
+ * @returns Array of file paths (relative to worktree root)
52
+ */
53
+ export async function extractFileScope(worktreePath: string): Promise<string[]> {
54
+ try {
55
+ const overlayPath = join(worktreePath, ".claude", "CLAUDE.md");
56
+ let overlayContent: string;
57
+ try {
58
+ overlayContent = await readFile(overlayPath, "utf-8");
59
+ } catch {
60
+ return [];
61
+ }
62
+ const content = overlayContent;
63
+
64
+ // Find the section between "## File Scope (exclusive ownership)" and "## Expertise"
65
+ const startMarker = "## File Scope (exclusive ownership)";
66
+ const endMarker = "## Expertise";
67
+
68
+ const startIdx = content.indexOf(startMarker);
69
+ if (startIdx === -1) {
70
+ return [];
71
+ }
72
+
73
+ const endIdx = content.indexOf(endMarker, startIdx);
74
+ if (endIdx === -1) {
75
+ return [];
76
+ }
77
+
78
+ const section = content.slice(startIdx, endIdx);
79
+
80
+ // Check for "No file scope restrictions"
81
+ if (section.includes("No file scope restrictions")) {
82
+ return [];
83
+ }
84
+
85
+ // Extract file paths from markdown list items: - `path`
86
+ const paths: string[] = [];
87
+ const regex = /^- `(.+)`$/gm;
88
+ let match = regex.exec(section);
89
+
90
+ while (match !== null) {
91
+ if (match[1]) {
92
+ paths.push(match[1]);
93
+ }
94
+ match = regex.exec(section);
95
+ }
96
+
97
+ return paths;
98
+ } catch {
99
+ // Best effort: return empty array if anything fails
100
+ return [];
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Discover agents in the project.
106
+ *
107
+ * @param root - Absolute path to project root
108
+ * @param opts - Filter options
109
+ * @returns Array of discovered agents with file scopes
110
+ */
111
+ export async function discoverAgents(
112
+ root: string,
113
+ opts?: { capability?: string; includeAll?: boolean },
114
+ ): Promise<DiscoveredAgent[]> {
115
+ const legioDir = join(root, ".legio");
116
+ const { store } = openSessionStore(legioDir);
117
+
118
+ try {
119
+ const sessions: AgentSession[] = opts?.includeAll ? store.getAll() : store.getActive();
120
+
121
+ // Filter by capability if specified
122
+ let filteredSessions = sessions;
123
+ if (opts?.capability) {
124
+ filteredSessions = sessions.filter((s) => s.capability === opts.capability);
125
+ }
126
+
127
+ // Extract file scopes for each agent
128
+ const agents: DiscoveredAgent[] = await Promise.all(
129
+ filteredSessions.map(async (session) => {
130
+ const fileScope = await extractFileScope(session.worktreePath);
131
+ return {
132
+ agentName: session.agentName,
133
+ capability: session.capability,
134
+ state: session.state,
135
+ beadId: session.beadId,
136
+ branchName: session.branchName,
137
+ parentAgent: session.parentAgent,
138
+ depth: session.depth,
139
+ fileScope,
140
+ startedAt: session.startedAt,
141
+ lastActivity: session.lastActivity,
142
+ };
143
+ }),
144
+ );
145
+
146
+ return agents;
147
+ } finally {
148
+ store.close();
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Format the state icon for display.
154
+ */
155
+ function getStateIcon(state: string): string {
156
+ switch (state) {
157
+ case "working":
158
+ return "●";
159
+ case "booting":
160
+ return "○";
161
+ default:
162
+ return " ";
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Print discovered agents in human-readable format.
168
+ */
169
+ function printAgents(agents: DiscoveredAgent[]): void {
170
+ const w = process.stdout.write.bind(process.stdout);
171
+
172
+ if (agents.length === 0) {
173
+ w("No agents found.\n");
174
+ return;
175
+ }
176
+
177
+ w(`Found ${agents.length} agent${agents.length === 1 ? "" : "s"}:\n\n`);
178
+
179
+ for (const agent of agents) {
180
+ const icon = getStateIcon(agent.state);
181
+ w(` ${icon} ${agent.agentName} [${agent.capability}]\n`);
182
+ w(` State: ${agent.state} | Task: ${agent.beadId}\n`);
183
+ w(` Branch: ${agent.branchName}\n`);
184
+ w(` Parent: ${agent.parentAgent ?? "none"} | Depth: ${agent.depth}\n`);
185
+
186
+ if (agent.fileScope.length === 0) {
187
+ w(" Files: (unrestricted)\n");
188
+ } else {
189
+ w(` Files: ${agent.fileScope.join(", ")}\n`);
190
+ }
191
+
192
+ w("\n");
193
+ }
194
+ }
195
+
196
+ const DISCOVER_HELP = `legio agents discover — Find active agents by capability
197
+
198
+ Usage: legio agents discover [--capability <type>] [--all] [--json]
199
+
200
+ Options:
201
+ --capability <type> Filter by capability (builder, scout, reviewer, lead, merger, coordinator, supervisor)
202
+ --all Include completed and zombie agents (default: active only)
203
+ --json Output as JSON
204
+ --help, -h Show this help`;
205
+
206
+ const AGENTS_HELP = `legio agents — Discover and query agents
207
+
208
+ Usage: legio agents <subcommand> [options]
209
+
210
+ Subcommands:
211
+ discover Find active agents by capability
212
+
213
+ Options:
214
+ --json Output as JSON
215
+ --help, -h Show this help
216
+
217
+ Run 'legio agents <subcommand> --help' for subcommand-specific help.`;
218
+
219
+ /**
220
+ * Handle the 'discover' subcommand.
221
+ */
222
+ async function discoverCommand(args: string[]): Promise<void> {
223
+ if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
224
+ process.stdout.write(`${DISCOVER_HELP}\n`);
225
+ return;
226
+ }
227
+
228
+ const json = hasFlag(args, "--json");
229
+ const includeAll = hasFlag(args, "--all");
230
+ const capability = getFlag(args, "--capability");
231
+
232
+ // Validate capability if provided
233
+ if (capability && !SUPPORTED_CAPABILITIES.includes(capability as never)) {
234
+ throw new ValidationError(
235
+ `Invalid capability: ${capability}. Must be one of: ${SUPPORTED_CAPABILITIES.join(", ")}`,
236
+ {
237
+ field: "capability",
238
+ value: capability,
239
+ },
240
+ );
241
+ }
242
+
243
+ const cwd = process.cwd();
244
+ const config = await loadConfig(cwd);
245
+ const root = config.project.root;
246
+
247
+ const agents = await discoverAgents(root, { capability, includeAll });
248
+
249
+ if (json) {
250
+ process.stdout.write(`${JSON.stringify(agents, null, "\t")}\n`);
251
+ } else {
252
+ printAgents(agents);
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Entry point for `legio agents <subcommand>`.
258
+ */
259
+ export async function agentsCommand(args: string[]): Promise<void> {
260
+ if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
261
+ process.stdout.write(`${AGENTS_HELP}\n`);
262
+ return;
263
+ }
264
+
265
+ // Extract subcommand: first arg that is not a flag
266
+ const subcommand = args.find((arg) => !arg.startsWith("-"));
267
+
268
+ if (!subcommand) {
269
+ process.stdout.write(`${AGENTS_HELP}\n`);
270
+ return;
271
+ }
272
+
273
+ // Remove the subcommand from args before passing to handler
274
+ const subArgs = args.filter((arg) => arg !== subcommand);
275
+
276
+ switch (subcommand) {
277
+ case "discover":
278
+ await discoverCommand(subArgs);
279
+ break;
280
+ default:
281
+ throw new ValidationError(`Unknown subcommand: ${subcommand}`, {
282
+ field: "subcommand",
283
+ value: subcommand,
284
+ });
285
+ }
286
+ }