@os-eco/overstory-cli 0.6.1

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 (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +381 -0
  3. package/agents/builder.md +137 -0
  4. package/agents/coordinator.md +263 -0
  5. package/agents/lead.md +301 -0
  6. package/agents/merger.md +160 -0
  7. package/agents/monitor.md +214 -0
  8. package/agents/reviewer.md +140 -0
  9. package/agents/scout.md +119 -0
  10. package/agents/supervisor.md +423 -0
  11. package/package.json +47 -0
  12. package/src/agents/checkpoint.test.ts +88 -0
  13. package/src/agents/checkpoint.ts +101 -0
  14. package/src/agents/hooks-deployer.test.ts +2040 -0
  15. package/src/agents/hooks-deployer.ts +607 -0
  16. package/src/agents/identity.test.ts +603 -0
  17. package/src/agents/identity.ts +384 -0
  18. package/src/agents/lifecycle.test.ts +196 -0
  19. package/src/agents/lifecycle.ts +183 -0
  20. package/src/agents/manifest.test.ts +746 -0
  21. package/src/agents/manifest.ts +354 -0
  22. package/src/agents/overlay.test.ts +676 -0
  23. package/src/agents/overlay.ts +308 -0
  24. package/src/beads/client.test.ts +217 -0
  25. package/src/beads/client.ts +202 -0
  26. package/src/beads/molecules.test.ts +338 -0
  27. package/src/beads/molecules.ts +198 -0
  28. package/src/commands/agents.test.ts +322 -0
  29. package/src/commands/agents.ts +287 -0
  30. package/src/commands/clean.test.ts +670 -0
  31. package/src/commands/clean.ts +618 -0
  32. package/src/commands/completions.test.ts +342 -0
  33. package/src/commands/completions.ts +887 -0
  34. package/src/commands/coordinator.test.ts +1530 -0
  35. package/src/commands/coordinator.ts +733 -0
  36. package/src/commands/costs.test.ts +1119 -0
  37. package/src/commands/costs.ts +564 -0
  38. package/src/commands/dashboard.test.ts +308 -0
  39. package/src/commands/dashboard.ts +838 -0
  40. package/src/commands/doctor.test.ts +294 -0
  41. package/src/commands/doctor.ts +213 -0
  42. package/src/commands/errors.test.ts +647 -0
  43. package/src/commands/errors.ts +248 -0
  44. package/src/commands/feed.test.ts +578 -0
  45. package/src/commands/feed.ts +361 -0
  46. package/src/commands/group.test.ts +262 -0
  47. package/src/commands/group.ts +511 -0
  48. package/src/commands/hooks.test.ts +458 -0
  49. package/src/commands/hooks.ts +253 -0
  50. package/src/commands/init.test.ts +347 -0
  51. package/src/commands/init.ts +650 -0
  52. package/src/commands/inspect.test.ts +670 -0
  53. package/src/commands/inspect.ts +431 -0
  54. package/src/commands/log.test.ts +1454 -0
  55. package/src/commands/log.ts +724 -0
  56. package/src/commands/logs.test.ts +379 -0
  57. package/src/commands/logs.ts +546 -0
  58. package/src/commands/mail.test.ts +1270 -0
  59. package/src/commands/mail.ts +771 -0
  60. package/src/commands/merge.test.ts +670 -0
  61. package/src/commands/merge.ts +355 -0
  62. package/src/commands/metrics.test.ts +444 -0
  63. package/src/commands/metrics.ts +143 -0
  64. package/src/commands/monitor.test.ts +191 -0
  65. package/src/commands/monitor.ts +390 -0
  66. package/src/commands/nudge.test.ts +230 -0
  67. package/src/commands/nudge.ts +372 -0
  68. package/src/commands/prime.test.ts +470 -0
  69. package/src/commands/prime.ts +381 -0
  70. package/src/commands/replay.test.ts +741 -0
  71. package/src/commands/replay.ts +360 -0
  72. package/src/commands/run.test.ts +431 -0
  73. package/src/commands/run.ts +351 -0
  74. package/src/commands/sling.test.ts +657 -0
  75. package/src/commands/sling.ts +661 -0
  76. package/src/commands/spec.test.ts +203 -0
  77. package/src/commands/spec.ts +168 -0
  78. package/src/commands/status.test.ts +430 -0
  79. package/src/commands/status.ts +398 -0
  80. package/src/commands/stop.test.ts +420 -0
  81. package/src/commands/stop.ts +151 -0
  82. package/src/commands/supervisor.test.ts +187 -0
  83. package/src/commands/supervisor.ts +535 -0
  84. package/src/commands/trace.test.ts +745 -0
  85. package/src/commands/trace.ts +325 -0
  86. package/src/commands/watch.test.ts +145 -0
  87. package/src/commands/watch.ts +247 -0
  88. package/src/commands/worktree.test.ts +786 -0
  89. package/src/commands/worktree.ts +311 -0
  90. package/src/config.test.ts +822 -0
  91. package/src/config.ts +829 -0
  92. package/src/doctor/agents.test.ts +454 -0
  93. package/src/doctor/agents.ts +396 -0
  94. package/src/doctor/config-check.test.ts +190 -0
  95. package/src/doctor/config-check.ts +183 -0
  96. package/src/doctor/consistency.test.ts +651 -0
  97. package/src/doctor/consistency.ts +294 -0
  98. package/src/doctor/databases.test.ts +290 -0
  99. package/src/doctor/databases.ts +218 -0
  100. package/src/doctor/dependencies.test.ts +184 -0
  101. package/src/doctor/dependencies.ts +175 -0
  102. package/src/doctor/logs.test.ts +251 -0
  103. package/src/doctor/logs.ts +295 -0
  104. package/src/doctor/merge-queue.test.ts +216 -0
  105. package/src/doctor/merge-queue.ts +144 -0
  106. package/src/doctor/structure.test.ts +291 -0
  107. package/src/doctor/structure.ts +198 -0
  108. package/src/doctor/types.ts +37 -0
  109. package/src/doctor/version.test.ts +136 -0
  110. package/src/doctor/version.ts +129 -0
  111. package/src/e2e/init-sling-lifecycle.test.ts +277 -0
  112. package/src/errors.ts +217 -0
  113. package/src/events/store.test.ts +660 -0
  114. package/src/events/store.ts +369 -0
  115. package/src/events/tool-filter.test.ts +330 -0
  116. package/src/events/tool-filter.ts +126 -0
  117. package/src/index.ts +316 -0
  118. package/src/insights/analyzer.test.ts +466 -0
  119. package/src/insights/analyzer.ts +203 -0
  120. package/src/logging/color.test.ts +142 -0
  121. package/src/logging/color.ts +71 -0
  122. package/src/logging/logger.test.ts +813 -0
  123. package/src/logging/logger.ts +266 -0
  124. package/src/logging/reporter.test.ts +259 -0
  125. package/src/logging/reporter.ts +109 -0
  126. package/src/logging/sanitizer.test.ts +190 -0
  127. package/src/logging/sanitizer.ts +57 -0
  128. package/src/mail/broadcast.test.ts +203 -0
  129. package/src/mail/broadcast.ts +92 -0
  130. package/src/mail/client.test.ts +773 -0
  131. package/src/mail/client.ts +223 -0
  132. package/src/mail/store.test.ts +705 -0
  133. package/src/mail/store.ts +387 -0
  134. package/src/merge/queue.test.ts +359 -0
  135. package/src/merge/queue.ts +231 -0
  136. package/src/merge/resolver.test.ts +1345 -0
  137. package/src/merge/resolver.ts +645 -0
  138. package/src/metrics/store.test.ts +667 -0
  139. package/src/metrics/store.ts +445 -0
  140. package/src/metrics/summary.test.ts +398 -0
  141. package/src/metrics/summary.ts +178 -0
  142. package/src/metrics/transcript.test.ts +356 -0
  143. package/src/metrics/transcript.ts +175 -0
  144. package/src/mulch/client.test.ts +671 -0
  145. package/src/mulch/client.ts +332 -0
  146. package/src/sessions/compat.test.ts +280 -0
  147. package/src/sessions/compat.ts +104 -0
  148. package/src/sessions/store.test.ts +873 -0
  149. package/src/sessions/store.ts +494 -0
  150. package/src/test-helpers.test.ts +124 -0
  151. package/src/test-helpers.ts +126 -0
  152. package/src/tracker/beads.ts +56 -0
  153. package/src/tracker/factory.test.ts +80 -0
  154. package/src/tracker/factory.ts +64 -0
  155. package/src/tracker/seeds.ts +182 -0
  156. package/src/tracker/types.ts +52 -0
  157. package/src/types.ts +724 -0
  158. package/src/watchdog/daemon.test.ts +1975 -0
  159. package/src/watchdog/daemon.ts +671 -0
  160. package/src/watchdog/health.test.ts +431 -0
  161. package/src/watchdog/health.ts +264 -0
  162. package/src/watchdog/triage.test.ts +164 -0
  163. package/src/watchdog/triage.ts +179 -0
  164. package/src/worktree/manager.test.ts +439 -0
  165. package/src/worktree/manager.ts +198 -0
  166. package/src/worktree/tmux.test.ts +1009 -0
  167. package/src/worktree/tmux.ts +509 -0
  168. package/templates/CLAUDE.md.tmpl +89 -0
  169. package/templates/hooks.json.tmpl +105 -0
  170. package/templates/overlay.md.tmpl +81 -0
@@ -0,0 +1,287 @@
1
+ /**
2
+ * CLI command: overstory agents <sub> [--json]
3
+ *
4
+ * Discover and query agents by capability.
5
+ */
6
+
7
+ import { join } from "node:path";
8
+ import { loadConfig } from "../config.ts";
9
+ import { ValidationError } from "../errors.ts";
10
+ import { openSessionStore } from "../sessions/compat.ts";
11
+ import { type AgentSession, SUPPORTED_CAPABILITIES } from "../types.ts";
12
+
13
+ /**
14
+ * Parse a named flag value from args.
15
+ */
16
+ function getFlag(args: string[], flag: string): string | undefined {
17
+ const idx = args.indexOf(flag);
18
+ if (idx === -1 || idx + 1 >= args.length) {
19
+ return undefined;
20
+ }
21
+ return args[idx + 1];
22
+ }
23
+
24
+ function hasFlag(args: string[], flag: string): boolean {
25
+ return args.includes(flag);
26
+ }
27
+
28
+ /**
29
+ * Discovered agent information including file scope.
30
+ */
31
+ export interface DiscoveredAgent {
32
+ agentName: string;
33
+ capability: string;
34
+ state: string;
35
+ beadId: string;
36
+ branchName: string;
37
+ parentAgent: string | null;
38
+ depth: number;
39
+ fileScope: string[];
40
+ startedAt: string;
41
+ lastActivity: string;
42
+ }
43
+
44
+ /**
45
+ * Extract file scope from an agent's overlay CLAUDE.md.
46
+ * Returns empty array if overlay doesn't exist, has no file scope restrictions,
47
+ * or can't be read.
48
+ *
49
+ * @param worktreePath - Absolute path to the agent's worktree
50
+ * @returns Array of file paths (relative to worktree root)
51
+ */
52
+ export async function extractFileScope(worktreePath: string): Promise<string[]> {
53
+ try {
54
+ const overlayPath = join(worktreePath, ".claude", "CLAUDE.md");
55
+ const overlayFile = Bun.file(overlayPath);
56
+
57
+ if (!(await overlayFile.exists())) {
58
+ return [];
59
+ }
60
+
61
+ const content = await overlayFile.text();
62
+
63
+ // Find the section between "## File Scope (exclusive ownership)" and "## Expertise"
64
+ const startMarker = "## File Scope (exclusive ownership)";
65
+ const endMarker = "## Expertise";
66
+
67
+ const startIdx = content.indexOf(startMarker);
68
+ if (startIdx === -1) {
69
+ return [];
70
+ }
71
+
72
+ const endIdx = content.indexOf(endMarker, startIdx);
73
+ if (endIdx === -1) {
74
+ return [];
75
+ }
76
+
77
+ const section = content.slice(startIdx, endIdx);
78
+
79
+ // Check for "No file scope restrictions"
80
+ if (section.includes("No file scope restrictions")) {
81
+ return [];
82
+ }
83
+
84
+ // Extract file paths from markdown list items: - `path`
85
+ const paths: string[] = [];
86
+ const regex = /^- `(.+)`$/gm;
87
+ let match = regex.exec(section);
88
+
89
+ while (match !== null) {
90
+ if (match[1]) {
91
+ paths.push(match[1]);
92
+ }
93
+ match = regex.exec(section);
94
+ }
95
+
96
+ return paths;
97
+ } catch {
98
+ // Best effort: return empty array if anything fails
99
+ return [];
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Discover agents in the project.
105
+ *
106
+ * @param root - Absolute path to project root
107
+ * @param opts - Filter options
108
+ * @returns Array of discovered agents with file scopes
109
+ */
110
+ export async function discoverAgents(
111
+ root: string,
112
+ opts?: { capability?: string; includeAll?: boolean },
113
+ ): Promise<DiscoveredAgent[]> {
114
+ const overstoryDir = join(root, ".overstory");
115
+ const { store } = openSessionStore(overstoryDir);
116
+
117
+ try {
118
+ const sessions: AgentSession[] = opts?.includeAll ? store.getAll() : store.getActive();
119
+
120
+ // Filter by capability if specified
121
+ let filteredSessions = sessions;
122
+ if (opts?.capability) {
123
+ filteredSessions = sessions.filter((s) => s.capability === opts.capability);
124
+ }
125
+
126
+ // Extract file scopes for each agent
127
+ const agents: DiscoveredAgent[] = await Promise.all(
128
+ filteredSessions.map(async (session) => {
129
+ const fileScope = await extractFileScope(session.worktreePath);
130
+ return {
131
+ agentName: session.agentName,
132
+ capability: session.capability,
133
+ state: session.state,
134
+ beadId: session.beadId,
135
+ branchName: session.branchName,
136
+ parentAgent: session.parentAgent,
137
+ depth: session.depth,
138
+ fileScope,
139
+ startedAt: session.startedAt,
140
+ lastActivity: session.lastActivity,
141
+ };
142
+ }),
143
+ );
144
+
145
+ return agents;
146
+ } finally {
147
+ store.close();
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Format the state icon for display.
153
+ */
154
+ function getStateIcon(state: string): string {
155
+ switch (state) {
156
+ case "working":
157
+ return "●";
158
+ case "booting":
159
+ return "○";
160
+ case "stalled":
161
+ return "◌";
162
+ default:
163
+ return " ";
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Print discovered agents in human-readable format.
169
+ */
170
+ function printAgents(agents: DiscoveredAgent[]): void {
171
+ const w = process.stdout.write.bind(process.stdout);
172
+
173
+ if (agents.length === 0) {
174
+ w("No agents found.\n");
175
+ return;
176
+ }
177
+
178
+ w(`Found ${agents.length} agent${agents.length === 1 ? "" : "s"}:\n\n`);
179
+
180
+ for (const agent of agents) {
181
+ const icon = getStateIcon(agent.state);
182
+ w(` ${icon} ${agent.agentName} [${agent.capability}]\n`);
183
+ w(` State: ${agent.state} | Task: ${agent.beadId}\n`);
184
+ w(` Branch: ${agent.branchName}\n`);
185
+ w(` Parent: ${agent.parentAgent ?? "none"} | Depth: ${agent.depth}\n`);
186
+
187
+ if (agent.fileScope.length === 0) {
188
+ w(" Files: (unrestricted)\n");
189
+ } else {
190
+ w(` Files: ${agent.fileScope.join(", ")}\n`);
191
+ }
192
+
193
+ w("\n");
194
+ }
195
+ }
196
+
197
+ const DISCOVER_HELP = `overstory agents discover — Find active agents by capability
198
+
199
+ Usage: overstory agents discover [--capability <type>] [--all] [--json]
200
+
201
+ Options:
202
+ --capability <type> Filter by capability (builder, scout, reviewer, lead, merger, coordinator, supervisor)
203
+ --all Include completed and zombie agents (default: active only)
204
+ --json Output as JSON
205
+ --help, -h Show this help`;
206
+
207
+ const AGENTS_HELP = `overstory agents — Discover and query agents
208
+
209
+ Usage: overstory agents <subcommand> [options]
210
+
211
+ Subcommands:
212
+ discover Find active agents by capability
213
+
214
+ Options:
215
+ --json Output as JSON
216
+ --help, -h Show this help
217
+
218
+ Run 'overstory agents <subcommand> --help' for subcommand-specific help.`;
219
+
220
+ /**
221
+ * Handle the 'discover' subcommand.
222
+ */
223
+ async function discoverCommand(args: string[]): Promise<void> {
224
+ if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
225
+ process.stdout.write(`${DISCOVER_HELP}\n`);
226
+ return;
227
+ }
228
+
229
+ const json = hasFlag(args, "--json");
230
+ const includeAll = hasFlag(args, "--all");
231
+ const capability = getFlag(args, "--capability");
232
+
233
+ // Validate capability if provided
234
+ if (capability && !SUPPORTED_CAPABILITIES.includes(capability as never)) {
235
+ throw new ValidationError(
236
+ `Invalid capability: ${capability}. Must be one of: ${SUPPORTED_CAPABILITIES.join(", ")}`,
237
+ {
238
+ field: "capability",
239
+ value: capability,
240
+ },
241
+ );
242
+ }
243
+
244
+ const cwd = process.cwd();
245
+ const config = await loadConfig(cwd);
246
+ const root = config.project.root;
247
+
248
+ const agents = await discoverAgents(root, { capability, includeAll });
249
+
250
+ if (json) {
251
+ process.stdout.write(`${JSON.stringify(agents, null, "\t")}\n`);
252
+ } else {
253
+ printAgents(agents);
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Entry point for `overstory agents <subcommand>`.
259
+ */
260
+ export async function agentsCommand(args: string[]): Promise<void> {
261
+ if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
262
+ process.stdout.write(`${AGENTS_HELP}\n`);
263
+ return;
264
+ }
265
+
266
+ // Extract subcommand: first arg that is not a flag
267
+ const subcommand = args.find((arg) => !arg.startsWith("-"));
268
+
269
+ if (!subcommand) {
270
+ process.stdout.write(`${AGENTS_HELP}\n`);
271
+ return;
272
+ }
273
+
274
+ // Remove the subcommand from args before passing to handler
275
+ const subArgs = args.filter((arg) => arg !== subcommand);
276
+
277
+ switch (subcommand) {
278
+ case "discover":
279
+ await discoverCommand(subArgs);
280
+ break;
281
+ default:
282
+ throw new ValidationError(`Unknown subcommand: ${subcommand}`, {
283
+ field: "subcommand",
284
+ value: subcommand,
285
+ });
286
+ }
287
+ }