sentinelayer-cli 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 (124) hide show
  1. package/README.md +996 -0
  2. package/bin/create-sentinelayer.js +5 -0
  3. package/bin/sentinelayer-cli.js +5 -0
  4. package/bin/sl.js +5 -0
  5. package/package.json +54 -0
  6. package/src/agents/jules/config/definition.js +209 -0
  7. package/src/agents/jules/config/system-prompt.js +175 -0
  8. package/src/agents/jules/error-intake.js +51 -0
  9. package/src/agents/jules/fix-cycle.js +377 -0
  10. package/src/agents/jules/loop.js +367 -0
  11. package/src/agents/jules/pulse.js +319 -0
  12. package/src/agents/jules/stream.js +186 -0
  13. package/src/agents/jules/swarm/file-scanner.js +74 -0
  14. package/src/agents/jules/swarm/index.js +11 -0
  15. package/src/agents/jules/swarm/orchestrator.js +362 -0
  16. package/src/agents/jules/swarm/pattern-hunter.js +123 -0
  17. package/src/agents/jules/swarm/sub-agent.js +308 -0
  18. package/src/agents/jules/tools/auth-audit.js +222 -0
  19. package/src/agents/jules/tools/dispatch.js +327 -0
  20. package/src/agents/jules/tools/file-edit.js +180 -0
  21. package/src/agents/jules/tools/file-read.js +100 -0
  22. package/src/agents/jules/tools/frontend-analyze.js +570 -0
  23. package/src/agents/jules/tools/glob.js +168 -0
  24. package/src/agents/jules/tools/grep.js +228 -0
  25. package/src/agents/jules/tools/index.js +29 -0
  26. package/src/agents/jules/tools/path-guards.js +161 -0
  27. package/src/agents/jules/tools/runtime-audit.js +409 -0
  28. package/src/agents/jules/tools/shell.js +383 -0
  29. package/src/ai/aidenid.js +945 -0
  30. package/src/ai/client.js +508 -0
  31. package/src/ai/domain-target-store.js +268 -0
  32. package/src/ai/identity-store.js +270 -0
  33. package/src/ai/site-store.js +145 -0
  34. package/src/audit/agents/architecture.js +180 -0
  35. package/src/audit/agents/compliance.js +179 -0
  36. package/src/audit/agents/documentation.js +165 -0
  37. package/src/audit/agents/performance.js +145 -0
  38. package/src/audit/agents/security.js +215 -0
  39. package/src/audit/agents/testing.js +172 -0
  40. package/src/audit/orchestrator.js +557 -0
  41. package/src/audit/package.js +204 -0
  42. package/src/audit/registry.js +284 -0
  43. package/src/audit/replay.js +103 -0
  44. package/src/auth/http.js +113 -0
  45. package/src/auth/service.js +848 -0
  46. package/src/auth/session-store.js +345 -0
  47. package/src/cli.js +244 -0
  48. package/src/commands/ai/identity-lifecycle.js +1337 -0
  49. package/src/commands/ai/provision-governance.js +1246 -0
  50. package/src/commands/ai/shared.js +147 -0
  51. package/src/commands/ai.js +11 -0
  52. package/src/commands/apply.js +19 -0
  53. package/src/commands/audit.js +1147 -0
  54. package/src/commands/auth.js +366 -0
  55. package/src/commands/chat.js +191 -0
  56. package/src/commands/config.js +184 -0
  57. package/src/commands/cost.js +311 -0
  58. package/src/commands/daemon/core.js +850 -0
  59. package/src/commands/daemon/extended.js +1048 -0
  60. package/src/commands/daemon/shared.js +213 -0
  61. package/src/commands/daemon.js +11 -0
  62. package/src/commands/guide.js +174 -0
  63. package/src/commands/ingest.js +58 -0
  64. package/src/commands/init.js +55 -0
  65. package/src/commands/legacy-args.js +30 -0
  66. package/src/commands/mcp.js +404 -0
  67. package/src/commands/omargate.js +21 -0
  68. package/src/commands/persona.js +27 -0
  69. package/src/commands/plugin.js +260 -0
  70. package/src/commands/policy.js +132 -0
  71. package/src/commands/prompt.js +238 -0
  72. package/src/commands/review.js +704 -0
  73. package/src/commands/scan.js +788 -0
  74. package/src/commands/spec.js +716 -0
  75. package/src/commands/swarm.js +651 -0
  76. package/src/commands/telemetry.js +202 -0
  77. package/src/commands/watch.js +510 -0
  78. package/src/config/agent-dictionary.js +182 -0
  79. package/src/config/io.js +56 -0
  80. package/src/config/paths.js +18 -0
  81. package/src/config/schema.js +55 -0
  82. package/src/config/service.js +184 -0
  83. package/src/cost/budget.js +235 -0
  84. package/src/cost/history.js +188 -0
  85. package/src/cost/tracker.js +171 -0
  86. package/src/daemon/artifact-lineage.js +534 -0
  87. package/src/daemon/assignment-ledger.js +770 -0
  88. package/src/daemon/ast-parser-layer.js +258 -0
  89. package/src/daemon/budget-governor.js +633 -0
  90. package/src/daemon/callgraph-overlay.js +646 -0
  91. package/src/daemon/error-worker.js +626 -0
  92. package/src/daemon/hybrid-mapper.js +929 -0
  93. package/src/daemon/jira-lifecycle.js +632 -0
  94. package/src/daemon/operator-control.js +657 -0
  95. package/src/daemon/reliability-lane.js +471 -0
  96. package/src/daemon/watchdog.js +971 -0
  97. package/src/guide/generator.js +316 -0
  98. package/src/ingest/engine.js +918 -0
  99. package/src/legacy-cli.js +2435 -0
  100. package/src/mcp/registry.js +695 -0
  101. package/src/memory/blackboard.js +301 -0
  102. package/src/memory/retrieval.js +581 -0
  103. package/src/plugin/manifest.js +553 -0
  104. package/src/policy/packs.js +144 -0
  105. package/src/prompt/generator.js +106 -0
  106. package/src/review/ai-review.js +669 -0
  107. package/src/review/local-review.js +1284 -0
  108. package/src/review/replay.js +235 -0
  109. package/src/review/report.js +664 -0
  110. package/src/review/spec-binding.js +487 -0
  111. package/src/scan/generator.js +351 -0
  112. package/src/spec/generator.js +519 -0
  113. package/src/spec/regenerate.js +237 -0
  114. package/src/spec/templates.js +91 -0
  115. package/src/swarm/dashboard.js +247 -0
  116. package/src/swarm/factory.js +363 -0
  117. package/src/swarm/pentest.js +934 -0
  118. package/src/swarm/registry.js +419 -0
  119. package/src/swarm/report.js +158 -0
  120. package/src/swarm/runtime.js +576 -0
  121. package/src/swarm/scenario-dsl.js +272 -0
  122. package/src/telemetry/ledger.js +302 -0
  123. package/src/ui/markdown.js +220 -0
  124. package/src/ui/progress.js +100 -0
