@katyella/legio 0.1.3 → 0.2.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.
- package/CHANGELOG.md +40 -3
- package/README.md +15 -8
- package/agents/builder.md +11 -10
- package/agents/coordinator.md +36 -27
- package/agents/cto.md +9 -8
- package/agents/gateway.md +28 -12
- package/agents/lead.md +45 -30
- package/agents/merger.md +4 -4
- package/agents/monitor.md +10 -9
- package/agents/reviewer.md +8 -8
- package/agents/scout.md +10 -10
- package/agents/supervisor.md +60 -45
- package/package.json +2 -2
- package/src/agents/hooks-deployer.test.ts +46 -41
- package/src/agents/hooks-deployer.ts +10 -9
- package/src/agents/manifest.test.ts +6 -2
- package/src/agents/overlay.test.ts +9 -7
- package/src/agents/overlay.ts +29 -7
- package/src/commands/agents.test.ts +1 -5
- package/src/commands/clean.test.ts +2 -5
- package/src/commands/clean.ts +25 -1
- package/src/commands/completions.test.ts +1 -1
- package/src/commands/completions.ts +26 -7
- package/src/commands/coordinator.test.ts +78 -78
- package/src/commands/coordinator.ts +92 -47
- package/src/commands/costs.test.ts +2 -6
- package/src/commands/dashboard.test.ts +2 -5
- package/src/commands/doctor.test.ts +2 -6
- package/src/commands/down.ts +3 -3
- package/src/commands/errors.test.ts +2 -6
- package/src/commands/feed.test.ts +2 -6
- package/src/commands/gateway.test.ts +39 -13
- package/src/commands/gateway.ts +95 -7
- package/src/commands/hooks.test.ts +2 -5
- package/src/commands/init.test.ts +4 -13
- package/src/commands/inspect.test.ts +2 -6
- package/src/commands/log.test.ts +2 -6
- package/src/commands/logs.test.ts +2 -9
- package/src/commands/mail.test.ts +76 -215
- package/src/commands/mail.ts +43 -187
- package/src/commands/metrics.test.ts +3 -10
- package/src/commands/nudge.ts +15 -0
- package/src/commands/prime.test.ts +4 -11
- package/src/commands/replay.test.ts +2 -6
- package/src/commands/server.test.ts +1 -5
- package/src/commands/server.ts +1 -1
- package/src/commands/sling.ts +40 -16
- package/src/commands/spec.test.ts +2 -5
- package/src/commands/status.test.ts +2 -4
- package/src/commands/stop.test.ts +2 -5
- package/src/commands/supervisor.ts +6 -6
- package/src/commands/trace.test.ts +2 -6
- package/src/commands/up.test.ts +43 -9
- package/src/commands/up.ts +15 -11
- package/src/commands/watchman.ts +327 -0
- package/src/commands/worktree.test.ts +2 -6
- package/src/config.test.ts +34 -104
- package/src/config.ts +120 -32
- package/src/doctor/agents.test.ts +7 -2
- package/src/doctor/config-check.test.ts +7 -2
- package/src/doctor/consistency.test.ts +7 -2
- package/src/doctor/databases.test.ts +6 -2
- package/src/doctor/dependencies.test.ts +35 -10
- package/src/doctor/dependencies.ts +16 -92
- package/src/doctor/logs.test.ts +7 -2
- package/src/doctor/merge-queue.test.ts +6 -2
- package/src/doctor/structure.test.ts +7 -2
- package/src/doctor/version.test.ts +7 -2
- package/src/e2e/init-sling-lifecycle.test.ts +2 -5
- package/src/index.ts +7 -7
- package/src/mail/pending.ts +120 -0
- package/src/mail/store.test.ts +89 -0
- package/src/mail/store.ts +11 -0
- package/src/merge/resolver.test.ts +518 -489
- package/src/server/index.ts +33 -2
- package/src/server/public/app.js +3 -3
- package/src/server/public/components/message-bubble.js +11 -1
- package/src/server/public/components/terminal-panel.js +66 -74
- package/src/server/public/views/chat.js +18 -2
- package/src/server/public/views/costs.js +5 -5
- package/src/server/public/views/dashboard.js +80 -51
- package/src/server/public/views/gateway-chat.js +37 -131
- package/src/server/public/views/inspect.js +16 -4
- package/src/server/public/views/issues.js +16 -12
- package/src/server/routes.test.ts +55 -39
- package/src/server/routes.ts +38 -26
- package/src/test-helpers.ts +6 -3
- package/src/tracker/beads.ts +159 -0
- package/src/tracker/exec.ts +44 -0
- package/src/tracker/factory.test.ts +283 -0
- package/src/tracker/factory.ts +59 -0
- package/src/tracker/seeds.ts +156 -0
- package/src/tracker/types.ts +46 -0
- package/src/types.ts +11 -2
- package/src/{watchdog → watchman}/daemon.test.ts +421 -515
- package/src/watchman/daemon.ts +940 -0
- package/src/worktree/tmux.test.ts +2 -1
- package/src/worktree/tmux.ts +4 -4
- package/templates/hooks.json.tmpl +17 -17
- package/src/beads/client.test.ts +0 -210
- package/src/commands/merge.test.ts +0 -676
- package/src/commands/watch.test.ts +0 -152
- package/src/commands/watch.ts +0 -238
- package/src/test-helpers.test.ts +0 -97
- package/src/watchdog/daemon.ts +0 -533
- package/src/watchdog/health.test.ts +0 -371
- package/src/watchdog/triage.test.ts +0 -162
- package/src/worktree/manager.test.ts +0 -444
- /package/src/{watchdog → watchman}/health.ts +0 -0
- /package/src/{watchdog → watchman}/triage.ts +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracker factory with auto-detection.
|
|
3
|
+
*
|
|
4
|
+
* Detects which tracker backend is present in the project root and
|
|
5
|
+
* returns the appropriate TrackerClient.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { createBeadsTrackerClient } from "./beads.ts";
|
|
11
|
+
import { createSeedsTrackerClient } from "./seeds.ts";
|
|
12
|
+
import type { TrackerBackend, TrackerClient } from "./types.ts";
|
|
13
|
+
|
|
14
|
+
// Re-export types for convenience
|
|
15
|
+
export type { TrackerBackend, TrackerClient, TrackerIssue } from "./types.ts";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Auto-detect which tracker backend to use.
|
|
19
|
+
* Checks .seeds/ first, then .beads/. Defaults to seeds.
|
|
20
|
+
*
|
|
21
|
+
* @param cwd - Directory to search for tracker markers
|
|
22
|
+
*/
|
|
23
|
+
export function resolveBackend(cwd: string): "seeds" | "beads" {
|
|
24
|
+
if (existsSync(join(cwd, ".seeds"))) return "seeds";
|
|
25
|
+
if (existsSync(join(cwd, ".beads"))) return "beads";
|
|
26
|
+
return "seeds"; // default
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Create a TrackerClient for the given backend.
|
|
31
|
+
*
|
|
32
|
+
* @param backend - The backend to use ("auto" detects from filesystem)
|
|
33
|
+
* @param cwd - Working directory for CLI commands and backend detection
|
|
34
|
+
*/
|
|
35
|
+
export function createTrackerClient(backend: TrackerBackend, cwd: string): TrackerClient {
|
|
36
|
+
const resolved = backend === "auto" ? resolveBackend(cwd) : backend;
|
|
37
|
+
switch (resolved) {
|
|
38
|
+
case "seeds":
|
|
39
|
+
return createSeedsTrackerClient(cwd);
|
|
40
|
+
case "beads":
|
|
41
|
+
return createBeadsTrackerClient(cwd);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Return the CLI tool name for a resolved backend.
|
|
47
|
+
* Used by the overlay generator to inject {{TRACKER_CLI}} into agent definitions.
|
|
48
|
+
*/
|
|
49
|
+
export function trackerCliName(backend: "seeds" | "beads"): string {
|
|
50
|
+
return backend === "seeds" ? "sd" : "bd";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Return the human-readable tracker name for a resolved backend.
|
|
55
|
+
* Used by the overlay generator to inject {{TRACKER_NAME}} into agent definitions.
|
|
56
|
+
*/
|
|
57
|
+
export function trackerDisplayName(backend: "seeds" | "beads"): string {
|
|
58
|
+
return backend === "seeds" ? "seeds" : "beads";
|
|
59
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seeds adapter for the tracker abstraction layer.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the `sd` CLI to implement TrackerClient using the seeds backend.
|
|
5
|
+
* Follows the same patterns as the beads adapter.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { AgentError } from "../errors.ts";
|
|
9
|
+
import { runTrackerCommand } from "./exec.ts";
|
|
10
|
+
import type { TrackerClient, TrackerIssue } from "./types.ts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Parse JSON output from an sd command.
|
|
14
|
+
*/
|
|
15
|
+
function parseJsonOutput<T>(stdout: string, context: string): T {
|
|
16
|
+
const trimmed = stdout.trim();
|
|
17
|
+
if (trimmed === "") {
|
|
18
|
+
throw new AgentError(`Empty output from sd ${context}`);
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(trimmed) as T;
|
|
22
|
+
} catch {
|
|
23
|
+
throw new AgentError(
|
|
24
|
+
`Failed to parse JSON output from sd ${context}: ${trimmed.slice(0, 200)}`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Raw issue shape from the sd CLI (snake_case fields).
|
|
31
|
+
*/
|
|
32
|
+
interface RawSeedIssue {
|
|
33
|
+
id: string;
|
|
34
|
+
title: string;
|
|
35
|
+
status: string;
|
|
36
|
+
priority: number;
|
|
37
|
+
issue_type?: string;
|
|
38
|
+
type?: string;
|
|
39
|
+
owner?: string;
|
|
40
|
+
assignee?: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
blocks?: string[];
|
|
43
|
+
blocked_by?: string[];
|
|
44
|
+
blockedBy?: string[];
|
|
45
|
+
closed_at?: string;
|
|
46
|
+
close_reason?: string;
|
|
47
|
+
created_at?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Normalize a raw sd issue into a TrackerIssue (camelCase).
|
|
52
|
+
*/
|
|
53
|
+
export function normalizeIssue(raw: RawSeedIssue): TrackerIssue {
|
|
54
|
+
return {
|
|
55
|
+
id: raw.id,
|
|
56
|
+
title: raw.title,
|
|
57
|
+
status: raw.status,
|
|
58
|
+
priority: raw.priority,
|
|
59
|
+
type: raw.issue_type ?? raw.type ?? "unknown",
|
|
60
|
+
assignee: raw.owner ?? raw.assignee,
|
|
61
|
+
description: raw.description,
|
|
62
|
+
blocks: raw.blocks,
|
|
63
|
+
blockedBy: raw.blocked_by ?? raw.blockedBy,
|
|
64
|
+
closedAt: raw.closed_at,
|
|
65
|
+
closeReason: raw.close_reason,
|
|
66
|
+
createdAt: raw.created_at,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create a TrackerClient backed by the seeds (sd) CLI.
|
|
72
|
+
*
|
|
73
|
+
* @param cwd - Working directory where sd commands should run
|
|
74
|
+
*/
|
|
75
|
+
export function createSeedsTrackerClient(cwd: string): TrackerClient {
|
|
76
|
+
async function runSd(
|
|
77
|
+
args: string[],
|
|
78
|
+
context: string,
|
|
79
|
+
): Promise<{ stdout: string; stderr: string }> {
|
|
80
|
+
const { stdout, stderr, exitCode } = await runTrackerCommand(["sd", ...args], cwd);
|
|
81
|
+
if (exitCode !== 0) {
|
|
82
|
+
throw new AgentError(`sd ${context} failed (exit ${exitCode}): ${stderr.trim()}`);
|
|
83
|
+
}
|
|
84
|
+
return { stdout, stderr };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
async ready(options): Promise<TrackerIssue[]> {
|
|
89
|
+
const args = ["ready", "--json"];
|
|
90
|
+
if (options?.mol) {
|
|
91
|
+
args.push("--mol", options.mol);
|
|
92
|
+
}
|
|
93
|
+
const { stdout } = await runSd(args, "ready");
|
|
94
|
+
const raw = parseJsonOutput<RawSeedIssue[]>(stdout, "ready");
|
|
95
|
+
return raw.map(normalizeIssue);
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
async show(id): Promise<TrackerIssue> {
|
|
99
|
+
const { stdout } = await runSd(["show", id, "--json"], `show ${id}`);
|
|
100
|
+
const raw = parseJsonOutput<RawSeedIssue[]>(stdout, `show ${id}`);
|
|
101
|
+
const first = raw[0];
|
|
102
|
+
if (!first) {
|
|
103
|
+
throw new AgentError(`sd show ${id} returned empty array`);
|
|
104
|
+
}
|
|
105
|
+
return normalizeIssue(first);
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
async create(title, options): Promise<string> {
|
|
109
|
+
const args = ["create", title, "--json"];
|
|
110
|
+
if (options?.type) {
|
|
111
|
+
args.push("--type", options.type);
|
|
112
|
+
}
|
|
113
|
+
if (options?.priority !== undefined) {
|
|
114
|
+
args.push("--priority", String(options.priority));
|
|
115
|
+
}
|
|
116
|
+
if (options?.description) {
|
|
117
|
+
args.push("--description", options.description);
|
|
118
|
+
}
|
|
119
|
+
const { stdout } = await runSd(args, "create");
|
|
120
|
+
const result = parseJsonOutput<{ id: string }>(stdout, "create");
|
|
121
|
+
return result.id;
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
async claim(id): Promise<void> {
|
|
125
|
+
await runSd(["update", id, "--status", "in_progress"], `claim ${id}`);
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
async close(id, reason): Promise<void> {
|
|
129
|
+
const args = ["close", id];
|
|
130
|
+
if (reason) {
|
|
131
|
+
args.push("--reason", reason);
|
|
132
|
+
}
|
|
133
|
+
await runSd(args, `close ${id}`);
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
async list(options): Promise<TrackerIssue[]> {
|
|
137
|
+
const args = ["list", "--json"];
|
|
138
|
+
if (options?.status) {
|
|
139
|
+
args.push("--status", options.status);
|
|
140
|
+
}
|
|
141
|
+
if (options?.limit !== undefined) {
|
|
142
|
+
args.push("--limit", String(options.limit));
|
|
143
|
+
}
|
|
144
|
+
if (options?.all) {
|
|
145
|
+
args.push("--all");
|
|
146
|
+
}
|
|
147
|
+
const { stdout } = await runSd(args, "list");
|
|
148
|
+
const raw = parseJsonOutput<RawSeedIssue[]>(stdout, "list");
|
|
149
|
+
return raw.map(normalizeIssue);
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
async sync(): Promise<void> {
|
|
153
|
+
await runSd(["sync"], "sync");
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracker abstraction layer types.
|
|
3
|
+
*
|
|
4
|
+
* Defines a pluggable TrackerClient interface that works with any backend
|
|
5
|
+
* (beads via bd CLI, or seeds via sd CLI).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** A tracker issue as returned by any backend. */
|
|
9
|
+
export interface TrackerIssue {
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
status: string;
|
|
13
|
+
priority: number;
|
|
14
|
+
type: string;
|
|
15
|
+
assignee?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
blocks?: string[];
|
|
18
|
+
blockedBy?: string[];
|
|
19
|
+
closedAt?: string;
|
|
20
|
+
closeReason?: string;
|
|
21
|
+
createdAt?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Supported tracker backends. */
|
|
25
|
+
export type TrackerBackend = "auto" | "seeds" | "beads";
|
|
26
|
+
|
|
27
|
+
/** Pluggable tracker client interface. */
|
|
28
|
+
export interface TrackerClient {
|
|
29
|
+
/** List issues that are ready for work (open, unblocked). */
|
|
30
|
+
ready(options?: { mol?: string }): Promise<TrackerIssue[]>;
|
|
31
|
+
/** Show details for a specific issue. */
|
|
32
|
+
show(id: string): Promise<TrackerIssue>;
|
|
33
|
+
/** Create a new issue. Returns the new issue ID. */
|
|
34
|
+
create(
|
|
35
|
+
title: string,
|
|
36
|
+
options?: { type?: string; priority?: number; description?: string },
|
|
37
|
+
): Promise<string>;
|
|
38
|
+
/** Claim an issue (mark as in_progress). */
|
|
39
|
+
claim(id: string): Promise<void>;
|
|
40
|
+
/** Close an issue with an optional reason. */
|
|
41
|
+
close(id: string, reason?: string): Promise<void>;
|
|
42
|
+
/** List issues with optional filters. */
|
|
43
|
+
list(options?: { status?: string; limit?: number; all?: boolean }): Promise<TrackerIssue[]>;
|
|
44
|
+
/** Sync tracker state (e.g., bd sync or sd sync). */
|
|
45
|
+
sync(): Promise<void>;
|
|
46
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -33,7 +33,12 @@ export interface LegioConfig {
|
|
|
33
33
|
worktrees: {
|
|
34
34
|
baseDir: string; // Where worktrees live
|
|
35
35
|
};
|
|
36
|
-
|
|
36
|
+
/** @deprecated Use taskTracker instead. Kept for backward compatibility with inline fixtures. */
|
|
37
|
+
beads?: {
|
|
38
|
+
enabled: boolean;
|
|
39
|
+
};
|
|
40
|
+
taskTracker: {
|
|
41
|
+
backend: "auto" | "seeds" | "beads";
|
|
37
42
|
enabled: boolean;
|
|
38
43
|
};
|
|
39
44
|
mulch: {
|
|
@@ -46,13 +51,17 @@ export interface LegioConfig {
|
|
|
46
51
|
aiResolveEnabled: boolean;
|
|
47
52
|
reimagineEnabled: boolean;
|
|
48
53
|
};
|
|
49
|
-
|
|
54
|
+
watchman: {
|
|
50
55
|
tier0Enabled: boolean; // Tier 0: Mechanical daemon (heartbeat, tmux/pid liveness)
|
|
51
56
|
tier0IntervalMs: number; // Default 30_000
|
|
52
57
|
tier1Enabled: boolean; // Tier 1: Triage agent (ephemeral AI analysis)
|
|
53
58
|
tier2Enabled: boolean; // Tier 2: Monitor agent (continuous patrol — not yet implemented)
|
|
54
59
|
zombieThresholdMs: number; // When to kill
|
|
55
60
|
nudgeIntervalMs: number; // Time between progressive nudge stages (default 60_000)
|
|
61
|
+
mailIntervalMs: number; // Mail polling interval (default 5_000)
|
|
62
|
+
reNudgeIntervalMs: number; // Time between re-nudges for the same agent (default 10_000)
|
|
63
|
+
warnAfterMs: number; // Warn after unread mail sits this long (default 60_000)
|
|
64
|
+
beaconNudgeMs: number; // Time before sending follow-up Enter to stuck beacon (default 20_000)
|
|
56
65
|
};
|
|
57
66
|
models: Partial<Record<string, ModelAlias>>;
|
|
58
67
|
logging: {
|