@katyella/legio 0.2.0 → 0.2.2

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 CHANGED
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.2] - 2026-03-02
11
+
12
+ ### Fixed
13
+ - Coordinator and sling beacons now explain what legio is — previously Claude Code on fresh machines rejected thin beacons as "unrecognized" foreign content (same fix as gateway in v0.2.1)
14
+ - Coordinator beacon startup instructions use `legio status` instead of `bd ready` / `legio group status` (doesn't assume beads is installed)
15
+ - `legio doctor` no longer flags persistent agent identity files (coordinator, gateway, monitor) as stale — they legitimately exist outside the agent manifest
16
+ - 2398 tests across 79 test files (up from 2397 across 79)
17
+
18
+ ## [0.2.1] - 2026-03-02
19
+
20
+ ### Fixed
21
+ - Gateway beacon now explains what legio is — previously Claude Code on fresh machines rejected the thin beacon as "unrecognized" foreign content
22
+ - Gateway tmux session now receives provider env vars (`collectProviderEnv()`) — was the only agent type missing them
23
+ - Removed hardcoded `bd create` from gateway beacon (not all users have beads)
24
+ - `legio doctor` checks for bun availability with install instructions (required runtime for seeds and mulch)
25
+ - `legio doctor` marks sd, mulch, and bd as optional dependencies (warn instead of fail)
26
+ - Doctor install hints explain what each tool does and note bun requirement
27
+ - README requirements section separates required (Node, Claude Code, git, tmux) from optional (sd, mulch, bd) dependencies
28
+
10
29
  ## [0.2.0] - 2026-03-02
11
30
 
12
31
  ### Added
@@ -182,7 +201,9 @@ Initial public release on npm as [`@katyella/legio`](https://www.npmjs.com/packa
182
201
  - E2E lifecycle tests via Playwright
183
202
  - Vitest test runner with forks pool for CI compatibility
184
203
 
185
- [Unreleased]: https://github.com/katyella/legio/compare/v0.2.0...HEAD
204
+ [Unreleased]: https://github.com/katyella/legio/compare/v0.2.2...HEAD
205
+ [0.2.2]: https://github.com/katyella/legio/compare/v0.2.1...v0.2.2
206
+ [0.2.1]: https://github.com/katyella/legio/compare/v0.2.0...v0.2.1
186
207
  [0.2.0]: https://github.com/katyella/legio/compare/v0.1.3...v0.2.0
187
208
  [0.1.3]: https://github.com/katyella/legio/compare/v0.1.2...v0.1.3
188
209
  [0.1.2]: https://github.com/katyella/legio/compare/v0.1.1...v0.1.2
package/README.md CHANGED
@@ -116,8 +116,12 @@ npm link
116
116
  - [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
117
117
  - git
118
118
  - tmux
119
- - [bd (beads)](https://github.com/steveyegge/beads) — issue tracking CLI
120
- - [mulch](https://github.com/jayminwest/mulch) — structured expertise management CLI (`npm install -g @os-eco/mulch-cli`)
119
+
120
+ ### Optional
121
+
122
+ - [sd (seeds)](https://github.com/jayminwest/seeds) — issue tracking CLI (`bun install -g @os-eco/seeds-cli`) — requires [Bun](https://bun.sh)
123
+ - [mulch](https://github.com/jayminwest/mulch) — structured expertise management CLI (`bun install -g @os-eco/mulch-cli`) — requires [Bun](https://bun.sh)
124
+ - [bd (beads)](https://github.com/steveyegge/beads) — legacy issue tracker (alternative to seeds)
121
125
 
122
126
  ## Documentation
123
127
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@katyella/legio",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Multi-agent orchestration for Claude Code — spawn worker agents in git worktrees via tmux, coordinate through SQLite mail, merge with tiered conflict resolution",
5
5
  "author": "Matthew Wojtowicz",
6
6
  "license": "MIT",
@@ -875,8 +875,7 @@ describe("buildCoordinatorBeacon", () => {
875
875
  const beacon = buildCoordinatorBeacon();
876
876
  expect(beacon).toContain("mulch prime");
877
877
  expect(beacon).toContain("legio mail check --agent coordinator");
878
- expect(beacon).toContain("bd ready");
879
- expect(beacon).toContain("legio group status");
878
+ expect(beacon).toContain("legio status");
880
879
  });
881
880
 
882
881
  test("includes hierarchy enforcement instruction", () => {
@@ -891,11 +890,17 @@ describe("buildCoordinatorBeacon", () => {
891
890
  expect(beacon).toContain("spawn a lead who will spawn scouts");
892
891
  });
893
892
 
893
+ test("explains what legio is", () => {
894
+ const beacon = buildCoordinatorBeacon();
895
+ expect(beacon).toContain("legio multi-agent orchestration system");
896
+ expect(beacon).toContain("CLI tool installed on this machine");
897
+ });
898
+
894
899
  test("parts are joined with em-dash separator", () => {
895
900
  const beacon = buildCoordinatorBeacon();
896
- // Should have exactly 4 " — " separators (5 parts)
901
+ // Should have exactly 5 " — " separators (6 parts)
897
902
  const dashes = beacon.split(" — ");
898
- expect(dashes).toHaveLength(5);
903
+ expect(dashes).toHaveLength(6);
899
904
  });
900
905
  });
901
906
 
@@ -281,10 +281,11 @@ export function buildCoordinatorBeacon(): string {
281
281
  const timestamp = new Date().toISOString();
282
282
  const parts = [
283
283
  `[LEGIO] ${COORDINATOR_NAME} (coordinator) ${timestamp}`,
284
+ "You are a coordinator agent in the legio multi-agent orchestration system. legio is a CLI tool installed on this machine that coordinates multiple Claude Code agents via tmux, SQLite mail, and git worktrees.",
284
285
  "Depth: 0 | Parent: none | Role: persistent orchestrator",
285
286
  "HIERARCHY: You ONLY spawn leads (legio sling --capability lead). Leads spawn scouts, builders, reviewers. NEVER spawn non-lead agents directly.",
286
287
  "DELEGATION: For any exploration/scouting, spawn a lead who will spawn scouts. Do NOT explore the codebase yourself beyond initial planning.",
287
- `Startup: run mulch prime, check mail (legio mail check --agent ${COORDINATOR_NAME}), check bd ready, check legio group status, then begin work`,
288
+ `Startup: run mulch prime, check mail (legio mail check --agent ${COORDINATOR_NAME}), check legio status, then begin work`,
288
289
  ];
289
290
  return parts.join(" — ");
290
291
  }
@@ -713,21 +713,21 @@ describe("buildGatewayBeacon", () => {
713
713
 
714
714
  test("includes ISSUES notice", () => {
715
715
  const beacon = buildGatewayBeacon();
716
- expect(beacon).toContain("ISSUES: Use bd create");
716
+ expect(beacon).toContain("ISSUES:");
717
+ expect(beacon).toContain("legio status");
717
718
  });
718
719
 
719
720
  test("includes startup instructions", () => {
720
721
  const beacon = buildGatewayBeacon();
721
- expect(beacon).toContain("mulch prime");
722
722
  expect(beacon).toContain("legio mail check --agent gateway");
723
723
  expect(beacon).toContain("respond to user via BOTH terminal AND mail");
724
724
  });
725
725
 
726
726
  test("parts are joined with em-dash separator", () => {
727
727
  const beacon = buildGatewayBeacon();
728
- // Should have exactly 4 " — " separators (5 parts)
728
+ // Should have exactly 5 " — " separators (6 parts)
729
729
  const dashes = beacon.split(" — ");
730
- expect(dashes).toHaveLength(5);
730
+ expect(dashes).toHaveLength(6);
731
731
  });
732
732
 
733
733
  test("default (no args) does not include FIRST_RUN", () => {
@@ -18,7 +18,7 @@ import { join } from "node:path";
18
18
  import { deployHooks } from "../agents/hooks-deployer.ts";
19
19
  import { createIdentity, loadIdentity } from "../agents/identity.ts";
20
20
  import { createManifestLoader, resolveModel } from "../agents/manifest.ts";
21
- import { loadConfig } from "../config.ts";
21
+ import { collectProviderEnv, loadConfig } from "../config.ts";
22
22
  import { AgentError, ValidationError } from "../errors.ts";
23
23
  import { openSessionStore } from "../sessions/compat.ts";
24
24
  import type { SessionStore } from "../sessions/store.ts";
@@ -82,10 +82,11 @@ export function buildGatewayBeacon(isFirstRun = false): string {
82
82
  const timestamp = new Date().toISOString();
83
83
  const parts = [
84
84
  `[LEGIO] ${GATEWAY_NAME} (gateway) ${timestamp}`,
85
- "Depth: 0 | Role: planning companion",
86
- "READONLY: No Write/Edit",
87
- "ISSUES: Use bd create",
88
- `Startup: run mulch prime, check mail (legio mail check --agent ${GATEWAY_NAME}), respond to user via BOTH terminal AND mail (legio mail send --to human --subject "chat" --body "..." --type status --agent ${GATEWAY_NAME}) so replies appear in dashboard chat`,
85
+ "You are a gateway agent in the legio multi-agent orchestration system. legio is a CLI tool installed on this machine that coordinates multiple Claude Code agents via tmux, SQLite mail, and git worktrees.",
86
+ "Depth: 0 | Role: planning companion | READONLY: No Write/Edit tool access",
87
+ 'COMMUNICATION: Use legio mail for all inter-agent messaging. Check your inbox with: legio mail check --agent gateway. Send replies via: legio mail send --to human --subject "chat" --body "..." --type status --agent gateway',
88
+ "ISSUES: If a task tracker is configured, use it to create issues for work decomposition. Check legio status for current agent fleet.",
89
+ `Startup: check mail (legio mail check --agent ${GATEWAY_NAME}), respond to user via BOTH terminal AND mail so replies appear in dashboard chat`,
89
90
  ];
90
91
  if (isFirstRun) {
91
92
  parts.push("FIRST_RUN: true — Follow the First Run workflow in your agent definition");
@@ -238,6 +239,7 @@ async function startGateway(args: string[], deps: GatewayDeps = {}): Promise<voi
238
239
  await writeFile(settingsPath, JSON.stringify(settings), "utf-8");
239
240
  const claudeCmd = `claude --model ${model} --dangerously-skip-permissions --settings ${settingsPath}`;
240
241
  const pid = await tmux.createSession(tmuxSession, projectRoot, claudeCmd, {
242
+ ...collectProviderEnv(),
241
243
  LEGIO_AGENT_NAME: GATEWAY_NAME,
242
244
  });
243
245
 
@@ -399,12 +399,17 @@ describe("buildBeacon", () => {
399
399
  expect(beacon).toContain("Depth: 0 | Parent: none");
400
400
  });
401
401
 
402
+ test("explains what legio is", () => {
403
+ const beacon = buildBeacon(makeBeaconOpts());
404
+ expect(beacon).toContain("legio multi-agent orchestration system");
405
+ expect(beacon).toContain("CLI tool installed on this machine");
406
+ });
407
+
402
408
  test("includes startup instructions with agent name and task ID", () => {
403
409
  const opts = makeBeaconOpts({ agentName: "scout-1", taskId: "legio-xyz" });
404
410
  const beacon = buildBeacon(opts);
405
411
 
406
412
  expect(beacon).toContain("read .claude/CLAUDE.md");
407
- expect(beacon).toContain("mulch prime");
408
413
  expect(beacon).toContain("legio mail check --agent scout-1");
409
414
  expect(beacon).toContain("begin task legio-xyz");
410
415
  });
@@ -115,8 +115,9 @@ export function buildBeacon(opts: BeaconOptions): string {
115
115
  const parent = opts.parentAgent ?? "none";
116
116
  const parts = [
117
117
  `[LEGIO] ${opts.agentName} (${opts.capability}) ${timestamp} task:${opts.taskId}`,
118
+ "You are an agent in the legio multi-agent orchestration system. legio is a CLI tool installed on this machine that coordinates multiple Claude Code agents via tmux, SQLite mail, and git worktrees.",
118
119
  `Depth: ${opts.depth} | Parent: ${parent}`,
119
- `Startup: read .claude/CLAUDE.md, run mulch prime, check mail (legio mail check --agent ${opts.agentName}), then begin task ${opts.taskId}`,
120
+ `Startup: read .claude/CLAUDE.md, check mail (legio mail check --agent ${opts.agentName}), then begin task ${opts.taskId}`,
120
121
  ];
121
122
  return parts.join(" — ");
122
123
  }
@@ -375,6 +375,51 @@ sessionsCompleted: -5
375
375
  expect(identityCheck?.details?.some((d) => d.includes("sessionsCompleted"))).toBe(true);
376
376
  });
377
377
 
378
+ test("does not flag persistent agent identities as stale", async () => {
379
+ const manifest = {
380
+ version: "1.0",
381
+ agents: {
382
+ scout: {
383
+ file: "scout.md",
384
+ model: "haiku",
385
+ tools: ["Read"],
386
+ capabilities: ["explore"],
387
+ canSpawn: false,
388
+ constraints: [],
389
+ },
390
+ },
391
+ capabilityIndex: {
392
+ explore: ["scout"],
393
+ },
394
+ };
395
+
396
+ await mkdir(join(legioDir, "agent-defs"), { recursive: true });
397
+ await mkdir(join(legioDir, "agents", "gateway"), { recursive: true });
398
+ await mkdir(join(legioDir, "agents", "coordinator"), { recursive: true });
399
+ await writeFile(join(legioDir, "agent-manifest.json"), JSON.stringify(manifest, null, 2));
400
+ await writeFile(join(legioDir, "agent-defs", "scout.md"), "# Scout");
401
+
402
+ const identity = `name: gateway
403
+ capability: gateway
404
+ created: "2024-01-01T00:00:00Z"
405
+ sessionsCompleted: 3
406
+ `;
407
+ await writeFile(join(legioDir, "agents", "gateway", "identity.yaml"), identity);
408
+
409
+ const coordIdentity = `name: coordinator
410
+ capability: coordinator
411
+ created: "2024-01-01T00:00:00Z"
412
+ sessionsCompleted: 10
413
+ `;
414
+ await writeFile(join(legioDir, "agents", "coordinator", "identity.yaml"), coordIdentity);
415
+
416
+ const checks = await checkAgents(mockConfig, legioDir);
417
+
418
+ const staleCheck = checks.find((c) => c.name === "Stale identities");
419
+ // Should not warn — persistent agents are expected to have identity files
420
+ expect(staleCheck?.status).not.toBe("warn");
421
+ });
422
+
378
423
  test("warns about stale identity files", async () => {
379
424
  const manifest = {
380
425
  version: "1.0",
@@ -5,6 +5,8 @@ import type { DoctorCheck, DoctorCheckFn } from "./types.ts";
5
5
 
6
6
  const VALID_MODELS = new Set(["sonnet", "opus", "haiku"]);
7
7
  const VALID_NAME_REGEX = /^[a-zA-Z0-9_-]+$/;
8
+ /** Persistent agents create identity files but are not registered in the agent manifest. */
9
+ const PERSISTENT_AGENTS = new Set(["coordinator", "gateway", "monitor"]);
8
10
 
9
11
  /**
10
12
  * Check if a path exists.
@@ -313,8 +315,8 @@ export const checkAgents: DoctorCheckFn = async (_config, legioDir): Promise<Doc
313
315
 
314
316
  identityFileCount++;
315
317
 
316
- // Check if agent still exists in manifest
317
- if (!manifest.agents[agentName]) {
318
+ // Check if agent still exists in manifest (persistent agents are not manifest-registered)
319
+ if (!manifest.agents[agentName] && !PERSISTENT_AGENTS.has(agentName)) {
318
320
  staleIdentities.push(agentName);
319
321
  continue;
320
322
  }
@@ -56,46 +56,26 @@ describe("checkDependencies", () => {
56
56
  const checks = await checkDependencies(mockConfig, "/tmp/.legio");
57
57
 
58
58
  expect(Array.isArray(checks)).toBe(true);
59
- expect(checks.length).toBeGreaterThanOrEqual(5);
59
+ expect(checks.length).toBeGreaterThanOrEqual(6);
60
60
 
61
61
  // Verify we have checks for each tool
62
62
  const toolNames = checks.map((c) => c.name);
63
63
  expect(toolNames).toContain("git availability");
64
64
  expect(toolNames).toContain("node availability");
65
65
  expect(toolNames).toContain("tmux availability");
66
+ expect(toolNames).toContain("bun availability");
66
67
  expect(toolNames).toContain("sd availability");
67
68
  expect(toolNames).toContain("mulch availability");
68
69
  expect(toolNames).toContain("bd availability");
69
70
  });
70
71
 
71
- test("sd is required when backend is auto", async () => {
72
+ test("bun, sd, mulch, and bd are all optional (warn if missing)", async () => {
72
73
  const checks = await checkDependencies(mockConfig, "/tmp/.legio");
73
- const sdCheck = checks.find((c) => c.name === "sd availability");
74
- // sd should be required (fail if missing) when backend is "auto"
75
- if (sdCheck?.status !== "pass") {
76
- expect(sdCheck?.status).toBe("fail");
77
- }
78
- });
79
-
80
- test("bd is optional when backend is auto", async () => {
81
- const checks = await checkDependencies(mockConfig, "/tmp/.legio");
82
- const bdCheck = checks.find((c) => c.name === "bd availability");
83
- // bd should be optional (warn if missing) when backend is "auto"
84
- if (bdCheck?.status !== "pass") {
85
- expect(bdCheck?.status).toBe("warn");
86
- }
87
- });
88
-
89
- test("bd is required when backend is beads", async () => {
90
- const beadsConfig = {
91
- ...mockConfig,
92
- taskTracker: { backend: "beads" as const, enabled: true },
93
- };
94
- const checks = await checkDependencies(beadsConfig, "/tmp/.legio");
95
- const bdCheck = checks.find((c) => c.name === "bd availability");
96
- // bd should be required (fail if missing) when backend is "beads"
97
- if (bdCheck?.status !== "pass") {
98
- expect(bdCheck?.status).toBe("fail");
74
+ for (const name of ["bun", "sd", "mulch", "bd"]) {
75
+ const check = checks.find((c) => c.name === `${name} availability`);
76
+ if (check?.status !== "pass") {
77
+ expect(check?.status).toBe("warn");
78
+ }
99
79
  }
100
80
  });
101
81
 
@@ -8,33 +8,38 @@ import type { DoctorCheck, DoctorCheckFn } from "./types.ts";
8
8
  * bd is checked as optional (legacy tracker backend).
9
9
  */
10
10
  export const checkDependencies: DoctorCheckFn = async (
11
- config: LegioConfig,
11
+ _config: LegioConfig,
12
12
  _legioDir,
13
13
  ): Promise<DoctorCheck[]> => {
14
- const backend = config.taskTracker.backend;
15
-
16
14
  const requiredTools = [
17
15
  { name: "git", versionFlag: "--version", required: true },
18
16
  { name: "node", versionFlag: "--version", required: true },
19
17
  { name: "tmux", versionFlag: "-V", required: true },
18
+ {
19
+ name: "bun",
20
+ versionFlag: "--version",
21
+ required: false,
22
+ installHint: "curl -fsSL https://bun.sh/install | bash — required runtime for sd and mulch",
23
+ },
20
24
  {
21
25
  name: "sd",
22
26
  versionFlag: "--version",
23
- required: backend === "auto" || backend === "seeds",
24
- installHint: "npm install -g @os-eco/seeds-cli — https://github.com/jayminwest/seeds",
27
+ required: false,
28
+ installHint:
29
+ "bun install -g @os-eco/seeds-cli — issue tracker for agent task dispatch (requires Bun)",
25
30
  },
26
31
  {
27
32
  name: "mulch",
28
33
  versionFlag: "--version",
29
- required: true,
30
- installHint: "npm install -g @os-eco/mulch-cli — https://github.com/jayminwest/mulch",
34
+ required: false,
35
+ installHint:
36
+ "bun install -g @os-eco/mulch-cli — structured expertise/memory across sessions (requires Bun)",
31
37
  },
32
38
  {
33
39
  name: "bd",
34
40
  versionFlag: "--version",
35
- required: backend === "beads",
36
- installHint:
37
- "https://github.com/steveyegge/beads (legacy — consider migrating to seeds: sd migrate-from-beads)",
41
+ required: false,
42
+ installHint: "https://github.com/steveyegge/beads — legacy issue tracker (alternative to sd)",
38
43
  },
39
44
  ];
40
45
 
package/src/index.ts CHANGED
@@ -45,7 +45,7 @@ import { worktreeCommand } from "./commands/worktree.ts";
45
45
  import { LegioError, WorktreeError } from "./errors.ts";
46
46
  import { setQuiet } from "./logging/color.ts";
47
47
 
48
- const VERSION = "0.2.0";
48
+ const VERSION = "0.2.2";
49
49
 
50
50
  const HELP = `legio v${VERSION} — Multi-agent orchestration for Claude Code
51
51