@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,386 @@
1
+ /**
2
+ * `legio prime` command.
3
+ *
4
+ * Loads context for the orchestrator or a specific agent and outputs it
5
+ * to stdout for injection into Claude Code's context via hooks.
6
+ *
7
+ * Called by the SessionStart hook.
8
+ */
9
+
10
+ import { spawn } from "node:child_process";
11
+ import { readFile, writeFile } from "node:fs/promises";
12
+ import { join } from "node:path";
13
+ import { loadCheckpoint } from "../agents/checkpoint.ts";
14
+ import { loadIdentity } from "../agents/identity.ts";
15
+ import { createManifestLoader } from "../agents/manifest.ts";
16
+ import { loadConfig } from "../config.ts";
17
+ import { AgentError } from "../errors.ts";
18
+ import { createMetricsStore } from "../metrics/store.ts";
19
+ import { createMulchClient } from "../mulch/client.ts";
20
+ import { openSessionStore } from "../sessions/compat.ts";
21
+ import type { AgentIdentity, AgentManifest, SessionCheckpoint, SessionMetrics } from "../types.ts";
22
+ import { getCurrentSessionName } from "../worktree/tmux.ts";
23
+
24
+ /**
25
+ * Gitignore content for .legio/.gitignore.
26
+ * TODO: Import from init.ts once it's exported (parallel branch change).
27
+ * Wildcard+whitelist pattern: ignore everything except tracked config files.
28
+ */
29
+ const LEGIO_GITIGNORE = `# Wildcard+whitelist: ignore everything, whitelist tracked files
30
+ # Auto-healed by legio prime on each session start
31
+ *
32
+ !.gitignore
33
+ !config.yaml
34
+ !agent-manifest.json
35
+ !hooks.json
36
+ !groups.json
37
+ !agent-defs/
38
+ `;
39
+
40
+ /**
41
+ * Parse CLI flags from the args array.
42
+ *
43
+ * Supports:
44
+ * - `--agent <name>` — Prime for a specific agent
45
+ * - `--compact` — Output reduced context
46
+ */
47
+ function parseArgs(args: string[]): { agentName: string | null; compact: boolean } {
48
+ let agentName: string | null = null;
49
+ let compact = false;
50
+
51
+ for (let i = 0; i < args.length; i++) {
52
+ const arg = args[i];
53
+ if (arg === "--agent") {
54
+ const next = args[i + 1];
55
+ if (next === undefined || next.startsWith("--")) {
56
+ throw new AgentError("--agent requires a name argument");
57
+ }
58
+ agentName = next;
59
+ i++; // Skip the value
60
+ } else if (arg === "--compact") {
61
+ compact = true;
62
+ }
63
+ }
64
+
65
+ return { agentName, compact };
66
+ }
67
+
68
+ /**
69
+ * Format the agent manifest section for output.
70
+ */
71
+ function formatManifest(manifest: AgentManifest): string {
72
+ const lines: string[] = [];
73
+ for (const [name, def] of Object.entries(manifest.agents)) {
74
+ const caps = def.capabilities.join(", ");
75
+ const spawn = def.canSpawn ? " (can spawn)" : "";
76
+ lines.push(`- **${name}** [${def.model}]: ${caps}${spawn}`);
77
+ }
78
+ return lines.length > 0 ? lines.join("\n") : "No agents registered.";
79
+ }
80
+
81
+ /**
82
+ * Format recent session metrics for output.
83
+ */
84
+ function formatMetrics(sessions: SessionMetrics[]): string {
85
+ if (sessions.length === 0) {
86
+ return "No recent sessions.";
87
+ }
88
+
89
+ const lines: string[] = [];
90
+ for (const s of sessions) {
91
+ const status = s.completedAt !== null ? "completed" : "in-progress";
92
+ const duration = s.durationMs > 0 ? ` (${Math.round(s.durationMs / 1000)}s)` : "";
93
+ const merge = s.mergeResult !== null ? ` [${s.mergeResult}]` : "";
94
+ lines.push(`- ${s.agentName} (${s.capability}): ${s.beadId} — ${status}${duration}${merge}`);
95
+ }
96
+ return lines.join("\n");
97
+ }
98
+
99
+ /**
100
+ * Format agent identity for output.
101
+ */
102
+ function formatIdentity(identity: AgentIdentity): string {
103
+ const lines: string[] = [];
104
+ lines.push(`Name: ${identity.name}`);
105
+ lines.push(`Capability: ${identity.capability}`);
106
+ lines.push(`Sessions completed: ${identity.sessionsCompleted}`);
107
+
108
+ if (identity.expertiseDomains.length > 0) {
109
+ lines.push(`Expertise: ${identity.expertiseDomains.join(", ")}`);
110
+ }
111
+
112
+ if (identity.recentTasks.length > 0) {
113
+ lines.push("Recent tasks:");
114
+ for (const task of identity.recentTasks) {
115
+ lines.push(` - ${task.beadId}: ${task.summary} (${task.completedAt})`);
116
+ }
117
+ }
118
+
119
+ return lines.join("\n");
120
+ }
121
+
122
+ /**
123
+ * Format checkpoint recovery section for compact priming.
124
+ */
125
+ function formatCheckpointRecovery(checkpoint: SessionCheckpoint): string {
126
+ const lines: string[] = [];
127
+ lines.push("\n## Session Recovery");
128
+ lines.push("");
129
+ lines.push("You are resuming from a previous session that was compacted.");
130
+ lines.push("");
131
+ lines.push(`**Progress so far:** ${checkpoint.progressSummary}`);
132
+ lines.push(`**Files modified:** ${checkpoint.filesModified.join(", ") || "none"}`);
133
+ lines.push(`**Pending work:** ${checkpoint.pendingWork}`);
134
+ lines.push(`**Branch:** ${checkpoint.currentBranch}`);
135
+ return lines.join("\n");
136
+ }
137
+
138
+ /**
139
+ * Auto-heal .legio/.gitignore if its content differs from the template.
140
+ * Ensures existing projects get updated gitignore on session start.
141
+ */
142
+ async function healGitignore(legioDir: string): Promise<void> {
143
+ const gitignorePath = join(legioDir, ".gitignore");
144
+ try {
145
+ const current = await readFile(gitignorePath, "utf-8");
146
+ if (current === LEGIO_GITIGNORE) {
147
+ return; // Already up to date
148
+ }
149
+ } catch {
150
+ // File does not exist — write it fresh
151
+ }
152
+ await writeFile(gitignorePath, LEGIO_GITIGNORE);
153
+ }
154
+
155
+ /**
156
+ * Prime command entry point.
157
+ *
158
+ * Gathers project state and outputs context to stdout for injection
159
+ * into Claude Code's context.
160
+ *
161
+ * @param args - CLI arguments after "prime" subcommand
162
+ */
163
+ const PRIME_HELP = `legio prime — Load context for orchestrator/agent
164
+
165
+ Usage: legio prime [--agent <name>] [--compact]
166
+
167
+ Options:
168
+ --agent <name> Prime for a specific agent (default: orchestrator)
169
+ --compact Output reduced context (for PreCompact hook)
170
+ --help, -h Show this help`;
171
+
172
+ export async function primeCommand(args: string[]): Promise<void> {
173
+ if (args.includes("--help") || args.includes("-h")) {
174
+ process.stdout.write(`${PRIME_HELP}\n`);
175
+ return;
176
+ }
177
+
178
+ const { agentName, compact } = parseArgs(args);
179
+
180
+ // 1. Load config
181
+ const config = await loadConfig(process.cwd());
182
+
183
+ // 2. Auto-heal .legio/.gitignore
184
+ const legioDir = join(config.project.root, ".legio");
185
+ await healGitignore(legioDir);
186
+
187
+ // 3. Load mulch expertise (optional — skip on failure)
188
+ let expertiseOutput: string | null = null;
189
+ if (!compact && config.mulch.enabled) {
190
+ try {
191
+ const mulch = createMulchClient(config.project.root);
192
+ const domains = config.mulch.domains.length > 0 ? config.mulch.domains : undefined;
193
+ expertiseOutput = await mulch.prime(domains, config.mulch.primeFormat);
194
+ } catch {
195
+ // Mulch is optional — silently skip if it fails
196
+ }
197
+ }
198
+
199
+ // 4. Output context (orchestrator or agent)
200
+ if (agentName !== null) {
201
+ // === Agent priming ===
202
+ await outputAgentContext(config, agentName, compact, expertiseOutput);
203
+ } else {
204
+ // === Orchestrator priming ===
205
+ await outputOrchestratorContext(config, compact, expertiseOutput);
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Output context for a specific agent.
211
+ */
212
+ async function outputAgentContext(
213
+ config: Awaited<ReturnType<typeof loadConfig>>,
214
+ agentName: string,
215
+ compact: boolean,
216
+ expertiseOutput: string | null,
217
+ ): Promise<void> {
218
+ const sections: string[] = [];
219
+
220
+ sections.push(`# Agent Context: ${agentName}`);
221
+
222
+ // Check if the agent exists in the SessionStore or has an identity file
223
+ const legioDir = join(config.project.root, ".legio");
224
+ const { store } = openSessionStore(legioDir);
225
+ let sessionExists = false;
226
+ let boundSession: { beadId: string } | null = null;
227
+ try {
228
+ const agentSession = store.getByName(agentName);
229
+ sessionExists = agentSession !== null;
230
+ if (
231
+ agentSession &&
232
+ agentSession.state !== "completed" &&
233
+ agentSession.state !== "zombie" &&
234
+ agentSession.beadId
235
+ ) {
236
+ boundSession = { beadId: agentSession.beadId };
237
+ }
238
+ } finally {
239
+ store.close();
240
+ }
241
+
242
+ // Identity section
243
+ let identity: AgentIdentity | null = null;
244
+ try {
245
+ const baseDir = join(config.project.root, ".legio", "agents");
246
+ identity = await loadIdentity(baseDir, agentName);
247
+ } catch {
248
+ // Identity may not exist yet
249
+ }
250
+
251
+ // Warn if agent is completely unknown (no session and no identity)
252
+ if (!sessionExists && identity === null) {
253
+ process.stderr.write(
254
+ `Warning: agent "${agentName}" not found in sessions or identity store.\n`,
255
+ );
256
+ }
257
+
258
+ sections.push("\n## Identity");
259
+ if (identity !== null) {
260
+ sections.push(formatIdentity(identity));
261
+ } else {
262
+ sections.push("New agent - no prior sessions");
263
+ }
264
+
265
+ // Activation context: if agent has a bound task, inject it
266
+ if (boundSession) {
267
+ sections.push("\n## Activation");
268
+ sections.push(`You have a bound task: **${boundSession.beadId}**`);
269
+ sections.push("Read your overlay at `.claude/CLAUDE.md` and begin working immediately.");
270
+ sections.push("Do not wait for dispatch mail. Your assignment was bound at spawn time.");
271
+ }
272
+
273
+ // In compact mode, check for checkpoint recovery
274
+ if (compact) {
275
+ const baseDir = join(config.project.root, ".legio", "agents");
276
+ const checkpoint = await loadCheckpoint(baseDir, agentName);
277
+ if (checkpoint !== null) {
278
+ sections.push(formatCheckpointRecovery(checkpoint));
279
+ }
280
+ }
281
+
282
+ // In compact mode, skip expertise
283
+ if (!compact && expertiseOutput !== null) {
284
+ sections.push("\n## Expertise");
285
+ sections.push(expertiseOutput.trim());
286
+ }
287
+
288
+ process.stdout.write(`${sections.join("\n")}\n`);
289
+ }
290
+
291
+ /**
292
+ * Output context for the orchestrator.
293
+ */
294
+ async function outputOrchestratorContext(
295
+ config: Awaited<ReturnType<typeof loadConfig>>,
296
+ compact: boolean,
297
+ expertiseOutput: string | null,
298
+ ): Promise<void> {
299
+ // Register orchestrator tmux session for reverse-nudge (agents → orchestrator)
300
+ try {
301
+ const tmuxSession = await getCurrentSessionName();
302
+ if (tmuxSession) {
303
+ const regPath = join(config.project.root, ".legio", "orchestrator-tmux.json");
304
+ await writeFile(
305
+ regPath,
306
+ `${JSON.stringify({ tmuxSession, registeredAt: new Date().toISOString() }, null, "\t")}\n`,
307
+ );
308
+ }
309
+ } catch {
310
+ // Tmux detection is optional — silently skip
311
+ }
312
+
313
+ // Record the orchestrator's current branch for merge targeting
314
+ let sessionBranch: string | null = null;
315
+ try {
316
+ const branch = await new Promise<string>((res) => {
317
+ let out = "";
318
+ const p = spawn("git", ["symbolic-ref", "--short", "HEAD"], {
319
+ cwd: config.project.root,
320
+ stdio: ["ignore", "pipe", "ignore"],
321
+ });
322
+ p.stdout.on("data", (d: Buffer) => {
323
+ out += d.toString();
324
+ });
325
+ p.on("close", () => res(out.trim()));
326
+ p.on("error", () => res(""));
327
+ });
328
+ if (branch) {
329
+ sessionBranch = branch;
330
+ const sessionBranchPath = join(config.project.root, ".legio", "session-branch.txt");
331
+ await writeFile(sessionBranchPath, `${branch}\n`);
332
+ }
333
+ } catch {
334
+ // Branch detection is optional — silently skip
335
+ }
336
+
337
+ const sections: string[] = [];
338
+
339
+ // Project section
340
+ sections.push("# Legio Context");
341
+ sections.push(`\n## Project: ${config.project.name}`);
342
+ sections.push(`Canonical branch: ${config.project.canonicalBranch}`);
343
+ if (sessionBranch && sessionBranch !== config.project.canonicalBranch) {
344
+ sections.push(`Session branch: ${sessionBranch} (merge target)`);
345
+ }
346
+ sections.push(`Max concurrent agents: ${config.agents.maxConcurrent}`);
347
+ sections.push(`Max depth: ${config.agents.maxDepth}`);
348
+
349
+ // Agent manifest section
350
+ sections.push("\n## Agent Manifest");
351
+ try {
352
+ const manifestPath = join(config.project.root, config.agents.manifestPath);
353
+ const baseDir = join(config.project.root, config.agents.baseDir);
354
+ const loader = createManifestLoader(manifestPath, baseDir);
355
+ const manifest = await loader.load();
356
+ sections.push(formatManifest(manifest));
357
+ } catch {
358
+ sections.push("No agent manifest found.");
359
+ }
360
+
361
+ // In compact mode, skip metrics and expertise
362
+ if (!compact) {
363
+ // Recent activity section
364
+ sections.push("\n## Recent Activity");
365
+ try {
366
+ const metricsPath = join(config.project.root, ".legio", "metrics.db");
367
+ const store = createMetricsStore(metricsPath);
368
+ try {
369
+ const sessions = store.getRecentSessions(5);
370
+ sections.push(formatMetrics(sessions));
371
+ } finally {
372
+ store.close();
373
+ }
374
+ } catch {
375
+ sections.push("No metrics available.");
376
+ }
377
+
378
+ // Expertise section
379
+ if (expertiseOutput !== null) {
380
+ sections.push("\n## Expertise");
381
+ sections.push(expertiseOutput.trim());
382
+ }
383
+ }
384
+
385
+ process.stdout.write(`${sections.join("\n")}\n`);
386
+ }