@@ -0,0 +1,419 @@
1
+ import fsp from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ const DEFAULT_BUDGET = Object.freeze({
5
+ maxCostUsd: 0.5,
6
+ maxOutputTokens: 3000,
7
+ maxRuntimeMs: 300000,
8
+ maxToolCalls: 40,
9
+ });
10
+
11
+ const BUILTIN_SWARM_AGENTS = Object.freeze([
12
+ {
13
+ id: "omar",
14
+ persona: "Omar Gate",
15
+ role: "orchestrator",
16
+ domain: "Autonomous Governance",
17
+ tools: ["planner", "budget-governor", "handoff-router"],
18
+ permissionMode: "plan",
19
+ maxTurns: 12,
20
+ confidenceFloor: 0.9,
21
+ allowedPaths: ["."],
22
+ networkMode: "restricted",
23
+ defaultBudget: {
24
+ maxCostUsd: 1.5,
25
+ maxOutputTokens: 10000,
26
+ maxRuntimeMs: 900000,
27
+ maxToolCalls: 120,
28
+ },
29
+ evidenceRequirements: ["scope_constraints", "handoff_manifest", "gate_decision"],
30
+ escalationTargets: ["security", "reliability", "release"],
31
+ },
32
+ {
33
+ id: "security",
34
+ persona: "Nina Patel",
35
+ role: "specialist",
36
+ domain: "Security",
37
+ tools: ["read", "grep", "dependency-audit"],
38
+ permissionMode: "plan",
39
+ maxTurns: 8,
40
+ confidenceFloor: 0.85,
41
+ allowedPaths: ["."],
42
+ networkMode: "restricted",
43
+ defaultBudget: {
44
+ maxCostUsd: 0.8,
45
+ maxOutputTokens: 4000,
46
+ maxRuntimeMs: 480000,
47
+ maxToolCalls: 60,
48
+ },
49
+ evidenceRequirements: ["file_line_reference", "repro_steps"],
50
+ escalationTargets: ["release", "reliability"],
51
+ },
52
+ {
53
+ id: "architecture",
54
+ persona: "Maya Volkov",
55
+ role: "specialist",
56
+ domain: "Architecture",
57
+ tools: ["read", "grep", "structure-map"],
58
+ permissionMode: "plan",
59
+ maxTurns: 8,
60
+ confidenceFloor: 0.82,
61
+ allowedPaths: ["."],
62
+ networkMode: "restricted",
63
+ defaultBudget: { ...DEFAULT_BUDGET },
64
+ evidenceRequirements: ["component_map", "impact_summary"],
65
+ escalationTargets: ["performance", "testing"],
66
+ },
67
+ {
68
+ id: "testing",
69
+ persona: "Priya Raman",
70
+ role: "specialist",
71
+ domain: "Testing",
72
+ tools: ["read", "grep", "test-runner"],
73
+ permissionMode: "plan",
74
+ maxTurns: 6,
75
+ confidenceFloor: 0.8,
76
+ allowedPaths: ["."],
77
+ networkMode: "restricted",
78
+ defaultBudget: { ...DEFAULT_BUDGET },
79
+ evidenceRequirements: ["coverage_gaps", "failing_paths"],
80
+ escalationTargets: ["architecture"],
81
+ },
82
+ {
83
+ id: "performance",
84
+ persona: "Arjun Mehta",
85
+ role: "specialist",
86
+ domain: "Performance",
87
+ tools: ["read", "grep", "profiler-hints"],
88
+ permissionMode: "plan",
89
+ maxTurns: 6,
90
+ confidenceFloor: 0.8,
91
+ allowedPaths: ["."],
92
+ networkMode: "restricted",
93
+ defaultBudget: { ...DEFAULT_BUDGET },
94
+ evidenceRequirements: ["latency_paths", "runtime_assumptions"],
95
+ escalationTargets: ["architecture", "reliability"],
96
+ },
97
+ {
98
+ id: "compliance",
99
+ persona: "Leila Farouk",
100
+ role: "specialist",
101
+ domain: "Compliance",
102
+ tools: ["read", "grep", "control-map"],
103
+ permissionMode: "plan",
104
+ maxTurns: 6,
105
+ confidenceFloor: 0.82,
106
+ allowedPaths: ["."],
107
+ networkMode: "restricted",
108
+ defaultBudget: { ...DEFAULT_BUDGET },
109
+ evidenceRequirements: ["control_refs", "evidence_paths"],
110
+ escalationTargets: ["security", "release"],
111
+ },
112
+ {
113
+ id: "documentation",
114
+ persona: "Samir Okafor",
115
+ role: "specialist",
116
+ domain: "Documentation",
117
+ tools: ["read", "grep", "spec-check"],
118
+ permissionMode: "plan",
119
+ maxTurns: 5,
120
+ confidenceFloor: 0.75,
121
+ allowedPaths: ["."],
122
+ networkMode: "restricted",
123
+ defaultBudget: {
124
+ maxCostUsd: 0.35,
125
+ maxOutputTokens: 2500,
126
+ maxRuntimeMs: 240000,
127
+ maxToolCalls: 30,
128
+ },
129
+ evidenceRequirements: ["doc_paths", "spec_mismatches"],
130
+ escalationTargets: ["architecture", "release"],
131
+ },
132
+ {
133
+ id: "reliability",
134
+ persona: "Noah Ben-David",
135
+ role: "specialist",
136
+ domain: "Reliability",
137
+ tools: ["read", "grep", "failure-analysis"],
138
+ permissionMode: "plan",
139
+ maxTurns: 6,
140
+ confidenceFloor: 0.8,
141
+ allowedPaths: ["."],
142
+ networkMode: "restricted",
143
+ defaultBudget: { ...DEFAULT_BUDGET },
144
+ evidenceRequirements: ["failure_modes", "rollback_plan"],
145
+ escalationTargets: ["release", "observability"],
146
+ },
147
+ {
148
+ id: "release",
149
+ persona: "Omar Singh",
150
+ role: "specialist",
151
+ domain: "Release Engineering",
152
+ tools: ["read", "grep", "workflow-review"],
153
+ permissionMode: "plan",
154
+ maxTurns: 6,
155
+ confidenceFloor: 0.8,
156
+ allowedPaths: ["."],
157
+ networkMode: "restricted",
158
+ defaultBudget: { ...DEFAULT_BUDGET },
159
+ evidenceRequirements: ["workflow_refs", "gate_matrix"],
160
+ escalationTargets: ["security", "reliability"],
161
+ },
162
+ {
163
+ id: "observability",
164
+ persona: "Sofia Alvarez",
165
+ role: "specialist",
166
+ domain: "Observability",
167
+ tools: ["read", "grep", "telemetry-review"],
168
+ permissionMode: "plan",
169
+ maxTurns: 6,
170
+ confidenceFloor: 0.78,
171
+ allowedPaths: ["."],
172
+ networkMode: "restricted",
173
+ defaultBudget: { ...DEFAULT_BUDGET },
174
+ evidenceRequirements: ["signal_inventory", "gaps"],
175
+ escalationTargets: ["reliability", "release"],
176
+ },
177
+ {
178
+ id: "infrastructure",
179
+ persona: "Kat Hughes",
180
+ role: "specialist",
181
+ domain: "Infrastructure",
182
+ tools: ["read", "grep", "infra-lint"],
183
+ permissionMode: "plan",
184
+ maxTurns: 6,
185
+ confidenceFloor: 0.78,
186
+ allowedPaths: ["."],
187
+ networkMode: "restricted",
188
+ defaultBudget: { ...DEFAULT_BUDGET },
189
+ evidenceRequirements: ["infra_paths", "blast_radius"],
190
+ escalationTargets: ["security", "reliability"],
191
+ },
192
+ {
193
+ id: "supply-chain",
194
+ persona: "Nora Kline",
195
+ role: "specialist",
196
+ domain: "Supply Chain",
197
+ tools: ["read", "grep", "dependency-audit"],
198
+ permissionMode: "plan",
199
+ maxTurns: 6,
200
+ confidenceFloor: 0.82,
201
+ allowedPaths: ["."],
202
+ networkMode: "restricted",
203
+ defaultBudget: { ...DEFAULT_BUDGET },
204
+ evidenceRequirements: ["dependency_refs", "version_risks"],
205
+ escalationTargets: ["security", "release"],
206
+ },
207
+ {
208
+ id: "frontend",
209
+ persona: "Jules Tanaka",
210
+ role: "specialist",
211
+ domain: "Frontend",
212
+ tools: ["read", "grep", "ui-lint"],
213
+ permissionMode: "plan",
214
+ maxTurns: 6,
215
+ confidenceFloor: 0.76,
216
+ allowedPaths: ["."],
217
+ networkMode: "restricted",
218
+ defaultBudget: { ...DEFAULT_BUDGET },
219
+ evidenceRequirements: ["component_paths", "repro_steps"],
220
+ escalationTargets: ["testing", "architecture"],
221
+ },
222
+ ]);
223
+
224
+ function normalizeString(value) {
225
+ return String(value || "").trim();
226
+ }
227
+
228
+ function normalizeAgentId(value) {
229
+ return normalizeString(value).toLowerCase();
230
+ }
231
+
232
+ function normalizeRole(value) {
233
+ const normalized = normalizeString(value).toLowerCase();
234
+ if (normalized === "orchestrator" || normalized === "specialist") {
235
+ return normalized;
236
+ }
237
+ return "specialist";
238
+ }
239
+
240
+ function normalizeNetworkMode(value) {
241
+ const normalized = normalizeString(value).toLowerCase();
242
+ if (normalized === "enabled" || normalized === "restricted" || normalized === "disabled") {
243
+ return normalized;
244
+ }
245
+ return "restricted";
246
+ }
247
+
248
+ function normalizePositiveNumber(value, fallback, field) {
249
+ const normalized = Number(value);
250
+ if (Number.isFinite(normalized) && normalized > 0) {
251
+ return normalized;
252
+ }
253
+ if (fallback !== undefined) {
254
+ return Number(fallback);
255
+ }
256
+ throw new Error(`${field} must be a positive number.`);
257
+ }
258
+
259
+ function normalizeBudget(budget = {}, fallback = DEFAULT_BUDGET) {
260
+ if (budget === undefined || budget === null || typeof budget !== "object" || Array.isArray(budget)) {
261
+ throw new Error("defaultBudget must be an object when provided.");
262
+ }
263
+
264
+ return {
265
+ maxCostUsd: normalizePositiveNumber(
266
+ budget.maxCostUsd,
267
+ fallback.maxCostUsd,
268
+ "defaultBudget.maxCostUsd"
269
+ ),
270
+ maxOutputTokens: Math.floor(
271
+ normalizePositiveNumber(
272
+ budget.maxOutputTokens,
273
+ fallback.maxOutputTokens,
274
+ "defaultBudget.maxOutputTokens"
275
+ )
276
+ ),
277
+ maxRuntimeMs: Math.floor(
278
+ normalizePositiveNumber(
279
+ budget.maxRuntimeMs,
280
+ fallback.maxRuntimeMs,
281
+ "defaultBudget.maxRuntimeMs"
282
+ )
283
+ ),
284
+ maxToolCalls: Math.floor(
285
+ normalizePositiveNumber(
286
+ budget.maxToolCalls,
287
+ fallback.maxToolCalls,
288
+ "defaultBudget.maxToolCalls"
289
+ )
290
+ ),
291
+ };
292
+ }
293
+
294
+ function normalizeAgentRecord(record = {}, existing = {}) {
295
+ const id = normalizeAgentId(record.id || existing.id);
296
+ const fallbackBudget = existing.defaultBudget || DEFAULT_BUDGET;
297
+
298
+ return {
299
+ id,
300
+ persona: normalizeString(record.persona || existing.persona),
301
+ role: normalizeRole(record.role || existing.role),
302
+ domain: normalizeString(record.domain || existing.domain),
303
+ tools: Array.isArray(record.tools || existing.tools)
304
+ ? (record.tools || existing.tools).map((item) => normalizeString(item)).filter(Boolean)
305
+ : [],
306
+ permissionMode: normalizeString(record.permissionMode || existing.permissionMode || "plan") || "plan",
307
+ maxTurns: Math.max(1, Math.floor(Number(record.maxTurns || existing.maxTurns || 1))),
308
+ confidenceFloor: Math.max(
309
+ 0,
310
+ Math.min(1, Number(record.confidenceFloor || existing.confidenceFloor || 0.7))
311
+ ),
312
+ allowedPaths: Array.isArray(record.allowedPaths || existing.allowedPaths)
313
+ ? (record.allowedPaths || existing.allowedPaths).map((item) => normalizeString(item)).filter(Boolean)
314
+ : ["."],
315
+ networkMode: normalizeNetworkMode(record.networkMode || existing.networkMode),
316
+ defaultBudget: normalizeBudget(record.defaultBudget || existing.defaultBudget || {}, fallbackBudget),
317
+ evidenceRequirements: Array.isArray(record.evidenceRequirements || existing.evidenceRequirements)
318
+ ? (record.evidenceRequirements || existing.evidenceRequirements)
319
+ .map((item) => normalizeString(item))
320
+ .filter(Boolean)
321
+ : [],
322
+ escalationTargets: Array.isArray(record.escalationTargets || existing.escalationTargets)
323
+ ? (record.escalationTargets || existing.escalationTargets)
324
+ .map((item) => normalizeAgentId(item))
325
+ .filter(Boolean)
326
+ : [],
327
+ };
328
+ }
329
+
330
+ function mergeRegistry(builtinAgents = [], overrideAgents = []) {
331
+ const byId = new Map();
332
+ const order = [];
333
+
334
+ for (const builtin of builtinAgents) {
335
+ const normalized = normalizeAgentRecord(builtin);
336
+ byId.set(normalized.id, normalized);
337
+ order.push(normalized.id);
338
+ }
339
+
340
+ for (const override of overrideAgents) {
341
+ const id = normalizeAgentId(override.id);
342
+ if (!id) {
343
+ continue;
344
+ }
345
+ if (!byId.has(id)) {
346
+ order.push(id);
347
+ }
348
+ const existing = byId.get(id) || { id };
349
+ byId.set(id, normalizeAgentRecord(override, existing));
350
+ }
351
+
352
+ return order.map((id) => ({ ...byId.get(id) })).filter(Boolean);
353
+ }
354
+
355
+ function parseAgentFilter(rawValue) {
356
+ const normalized = normalizeString(rawValue);
357
+ if (!normalized) {
358
+ return [];
359
+ }
360
+ return [...new Set(normalized.split(",").map((item) => normalizeAgentId(item)).filter(Boolean))];
361
+ }
362
+
363
+ export function listBuiltinSwarmAgents() {
364
+ return BUILTIN_SWARM_AGENTS.map((agent) => normalizeAgentRecord(agent));
365
+ }
366
+
367
+ export async function loadSwarmRegistry({ registryFile = "" } = {}) {
368
+ const builtin = listBuiltinSwarmAgents();
369
+ const resolvedRegistryFile = normalizeString(registryFile)
370
+ ? path.resolve(process.cwd(), registryFile)
371
+ : "";
372
+ if (!resolvedRegistryFile) {
373
+ return {
374
+ registrySource: "builtin",
375
+ registryFile: "",
376
+ agents: builtin,
377
+ };
378
+ }
379
+
380
+ const raw = await fsp.readFile(resolvedRegistryFile, "utf-8");
381
+ const parsed = JSON.parse(raw);
382
+ if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.agents)) {
383
+ throw new Error("Invalid swarm registry file: expected { agents: [...] }.");
384
+ }
385
+
386
+ return {
387
+ registrySource: "custom",
388
+ registryFile: resolvedRegistryFile,
389
+ agents: mergeRegistry(builtin, parsed.agents),
390
+ };
391
+ }
392
+
393
+ export function selectSwarmAgents(agents = [], requested = "") {
394
+ const requestedIds = parseAgentFilter(requested);
395
+ if (requestedIds.length === 0) {
396
+ return {
397
+ selected: [...agents],
398
+ requestedIds,
399
+ missing: [],
400
+ };
401
+ }
402
+
403
+ const byId = new Map(agents.map((agent) => [agent.id, agent]));
404
+ const selected = [];
405
+ const missing = [];
406
+ for (const id of requestedIds) {
407
+ const agent = byId.get(id);
408
+ if (!agent) {
409
+ missing.push(id);
410
+ continue;
411
+ }
412
+ selected.push(agent);
413
+ }
414
+ return {
415
+ selected,
416
+ requestedIds,
417
+ missing,
418
+ };
419
+ }
@@ -0,0 +1,158 @@
1
+ import fsp from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ import { resolveOutputRoot } from "../config/service.js";
5
+ import { loadSwarmDashboardSnapshot, resolveSwarmRuntimeFiles } from "./dashboard.js";
6
+
7
+ function normalizeString(value) {
8
+ return String(value || "").trim();
9
+ }
10
+
11
+ async function pathExists(targetPath) {
12
+ try {
13
+ await fsp.access(targetPath);
14
+ return true;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ function summarizeAgents(agentRows = []) {
21
+ const summary = {
22
+ completed: 0,
23
+ running: 0,
24
+ stopped: 0,
25
+ unknown: 0,
26
+ };
27
+ for (const row of agentRows) {
28
+ const status = normalizeString(row.status).toLowerCase();
29
+ if (status === "completed") {
30
+ summary.completed += 1;
31
+ continue;
32
+ }
33
+ if (status === "running") {
34
+ summary.running += 1;
35
+ continue;
36
+ }
37
+ if (status === "stopped") {
38
+ summary.stopped += 1;
39
+ continue;
40
+ }
41
+ summary.unknown += 1;
42
+ }
43
+ return summary;
44
+ }
45
+
46
+ function buildReportMarkdown(report = {}) {
47
+ const agentRows = (report.agentRows || [])
48
+ .map(
49
+ (row) =>
50
+ `- ${row.agentId} status=${row.status} events=${row.eventCount} last=${row.lastEventType || "n/a"}`
51
+ )
52
+ .join("\n");
53
+ const recentEvents = (report.recentEvents || [])
54
+ .map((event) => `- ${event.timestamp} [${event.eventType}] ${event.agentId}: ${event.message}`)
55
+ .join("\n");
56
+
57
+ return `# SWARM_EXECUTION_REPORT
58
+
59
+ Generated: ${report.generatedAt}
60
+ Runtime run ID: ${report.runtimeRunId}
61
+ Plan run ID: ${report.planRunId || "n/a"}
62
+ Scenario: ${report.scenario}
63
+ Engine: ${report.engine}
64
+ Execute: ${report.execute ? "yes" : "no"}
65
+ Completed: ${report.completed ? "yes" : "no"}
66
+ Stop class: ${report.stop?.stopClass || "NONE"}
67
+ Stop reason: ${report.stop?.reason || "none"}
68
+
69
+ Usage:
70
+ - output_tokens: ${report.usage.outputTokens || 0}
71
+ - tool_calls: ${report.usage.toolCalls || 0}
72
+ - duration_ms: ${report.usage.durationMs || 0}
73
+ - cost_usd: ${report.usage.costUsd || 0}
74
+
75
+ Agents:
76
+ ${agentRows || "- none"}
77
+
78
+ Recent events:
79
+ ${recentEvents || "- none"}
80
+
81
+ Artifacts:
82
+ - runtime_json: ${report.runtimeJsonPath}
83
+ - runtime_events: ${report.runtimeEventsPath}
84
+ - plan_json: ${report.planJsonPath || "n/a"}
85
+ - report_json: ${report.reportJsonPath}
86
+ `;
87
+ }
88
+
89
+ export async function buildSwarmExecutionReport({
90
+ targetPath = ".",
91
+ outputDir = "",
92
+ runId = "",
93
+ planFile = "",
94
+ env,
95
+ homeDir,
96
+ } = {}) {
97
+ const files = await resolveSwarmRuntimeFiles({
98
+ targetPath,
99
+ outputDir,
100
+ runId,
101
+ env,
102
+ homeDir,
103
+ });
104
+ const snapshot = await loadSwarmDashboardSnapshot({
105
+ targetPath,
106
+ outputDir,
107
+ runId: files.runId,
108
+ env,
109
+ homeDir,
110
+ });
111
+
112
+ const outputRoot = await resolveOutputRoot({
113
+ cwd: path.resolve(String(targetPath || ".")),
114
+ outputDirOverride: outputDir,
115
+ env,
116
+ homeDir,
117
+ });
118
+
119
+ const runtimeSummary = JSON.parse(await fsp.readFile(files.runtimeJsonPath, "utf-8"));
120
+ const selectedPlanPath = normalizeString(planFile)
121
+ ? path.resolve(process.cwd(), planFile)
122
+ : path.join(outputRoot, "swarms", normalizeString(runtimeSummary.planRunId), "SWARM_PLAN.json");
123
+ const planJsonPath = (await pathExists(selectedPlanPath)) ? selectedPlanPath : "";
124
+ const plan = planJsonPath ? JSON.parse(await fsp.readFile(planJsonPath, "utf-8")) : null;
125
+
126
+ const reportJsonPath = path.join(files.runtimeDirectory, "SWARM_EXECUTION_REPORT.json");
127
+ const reportMarkdownPath = path.join(files.runtimeDirectory, "SWARM_EXECUTION_REPORT.md");
128
+ const report = {
129
+ schemaVersion: 1,
130
+ generatedAt: new Date().toISOString(),
131
+ runtimeRunId: runtimeSummary.runId,
132
+ planRunId: normalizeString(runtimeSummary.planRunId),
133
+ scenario: runtimeSummary.scenario,
134
+ engine: runtimeSummary.engine,
135
+ execute: Boolean(runtimeSummary.execute),
136
+ completed: Boolean(runtimeSummary.completed),
137
+ stop: runtimeSummary.stop || {
138
+ stopClass: "NONE",
139
+ reason: "",
140
+ blocking: false,
141
+ },
142
+ usage: runtimeSummary.usage || {},
143
+ eventCount: runtimeSummary.eventCount || snapshot.eventCount,
144
+ agentRows: snapshot.agentRows,
145
+ agentSummary: summarizeAgents(snapshot.agentRows),
146
+ recentEvents: snapshot.recentEvents,
147
+ runtimeJsonPath: files.runtimeJsonPath,
148
+ runtimeEventsPath: files.runtimeEventsPath,
149
+ planJsonPath,
150
+ planSelectedAgents: Array.isArray(plan?.selectedAgents) ? plan.selectedAgents : [],
151
+ reportJsonPath,
152
+ reportMarkdownPath,
153
+ };
154
+
155
+ await fsp.writeFile(reportJsonPath, `${JSON.stringify(report, null, 2)}\n`, "utf-8");
156
+ await fsp.writeFile(reportMarkdownPath, `${buildReportMarkdown(report).trim()}\n`, "utf-8");
157
+ return report;
158
+ }