squads-cli 0.6.1 → 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 +2151 -12594
- 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,335 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
computeAllScorecards,
|
|
4
|
+
getOutcomeRecords
|
|
5
|
+
} from "./chunk-7JVD7RD4.js";
|
|
6
|
+
import {
|
|
7
|
+
RESET,
|
|
8
|
+
bold,
|
|
9
|
+
colors,
|
|
10
|
+
writeLine
|
|
11
|
+
} from "./chunk-N7KDWU4W.js";
|
|
12
|
+
import "./chunk-7OCVIDC7.js";
|
|
13
|
+
|
|
14
|
+
// src/lib/insights.ts
|
|
15
|
+
var HOURS_PER_ISSUE_RESOLVED = parseFloat(process.env.SQUADS_HOURS_PER_ISSUE || "4");
|
|
16
|
+
var HOURS_PER_PR_MERGED = parseFloat(process.env.SQUADS_HOURS_PER_PR || "2");
|
|
17
|
+
var HOURLY_RATE = parseFloat(process.env.SQUADS_HOURLY_RATE || "75");
|
|
18
|
+
function generateWorkforceSummary(period = "7d") {
|
|
19
|
+
const scorecards = computeAllScorecards(period);
|
|
20
|
+
const records = getOutcomeRecords();
|
|
21
|
+
const periodMs = period === "7d" ? 7 * 24 * 60 * 60 * 1e3 : 30 * 24 * 60 * 60 * 1e3;
|
|
22
|
+
const cutoff = Date.now() - periodMs;
|
|
23
|
+
const periodRecords = records.filter(
|
|
24
|
+
(r) => new Date(r.completedAt).getTime() > cutoff
|
|
25
|
+
);
|
|
26
|
+
const totalExecutions = periodRecords.length;
|
|
27
|
+
const totalCostUsd = periodRecords.reduce((sum, r) => sum + r.costUsd, 0);
|
|
28
|
+
const issuesResolved = periodRecords.reduce((sum, r) => sum + r.outcomes.issuesClosed, 0);
|
|
29
|
+
const prsMerged = periodRecords.reduce((sum, r) => sum + r.outcomes.prsMerged, 0);
|
|
30
|
+
const estimatedHoursSaved = issuesResolved * HOURS_PER_ISSUE_RESOLVED + prsMerged * HOURS_PER_PR_MERGED;
|
|
31
|
+
const estimatedValueUsd = estimatedHoursSaved * HOURLY_RATE;
|
|
32
|
+
const roiMultiplier = totalCostUsd > 0 ? estimatedValueUsd / totalCostUsd : 0;
|
|
33
|
+
const totalPRs = periodRecords.reduce((sum, r) => sum + r.artifacts.prsCreated.length, 0);
|
|
34
|
+
const overallMergeRate = totalPRs > 0 ? prsMerged / totalPRs : 0;
|
|
35
|
+
const wasteRuns = periodRecords.filter(
|
|
36
|
+
(r) => r.artifacts.prsCreated.length === 0 && r.artifacts.issuesCreated.length === 0 && r.artifacts.commits === 0
|
|
37
|
+
).length;
|
|
38
|
+
const overallWasteRate = totalExecutions > 0 ? wasteRuns / totalExecutions : 0;
|
|
39
|
+
const withData = scorecards.filter((s) => s.executions >= 2);
|
|
40
|
+
const topPerformer = findTopPerformer(withData);
|
|
41
|
+
const underperformer = findUnderperformer(withData);
|
|
42
|
+
const insights = generateInsights(scorecards, periodRecords, {
|
|
43
|
+
totalCostUsd,
|
|
44
|
+
issuesResolved,
|
|
45
|
+
prsMerged,
|
|
46
|
+
overallMergeRate,
|
|
47
|
+
overallWasteRate,
|
|
48
|
+
roiMultiplier,
|
|
49
|
+
estimatedHoursSaved
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
period,
|
|
53
|
+
totalExecutions,
|
|
54
|
+
totalCostUsd,
|
|
55
|
+
issuesResolved,
|
|
56
|
+
prsMerged,
|
|
57
|
+
estimatedHoursSaved,
|
|
58
|
+
estimatedValueUsd,
|
|
59
|
+
roiMultiplier,
|
|
60
|
+
overallMergeRate,
|
|
61
|
+
overallWasteRate,
|
|
62
|
+
topPerformer,
|
|
63
|
+
underperformer,
|
|
64
|
+
insights
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function findTopPerformer(cards) {
|
|
68
|
+
if (cards.length === 0) return null;
|
|
69
|
+
const scored = cards.map((c) => ({
|
|
70
|
+
name: `${c.squad}/${c.agent}`,
|
|
71
|
+
mergeRate: c.mergeRate,
|
|
72
|
+
composite: c.mergeRate * 0.4 + c.issueResolutionRate * 0.4 - c.wasteRate * 0.2
|
|
73
|
+
}));
|
|
74
|
+
scored.sort((a, b) => b.composite - a.composite);
|
|
75
|
+
return scored[0] ? { name: scored[0].name, mergeRate: scored[0].mergeRate } : null;
|
|
76
|
+
}
|
|
77
|
+
function findUnderperformer(cards) {
|
|
78
|
+
if (cards.length === 0) return null;
|
|
79
|
+
for (const c of cards) {
|
|
80
|
+
if (c.wasteRate > 0.5) {
|
|
81
|
+
return {
|
|
82
|
+
name: `${c.squad}/${c.agent}`,
|
|
83
|
+
reason: `${Math.round(c.wasteRate * 100)}% of runs produce no output`
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (c.mergeRate < 0.2 && c.executions >= 3) {
|
|
87
|
+
return {
|
|
88
|
+
name: `${c.squad}/${c.agent}`,
|
|
89
|
+
reason: `Only ${Math.round(c.mergeRate * 100)}% of PRs get merged`
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
if (c.costPerOutcome > 5) {
|
|
93
|
+
return {
|
|
94
|
+
name: `${c.squad}/${c.agent}`,
|
|
95
|
+
reason: `$${c.costPerOutcome.toFixed(2)} per outcome \u2014 most expensive agent`
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function generateInsights(scorecards, records, metrics) {
|
|
102
|
+
const insights = [];
|
|
103
|
+
if (metrics.roiMultiplier > 0) {
|
|
104
|
+
if (metrics.roiMultiplier >= 3) {
|
|
105
|
+
insights.push({
|
|
106
|
+
type: "highlight",
|
|
107
|
+
title: "Strong ROI",
|
|
108
|
+
detail: `Your AI workforce delivered ${metrics.roiMultiplier.toFixed(1)}x return \u2014 $${metrics.totalCostUsd.toFixed(2)} spent, ~$${(metrics.estimatedHoursSaved * HOURLY_RATE).toFixed(0)} in estimated engineering time saved.`,
|
|
109
|
+
metric: "roi",
|
|
110
|
+
value: metrics.roiMultiplier
|
|
111
|
+
});
|
|
112
|
+
} else if (metrics.roiMultiplier >= 1) {
|
|
113
|
+
insights.push({
|
|
114
|
+
type: "highlight",
|
|
115
|
+
title: "Positive ROI",
|
|
116
|
+
detail: `AI workforce is paying for itself at ${metrics.roiMultiplier.toFixed(1)}x. ${metrics.estimatedHoursSaved.toFixed(0)} engineering hours saved.`,
|
|
117
|
+
metric: "roi",
|
|
118
|
+
value: metrics.roiMultiplier
|
|
119
|
+
});
|
|
120
|
+
} else if (metrics.totalCostUsd > 0) {
|
|
121
|
+
insights.push({
|
|
122
|
+
type: "warning",
|
|
123
|
+
title: "ROI below breakeven",
|
|
124
|
+
detail: `Currently at ${metrics.roiMultiplier.toFixed(1)}x \u2014 spending more than the estimated value of output. Review agent effectiveness.`,
|
|
125
|
+
metric: "roi",
|
|
126
|
+
value: metrics.roiMultiplier
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (metrics.issuesResolved > 0 || metrics.prsMerged > 0) {
|
|
131
|
+
insights.push({
|
|
132
|
+
type: "highlight",
|
|
133
|
+
title: "Work delivered",
|
|
134
|
+
detail: `${metrics.issuesResolved} issue${metrics.issuesResolved !== 1 ? "s" : ""} resolved, ${metrics.prsMerged} PR${metrics.prsMerged !== 1 ? "s" : ""} merged \u2014 equivalent to ~${metrics.estimatedHoursSaved.toFixed(0)} hours of engineering work.`,
|
|
135
|
+
metric: "productivity"
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (metrics.overallWasteRate > 0.3 && records.length >= 3) {
|
|
139
|
+
insights.push({
|
|
140
|
+
type: "warning",
|
|
141
|
+
title: "High waste rate",
|
|
142
|
+
detail: `${Math.round(metrics.overallWasteRate * 100)}% of agent runs produce no output. Review agent prompts, issue quality, or available context.`,
|
|
143
|
+
metric: "waste",
|
|
144
|
+
value: metrics.overallWasteRate
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
for (const card of scorecards) {
|
|
148
|
+
if (card.executions < 3) continue;
|
|
149
|
+
if (card.mergeRate > 0.8 && card.wasteRate < 0.1) {
|
|
150
|
+
insights.push({
|
|
151
|
+
type: "highlight",
|
|
152
|
+
title: `${card.squad}/${card.agent} is a star`,
|
|
153
|
+
detail: `${Math.round(card.mergeRate * 100)}% merge rate with only ${Math.round(card.wasteRate * 100)}% waste across ${card.executions} runs.`,
|
|
154
|
+
squad: card.squad,
|
|
155
|
+
agent: card.agent,
|
|
156
|
+
metric: "performance"
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
if (card.wasteRate > 0.5) {
|
|
160
|
+
insights.push({
|
|
161
|
+
type: "recommendation",
|
|
162
|
+
title: `Review ${card.squad}/${card.agent}`,
|
|
163
|
+
detail: `${Math.round(card.wasteRate * 100)}% waste rate. Consider: improving agent prompt, adding more context, or pausing this agent until issues are better-scoped.`,
|
|
164
|
+
squad: card.squad,
|
|
165
|
+
agent: card.agent,
|
|
166
|
+
metric: "waste"
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (card.mergeRate < 0.3 && card.executions >= 5) {
|
|
170
|
+
insights.push({
|
|
171
|
+
type: "recommendation",
|
|
172
|
+
title: `${card.squad}/${card.agent} PRs rarely merge`,
|
|
173
|
+
detail: `Only ${Math.round(card.mergeRate * 100)}% merge rate. PRs may need better scoping, testing, or review workflow adjustments.`,
|
|
174
|
+
squad: card.squad,
|
|
175
|
+
agent: card.agent,
|
|
176
|
+
metric: "merge_rate"
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (card.ciPassRate < 0.5 && card.executions >= 3) {
|
|
180
|
+
insights.push({
|
|
181
|
+
type: "recommendation",
|
|
182
|
+
title: `${card.squad}/${card.agent} CI issues`,
|
|
183
|
+
detail: `Only ${Math.round(card.ciPassRate * 100)}% of PRs pass CI on first push. Agent may need build/test context in its prompt.`,
|
|
184
|
+
squad: card.squad,
|
|
185
|
+
agent: card.agent,
|
|
186
|
+
metric: "ci_pass_rate"
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (records.length === 0) {
|
|
191
|
+
insights.push({
|
|
192
|
+
type: "recommendation",
|
|
193
|
+
title: "Start tracking",
|
|
194
|
+
detail: "No outcome data yet. Run the daemon to start tracking agent productivity automatically."
|
|
195
|
+
});
|
|
196
|
+
} else if (records.length < 5) {
|
|
197
|
+
insights.push({
|
|
198
|
+
type: "recommendation",
|
|
199
|
+
title: "Building baseline",
|
|
200
|
+
detail: `${records.length} execution${records.length !== 1 ? "s" : ""} tracked so far. Insights improve with more data \u2014 10+ executions recommended for reliable patterns.`
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return insights;
|
|
204
|
+
}
|
|
205
|
+
function generateExecutiveSummary(period = "7d") {
|
|
206
|
+
const summary = generateWorkforceSummary(period);
|
|
207
|
+
const periodLabel = period === "7d" ? "this week" : "this month";
|
|
208
|
+
if (summary.totalExecutions === 0) {
|
|
209
|
+
return `No AI workforce activity ${periodLabel}. Start the daemon to begin autonomous operations.`;
|
|
210
|
+
}
|
|
211
|
+
const parts = [];
|
|
212
|
+
parts.push(
|
|
213
|
+
`Your AI workforce ran ${summary.totalExecutions} time${summary.totalExecutions !== 1 ? "s" : ""} ${periodLabel}`
|
|
214
|
+
);
|
|
215
|
+
if (summary.issuesResolved > 0 || summary.prsMerged > 0) {
|
|
216
|
+
const outputs = [];
|
|
217
|
+
if (summary.issuesResolved > 0) outputs.push(`${summary.issuesResolved} issue${summary.issuesResolved !== 1 ? "s" : ""}`);
|
|
218
|
+
if (summary.prsMerged > 0) outputs.push(`${summary.prsMerged} PR${summary.prsMerged !== 1 ? "s" : ""}`);
|
|
219
|
+
parts.push(`delivering ${outputs.join(" and ")}`);
|
|
220
|
+
}
|
|
221
|
+
parts.push(`at a cost of $${summary.totalCostUsd.toFixed(2)}`);
|
|
222
|
+
if (summary.roiMultiplier > 0) {
|
|
223
|
+
parts.push(
|
|
224
|
+
`\u2014 an estimated ${summary.roiMultiplier.toFixed(1)}x return on investment (${summary.estimatedHoursSaved.toFixed(0)} engineering hours saved)`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
let text = parts.join(", ").replace(/, —/, " \u2014") + ".";
|
|
228
|
+
if (summary.topPerformer) {
|
|
229
|
+
text += ` Top performer: ${summary.topPerformer.name} (${Math.round(summary.topPerformer.mergeRate * 100)}% merge rate).`;
|
|
230
|
+
}
|
|
231
|
+
if (summary.underperformer) {
|
|
232
|
+
text += ` Needs attention: ${summary.underperformer.name} \u2014 ${summary.underperformer.reason}.`;
|
|
233
|
+
}
|
|
234
|
+
return text;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/commands/stats.ts
|
|
238
|
+
function pct(value) {
|
|
239
|
+
return `${Math.round(value * 100)}%`;
|
|
240
|
+
}
|
|
241
|
+
function padRight(str, len) {
|
|
242
|
+
const plain = str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
243
|
+
const pad = Math.max(0, len - plain.length);
|
|
244
|
+
return str + " ".repeat(pad);
|
|
245
|
+
}
|
|
246
|
+
function rateColor(rate, goodThreshold, badThreshold) {
|
|
247
|
+
if (rate >= goodThreshold) return colors.green;
|
|
248
|
+
if (rate <= badThreshold) return colors.red;
|
|
249
|
+
return colors.yellow;
|
|
250
|
+
}
|
|
251
|
+
var insightIcons = {
|
|
252
|
+
highlight: `${colors.green}*${RESET}`,
|
|
253
|
+
warning: `${colors.yellow}!${RESET}`,
|
|
254
|
+
recommendation: `${colors.cyan}>${RESET}`,
|
|
255
|
+
trend: `${colors.purple}~${RESET}`
|
|
256
|
+
};
|
|
257
|
+
async function statsCommand(options) {
|
|
258
|
+
const period = options.period === "30d" ? "30d" : "7d";
|
|
259
|
+
const summary = generateWorkforceSummary(period);
|
|
260
|
+
const scorecards = options.squad ? computeAllScorecards(period).filter((s) => s.squad === options.squad) : computeAllScorecards(period);
|
|
261
|
+
if (options.json) {
|
|
262
|
+
writeLine(JSON.stringify({
|
|
263
|
+
period,
|
|
264
|
+
executive_summary: generateExecutiveSummary(period),
|
|
265
|
+
...summary,
|
|
266
|
+
scorecards
|
|
267
|
+
}, null, 2));
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const periodLabel = period === "7d" ? "Last 7 days" : "Last 30 days";
|
|
271
|
+
writeLine();
|
|
272
|
+
writeLine(` ${bold}AI Workforce Intelligence${RESET} ${colors.dim}(${periodLabel})${RESET}`);
|
|
273
|
+
writeLine();
|
|
274
|
+
const execSummary = generateExecutiveSummary(period);
|
|
275
|
+
writeLine(` ${execSummary}`);
|
|
276
|
+
writeLine();
|
|
277
|
+
if (summary.totalExecutions > 0) {
|
|
278
|
+
writeLine(` ${bold}Key Metrics${RESET}`);
|
|
279
|
+
writeLine(` ${colors.dim}${"\u2500".repeat(50)}${RESET}`);
|
|
280
|
+
const roiColor = summary.roiMultiplier >= 3 ? colors.green : summary.roiMultiplier >= 1 ? colors.yellow : colors.red;
|
|
281
|
+
writeLine(` Executions ${bold}${summary.totalExecutions}${RESET}`);
|
|
282
|
+
writeLine(` Issues resolved${bold} ${summary.issuesResolved}${RESET} PRs merged ${bold}${summary.prsMerged}${RESET}`);
|
|
283
|
+
writeLine(` Total cost ${bold}$${summary.totalCostUsd.toFixed(2)}${RESET}`);
|
|
284
|
+
writeLine(` Hours saved ${bold}~${summary.estimatedHoursSaved.toFixed(0)}h${RESET} ${colors.dim}(at $${parseFloat(process.env.SQUADS_HOURLY_RATE || "75")}/hr)${RESET}`);
|
|
285
|
+
writeLine(` ROI ${roiColor}${bold}${summary.roiMultiplier.toFixed(1)}x${RESET} ${colors.dim}($${summary.estimatedValueUsd.toFixed(0)} estimated value)${RESET}`);
|
|
286
|
+
writeLine();
|
|
287
|
+
}
|
|
288
|
+
if (scorecards.length > 0) {
|
|
289
|
+
writeLine(` ${bold}Agent Scorecards${RESET}`);
|
|
290
|
+
writeLine(` ${colors.dim}${"\u2500".repeat(82)}${RESET}`);
|
|
291
|
+
const header = [
|
|
292
|
+
padRight(`${colors.dim}Squad/Agent${RESET}`, 30),
|
|
293
|
+
padRight(`${colors.dim}Runs${RESET}`, 8),
|
|
294
|
+
padRight(`${colors.dim}Merge${RESET}`, 10),
|
|
295
|
+
padRight(`${colors.dim}Resolve${RESET}`, 10),
|
|
296
|
+
padRight(`${colors.dim}Waste${RESET}`, 10),
|
|
297
|
+
padRight(`${colors.dim}CI${RESET}`, 8),
|
|
298
|
+
`${colors.dim}$/out${RESET}`
|
|
299
|
+
].join("");
|
|
300
|
+
writeLine(` ${header}`);
|
|
301
|
+
for (const card of scorecards) {
|
|
302
|
+
const name = `${card.squad}/${card.agent}`;
|
|
303
|
+
const mergeColor = rateColor(card.mergeRate, 0.7, 0.3);
|
|
304
|
+
const resolveColor = rateColor(card.issueResolutionRate, 0.5, 0.2);
|
|
305
|
+
const wasteColor = rateColor(1 - card.wasteRate, 0.7, 0.5);
|
|
306
|
+
const ciColor = rateColor(card.ciPassRate, 0.8, 0.5);
|
|
307
|
+
const costColor = card.costPerOutcome > 5 ? colors.red : card.costPerOutcome > 3 ? colors.yellow : colors.green;
|
|
308
|
+
const row = [
|
|
309
|
+
padRight(`${colors.cyan}${name}${RESET}`, 30),
|
|
310
|
+
padRight(`${card.executions}`, 8),
|
|
311
|
+
padRight(`${mergeColor}${pct(card.mergeRate)}${RESET}`, 10),
|
|
312
|
+
padRight(`${resolveColor}${pct(card.issueResolutionRate)}${RESET}`, 10),
|
|
313
|
+
padRight(`${wasteColor}${pct(card.wasteRate)}${RESET}`, 10),
|
|
314
|
+
padRight(`${ciColor}${pct(card.ciPassRate)}${RESET}`, 8),
|
|
315
|
+
`${costColor}$${card.costPerOutcome.toFixed(2)}${RESET}`
|
|
316
|
+
].join("");
|
|
317
|
+
writeLine(` ${row}`);
|
|
318
|
+
}
|
|
319
|
+
writeLine();
|
|
320
|
+
}
|
|
321
|
+
if (summary.insights.length > 0) {
|
|
322
|
+
writeLine(` ${bold}Insights${RESET}`);
|
|
323
|
+
writeLine(` ${colors.dim}${"\u2500".repeat(50)}${RESET}`);
|
|
324
|
+
for (const insight of summary.insights) {
|
|
325
|
+
const icon = insightIcons[insight.type] || " ";
|
|
326
|
+
writeLine(` ${icon} ${bold}${insight.title}${RESET}`);
|
|
327
|
+
writeLine(` ${colors.dim}${insight.detail}${RESET}`);
|
|
328
|
+
}
|
|
329
|
+
writeLine();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
export {
|
|
333
|
+
statsCommand
|
|
334
|
+
};
|
|
335
|
+
//# sourceMappingURL=stats-ONZI557Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/insights.ts","../src/commands/stats.ts"],"sourcesContent":["/**\n * Intelligence layer — turns raw outcome data into executive-grade insights.\n *\n * This is the enterprise value: less technical users see plain-language\n * summaries, ROI calculations, trends, and actionable recommendations\n * instead of raw merge rates and CI percentages.\n */\n\nimport {\n computeAllScorecards,\n getOutcomeRecords,\n type AgentScorecard,\n type OutcomeRecord,\n} from './outcomes.js';\n\n// ── Types ────────────────────────────────────────────────────────────\n\nexport interface ExecutiveInsight {\n type: 'highlight' | 'warning' | 'recommendation' | 'trend';\n title: string;\n detail: string;\n squad?: string;\n agent?: string;\n metric?: string;\n value?: number;\n}\n\nexport interface WorkforceSummary {\n period: '7d' | '30d';\n totalExecutions: number;\n totalCostUsd: number;\n issuesResolved: number;\n prsMerged: number;\n estimatedHoursSaved: number;\n estimatedValueUsd: number;\n roiMultiplier: number;\n overallMergeRate: number;\n overallWasteRate: number;\n topPerformer: { name: string; mergeRate: number } | null;\n underperformer: { name: string; reason: string } | null;\n insights: ExecutiveInsight[];\n}\n\n// ── Constants ────────────────────────────────────────────────────────\n\n// Configurable via env vars — enterprise customers set their own values\nconst HOURS_PER_ISSUE_RESOLVED = parseFloat(process.env.SQUADS_HOURS_PER_ISSUE || '4');\nconst HOURS_PER_PR_MERGED = parseFloat(process.env.SQUADS_HOURS_PER_PR || '2');\nconst HOURLY_RATE = parseFloat(process.env.SQUADS_HOURLY_RATE || '75');\n\n// ── Core ─────────────────────────────────────────────────────────────\n\n/**\n * Generate a full workforce summary with insights.\n * This is what enterprise dashboards and executive reports consume.\n */\nexport function generateWorkforceSummary(period: '7d' | '30d' = '7d'): WorkforceSummary {\n const scorecards = computeAllScorecards(period);\n const records = getOutcomeRecords();\n const periodMs = period === '7d' ? 7 * 24 * 60 * 60 * 1000 : 30 * 24 * 60 * 60 * 1000;\n const cutoff = Date.now() - periodMs;\n\n const periodRecords = records.filter(\n r => new Date(r.completedAt).getTime() > cutoff,\n );\n\n // Aggregate metrics\n const totalExecutions = periodRecords.length;\n const totalCostUsd = periodRecords.reduce((sum, r) => sum + r.costUsd, 0);\n const issuesResolved = periodRecords.reduce((sum, r) => sum + r.outcomes.issuesClosed, 0);\n const prsMerged = periodRecords.reduce((sum, r) => sum + r.outcomes.prsMerged, 0);\n\n // ROI calculation\n const estimatedHoursSaved =\n (issuesResolved * HOURS_PER_ISSUE_RESOLVED) +\n (prsMerged * HOURS_PER_PR_MERGED);\n const estimatedValueUsd = estimatedHoursSaved * HOURLY_RATE;\n const roiMultiplier = totalCostUsd > 0 ? estimatedValueUsd / totalCostUsd : 0;\n\n // Aggregate rates\n const totalPRs = periodRecords.reduce((sum, r) => sum + r.artifacts.prsCreated.length, 0);\n const overallMergeRate = totalPRs > 0 ? prsMerged / totalPRs : 0;\n\n const wasteRuns = periodRecords.filter(\n r => r.artifacts.prsCreated.length === 0 &&\n r.artifacts.issuesCreated.length === 0 &&\n r.artifacts.commits === 0,\n ).length;\n const overallWasteRate = totalExecutions > 0 ? wasteRuns / totalExecutions : 0;\n\n // Find top performer and underperformer\n const withData = scorecards.filter(s => s.executions >= 2);\n const topPerformer = findTopPerformer(withData);\n const underperformer = findUnderperformer(withData);\n\n // Generate insights\n const insights = generateInsights(scorecards, periodRecords, {\n totalCostUsd,\n issuesResolved,\n prsMerged,\n overallMergeRate,\n overallWasteRate,\n roiMultiplier,\n estimatedHoursSaved,\n });\n\n return {\n period,\n totalExecutions,\n totalCostUsd,\n issuesResolved,\n prsMerged,\n estimatedHoursSaved,\n estimatedValueUsd,\n roiMultiplier,\n overallMergeRate,\n overallWasteRate,\n topPerformer,\n underperformer,\n insights,\n };\n}\n\n// ── Insight generation ───────────────────────────────────────────────\n\nfunction findTopPerformer(cards: AgentScorecard[]): { name: string; mergeRate: number } | null {\n if (cards.length === 0) return null;\n\n // Score by weighted composite: merge rate + resolution rate - waste rate\n const scored = cards.map(c => ({\n name: `${c.squad}/${c.agent}`,\n mergeRate: c.mergeRate,\n composite: (c.mergeRate * 0.4) + (c.issueResolutionRate * 0.4) - (c.wasteRate * 0.2),\n }));\n\n scored.sort((a, b) => b.composite - a.composite);\n return scored[0] ? { name: scored[0].name, mergeRate: scored[0].mergeRate } : null;\n}\n\nfunction findUnderperformer(cards: AgentScorecard[]): { name: string; reason: string } | null {\n if (cards.length === 0) return null;\n\n for (const c of cards) {\n if (c.wasteRate > 0.5) {\n return {\n name: `${c.squad}/${c.agent}`,\n reason: `${Math.round(c.wasteRate * 100)}% of runs produce no output`,\n };\n }\n if (c.mergeRate < 0.2 && c.executions >= 3) {\n return {\n name: `${c.squad}/${c.agent}`,\n reason: `Only ${Math.round(c.mergeRate * 100)}% of PRs get merged`,\n };\n }\n if (c.costPerOutcome > 5) {\n return {\n name: `${c.squad}/${c.agent}`,\n reason: `$${c.costPerOutcome.toFixed(2)} per outcome — most expensive agent`,\n };\n }\n }\n\n return null;\n}\n\ninterface AggregateMetrics {\n totalCostUsd: number;\n issuesResolved: number;\n prsMerged: number;\n overallMergeRate: number;\n overallWasteRate: number;\n roiMultiplier: number;\n estimatedHoursSaved: number;\n}\n\nfunction generateInsights(\n scorecards: AgentScorecard[],\n records: OutcomeRecord[],\n metrics: AggregateMetrics,\n): ExecutiveInsight[] {\n const insights: ExecutiveInsight[] = [];\n\n // ROI insight\n if (metrics.roiMultiplier > 0) {\n if (metrics.roiMultiplier >= 3) {\n insights.push({\n type: 'highlight',\n title: 'Strong ROI',\n detail: `Your AI workforce delivered ${metrics.roiMultiplier.toFixed(1)}x return — $${metrics.totalCostUsd.toFixed(2)} spent, ~$${(metrics.estimatedHoursSaved * HOURLY_RATE).toFixed(0)} in estimated engineering time saved.`,\n metric: 'roi',\n value: metrics.roiMultiplier,\n });\n } else if (metrics.roiMultiplier >= 1) {\n insights.push({\n type: 'highlight',\n title: 'Positive ROI',\n detail: `AI workforce is paying for itself at ${metrics.roiMultiplier.toFixed(1)}x. ${metrics.estimatedHoursSaved.toFixed(0)} engineering hours saved.`,\n metric: 'roi',\n value: metrics.roiMultiplier,\n });\n } else if (metrics.totalCostUsd > 0) {\n insights.push({\n type: 'warning',\n title: 'ROI below breakeven',\n detail: `Currently at ${metrics.roiMultiplier.toFixed(1)}x — spending more than the estimated value of output. Review agent effectiveness.`,\n metric: 'roi',\n value: metrics.roiMultiplier,\n });\n }\n }\n\n // Productivity highlights\n if (metrics.issuesResolved > 0 || metrics.prsMerged > 0) {\n insights.push({\n type: 'highlight',\n title: 'Work delivered',\n detail: `${metrics.issuesResolved} issue${metrics.issuesResolved !== 1 ? 's' : ''} resolved, ${metrics.prsMerged} PR${metrics.prsMerged !== 1 ? 's' : ''} merged — equivalent to ~${metrics.estimatedHoursSaved.toFixed(0)} hours of engineering work.`,\n metric: 'productivity',\n });\n }\n\n // Waste warning\n if (metrics.overallWasteRate > 0.3 && records.length >= 3) {\n insights.push({\n type: 'warning',\n title: 'High waste rate',\n detail: `${Math.round(metrics.overallWasteRate * 100)}% of agent runs produce no output. Review agent prompts, issue quality, or available context.`,\n metric: 'waste',\n value: metrics.overallWasteRate,\n });\n }\n\n // Per-agent insights\n for (const card of scorecards) {\n if (card.executions < 3) continue;\n\n // Star performer\n if (card.mergeRate > 0.8 && card.wasteRate < 0.1) {\n insights.push({\n type: 'highlight',\n title: `${card.squad}/${card.agent} is a star`,\n detail: `${Math.round(card.mergeRate * 100)}% merge rate with only ${Math.round(card.wasteRate * 100)}% waste across ${card.executions} runs.`,\n squad: card.squad,\n agent: card.agent,\n metric: 'performance',\n });\n }\n\n // Struggling agent\n if (card.wasteRate > 0.5) {\n insights.push({\n type: 'recommendation',\n title: `Review ${card.squad}/${card.agent}`,\n detail: `${Math.round(card.wasteRate * 100)}% waste rate. Consider: improving agent prompt, adding more context, or pausing this agent until issues are better-scoped.`,\n squad: card.squad,\n agent: card.agent,\n metric: 'waste',\n });\n }\n\n // Low merge rate with enough data\n if (card.mergeRate < 0.3 && card.executions >= 5) {\n insights.push({\n type: 'recommendation',\n title: `${card.squad}/${card.agent} PRs rarely merge`,\n detail: `Only ${Math.round(card.mergeRate * 100)}% merge rate. PRs may need better scoping, testing, or review workflow adjustments.`,\n squad: card.squad,\n agent: card.agent,\n metric: 'merge_rate',\n });\n }\n\n // CI failures\n if (card.ciPassRate < 0.5 && card.executions >= 3) {\n insights.push({\n type: 'recommendation',\n title: `${card.squad}/${card.agent} CI issues`,\n detail: `Only ${Math.round(card.ciPassRate * 100)}% of PRs pass CI on first push. Agent may need build/test context in its prompt.`,\n squad: card.squad,\n agent: card.agent,\n metric: 'ci_pass_rate',\n });\n }\n }\n\n // No data yet\n if (records.length === 0) {\n insights.push({\n type: 'recommendation',\n title: 'Start tracking',\n detail: 'No outcome data yet. Run the daemon to start tracking agent productivity automatically.',\n });\n } else if (records.length < 5) {\n insights.push({\n type: 'recommendation',\n title: 'Building baseline',\n detail: `${records.length} execution${records.length !== 1 ? 's' : ''} tracked so far. Insights improve with more data — 10+ executions recommended for reliable patterns.`,\n });\n }\n\n return insights;\n}\n\n/**\n * Generate a one-paragraph executive summary.\n * This is the \"surprise\" for less technical users — plain English.\n */\nexport function generateExecutiveSummary(period: '7d' | '30d' = '7d'): string {\n const summary = generateWorkforceSummary(period);\n const periodLabel = period === '7d' ? 'this week' : 'this month';\n\n if (summary.totalExecutions === 0) {\n return `No AI workforce activity ${periodLabel}. Start the daemon to begin autonomous operations.`;\n }\n\n const parts: string[] = [];\n\n // Activity\n parts.push(\n `Your AI workforce ran ${summary.totalExecutions} time${summary.totalExecutions !== 1 ? 's' : ''} ${periodLabel}`,\n );\n\n // Output\n if (summary.issuesResolved > 0 || summary.prsMerged > 0) {\n const outputs: string[] = [];\n if (summary.issuesResolved > 0) outputs.push(`${summary.issuesResolved} issue${summary.issuesResolved !== 1 ? 's' : ''}`);\n if (summary.prsMerged > 0) outputs.push(`${summary.prsMerged} PR${summary.prsMerged !== 1 ? 's' : ''}`);\n parts.push(`delivering ${outputs.join(' and ')}`);\n }\n\n // Cost and ROI\n parts.push(`at a cost of $${summary.totalCostUsd.toFixed(2)}`);\n\n if (summary.roiMultiplier > 0) {\n parts.push(\n `— an estimated ${summary.roiMultiplier.toFixed(1)}x return on investment (${summary.estimatedHoursSaved.toFixed(0)} engineering hours saved)`,\n );\n }\n\n let text = parts.join(', ').replace(/, —/, ' —') + '.';\n\n // Top performer callout\n if (summary.topPerformer) {\n text += ` Top performer: ${summary.topPerformer.name} (${Math.round(summary.topPerformer.mergeRate * 100)}% merge rate).`;\n }\n\n // Issue callout\n if (summary.underperformer) {\n text += ` Needs attention: ${summary.underperformer.name} — ${summary.underperformer.reason}.`;\n }\n\n return text;\n}\n","/**\n * squads stats — AI workforce intelligence.\n *\n * Two modes:\n * squads stats → executive summary + scorecard table + insights\n * squads stats --json → machine-readable for dashboards\n *\n * The intelligence layer turns raw GitHub outcomes into business language:\n * ROI, hours saved, recommendations, trends. This is what enterprise\n * customers see — not merge percentages.\n */\n\nimport {\n computeAllScorecards,\n getOutcomeRecords,\n} from '../lib/outcomes.js';\nimport {\n generateWorkforceSummary,\n generateExecutiveSummary,\n} from '../lib/insights.js';\nimport {\n colors,\n bold,\n RESET,\n writeLine,\n} from '../lib/terminal.js';\n\nfunction pct(value: number): string {\n return `${Math.round(value * 100)}%`;\n}\n\nfunction padRight(str: string, len: number): string {\n const plain = str.replace(/\\x1b\\[[0-9;]*m/g, '');\n const pad = Math.max(0, len - plain.length);\n return str + ' '.repeat(pad);\n}\n\nfunction rateColor(rate: number, goodThreshold: number, badThreshold: number): string {\n if (rate >= goodThreshold) return colors.green;\n if (rate <= badThreshold) return colors.red;\n return colors.yellow;\n}\n\nconst insightIcons: Record<string, string> = {\n highlight: `${colors.green}*${RESET}`,\n warning: `${colors.yellow}!${RESET}`,\n recommendation: `${colors.cyan}>${RESET}`,\n trend: `${colors.purple}~${RESET}`,\n};\n\nexport async function statsCommand(options: {\n squad?: string;\n period?: string;\n json?: boolean;\n}): Promise<void> {\n const period = (options.period === '30d' ? '30d' : '7d') as '7d' | '30d';\n const summary = generateWorkforceSummary(period);\n\n // Filter scorecards by squad\n const scorecards = options.squad\n ? computeAllScorecards(period).filter(s => s.squad === options.squad)\n : computeAllScorecards(period);\n\n if (options.json) {\n writeLine(JSON.stringify({\n period,\n executive_summary: generateExecutiveSummary(period),\n ...summary,\n scorecards,\n }, null, 2));\n return;\n }\n\n const periodLabel = period === '7d' ? 'Last 7 days' : 'Last 30 days';\n\n writeLine();\n writeLine(` ${bold}AI Workforce Intelligence${RESET} ${colors.dim}(${periodLabel})${RESET}`);\n writeLine();\n\n // ── Executive summary ──────────────────────────────────────────────\n\n const execSummary = generateExecutiveSummary(period);\n writeLine(` ${execSummary}`);\n writeLine();\n\n // ── Key metrics ────────────────────────────────────────────────────\n\n if (summary.totalExecutions > 0) {\n writeLine(` ${bold}Key Metrics${RESET}`);\n writeLine(` ${colors.dim}${'─'.repeat(50)}${RESET}`);\n\n const roiColor = summary.roiMultiplier >= 3 ? colors.green\n : summary.roiMultiplier >= 1 ? colors.yellow\n : colors.red;\n\n writeLine(` Executions ${bold}${summary.totalExecutions}${RESET}`);\n writeLine(` Issues resolved${bold} ${summary.issuesResolved}${RESET} PRs merged ${bold}${summary.prsMerged}${RESET}`);\n writeLine(` Total cost ${bold}$${summary.totalCostUsd.toFixed(2)}${RESET}`);\n writeLine(` Hours saved ${bold}~${summary.estimatedHoursSaved.toFixed(0)}h${RESET} ${colors.dim}(at $${parseFloat(process.env.SQUADS_HOURLY_RATE || '75')}/hr)${RESET}`);\n writeLine(` ROI ${roiColor}${bold}${summary.roiMultiplier.toFixed(1)}x${RESET} ${colors.dim}($${summary.estimatedValueUsd.toFixed(0)} estimated value)${RESET}`);\n writeLine();\n }\n\n // ── Scorecard table ────────────────────────────────────────────────\n\n if (scorecards.length > 0) {\n writeLine(` ${bold}Agent Scorecards${RESET}`);\n writeLine(` ${colors.dim}${'─'.repeat(82)}${RESET}`);\n\n const header = [\n padRight(`${colors.dim}Squad/Agent${RESET}`, 30),\n padRight(`${colors.dim}Runs${RESET}`, 8),\n padRight(`${colors.dim}Merge${RESET}`, 10),\n padRight(`${colors.dim}Resolve${RESET}`, 10),\n padRight(`${colors.dim}Waste${RESET}`, 10),\n padRight(`${colors.dim}CI${RESET}`, 8),\n `${colors.dim}$/out${RESET}`,\n ].join('');\n writeLine(` ${header}`);\n\n for (const card of scorecards) {\n const name = `${card.squad}/${card.agent}`;\n const mergeColor = rateColor(card.mergeRate, 0.7, 0.3);\n const resolveColor = rateColor(card.issueResolutionRate, 0.5, 0.2);\n const wasteColor = rateColor(1 - card.wasteRate, 0.7, 0.5);\n const ciColor = rateColor(card.ciPassRate, 0.8, 0.5);\n const costColor = card.costPerOutcome > 5 ? colors.red : card.costPerOutcome > 3 ? colors.yellow : colors.green;\n\n const row = [\n padRight(`${colors.cyan}${name}${RESET}`, 30),\n padRight(`${card.executions}`, 8),\n padRight(`${mergeColor}${pct(card.mergeRate)}${RESET}`, 10),\n padRight(`${resolveColor}${pct(card.issueResolutionRate)}${RESET}`, 10),\n padRight(`${wasteColor}${pct(card.wasteRate)}${RESET}`, 10),\n padRight(`${ciColor}${pct(card.ciPassRate)}${RESET}`, 8),\n `${costColor}$${card.costPerOutcome.toFixed(2)}${RESET}`,\n ].join('');\n\n writeLine(` ${row}`);\n }\n writeLine();\n }\n\n // ── Insights ───────────────────────────────────────────────────────\n\n if (summary.insights.length > 0) {\n writeLine(` ${bold}Insights${RESET}`);\n writeLine(` ${colors.dim}${'─'.repeat(50)}${RESET}`);\n\n for (const insight of summary.insights) {\n const icon = insightIcons[insight.type] || ' ';\n writeLine(` ${icon} ${bold}${insight.title}${RESET}`);\n writeLine(` ${colors.dim}${insight.detail}${RESET}`);\n }\n writeLine();\n }\n}\n"],"mappings":"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","names":[]}
|