squads-cli 0.6.2 → 0.7.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/README.md +196 -1152
- package/dist/auth-YW3UPFSB.js +23 -0
- package/dist/autonomy-BWTVDEAT.js +102 -0
- package/dist/autonomy-BWTVDEAT.js.map +1 -0
- package/dist/chunk-3KCWNZWW.js +401 -0
- package/dist/chunk-3KCWNZWW.js.map +1 -0
- package/dist/chunk-67RO2HKR.js +174 -0
- package/dist/chunk-67RO2HKR.js.map +1 -0
- package/dist/chunk-7JVD7RD4.js +275 -0
- package/dist/chunk-7JVD7RD4.js.map +1 -0
- package/dist/chunk-BODLDQY7.js +452 -0
- package/dist/chunk-BODLDQY7.js.map +1 -0
- package/dist/chunk-FFFCFZ6A.js +121 -0
- package/dist/chunk-FFFCFZ6A.js.map +1 -0
- package/dist/chunk-FIWT2NMM.js +165 -0
- package/dist/chunk-FIWT2NMM.js.map +1 -0
- package/dist/chunk-L6GQCHDF.js +222 -0
- package/dist/chunk-L6GQCHDF.js.map +1 -0
- package/dist/{chunk-O7UV3FWI.js → chunk-LDM62TIX.js} +2 -2
- package/dist/chunk-LDM62TIX.js.map +1 -0
- package/dist/chunk-LOA3KWYJ.js +294 -0
- package/dist/chunk-LOA3KWYJ.js.map +1 -0
- package/dist/chunk-NA45DFXY.js +616 -0
- package/dist/chunk-NA45DFXY.js.map +1 -0
- package/dist/{chunk-4CMAEQQY.js → chunk-NQN6JPI7.js} +4 -3
- package/dist/chunk-NQN6JPI7.js.map +1 -0
- package/dist/chunk-OQJHPULO.js +103 -0
- package/dist/chunk-OQJHPULO.js.map +1 -0
- package/dist/chunk-QHNUMM4V.js +87 -0
- package/dist/chunk-QHNUMM4V.js.map +1 -0
- package/dist/chunk-RM6BWILN.js +74 -0
- package/dist/chunk-RM6BWILN.js.map +1 -0
- package/dist/chunk-WBR5J7EX.js +90 -0
- package/dist/chunk-WBR5J7EX.js.map +1 -0
- package/dist/chunk-Z2UKDBNL.js +162 -0
- package/dist/chunk-Z2UKDBNL.js.map +1 -0
- package/dist/cli.js +2136 -12600
- package/dist/cli.js.map +1 -1
- package/dist/context-M2A2DOFV.js +291 -0
- package/dist/context-M2A2DOFV.js.map +1 -0
- package/dist/context-feed-JMNW4GAM.js +391 -0
- package/dist/context-feed-JMNW4GAM.js.map +1 -0
- package/dist/cost-N37I4UTA.js +274 -0
- package/dist/cost-N37I4UTA.js.map +1 -0
- package/dist/create-554W5HNU.js +286 -0
- package/dist/create-554W5HNU.js.map +1 -0
- package/dist/daemon-XWPQPPPN.js +546 -0
- package/dist/daemon-XWPQPPPN.js.map +1 -0
- package/dist/dashboard-L7YKVQEB.js +945 -0
- package/dist/dashboard-L7YKVQEB.js.map +1 -0
- package/dist/dashboard-MFNRLCEE.js +794 -0
- package/dist/dashboard-MFNRLCEE.js.map +1 -0
- package/dist/doctor-RG75M5RO.js +346 -0
- package/dist/doctor-RG75M5RO.js.map +1 -0
- package/dist/env-config-KCLDBKYX.js +21 -0
- package/dist/exec-JQKBF7BL.js +197 -0
- package/dist/exec-JQKBF7BL.js.map +1 -0
- package/dist/feedback-KA2UYBZG.js +229 -0
- package/dist/feedback-KA2UYBZG.js.map +1 -0
- package/dist/github-UQTM5KMS.js +23 -0
- package/dist/goal-EOPC5ZCD.js +168 -0
- package/dist/goal-EOPC5ZCD.js.map +1 -0
- package/dist/health-3FZDOSR5.js +209 -0
- package/dist/health-3FZDOSR5.js.map +1 -0
- package/dist/history-TFVXJEDH.js +229 -0
- package/dist/history-TFVXJEDH.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/init-UOWTNMIE.js +747 -0
- package/dist/init-UOWTNMIE.js.map +1 -0
- package/dist/kpi-2SQ2WCVT.js +413 -0
- package/dist/kpi-2SQ2WCVT.js.map +1 -0
- package/dist/learn-6ERTERAO.js +269 -0
- package/dist/learn-6ERTERAO.js.map +1 -0
- package/dist/list-KSOMUBMB.js +92 -0
- package/dist/list-KSOMUBMB.js.map +1 -0
- package/dist/login-ST6PAXYE.js +155 -0
- package/dist/login-ST6PAXYE.js.map +1 -0
- package/dist/memory-3CSNKXIL.js +562 -0
- package/dist/memory-3CSNKXIL.js.map +1 -0
- package/dist/progress-FKG4V2VH.js +202 -0
- package/dist/progress-FKG4V2VH.js.map +1 -0
- package/dist/providers-66PDCORB.js +65 -0
- package/dist/providers-66PDCORB.js.map +1 -0
- package/dist/results-2MJFLWEO.js +224 -0
- package/dist/results-2MJFLWEO.js.map +1 -0
- package/dist/run-72OQLH5A.js +2685 -0
- package/dist/run-72OQLH5A.js.map +1 -0
- package/dist/session-6H67XPAQ.js +64 -0
- package/dist/session-6H67XPAQ.js.map +1 -0
- package/dist/{chunk-NHGLXN2F.js → sessions-GVQIMN4W.js} +23 -459
- package/dist/sessions-GVQIMN4W.js.map +1 -0
- package/dist/{squad-parser-4BI3G4RS.js → squad-parser-CM3HOIWM.js} +2 -2
- package/dist/squad-parser-CM3HOIWM.js.map +1 -0
- package/dist/stats-ONZI557Q.js +335 -0
- package/dist/stats-ONZI557Q.js.map +1 -0
- package/dist/status-FYH42FTB.js +346 -0
- package/dist/status-FYH42FTB.js.map +1 -0
- package/dist/sync-HJZJNXHW.js +800 -0
- package/dist/sync-HJZJNXHW.js.map +1 -0
- package/dist/update-B4WMUOPO.js +83 -0
- package/dist/update-B4WMUOPO.js.map +1 -0
- package/dist/{update-ALJKFFM7.js → update-L7FGHN6W.js} +2 -2
- package/dist/update-L7FGHN6W.js.map +1 -0
- package/package.json +18 -10
- package/dist/chunk-4CMAEQQY.js.map +0 -1
- package/dist/chunk-NHGLXN2F.js.map +0 -1
- package/dist/chunk-O7UV3FWI.js.map +0 -1
- package/dist/sessions-6PB7ALCE.js +0 -16
- /package/dist/{sessions-6PB7ALCE.js.map → auth-YW3UPFSB.js.map} +0 -0
- /package/dist/{squad-parser-4BI3G4RS.js.map → env-config-KCLDBKYX.js.map} +0 -0
- /package/dist/{update-ALJKFFM7.js.map → github-UQTM5KMS.js.map} +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
findMemoryDir
|
|
4
|
+
} from "./chunk-ZTQ7ISUR.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/executions.ts
|
|
7
|
+
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
function parseExecutionEntry(content, squad, agent) {
|
|
10
|
+
const idMatch = content.match(/<!-- exec:(\S+) -->/);
|
|
11
|
+
if (!idMatch) return null;
|
|
12
|
+
const id = idMatch[1];
|
|
13
|
+
const headerMatch = content.match(/\*\*([^*]+)\*\* \| Status: (\w+)/);
|
|
14
|
+
if (!headerMatch) return null;
|
|
15
|
+
const startTime = headerMatch[1].trim();
|
|
16
|
+
const status = headerMatch[2];
|
|
17
|
+
const triggerMatch = content.match(/- Trigger: (\w+)/);
|
|
18
|
+
const taskTypeMatch = content.match(/- Task Type: (\w+)/);
|
|
19
|
+
const completedMatch = content.match(/- Completed: ([^\n]+)/);
|
|
20
|
+
const durationMatch = content.match(/- Duration: ([^\n]+)/);
|
|
21
|
+
const outcomeMatch = content.match(/- Outcome: ([^\n]+)/);
|
|
22
|
+
const errorMatch = content.match(/- Error: ([^\n]+)/);
|
|
23
|
+
let durationMs;
|
|
24
|
+
if (durationMatch) {
|
|
25
|
+
const durationStr = durationMatch[1].trim();
|
|
26
|
+
const secMatch = durationStr.match(/^([\d.]+)s$/);
|
|
27
|
+
if (secMatch) {
|
|
28
|
+
durationMs = parseFloat(secMatch[1]) * 1e3;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
id,
|
|
33
|
+
squad,
|
|
34
|
+
agent,
|
|
35
|
+
startTime,
|
|
36
|
+
endTime: completedMatch?.[1]?.trim(),
|
|
37
|
+
durationMs,
|
|
38
|
+
status,
|
|
39
|
+
trigger: triggerMatch?.[1] || "manual",
|
|
40
|
+
taskType: taskTypeMatch?.[1] || "execution",
|
|
41
|
+
outcome: outcomeMatch?.[1]?.trim(),
|
|
42
|
+
error: errorMatch?.[1]?.trim()
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function parseExecutionLog(filePath, squad, agent) {
|
|
46
|
+
if (!existsSync(filePath)) return [];
|
|
47
|
+
const content = readFileSync(filePath, "utf-8");
|
|
48
|
+
const executions = [];
|
|
49
|
+
const entries = content.split(/\n---\n/);
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
if (!entry.includes("<!-- exec:")) continue;
|
|
52
|
+
const execution = parseExecutionEntry(entry, squad, agent);
|
|
53
|
+
if (execution) {
|
|
54
|
+
executions.push(execution);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
if (entry.includes("<!-- exec:")) continue;
|
|
59
|
+
const headerMatch = entry.match(/\*\*([^*]+)\*\* \| Status: (\w+)/);
|
|
60
|
+
if (!headerMatch) continue;
|
|
61
|
+
const startTime = headerMatch[1].trim();
|
|
62
|
+
const status = headerMatch[2];
|
|
63
|
+
const legacyId = `legacy_${startTime.replace(/[^a-z0-9]/gi, "")}`;
|
|
64
|
+
if (executions.some((e) => e.startTime === startTime)) continue;
|
|
65
|
+
executions.push({
|
|
66
|
+
id: legacyId,
|
|
67
|
+
squad,
|
|
68
|
+
agent,
|
|
69
|
+
startTime,
|
|
70
|
+
status,
|
|
71
|
+
trigger: "manual",
|
|
72
|
+
taskType: "execution"
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return executions;
|
|
76
|
+
}
|
|
77
|
+
function listExecutions(options = {}) {
|
|
78
|
+
const memoryDir = findMemoryDir();
|
|
79
|
+
if (!memoryDir) return [];
|
|
80
|
+
const executions = [];
|
|
81
|
+
const { squad: filterSquad, agent: filterAgent, status: filterStatus, limit, since } = options;
|
|
82
|
+
const squads = readdirSync(memoryDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
83
|
+
for (const squad of squads) {
|
|
84
|
+
if (filterSquad && squad !== filterSquad) continue;
|
|
85
|
+
const squadPath = join(memoryDir, squad);
|
|
86
|
+
const agents = readdirSync(squadPath, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
87
|
+
for (const agent of agents) {
|
|
88
|
+
if (filterAgent && agent !== filterAgent) continue;
|
|
89
|
+
const logPath = join(squadPath, agent, "executions.md");
|
|
90
|
+
const agentExecutions = parseExecutionLog(logPath, squad, agent);
|
|
91
|
+
executions.push(...agentExecutions);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
let filtered = filterStatus ? executions.filter((e) => e.status === filterStatus) : executions;
|
|
95
|
+
if (since) {
|
|
96
|
+
const sinceMs = since.getTime();
|
|
97
|
+
filtered = filtered.filter((e) => {
|
|
98
|
+
const execDate = new Date(e.startTime).getTime();
|
|
99
|
+
return !isNaN(execDate) && execDate >= sinceMs;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
filtered.sort((a, b) => {
|
|
103
|
+
const aTime = new Date(a.startTime).getTime();
|
|
104
|
+
const bTime = new Date(b.startTime).getTime();
|
|
105
|
+
if (isNaN(aTime) || isNaN(bTime)) return 0;
|
|
106
|
+
return bTime - aTime;
|
|
107
|
+
});
|
|
108
|
+
if (limit && limit > 0) {
|
|
109
|
+
filtered = filtered.slice(0, limit);
|
|
110
|
+
}
|
|
111
|
+
return filtered;
|
|
112
|
+
}
|
|
113
|
+
function getExecutionStats(options = {}) {
|
|
114
|
+
const executions = listExecutions(options);
|
|
115
|
+
const running = executions.filter((e) => e.status === "running").length;
|
|
116
|
+
const completed = executions.filter((e) => e.status === "completed").length;
|
|
117
|
+
const failed = executions.filter((e) => e.status === "failed").length;
|
|
118
|
+
const durations = executions.filter((e) => e.status === "completed" && e.durationMs).map((e) => e.durationMs);
|
|
119
|
+
const avgDurationMs = durations.length > 0 ? durations.reduce((a, b) => a + b, 0) / durations.length : null;
|
|
120
|
+
const bySquad = {};
|
|
121
|
+
for (const e of executions) {
|
|
122
|
+
bySquad[e.squad] = (bySquad[e.squad] || 0) + 1;
|
|
123
|
+
}
|
|
124
|
+
const byAgent = {};
|
|
125
|
+
for (const e of executions) {
|
|
126
|
+
const key = `${e.squad}/${e.agent}`;
|
|
127
|
+
byAgent[key] = (byAgent[key] || 0) + 1;
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
total: executions.length,
|
|
131
|
+
running,
|
|
132
|
+
completed,
|
|
133
|
+
failed,
|
|
134
|
+
avgDurationMs,
|
|
135
|
+
bySquad,
|
|
136
|
+
byAgent
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function formatDuration(ms) {
|
|
140
|
+
if (!ms) return "\u2014";
|
|
141
|
+
if (ms < 1e3) {
|
|
142
|
+
return `${ms}ms`;
|
|
143
|
+
}
|
|
144
|
+
if (ms < 6e4) {
|
|
145
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
146
|
+
}
|
|
147
|
+
if (ms < 36e5) {
|
|
148
|
+
const mins2 = Math.floor(ms / 6e4);
|
|
149
|
+
const secs = Math.round(ms % 6e4 / 1e3);
|
|
150
|
+
return `${mins2}m ${secs}s`;
|
|
151
|
+
}
|
|
152
|
+
const hours = Math.floor(ms / 36e5);
|
|
153
|
+
const mins = Math.round(ms % 36e5 / 6e4);
|
|
154
|
+
return `${hours}h ${mins}m`;
|
|
155
|
+
}
|
|
156
|
+
function formatRelativeTime(isoTime) {
|
|
157
|
+
const date = new Date(isoTime);
|
|
158
|
+
if (isNaN(date.getTime())) return isoTime;
|
|
159
|
+
const now = Date.now();
|
|
160
|
+
const diff = now - date.getTime();
|
|
161
|
+
if (diff < 6e4) return "just now";
|
|
162
|
+
if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
|
|
163
|
+
if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
|
|
164
|
+
if (diff < 6048e5) return `${Math.floor(diff / 864e5)}d ago`;
|
|
165
|
+
return date.toLocaleDateString();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export {
|
|
169
|
+
listExecutions,
|
|
170
|
+
getExecutionStats,
|
|
171
|
+
formatDuration,
|
|
172
|
+
formatRelativeTime
|
|
173
|
+
};
|
|
174
|
+
//# sourceMappingURL=chunk-67RO2HKR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/executions.ts"],"sourcesContent":["/**\n * Execution log parsing and querying\n *\n * Parses execution logs from .agents/memory/<squad>/<agent>/executions.md\n * and provides query functions for the `squads exec` command.\n */\n\nimport { readFileSync, existsSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { findMemoryDir } from './memory.js';\n\nexport interface Execution {\n id: string;\n squad: string;\n agent: string;\n startTime: string;\n endTime?: string;\n durationMs?: number;\n status: 'running' | 'completed' | 'failed';\n trigger: 'manual' | 'scheduled' | 'event' | 'smart';\n taskType: 'evaluation' | 'execution' | 'research' | 'lead';\n outcome?: string;\n error?: string;\n}\n\nexport interface ExecutionListOptions {\n squad?: string;\n agent?: string;\n status?: Execution['status'];\n limit?: number;\n since?: Date;\n}\n\n/**\n * Parse a single execution entry from markdown\n */\nfunction parseExecutionEntry(\n content: string,\n squad: string,\n agent: string\n): Execution | null {\n // Extract execution ID from comment marker\n const idMatch = content.match(/<!-- exec:(\\S+) -->/);\n if (!idMatch) return null;\n\n const id = idMatch[1];\n\n // Extract timestamp and status from header\n const headerMatch = content.match(/\\*\\*([^*]+)\\*\\* \\| Status: (\\w+)/);\n if (!headerMatch) return null;\n\n const startTime = headerMatch[1].trim();\n const status = headerMatch[2] as Execution['status'];\n\n // Parse structured fields\n const triggerMatch = content.match(/- Trigger: (\\w+)/);\n const taskTypeMatch = content.match(/- Task Type: (\\w+)/);\n const completedMatch = content.match(/- Completed: ([^\\n]+)/);\n const durationMatch = content.match(/- Duration: ([^\\n]+)/);\n const outcomeMatch = content.match(/- Outcome: ([^\\n]+)/);\n const errorMatch = content.match(/- Error: ([^\\n]+)/);\n\n // Parse duration string to ms\n let durationMs: number | undefined;\n if (durationMatch) {\n const durationStr = durationMatch[1].trim();\n const secMatch = durationStr.match(/^([\\d.]+)s$/);\n if (secMatch) {\n durationMs = parseFloat(secMatch[1]) * 1000;\n }\n }\n\n return {\n id,\n squad,\n agent,\n startTime,\n endTime: completedMatch?.[1]?.trim(),\n durationMs,\n status,\n trigger: (triggerMatch?.[1] || 'manual') as Execution['trigger'],\n taskType: (taskTypeMatch?.[1] || 'execution') as Execution['taskType'],\n outcome: outcomeMatch?.[1]?.trim(),\n error: errorMatch?.[1]?.trim(),\n };\n}\n\n/**\n * Parse all executions from an agent's execution log\n */\nfunction parseExecutionLog(filePath: string, squad: string, agent: string): Execution[] {\n if (!existsSync(filePath)) return [];\n\n const content = readFileSync(filePath, 'utf-8');\n const executions: Execution[] = [];\n\n // Split by entry separator\n const entries = content.split(/\\n---\\n/);\n\n for (const entry of entries) {\n if (!entry.includes('<!-- exec:')) continue;\n\n const execution = parseExecutionEntry(entry, squad, agent);\n if (execution) {\n executions.push(execution);\n }\n }\n\n // Also try to parse legacy format entries (without exec: marker)\n // These will have limited data but we can still extract basic info\n for (const entry of entries) {\n if (entry.includes('<!-- exec:')) continue; // Already parsed\n\n const headerMatch = entry.match(/\\*\\*([^*]+)\\*\\* \\| Status: (\\w+)/);\n if (!headerMatch) continue;\n\n const startTime = headerMatch[1].trim();\n const status = headerMatch[2] as Execution['status'];\n\n // Generate a deterministic ID from timestamp for legacy entries\n const legacyId = `legacy_${startTime.replace(/[^a-z0-9]/gi, '')}`;\n\n // Skip if we already have this (by timestamp proximity)\n if (executions.some(e => e.startTime === startTime)) continue;\n\n executions.push({\n id: legacyId,\n squad,\n agent,\n startTime,\n status,\n trigger: 'manual',\n taskType: 'execution',\n });\n }\n\n return executions;\n}\n\n/**\n * List all executions across all squads\n */\nexport function listExecutions(options: ExecutionListOptions = {}): Execution[] {\n const memoryDir = findMemoryDir();\n if (!memoryDir) return [];\n\n const executions: Execution[] = [];\n const { squad: filterSquad, agent: filterAgent, status: filterStatus, limit, since } = options;\n\n // Find all squad directories\n const squads = readdirSync(memoryDir, { withFileTypes: true })\n .filter(e => e.isDirectory())\n .map(e => e.name);\n\n for (const squad of squads) {\n if (filterSquad && squad !== filterSquad) continue;\n\n const squadPath = join(memoryDir, squad);\n const agents = readdirSync(squadPath, { withFileTypes: true })\n .filter(e => e.isDirectory())\n .map(e => e.name);\n\n for (const agent of agents) {\n if (filterAgent && agent !== filterAgent) continue;\n\n const logPath = join(squadPath, agent, 'executions.md');\n const agentExecutions = parseExecutionLog(logPath, squad, agent);\n executions.push(...agentExecutions);\n }\n }\n\n // Filter by status\n let filtered = filterStatus\n ? executions.filter(e => e.status === filterStatus)\n : executions;\n\n // Filter by date\n if (since) {\n const sinceMs = since.getTime();\n filtered = filtered.filter(e => {\n const execDate = new Date(e.startTime).getTime();\n return !isNaN(execDate) && execDate >= sinceMs;\n });\n }\n\n // Sort by start time (most recent first)\n filtered.sort((a, b) => {\n const aTime = new Date(a.startTime).getTime();\n const bTime = new Date(b.startTime).getTime();\n if (isNaN(aTime) || isNaN(bTime)) return 0;\n return bTime - aTime;\n });\n\n // Apply limit\n if (limit && limit > 0) {\n filtered = filtered.slice(0, limit);\n }\n\n return filtered;\n}\n\n/**\n * Get a specific execution by ID\n */\nexport function getExecution(executionId: string): Execution | null {\n const executions = listExecutions();\n return executions.find(e => e.id === executionId) || null;\n}\n\n/**\n * Get execution statistics\n */\nexport function getExecutionStats(options: ExecutionListOptions = {}): {\n total: number;\n running: number;\n completed: number;\n failed: number;\n avgDurationMs: number | null;\n bySquad: Record<string, number>;\n byAgent: Record<string, number>;\n} {\n const executions = listExecutions(options);\n\n const running = executions.filter(e => e.status === 'running').length;\n const completed = executions.filter(e => e.status === 'completed').length;\n const failed = executions.filter(e => e.status === 'failed').length;\n\n // Calculate average duration from completed executions\n const durations = executions\n .filter(e => e.status === 'completed' && e.durationMs)\n .map(e => e.durationMs!);\n const avgDurationMs = durations.length > 0\n ? durations.reduce((a, b) => a + b, 0) / durations.length\n : null;\n\n // Count by squad\n const bySquad: Record<string, number> = {};\n for (const e of executions) {\n bySquad[e.squad] = (bySquad[e.squad] || 0) + 1;\n }\n\n // Count by agent\n const byAgent: Record<string, number> = {};\n for (const e of executions) {\n const key = `${e.squad}/${e.agent}`;\n byAgent[key] = (byAgent[key] || 0) + 1;\n }\n\n return {\n total: executions.length,\n running,\n completed,\n failed,\n avgDurationMs,\n bySquad,\n byAgent,\n };\n}\n\n/**\n * Format duration for display\n */\nexport function formatDuration(ms: number | undefined): string {\n if (!ms) return '—';\n\n if (ms < 1000) {\n return `${ms}ms`;\n }\n if (ms < 60000) {\n return `${(ms / 1000).toFixed(1)}s`;\n }\n if (ms < 3600000) {\n const mins = Math.floor(ms / 60000);\n const secs = Math.round((ms % 60000) / 1000);\n return `${mins}m ${secs}s`;\n }\n\n const hours = Math.floor(ms / 3600000);\n const mins = Math.round((ms % 3600000) / 60000);\n return `${hours}h ${mins}m`;\n}\n\n/**\n * Format relative time for display\n */\nexport function formatRelativeTime(isoTime: string): string {\n const date = new Date(isoTime);\n if (isNaN(date.getTime())) return isoTime;\n\n const now = Date.now();\n const diff = now - date.getTime();\n\n if (diff < 60000) return 'just now';\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n if (diff < 604800000) return `${Math.floor(diff / 86400000)}d ago`;\n\n return date.toLocaleDateString();\n}\n"],"mappings":"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","names":["mins"]}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/outcomes.ts
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
var OUTCOMES_DIR = join(homedir(), ".squads", "daemon");
|
|
9
|
+
var OUTCOMES_FILE = join(OUTCOMES_DIR, "outcomes.json");
|
|
10
|
+
function loadOutcomes() {
|
|
11
|
+
if (!existsSync(OUTCOMES_DIR)) mkdirSync(OUTCOMES_DIR, { recursive: true });
|
|
12
|
+
if (!existsSync(OUTCOMES_FILE)) {
|
|
13
|
+
return { records: [], scorecards: [], lastUpdated: "" };
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(readFileSync(OUTCOMES_FILE, "utf-8"));
|
|
17
|
+
} catch {
|
|
18
|
+
return { records: [], scorecards: [], lastUpdated: "" };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function saveOutcomes(data) {
|
|
22
|
+
if (!existsSync(OUTCOMES_DIR)) mkdirSync(OUTCOMES_DIR, { recursive: true });
|
|
23
|
+
data.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
24
|
+
writeFileSync(OUTCOMES_FILE, JSON.stringify(data, null, 2));
|
|
25
|
+
}
|
|
26
|
+
function ghExec(cmd, env) {
|
|
27
|
+
try {
|
|
28
|
+
return execSync(cmd, {
|
|
29
|
+
encoding: "utf-8",
|
|
30
|
+
timeout: 15e3,
|
|
31
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
32
|
+
env: { ...process.env, ...env }
|
|
33
|
+
}).trim();
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function findRecentBotPRs(repo, sinceMins, ghEnv) {
|
|
39
|
+
const raw = ghExec(
|
|
40
|
+
`gh pr list -R ${repo} --author "agents-squads[bot]" --state all --json number,createdAt --limit 10`,
|
|
41
|
+
ghEnv
|
|
42
|
+
);
|
|
43
|
+
if (!raw) return [];
|
|
44
|
+
try {
|
|
45
|
+
const prs = JSON.parse(raw);
|
|
46
|
+
const cutoff = Date.now() - sinceMins * 60 * 1e3;
|
|
47
|
+
return prs.filter((pr) => new Date(pr.createdAt).getTime() > cutoff).map((pr) => ({ repo, number: pr.number }));
|
|
48
|
+
} catch {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function findRecentBotIssues(repo, sinceMins, ghEnv) {
|
|
53
|
+
const raw = ghExec(
|
|
54
|
+
`gh issue list -R ${repo} --author "agents-squads[bot]" --state all --json number,createdAt --limit 10`,
|
|
55
|
+
ghEnv
|
|
56
|
+
);
|
|
57
|
+
if (!raw) return [];
|
|
58
|
+
try {
|
|
59
|
+
const issues = JSON.parse(raw);
|
|
60
|
+
const cutoff = Date.now() - sinceMins * 60 * 1e3;
|
|
61
|
+
return issues.filter((i) => new Date(i.createdAt).getTime() > cutoff).map((i) => ({ repo, number: i.number }));
|
|
62
|
+
} catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function countRecentCommits(repo, sinceMins, ghEnv) {
|
|
67
|
+
const since = new Date(Date.now() - sinceMins * 60 * 1e3).toISOString();
|
|
68
|
+
const raw = ghExec(
|
|
69
|
+
`gh api repos/${repo}/commits --jq 'length' -f since="${since}" -f per_page=50`,
|
|
70
|
+
ghEnv
|
|
71
|
+
);
|
|
72
|
+
return raw ? parseInt(raw, 10) || 0 : 0;
|
|
73
|
+
}
|
|
74
|
+
function recordArtifacts(exec, ghEnv) {
|
|
75
|
+
if (!exec.repo) return null;
|
|
76
|
+
const data = loadOutcomes();
|
|
77
|
+
if (data.records.some((r) => r.executionId === exec.executionId)) return null;
|
|
78
|
+
const prs = findRecentBotPRs(exec.repo, 30, ghEnv);
|
|
79
|
+
const issues = findRecentBotIssues(exec.repo, 30, ghEnv);
|
|
80
|
+
const commits = countRecentCommits(exec.repo, 30, ghEnv);
|
|
81
|
+
const record = {
|
|
82
|
+
executionId: exec.executionId,
|
|
83
|
+
squad: exec.squad,
|
|
84
|
+
agent: exec.agent,
|
|
85
|
+
completedAt: exec.completedAt,
|
|
86
|
+
costUsd: exec.costUsd,
|
|
87
|
+
artifacts: {
|
|
88
|
+
issuesCreated: issues,
|
|
89
|
+
prsCreated: prs,
|
|
90
|
+
commits
|
|
91
|
+
},
|
|
92
|
+
outcomes: {
|
|
93
|
+
issuesClosed: 0,
|
|
94
|
+
issuesOpen: issues.length,
|
|
95
|
+
prsMerged: 0,
|
|
96
|
+
prsClosedUnmerged: 0,
|
|
97
|
+
prsOpen: prs.length,
|
|
98
|
+
ciPassFirstPush: null,
|
|
99
|
+
reviewCycleHours: null
|
|
100
|
+
},
|
|
101
|
+
lastPolledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
102
|
+
settled: prs.length === 0 && issues.length === 0
|
|
103
|
+
// No artifacts = settled immediately
|
|
104
|
+
};
|
|
105
|
+
data.records.push(record);
|
|
106
|
+
if (data.records.length > 200) {
|
|
107
|
+
data.records = data.records.slice(-200);
|
|
108
|
+
}
|
|
109
|
+
saveOutcomes(data);
|
|
110
|
+
return record;
|
|
111
|
+
}
|
|
112
|
+
function pollOutcomes(ghEnv) {
|
|
113
|
+
const data = loadOutcomes();
|
|
114
|
+
const unsettled = data.records.filter((r) => !r.settled);
|
|
115
|
+
let apiCalls = 0;
|
|
116
|
+
let newlySettled = 0;
|
|
117
|
+
const MAX_CALLS = 30;
|
|
118
|
+
for (const record of unsettled) {
|
|
119
|
+
if (apiCalls >= MAX_CALLS) break;
|
|
120
|
+
let allTerminal = true;
|
|
121
|
+
for (const pr of record.artifacts.prsCreated) {
|
|
122
|
+
if (apiCalls >= MAX_CALLS) break;
|
|
123
|
+
apiCalls++;
|
|
124
|
+
const raw = ghExec(
|
|
125
|
+
`gh pr view ${pr.number} -R ${pr.repo} --json state,mergedAt,createdAt,statusCheckRollup`,
|
|
126
|
+
ghEnv
|
|
127
|
+
);
|
|
128
|
+
if (!raw) {
|
|
129
|
+
allTerminal = false;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const prData = JSON.parse(raw);
|
|
134
|
+
if (prData.state === "MERGED") {
|
|
135
|
+
record.outcomes.prsMerged++;
|
|
136
|
+
record.outcomes.prsOpen = Math.max(0, record.outcomes.prsOpen - 1);
|
|
137
|
+
if (prData.mergedAt && prData.createdAt) {
|
|
138
|
+
const created = new Date(prData.createdAt).getTime();
|
|
139
|
+
const merged = new Date(prData.mergedAt).getTime();
|
|
140
|
+
record.outcomes.reviewCycleHours = (merged - created) / (1e3 * 60 * 60);
|
|
141
|
+
}
|
|
142
|
+
if (record.outcomes.ciPassFirstPush === null && prData.statusCheckRollup) {
|
|
143
|
+
record.outcomes.ciPassFirstPush = prData.statusCheckRollup.every(
|
|
144
|
+
(c) => c.conclusion === "SUCCESS"
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
} else if (prData.state === "CLOSED") {
|
|
148
|
+
record.outcomes.prsClosedUnmerged++;
|
|
149
|
+
record.outcomes.prsOpen = Math.max(0, record.outcomes.prsOpen - 1);
|
|
150
|
+
} else {
|
|
151
|
+
allTerminal = false;
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
allTerminal = false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const issue of record.artifacts.issuesCreated) {
|
|
158
|
+
if (apiCalls >= MAX_CALLS) break;
|
|
159
|
+
apiCalls++;
|
|
160
|
+
const raw = ghExec(
|
|
161
|
+
`gh issue view ${issue.number} -R ${issue.repo} --json state`,
|
|
162
|
+
ghEnv
|
|
163
|
+
);
|
|
164
|
+
if (!raw) {
|
|
165
|
+
allTerminal = false;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const issueData = JSON.parse(raw);
|
|
170
|
+
if (issueData.state === "CLOSED") {
|
|
171
|
+
record.outcomes.issuesClosed++;
|
|
172
|
+
record.outcomes.issuesOpen = Math.max(0, record.outcomes.issuesOpen - 1);
|
|
173
|
+
} else {
|
|
174
|
+
allTerminal = false;
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
allTerminal = false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (allTerminal && record.artifacts.prsCreated.length + record.artifacts.issuesCreated.length > 0) {
|
|
181
|
+
record.settled = true;
|
|
182
|
+
newlySettled++;
|
|
183
|
+
}
|
|
184
|
+
const age = Date.now() - new Date(record.completedAt).getTime();
|
|
185
|
+
if (age > 30 * 24 * 60 * 60 * 1e3) {
|
|
186
|
+
record.settled = true;
|
|
187
|
+
if (!allTerminal) newlySettled++;
|
|
188
|
+
}
|
|
189
|
+
record.lastPolledAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
190
|
+
}
|
|
191
|
+
saveOutcomes(data);
|
|
192
|
+
return { polled: apiCalls, settled: newlySettled };
|
|
193
|
+
}
|
|
194
|
+
function computeScorecard(squad, agent, period) {
|
|
195
|
+
const data = loadOutcomes();
|
|
196
|
+
const periodMs = period === "7d" ? 7 * 24 * 60 * 60 * 1e3 : 30 * 24 * 60 * 60 * 1e3;
|
|
197
|
+
const cutoff = Date.now() - periodMs;
|
|
198
|
+
const records = data.records.filter(
|
|
199
|
+
(r) => r.squad === squad && r.agent === agent && new Date(r.completedAt).getTime() > cutoff
|
|
200
|
+
);
|
|
201
|
+
if (records.length === 0) return null;
|
|
202
|
+
const totalPRs = records.reduce((sum, r) => sum + r.artifacts.prsCreated.length, 0);
|
|
203
|
+
const mergedPRs = records.reduce((sum, r) => sum + r.outcomes.prsMerged, 0);
|
|
204
|
+
const unmergedPRs = records.reduce((sum, r) => sum + r.outcomes.prsClosedUnmerged, 0);
|
|
205
|
+
const totalIssues = records.reduce((sum, r) => sum + r.artifacts.issuesCreated.length, 0);
|
|
206
|
+
const closedIssues = records.reduce((sum, r) => sum + r.outcomes.issuesClosed, 0);
|
|
207
|
+
const totalCost = records.reduce((sum, r) => sum + r.costUsd, 0);
|
|
208
|
+
const wasteRuns = records.filter(
|
|
209
|
+
(r) => r.artifacts.prsCreated.length === 0 && r.artifacts.issuesCreated.length === 0 && r.artifacts.commits === 0
|
|
210
|
+
).length;
|
|
211
|
+
const ciRecords = records.filter((r) => r.outcomes.ciPassFirstPush !== null);
|
|
212
|
+
const ciPassed = ciRecords.filter((r) => r.outcomes.ciPassFirstPush === true).length;
|
|
213
|
+
const reviewCycles = records.filter((r) => r.outcomes.reviewCycleHours !== null).map((r) => r.outcomes.reviewCycleHours);
|
|
214
|
+
const avgReviewCycleHours = reviewCycles.length > 0 ? reviewCycles.reduce((a, b) => a + b, 0) / reviewCycles.length : 0;
|
|
215
|
+
const outcomes = closedIssues + mergedPRs;
|
|
216
|
+
return {
|
|
217
|
+
squad,
|
|
218
|
+
agent,
|
|
219
|
+
period,
|
|
220
|
+
executions: records.length,
|
|
221
|
+
wasteRate: records.length > 0 ? wasteRuns / records.length : 0,
|
|
222
|
+
mergeRate: totalPRs > 0 ? mergedPRs / totalPRs : 0,
|
|
223
|
+
issueResolutionRate: totalIssues > 0 ? closedIssues / totalIssues : 0,
|
|
224
|
+
ciPassRate: ciRecords.length > 0 ? ciPassed / ciRecords.length : 0,
|
|
225
|
+
avgReviewCycleHours,
|
|
226
|
+
costPerOutcome: outcomes > 0 ? totalCost / outcomes : totalCost
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function computeAllScorecards(period = "7d") {
|
|
230
|
+
const data = loadOutcomes();
|
|
231
|
+
const periodMs = period === "7d" ? 7 * 24 * 60 * 60 * 1e3 : 30 * 24 * 60 * 60 * 1e3;
|
|
232
|
+
const cutoff = Date.now() - periodMs;
|
|
233
|
+
const agents = /* @__PURE__ */ new Set();
|
|
234
|
+
for (const r of data.records) {
|
|
235
|
+
if (new Date(r.completedAt).getTime() > cutoff) {
|
|
236
|
+
agents.add(`${r.squad}/${r.agent}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const scorecards = [];
|
|
240
|
+
for (const key of agents) {
|
|
241
|
+
const [squad, agent] = key.split("/");
|
|
242
|
+
const card = computeScorecard(squad, agent, period);
|
|
243
|
+
if (card) scorecards.push(card);
|
|
244
|
+
}
|
|
245
|
+
scorecards.sort((a, b) => b.executions - a.executions);
|
|
246
|
+
data.scorecards = scorecards;
|
|
247
|
+
saveOutcomes(data);
|
|
248
|
+
return scorecards;
|
|
249
|
+
}
|
|
250
|
+
function getScorecards() {
|
|
251
|
+
return loadOutcomes().scorecards;
|
|
252
|
+
}
|
|
253
|
+
function getOutcomeRecords() {
|
|
254
|
+
return loadOutcomes().records;
|
|
255
|
+
}
|
|
256
|
+
function getOutcomeScoreModifier(squad, agent) {
|
|
257
|
+
const scorecards = getScorecards();
|
|
258
|
+
const card = scorecards.find((s) => s.squad === squad && s.agent === agent);
|
|
259
|
+
if (!card || card.executions < 3) return 0;
|
|
260
|
+
let modifier = 0;
|
|
261
|
+
if (card.wasteRate > 0.5) modifier -= 30;
|
|
262
|
+
if (card.mergeRate < 0.3 && card.executions >= 3) modifier -= 20;
|
|
263
|
+
if (card.mergeRate > 0.7 && card.issueResolutionRate > 0.5) modifier += 15;
|
|
264
|
+
if (card.costPerOutcome > 5) modifier -= 10;
|
|
265
|
+
return modifier;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export {
|
|
269
|
+
recordArtifacts,
|
|
270
|
+
pollOutcomes,
|
|
271
|
+
computeAllScorecards,
|
|
272
|
+
getOutcomeRecords,
|
|
273
|
+
getOutcomeScoreModifier
|
|
274
|
+
};
|
|
275
|
+
//# sourceMappingURL=chunk-7JVD7RD4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/outcomes.ts"],"sourcesContent":["/**\n * Outcome tracking — observes GitHub for artifact outcomes.\n *\n * Polls issues/PRs created by agent runs to determine if work\n * was productive (merged, closed) or wasteful (abandoned, unmerged).\n * Uses `gh` CLI for GitHub queries — no API keys needed.\n */\n\nimport { execSync } from 'child_process';\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\n// ── Types ────────────────────────────────────────────────────────────\n\nexport interface ArtifactRef {\n repo: string;\n number: number;\n}\n\nexport interface OutcomeRecord {\n executionId: string;\n squad: string;\n agent: string;\n completedAt: string;\n costUsd: number;\n artifacts: {\n issuesCreated: ArtifactRef[];\n prsCreated: ArtifactRef[];\n commits: number;\n };\n outcomes: {\n issuesClosed: number;\n issuesOpen: number;\n prsMerged: number;\n prsClosedUnmerged: number;\n prsOpen: number;\n ciPassFirstPush: boolean | null;\n reviewCycleHours: number | null;\n };\n lastPolledAt: string;\n settled: boolean;\n}\n\nexport interface AgentScorecard {\n squad: string;\n agent: string;\n period: '7d' | '30d';\n executions: number;\n wasteRate: number;\n mergeRate: number;\n issueResolutionRate: number;\n ciPassRate: number;\n avgReviewCycleHours: number;\n costPerOutcome: number;\n}\n\n// ── Storage ──────────────────────────────────────────────────────────\n\nconst OUTCOMES_DIR = join(homedir(), '.squads', 'daemon');\nconst OUTCOMES_FILE = join(OUTCOMES_DIR, 'outcomes.json');\n\ninterface OutcomesData {\n records: OutcomeRecord[];\n scorecards: AgentScorecard[];\n lastUpdated: string;\n}\n\nfunction loadOutcomes(): OutcomesData {\n if (!existsSync(OUTCOMES_DIR)) mkdirSync(OUTCOMES_DIR, { recursive: true });\n if (!existsSync(OUTCOMES_FILE)) {\n return { records: [], scorecards: [], lastUpdated: '' };\n }\n try {\n return JSON.parse(readFileSync(OUTCOMES_FILE, 'utf-8'));\n } catch {\n return { records: [], scorecards: [], lastUpdated: '' };\n }\n}\n\nfunction saveOutcomes(data: OutcomesData): void {\n if (!existsSync(OUTCOMES_DIR)) mkdirSync(OUTCOMES_DIR, { recursive: true });\n data.lastUpdated = new Date().toISOString();\n writeFileSync(OUTCOMES_FILE, JSON.stringify(data, null, 2));\n}\n\n// ── GitHub helpers ───────────────────────────────────────────────────\n\nfunction ghExec(cmd: string, env?: Record<string, string>): string | null {\n try {\n return execSync(cmd, {\n encoding: 'utf-8',\n timeout: 15000,\n stdio: ['pipe', 'pipe', 'pipe'],\n env: { ...process.env, ...env },\n }).trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Find PRs created by the bot in the last N minutes for a repo.\n */\nfunction findRecentBotPRs(\n repo: string,\n sinceMins: number,\n ghEnv?: Record<string, string>,\n): ArtifactRef[] {\n const raw = ghExec(\n `gh pr list -R ${repo} --author \"agents-squads[bot]\" --state all --json number,createdAt --limit 10`,\n ghEnv,\n );\n if (!raw) return [];\n\n try {\n const prs = JSON.parse(raw) as Array<{ number: number; createdAt: string }>;\n const cutoff = Date.now() - sinceMins * 60 * 1000;\n return prs\n .filter(pr => new Date(pr.createdAt).getTime() > cutoff)\n .map(pr => ({ repo, number: pr.number }));\n } catch {\n return [];\n }\n}\n\n/**\n * Find issues created by the bot in the last N minutes for a repo.\n */\nfunction findRecentBotIssues(\n repo: string,\n sinceMins: number,\n ghEnv?: Record<string, string>,\n): ArtifactRef[] {\n const raw = ghExec(\n `gh issue list -R ${repo} --author \"agents-squads[bot]\" --state all --json number,createdAt --limit 10`,\n ghEnv,\n );\n if (!raw) return [];\n\n try {\n const issues = JSON.parse(raw) as Array<{ number: number; createdAt: string }>;\n const cutoff = Date.now() - sinceMins * 60 * 1000;\n return issues\n .filter(i => new Date(i.createdAt).getTime() > cutoff)\n .map(i => ({ repo, number: i.number }));\n } catch {\n return [];\n }\n}\n\n/**\n * Count commits on the default branch in the last N minutes.\n */\nfunction countRecentCommits(\n repo: string,\n sinceMins: number,\n ghEnv?: Record<string, string>,\n): number {\n const since = new Date(Date.now() - sinceMins * 60 * 1000).toISOString();\n const raw = ghExec(\n `gh api repos/${repo}/commits --jq 'length' -f since=\"${since}\" -f per_page=50`,\n ghEnv,\n );\n return raw ? parseInt(raw, 10) || 0 : 0;\n}\n\n// ── Core functions ───────────────────────────────────────────────────\n\n/**\n * Record artifacts created by a completed agent run.\n * Called after each `squads run` finishes in the daemon.\n */\nexport function recordArtifacts(\n exec: {\n executionId: string;\n squad: string;\n agent: string;\n completedAt: string;\n costUsd: number;\n repo?: string;\n },\n ghEnv?: Record<string, string>,\n): OutcomeRecord | null {\n if (!exec.repo) return null;\n\n const data = loadOutcomes();\n\n // Don't double-record\n if (data.records.some(r => r.executionId === exec.executionId)) return null;\n\n // Look for artifacts created in the last 30 minutes (typical agent run window)\n const prs = findRecentBotPRs(exec.repo, 30, ghEnv);\n const issues = findRecentBotIssues(exec.repo, 30, ghEnv);\n const commits = countRecentCommits(exec.repo, 30, ghEnv);\n\n const record: OutcomeRecord = {\n executionId: exec.executionId,\n squad: exec.squad,\n agent: exec.agent,\n completedAt: exec.completedAt,\n costUsd: exec.costUsd,\n artifacts: {\n issuesCreated: issues,\n prsCreated: prs,\n commits,\n },\n outcomes: {\n issuesClosed: 0,\n issuesOpen: issues.length,\n prsMerged: 0,\n prsClosedUnmerged: 0,\n prsOpen: prs.length,\n ciPassFirstPush: null,\n reviewCycleHours: null,\n },\n lastPolledAt: new Date().toISOString(),\n settled: prs.length === 0 && issues.length === 0, // No artifacts = settled immediately\n };\n\n data.records.push(record);\n\n // Trim to last 200 records\n if (data.records.length > 200) {\n data.records = data.records.slice(-200);\n }\n\n saveOutcomes(data);\n return record;\n}\n\n/**\n * Poll GitHub for outcome updates on unsettled records.\n * Rate-limited to 30 API calls per cycle.\n */\nexport function pollOutcomes(ghEnv?: Record<string, string>): {\n polled: number;\n settled: number;\n} {\n const data = loadOutcomes();\n const unsettled = data.records.filter(r => !r.settled);\n let apiCalls = 0;\n let newlySettled = 0;\n const MAX_CALLS = 30;\n\n for (const record of unsettled) {\n if (apiCalls >= MAX_CALLS) break;\n\n let allTerminal = true;\n\n // Check PRs\n for (const pr of record.artifacts.prsCreated) {\n if (apiCalls >= MAX_CALLS) break;\n apiCalls++;\n\n const raw = ghExec(\n `gh pr view ${pr.number} -R ${pr.repo} --json state,mergedAt,createdAt,statusCheckRollup`,\n ghEnv,\n );\n if (!raw) { allTerminal = false; continue; }\n\n try {\n const prData = JSON.parse(raw) as {\n state: string;\n mergedAt: string | null;\n createdAt: string;\n statusCheckRollup: Array<{ conclusion: string }> | null;\n };\n\n if (prData.state === 'MERGED') {\n record.outcomes.prsMerged++;\n record.outcomes.prsOpen = Math.max(0, record.outcomes.prsOpen - 1);\n\n // Calculate review cycle hours\n if (prData.mergedAt && prData.createdAt) {\n const created = new Date(prData.createdAt).getTime();\n const merged = new Date(prData.mergedAt).getTime();\n record.outcomes.reviewCycleHours = (merged - created) / (1000 * 60 * 60);\n }\n\n // CI pass on first push\n if (record.outcomes.ciPassFirstPush === null && prData.statusCheckRollup) {\n record.outcomes.ciPassFirstPush = prData.statusCheckRollup.every(\n c => c.conclusion === 'SUCCESS',\n );\n }\n } else if (prData.state === 'CLOSED') {\n record.outcomes.prsClosedUnmerged++;\n record.outcomes.prsOpen = Math.max(0, record.outcomes.prsOpen - 1);\n } else {\n allTerminal = false; // Still open\n }\n } catch {\n allTerminal = false;\n }\n }\n\n // Check issues\n for (const issue of record.artifacts.issuesCreated) {\n if (apiCalls >= MAX_CALLS) break;\n apiCalls++;\n\n const raw = ghExec(\n `gh issue view ${issue.number} -R ${issue.repo} --json state`,\n ghEnv,\n );\n if (!raw) { allTerminal = false; continue; }\n\n try {\n const issueData = JSON.parse(raw) as { state: string };\n if (issueData.state === 'CLOSED') {\n record.outcomes.issuesClosed++;\n record.outcomes.issuesOpen = Math.max(0, record.outcomes.issuesOpen - 1);\n } else {\n allTerminal = false;\n }\n } catch {\n allTerminal = false;\n }\n }\n\n // Mark settled if all artifacts reached terminal state\n if (allTerminal && record.artifacts.prsCreated.length + record.artifacts.issuesCreated.length > 0) {\n record.settled = true;\n newlySettled++;\n }\n\n // Also settle records older than 30 days regardless\n const age = Date.now() - new Date(record.completedAt).getTime();\n if (age > 30 * 24 * 60 * 60 * 1000) {\n record.settled = true;\n if (!allTerminal) newlySettled++;\n }\n\n record.lastPolledAt = new Date().toISOString();\n }\n\n saveOutcomes(data);\n return { polled: apiCalls, settled: newlySettled };\n}\n\n/**\n * Compute scorecard for an agent over a time period.\n */\nexport function computeScorecard(\n squad: string,\n agent: string,\n period: '7d' | '30d',\n): AgentScorecard | null {\n const data = loadOutcomes();\n const periodMs = period === '7d' ? 7 * 24 * 60 * 60 * 1000 : 30 * 24 * 60 * 60 * 1000;\n const cutoff = Date.now() - periodMs;\n\n const records = data.records.filter(\n r => r.squad === squad && r.agent === agent &&\n new Date(r.completedAt).getTime() > cutoff,\n );\n\n if (records.length === 0) return null;\n\n const totalPRs = records.reduce((sum, r) => sum + r.artifacts.prsCreated.length, 0);\n const mergedPRs = records.reduce((sum, r) => sum + r.outcomes.prsMerged, 0);\n const unmergedPRs = records.reduce((sum, r) => sum + r.outcomes.prsClosedUnmerged, 0);\n const totalIssues = records.reduce((sum, r) => sum + r.artifacts.issuesCreated.length, 0);\n const closedIssues = records.reduce((sum, r) => sum + r.outcomes.issuesClosed, 0);\n const totalCost = records.reduce((sum, r) => sum + r.costUsd, 0);\n\n // Waste = runs with zero artifacts\n const wasteRuns = records.filter(\n r => r.artifacts.prsCreated.length === 0 &&\n r.artifacts.issuesCreated.length === 0 &&\n r.artifacts.commits === 0,\n ).length;\n\n // CI pass rate\n const ciRecords = records.filter(r => r.outcomes.ciPassFirstPush !== null);\n const ciPassed = ciRecords.filter(r => r.outcomes.ciPassFirstPush === true).length;\n\n // Avg review cycle\n const reviewCycles = records\n .filter(r => r.outcomes.reviewCycleHours !== null)\n .map(r => r.outcomes.reviewCycleHours!);\n const avgReviewCycleHours = reviewCycles.length > 0\n ? reviewCycles.reduce((a, b) => a + b, 0) / reviewCycles.length\n : 0;\n\n // Cost per outcome (issues closed + PRs merged)\n const outcomes = closedIssues + mergedPRs;\n\n return {\n squad,\n agent,\n period,\n executions: records.length,\n wasteRate: records.length > 0 ? wasteRuns / records.length : 0,\n mergeRate: totalPRs > 0 ? mergedPRs / totalPRs : 0,\n issueResolutionRate: totalIssues > 0 ? closedIssues / totalIssues : 0,\n ciPassRate: ciRecords.length > 0 ? ciPassed / ciRecords.length : 0,\n avgReviewCycleHours,\n costPerOutcome: outcomes > 0 ? totalCost / outcomes : totalCost,\n };\n}\n\n/**\n * Compute scorecards for all agents that have outcome data.\n */\nexport function computeAllScorecards(period: '7d' | '30d' = '7d'): AgentScorecard[] {\n const data = loadOutcomes();\n const periodMs = period === '7d' ? 7 * 24 * 60 * 60 * 1000 : 30 * 24 * 60 * 60 * 1000;\n const cutoff = Date.now() - periodMs;\n\n // Find unique squad/agent combos in period\n const agents = new Set<string>();\n for (const r of data.records) {\n if (new Date(r.completedAt).getTime() > cutoff) {\n agents.add(`${r.squad}/${r.agent}`);\n }\n }\n\n const scorecards: AgentScorecard[] = [];\n for (const key of agents) {\n const [squad, agent] = key.split('/');\n const card = computeScorecard(squad, agent, period);\n if (card) scorecards.push(card);\n }\n\n // Sort by executions descending\n scorecards.sort((a, b) => b.executions - a.executions);\n\n // Persist\n data.scorecards = scorecards;\n saveOutcomes(data);\n\n return scorecards;\n}\n\n/**\n * Get cached scorecards (no recompute).\n */\nexport function getScorecards(): AgentScorecard[] {\n return loadOutcomes().scorecards;\n}\n\n/**\n * Get all outcome records.\n */\nexport function getOutcomeRecords(): OutcomeRecord[] {\n return loadOutcomes().records;\n}\n\n/**\n * Apply outcome-based score modifiers to daemon squad scoring.\n * Returns a score adjustment (positive or negative).\n */\nexport function getOutcomeScoreModifier(squad: string, agent: string): number {\n const scorecards = getScorecards();\n const card = scorecards.find(s => s.squad === squad && s.agent === agent);\n\n // Minimum data threshold: need 3+ executions for modifiers\n if (!card || card.executions < 3) return 0;\n\n let modifier = 0;\n\n // High waste rate penalty\n if (card.wasteRate > 0.5) modifier -= 30;\n\n // Low merge rate penalty\n if (card.mergeRate < 0.3 && card.executions >= 3) modifier -= 20;\n\n // High performance bonus\n if (card.mergeRate > 0.7 && card.issueResolutionRate > 0.5) modifier += 15;\n\n // Expensive + low-scoring penalty\n if (card.costPerOutcome > 5) modifier -= 10;\n\n return modifier;\n}\n"],"mappings":"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","names":[]}
|