zeitzeuge 0.6.4 → 0.6.5
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/dist/cli.js +170 -47
- package/dist/output/progress.d.ts +10 -0
- package/dist/output/progress.d.ts.map +1 -1
- package/dist/vitest/index.js +127 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1210,12 +1210,18 @@ var FindingsSchema = z.object({
|
|
|
1210
1210
|
});
|
|
1211
1211
|
|
|
1212
1212
|
// src/output/progress.ts
|
|
1213
|
+
import pc from "picocolors";
|
|
1214
|
+
|
|
1213
1215
|
class TodoProgressRenderer {
|
|
1214
1216
|
spinner;
|
|
1215
1217
|
lastStatusByKey = new Map;
|
|
1216
1218
|
lastInProgressKey;
|
|
1217
1219
|
baseSpinnerText;
|
|
1218
1220
|
printedHeader = false;
|
|
1221
|
+
lastToolCallName;
|
|
1222
|
+
currentInProgressContent;
|
|
1223
|
+
totalTodos = 0;
|
|
1224
|
+
completedTodos = 0;
|
|
1219
1225
|
constructor(spinner) {
|
|
1220
1226
|
this.spinner = spinner;
|
|
1221
1227
|
this.baseSpinnerText = spinner.text;
|
|
@@ -1228,7 +1234,46 @@ class TodoProgressRenderer {
|
|
|
1228
1234
|
this.spinner.stopAndPersist({ symbol: " ", text: header });
|
|
1229
1235
|
this.spinner.start();
|
|
1230
1236
|
}
|
|
1237
|
+
progressPrefix() {
|
|
1238
|
+
if (this.totalTodos === 0)
|
|
1239
|
+
return "";
|
|
1240
|
+
return pc.dim(`[${this.completedTodos}/${this.totalTodos}]`) + " ";
|
|
1241
|
+
}
|
|
1242
|
+
recomputeCounts() {
|
|
1243
|
+
let total = 0;
|
|
1244
|
+
let completed = 0;
|
|
1245
|
+
for (const status of this.lastStatusByKey.values()) {
|
|
1246
|
+
if (status !== "cancelled")
|
|
1247
|
+
total++;
|
|
1248
|
+
if (status === "completed")
|
|
1249
|
+
completed++;
|
|
1250
|
+
}
|
|
1251
|
+
this.totalTodos = total;
|
|
1252
|
+
this.completedTodos = completed;
|
|
1253
|
+
}
|
|
1254
|
+
updateSpinnerText(contextLabel) {
|
|
1255
|
+
const prefix = this.progressPrefix();
|
|
1256
|
+
const base = this.baseSpinnerText ?? "";
|
|
1257
|
+
const ctx = contextLabel ? ` (${contextLabel})` : "";
|
|
1258
|
+
this.spinner.text = `${prefix}${base}${ctx}`;
|
|
1259
|
+
}
|
|
1231
1260
|
handleChunk(chunk) {
|
|
1261
|
+
const toolCalls = extractToolCallsFromStreamChunk(chunk);
|
|
1262
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
1263
|
+
for (const tc of toolCalls) {
|
|
1264
|
+
const signature = formatToolCall(tc);
|
|
1265
|
+
if (tc.name !== this.lastToolCallName) {
|
|
1266
|
+
this.lastToolCallName = tc.name;
|
|
1267
|
+
this.printHeaderOnce();
|
|
1268
|
+
this.spinner.stopAndPersist({
|
|
1269
|
+
symbol: " ",
|
|
1270
|
+
text: pc.dim(` ↳ ${signature}`)
|
|
1271
|
+
});
|
|
1272
|
+
this.spinner.start();
|
|
1273
|
+
this.updateSpinnerText(this.currentInProgressContent);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1232
1277
|
const todos = extractTodosFromStreamChunk(chunk);
|
|
1233
1278
|
if (!todos)
|
|
1234
1279
|
return;
|
|
@@ -1238,16 +1283,22 @@ class TodoProgressRenderer {
|
|
|
1238
1283
|
const nextStatus = todo.status;
|
|
1239
1284
|
if (prevStatus !== nextStatus) {
|
|
1240
1285
|
this.lastStatusByKey.set(key, nextStatus);
|
|
1286
|
+
this.recomputeCounts();
|
|
1241
1287
|
if (nextStatus === "completed" && prevStatus !== "completed") {
|
|
1242
1288
|
this.printHeaderOnce();
|
|
1243
|
-
this.spinner.stopAndPersist({
|
|
1289
|
+
this.spinner.stopAndPersist({
|
|
1290
|
+
symbol: " ",
|
|
1291
|
+
text: ` ${this.progressPrefix()}${pc.green("✓")} ${todo.content}`
|
|
1292
|
+
});
|
|
1244
1293
|
this.spinner.start();
|
|
1294
|
+
this.lastToolCallName = undefined;
|
|
1245
1295
|
}
|
|
1246
1296
|
if (nextStatus === "in_progress" && this.lastInProgressKey !== key) {
|
|
1247
1297
|
this.lastInProgressKey = key;
|
|
1298
|
+
this.currentInProgressContent = todo.content;
|
|
1248
1299
|
this.printHeaderOnce();
|
|
1249
|
-
|
|
1250
|
-
this.
|
|
1300
|
+
this.updateSpinnerText(todo.content);
|
|
1301
|
+
this.lastToolCallName = undefined;
|
|
1251
1302
|
}
|
|
1252
1303
|
}
|
|
1253
1304
|
}
|
|
@@ -1267,6 +1318,78 @@ function extractTodosFromStreamChunk(chunk) {
|
|
|
1267
1318
|
return nested.todos;
|
|
1268
1319
|
}
|
|
1269
1320
|
}
|
|
1321
|
+
function extractToolCallsFromStreamChunk(chunk) {
|
|
1322
|
+
if (!chunk || typeof chunk !== "object")
|
|
1323
|
+
return;
|
|
1324
|
+
const results = [];
|
|
1325
|
+
const extractFromMessage = (msg) => {
|
|
1326
|
+
if (!msg || typeof msg !== "object")
|
|
1327
|
+
return;
|
|
1328
|
+
const m = msg;
|
|
1329
|
+
if (!Array.isArray(m.tool_calls) || m.tool_calls.length === 0)
|
|
1330
|
+
return;
|
|
1331
|
+
for (const tc of m.tool_calls) {
|
|
1332
|
+
if (tc.name) {
|
|
1333
|
+
results.push({ name: tc.name, args: tc.args ?? {} });
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
};
|
|
1337
|
+
const extractFromMessages = (messages) => {
|
|
1338
|
+
if (!Array.isArray(messages))
|
|
1339
|
+
return;
|
|
1340
|
+
const last = messages[messages.length - 1];
|
|
1341
|
+
extractFromMessage(last);
|
|
1342
|
+
};
|
|
1343
|
+
const direct = chunk;
|
|
1344
|
+
if (Array.isArray(direct.messages)) {
|
|
1345
|
+
extractFromMessages(direct.messages);
|
|
1346
|
+
if (results.length > 0)
|
|
1347
|
+
return results;
|
|
1348
|
+
}
|
|
1349
|
+
for (const value of Object.values(chunk)) {
|
|
1350
|
+
if (!value || typeof value !== "object")
|
|
1351
|
+
continue;
|
|
1352
|
+
const nested = value;
|
|
1353
|
+
if (Array.isArray(nested.messages)) {
|
|
1354
|
+
extractFromMessages(nested.messages);
|
|
1355
|
+
if (results.length > 0)
|
|
1356
|
+
return results;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
return results.length > 0 ? results : undefined;
|
|
1360
|
+
}
|
|
1361
|
+
function formatToolCall(tc) {
|
|
1362
|
+
const args = tc.args;
|
|
1363
|
+
const keys = Object.keys(args);
|
|
1364
|
+
if (keys.length === 0)
|
|
1365
|
+
return `${tc.name}()`;
|
|
1366
|
+
if (keys.length === 1) {
|
|
1367
|
+
const key = keys[0];
|
|
1368
|
+
const val = args[key];
|
|
1369
|
+
if (typeof val === "string" && val.length <= 80) {
|
|
1370
|
+
return `${tc.name}(${key}: ${JSON.stringify(val)})`;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
const parts = [];
|
|
1374
|
+
for (const key of keys.slice(0, 3)) {
|
|
1375
|
+
const val = args[key];
|
|
1376
|
+
parts.push(`${key}: ${truncateValue(val)}`);
|
|
1377
|
+
}
|
|
1378
|
+
if (keys.length > 3)
|
|
1379
|
+
parts.push("...");
|
|
1380
|
+
return `${tc.name}(${parts.join(", ")})`;
|
|
1381
|
+
}
|
|
1382
|
+
function truncateValue(val, maxLen = 40) {
|
|
1383
|
+
if (typeof val === "string") {
|
|
1384
|
+
return val.length > maxLen ? JSON.stringify(val.slice(0, maxLen - 3) + "...") : JSON.stringify(val);
|
|
1385
|
+
}
|
|
1386
|
+
if (typeof val === "number" || typeof val === "boolean")
|
|
1387
|
+
return String(val);
|
|
1388
|
+
const json = JSON.stringify(val);
|
|
1389
|
+
if (json && json.length > maxLen)
|
|
1390
|
+
return json.slice(0, maxLen - 3) + "...";
|
|
1391
|
+
return json ?? "undefined";
|
|
1392
|
+
}
|
|
1270
1393
|
|
|
1271
1394
|
// src/analysis/agent.ts
|
|
1272
1395
|
async function invokeWithTodoStreaming(agent, userMessage, spinner) {
|
|
@@ -1535,7 +1658,7 @@ function buildResourceBreakdown(requests) {
|
|
|
1535
1658
|
}
|
|
1536
1659
|
|
|
1537
1660
|
// src/output/terminal.ts
|
|
1538
|
-
import
|
|
1661
|
+
import pc2 from "picocolors";
|
|
1539
1662
|
import ora from "ora";
|
|
1540
1663
|
|
|
1541
1664
|
// src/vitest/listener-tracker.ts
|
|
@@ -1739,14 +1862,14 @@ function aggregateListenerTracking(entries) {
|
|
|
1739
1862
|
|
|
1740
1863
|
// src/output/terminal.ts
|
|
1741
1864
|
var SEVERITY_ICONS = {
|
|
1742
|
-
critical:
|
|
1743
|
-
warning:
|
|
1744
|
-
info:
|
|
1865
|
+
critical: pc2.red("\uD83D\uDD34 CRITICAL"),
|
|
1866
|
+
warning: pc2.yellow("\uD83D\uDFE1 WARNING"),
|
|
1867
|
+
info: pc2.green("\uD83D\uDFE2 INFO")
|
|
1745
1868
|
};
|
|
1746
1869
|
var SEVERITY_LABELS = {
|
|
1747
|
-
critical:
|
|
1748
|
-
warning:
|
|
1749
|
-
info:
|
|
1870
|
+
critical: pc2.red("CRITICAL"),
|
|
1871
|
+
warning: pc2.yellow("WARNING"),
|
|
1872
|
+
info: pc2.green("INFO")
|
|
1750
1873
|
};
|
|
1751
1874
|
var CATEGORY_LABELS = {
|
|
1752
1875
|
"memory-leak": "Memory Leak",
|
|
@@ -1775,7 +1898,7 @@ var CATEGORY_LABELS = {
|
|
|
1775
1898
|
};
|
|
1776
1899
|
function printHeader(url, version) {
|
|
1777
1900
|
const urlDisplay = url.length > 44 ? url.slice(0, 41) + "..." : url;
|
|
1778
|
-
console.log(
|
|
1901
|
+
console.log(pc2.cyan(`
|
|
1779
1902
|
┌${"─".repeat(57)}┐
|
|
1780
1903
|
` + `│ zeitzeuge v${version.padEnd(44)}│
|
|
1781
1904
|
` + `│ Analyzing: ${urlDisplay.padEnd(44)}│
|
|
@@ -1786,62 +1909,62 @@ function createSpinner(text) {
|
|
|
1786
1909
|
return ora({ text, color: "cyan" }).start();
|
|
1787
1910
|
}
|
|
1788
1911
|
function printFindings(findings) {
|
|
1789
|
-
console.log(
|
|
1912
|
+
console.log(pc2.dim(`
|
|
1790
1913
|
` + "━".repeat(58) + `
|
|
1791
1914
|
`));
|
|
1792
1915
|
if (findings.length === 0) {
|
|
1793
|
-
console.log(
|
|
1916
|
+
console.log(pc2.green(` ✔ No significant performance issues found. Page looks healthy!
|
|
1794
1917
|
`));
|
|
1795
|
-
console.log(
|
|
1918
|
+
console.log(pc2.dim("━".repeat(58)));
|
|
1796
1919
|
return;
|
|
1797
1920
|
}
|
|
1798
1921
|
for (const finding of findings) {
|
|
1799
1922
|
const icon = SEVERITY_ICONS[finding.severity];
|
|
1800
1923
|
const categoryLabel = CATEGORY_LABELS[finding.category] ?? finding.category;
|
|
1801
|
-
console.log(`${icon} [${categoryLabel}]: ${
|
|
1924
|
+
console.log(`${icon} [${categoryLabel}]: ${pc2.bold(finding.title)}`);
|
|
1802
1925
|
if (finding.retainedSize != null) {
|
|
1803
|
-
console.log(
|
|
1926
|
+
console.log(pc2.dim(` Retained size: ${formatBytes2(finding.retainedSize)}`));
|
|
1804
1927
|
}
|
|
1805
1928
|
if (finding.impactMs != null) {
|
|
1806
|
-
console.log(
|
|
1929
|
+
console.log(pc2.dim(` Impact: ${finding.impactMs.toFixed(0)}ms`));
|
|
1807
1930
|
}
|
|
1808
1931
|
if (finding.resourceUrl) {
|
|
1809
|
-
console.log(
|
|
1932
|
+
console.log(pc2.dim(` Resource: ${finding.resourceUrl}`));
|
|
1810
1933
|
}
|
|
1811
1934
|
if (finding.retainerPath && finding.retainerPath.length > 0) {
|
|
1812
|
-
console.log(
|
|
1935
|
+
console.log(pc2.dim(` Path: ${finding.retainerPath.join(" → ")}`));
|
|
1813
1936
|
}
|
|
1814
1937
|
if (finding.testFile) {
|
|
1815
|
-
console.log(
|
|
1938
|
+
console.log(pc2.dim(` Test file: ${finding.testFile}`));
|
|
1816
1939
|
}
|
|
1817
1940
|
if (finding.hotFunction) {
|
|
1818
1941
|
const hf = finding.hotFunction;
|
|
1819
|
-
console.log(
|
|
1942
|
+
console.log(pc2.dim(` Function: ${hf.name} at ${hf.scriptUrl}:${hf.lineNumber} (selfTime: ${hf.selfTime.toFixed(0)}ms, ${hf.selfPercent.toFixed(1)}%)`));
|
|
1820
1943
|
}
|
|
1821
1944
|
console.log(`
|
|
1822
1945
|
${finding.description}
|
|
1823
1946
|
`);
|
|
1824
1947
|
if (finding.suggestedFix) {
|
|
1825
|
-
console.log(
|
|
1948
|
+
console.log(pc2.dim(" Suggested fix:"));
|
|
1826
1949
|
const lines = finding.suggestedFix.split(`
|
|
1827
1950
|
`);
|
|
1828
1951
|
const boxWidth = Math.max(...lines.map((l) => l.length), 20) + 4;
|
|
1829
|
-
console.log(
|
|
1952
|
+
console.log(pc2.dim(` ┌${"─".repeat(boxWidth)}┐`));
|
|
1830
1953
|
for (const line of lines) {
|
|
1831
|
-
console.log(
|
|
1954
|
+
console.log(pc2.dim(" │ ") + pc2.white(line.padEnd(boxWidth - 2)) + pc2.dim(" │"));
|
|
1832
1955
|
}
|
|
1833
|
-
console.log(
|
|
1956
|
+
console.log(pc2.dim(` └${"─".repeat(boxWidth)}┘`));
|
|
1834
1957
|
}
|
|
1835
1958
|
console.log();
|
|
1836
1959
|
}
|
|
1837
|
-
console.log(
|
|
1960
|
+
console.log(pc2.dim("━".repeat(58)));
|
|
1838
1961
|
const counts = {
|
|
1839
1962
|
critical: findings.filter((f) => f.severity === "critical").length,
|
|
1840
1963
|
warning: findings.filter((f) => f.severity === "warning").length,
|
|
1841
1964
|
info: findings.filter((f) => f.severity === "info").length
|
|
1842
1965
|
};
|
|
1843
1966
|
console.log(`
|
|
1844
|
-
Summary: ${
|
|
1967
|
+
Summary: ${pc2.red(`${counts.critical} critical`)}, ` + `${pc2.yellow(`${counts.warning} warning`)}, ` + `${pc2.green(`${counts.info} info`)}
|
|
1845
1968
|
`);
|
|
1846
1969
|
}
|
|
1847
1970
|
function wrapText(text, maxWidth) {
|
|
@@ -1872,28 +1995,28 @@ function printFindingsVitest(findings) {
|
|
|
1872
1995
|
const indent = " ";
|
|
1873
1996
|
const subIndent = indent + " ";
|
|
1874
1997
|
if (findings.length === 0) {
|
|
1875
|
-
console.log(`${indent}${
|
|
1998
|
+
console.log(`${indent}${pc2.green("✔")} No significant performance issues found.`);
|
|
1876
1999
|
return;
|
|
1877
2000
|
}
|
|
1878
2001
|
for (const finding of findings) {
|
|
1879
2002
|
const severity = SEVERITY_LABELS[finding.severity];
|
|
1880
2003
|
const categoryLabel = CATEGORY_LABELS[finding.category] ?? finding.category;
|
|
1881
|
-
console.log(`${indent}${severity} [${categoryLabel}]: ${
|
|
2004
|
+
console.log(`${indent}${severity} [${categoryLabel}]: ${pc2.bold(finding.title)}`);
|
|
1882
2005
|
if (finding.testFile)
|
|
1883
|
-
console.log(
|
|
2006
|
+
console.log(pc2.dim(`${subIndent}Test file: ${finding.testFile}`));
|
|
1884
2007
|
if (finding.impactMs != null)
|
|
1885
|
-
console.log(
|
|
2008
|
+
console.log(pc2.dim(`${subIndent}Impact: ${finding.impactMs.toFixed(0)}ms`));
|
|
1886
2009
|
if (finding.resourceUrl)
|
|
1887
|
-
console.log(
|
|
2010
|
+
console.log(pc2.dim(`${subIndent}Resource: ${finding.resourceUrl}`));
|
|
1888
2011
|
if (finding.hotFunction) {
|
|
1889
2012
|
const hf = finding.hotFunction;
|
|
1890
|
-
console.log(
|
|
2013
|
+
console.log(pc2.dim(`${subIndent}Function: ${hf.name} at ${hf.scriptUrl}:${hf.lineNumber} (selfTime: ${hf.selfTime.toFixed(0)}ms, ${hf.selfPercent.toFixed(1)}%)`));
|
|
1891
2014
|
}
|
|
1892
2015
|
for (const line of wrapText(finding.description, 100)) {
|
|
1893
2016
|
console.log(`${subIndent}${line}`);
|
|
1894
2017
|
}
|
|
1895
2018
|
if (finding.suggestedFix) {
|
|
1896
|
-
console.log(
|
|
2019
|
+
console.log(pc2.dim(`${subIndent}Suggested fix:`));
|
|
1897
2020
|
for (const line of finding.suggestedFix.split(`
|
|
1898
2021
|
`)) {
|
|
1899
2022
|
console.log(`${subIndent} ${line}`);
|
|
@@ -1906,13 +2029,13 @@ function printFindingsVitest(findings) {
|
|
|
1906
2029
|
warning: findings.filter((f) => f.severity === "warning").length,
|
|
1907
2030
|
info: findings.filter((f) => f.severity === "info").length
|
|
1908
2031
|
};
|
|
1909
|
-
console.log(`${indent}${
|
|
2032
|
+
console.log(`${indent}${pc2.dim("Summary:")} ${pc2.red(`${counts.critical} critical`)}, ${pc2.yellow(`${counts.warning} warning`)}, ${pc2.green(`${counts.info} info`)}`);
|
|
1910
2033
|
}
|
|
1911
2034
|
function printMetricsSummary(metrics) {
|
|
1912
2035
|
const indent = " ";
|
|
1913
2036
|
const s = metrics.suite;
|
|
1914
2037
|
const c = metrics.cpu;
|
|
1915
|
-
console.log(`${indent}${
|
|
2038
|
+
console.log(`${indent}${pc2.bold("Suite")}`);
|
|
1916
2039
|
console.log(`${indent} Total: ${formatMs(s.totalDuration)} · ` + `${s.totalTests} tests (${s.passCount} pass, ${s.failCount} fail) · ` + `Setup: ${formatMs(s.totalSetupTime)}`);
|
|
1917
2040
|
console.log(`${indent} Avg: ${formatMs(s.averageTestDuration)} · ` + `Median: ${formatMs(s.medianTestDuration)} · ` + `P95: ${formatMs(s.p95TestDuration)} · ` + `Slowest: ${formatMs(s.slowestTestDuration)}`);
|
|
1918
2041
|
if (s.slowestFile) {
|
|
@@ -1920,37 +2043,37 @@ function printMetricsSummary(metrics) {
|
|
|
1920
2043
|
}
|
|
1921
2044
|
if (c.gcTime > 0 || c.applicationTime > 0) {
|
|
1922
2045
|
console.log("");
|
|
1923
|
-
console.log(`${indent}${
|
|
2046
|
+
console.log(`${indent}${pc2.bold("CPU Breakdown")}`);
|
|
1924
2047
|
console.log(`${indent} Application: ${formatMs(c.applicationTime)} (${c.applicationPercent}%) · ` + `Dependencies: ${formatMs(c.dependencyTime)} (${c.dependencyPercent}%) · ` + `Test/Framework: ${formatMs(c.testFrameworkTime)} (${c.testFrameworkPercent}%)`);
|
|
1925
2048
|
console.log(`${indent} GC: ${formatMs(c.gcTime)} (${c.gcPercentage}%) · ` + `Idle: ${formatMs(c.idleTime)} (${c.idlePercentage}%)`);
|
|
1926
2049
|
}
|
|
1927
2050
|
if (metrics.hotFunctions.length > 0) {
|
|
1928
2051
|
console.log("");
|
|
1929
|
-
console.log(`${indent}${
|
|
2052
|
+
console.log(`${indent}${pc2.bold("Top Hot Functions")}`);
|
|
1930
2053
|
const top5 = metrics.hotFunctions.slice(0, 5);
|
|
1931
2054
|
for (const fn of top5) {
|
|
1932
|
-
const category = fn.sourceCategory !== "unknown" ?
|
|
2055
|
+
const category = fn.sourceCategory !== "unknown" ? pc2.dim(` [${fn.sourceCategory}]`) : "";
|
|
1933
2056
|
console.log(`${indent} ${formatMs(fn.selfTime)} (${fn.selfPercent}%) ${fn.functionName}${category}`);
|
|
1934
2057
|
if (fn.scriptUrl) {
|
|
1935
|
-
console.log(
|
|
2058
|
+
console.log(pc2.dim(`${indent} ${fn.scriptUrl}:${fn.lineNumber}`));
|
|
1936
2059
|
}
|
|
1937
2060
|
}
|
|
1938
2061
|
}
|
|
1939
2062
|
if (metrics.heap) {
|
|
1940
2063
|
console.log("");
|
|
1941
|
-
console.log(`${indent}${
|
|
2064
|
+
console.log(`${indent}${pc2.bold("Heap")}: ${formatBytes2(metrics.heap.totalAllocatedBytes)} allocated`);
|
|
1942
2065
|
}
|
|
1943
2066
|
if (metrics.listenerTracking) {
|
|
1944
2067
|
const lt = metrics.listenerTracking;
|
|
1945
2068
|
console.log("");
|
|
1946
|
-
console.log(`${indent}${
|
|
2069
|
+
console.log(`${indent}${pc2.bold("Event Listener Tracking")}`);
|
|
1947
2070
|
if (lt.exceedances.length > 0) {
|
|
1948
2071
|
for (const exc of lt.exceedances) {
|
|
1949
|
-
console.log(`${indent} ${
|
|
2072
|
+
console.log(`${indent} ${pc2.red("⚠")} ${pc2.red(`${exc.targetType}.${exc.eventType}`)}: ` + `${exc.listenerCount} listeners (max: ${exc.threshold})`);
|
|
1950
2073
|
if (exc.stack) {
|
|
1951
2074
|
for (const line of exc.stack.split(`
|
|
1952
2075
|
`).slice(0, 2)) {
|
|
1953
|
-
console.log(
|
|
2076
|
+
console.log(pc2.dim(`${indent} ${line}`));
|
|
1954
2077
|
}
|
|
1955
2078
|
}
|
|
1956
2079
|
}
|
|
@@ -1959,7 +2082,7 @@ function printMetricsSummary(metrics) {
|
|
|
1959
2082
|
if (allImbalances.length > 0) {
|
|
1960
2083
|
for (const entry of allImbalances.slice(0, 5)) {
|
|
1961
2084
|
const leaked = entry.addCount - entry.removeCount;
|
|
1962
|
-
console.log(`${indent} ${
|
|
2085
|
+
console.log(`${indent} ${pc2.yellow("⚠")} ${entry.api} "${entry.type}": ` + `${entry.addCount} adds, ${entry.removeCount} removes ` + pc2.yellow(`(${leaked} not cleaned up)`));
|
|
1963
2086
|
}
|
|
1964
2087
|
}
|
|
1965
2088
|
}
|
|
@@ -1973,11 +2096,11 @@ function formatMs(ms) {
|
|
|
1973
2096
|
return `${(ms / 1000).toFixed(2)}s`;
|
|
1974
2097
|
}
|
|
1975
2098
|
function printCaptureInfo(heapSummary, trace) {
|
|
1976
|
-
console.log(
|
|
2099
|
+
console.log(pc2.dim(`Heap: ${formatBytes2(heapSummary.metadata.totalSize)} | ` + `Nodes: ${heapSummary.metadata.nodeCount.toLocaleString()} | ` + `Requests: ${trace.networkRequests.length} | ` + `Long tasks: ${trace.metrics.longTasks.length}`));
|
|
1977
2100
|
}
|
|
1978
2101
|
function printError(err) {
|
|
1979
2102
|
const message = err instanceof Error ? err.message : String(err);
|
|
1980
|
-
console.error(
|
|
2103
|
+
console.error(pc2.red(`
|
|
1981
2104
|
✖ Error: ${message}
|
|
1982
2105
|
`));
|
|
1983
2106
|
}
|
|
@@ -5,8 +5,18 @@ export declare class TodoProgressRenderer {
|
|
|
5
5
|
private lastInProgressKey;
|
|
6
6
|
private baseSpinnerText;
|
|
7
7
|
private printedHeader;
|
|
8
|
+
private lastToolCallName;
|
|
9
|
+
private currentInProgressContent;
|
|
10
|
+
private totalTodos;
|
|
11
|
+
private completedTodos;
|
|
8
12
|
constructor(spinner: Ora);
|
|
9
13
|
private printHeaderOnce;
|
|
14
|
+
/** Build a progress prefix like `[2/5]` from the current todo counts. */
|
|
15
|
+
private progressPrefix;
|
|
16
|
+
/** Recompute todo counts from the full status map. */
|
|
17
|
+
private recomputeCounts;
|
|
18
|
+
/** Update the spinner text with current progress & context. */
|
|
19
|
+
private updateSpinnerText;
|
|
10
20
|
handleChunk(chunk: unknown): void;
|
|
11
21
|
}
|
|
12
22
|
//# sourceMappingURL=progress.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/output/progress.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/output/progress.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAgB/B,qBAAa,oBAAoB;IAUnB,OAAO,CAAC,OAAO;IAT3B,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,wBAAwB,CAAqB;IACrD,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,cAAc,CAAK;gBAEP,OAAO,EAAE,GAAG;IAIhC,OAAO,CAAC,eAAe;IASvB,yEAAyE;IACzE,OAAO,CAAC,cAAc;IAKtB,sDAAsD;IACtD,OAAO,CAAC,eAAe;IAWvB,+DAA+D;IAC/D,OAAO,CAAC,iBAAiB;IAOzB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;CAuDlC"}
|
package/dist/vitest/index.js
CHANGED
|
@@ -1515,12 +1515,18 @@ var FindingsSchema = z.object({
|
|
|
1515
1515
|
});
|
|
1516
1516
|
|
|
1517
1517
|
// src/output/progress.ts
|
|
1518
|
+
import pc2 from "picocolors";
|
|
1519
|
+
|
|
1518
1520
|
class TodoProgressRenderer {
|
|
1519
1521
|
spinner;
|
|
1520
1522
|
lastStatusByKey = new Map;
|
|
1521
1523
|
lastInProgressKey;
|
|
1522
1524
|
baseSpinnerText;
|
|
1523
1525
|
printedHeader = false;
|
|
1526
|
+
lastToolCallName;
|
|
1527
|
+
currentInProgressContent;
|
|
1528
|
+
totalTodos = 0;
|
|
1529
|
+
completedTodos = 0;
|
|
1524
1530
|
constructor(spinner) {
|
|
1525
1531
|
this.spinner = spinner;
|
|
1526
1532
|
this.baseSpinnerText = spinner.text;
|
|
@@ -1533,7 +1539,46 @@ class TodoProgressRenderer {
|
|
|
1533
1539
|
this.spinner.stopAndPersist({ symbol: " ", text: header });
|
|
1534
1540
|
this.spinner.start();
|
|
1535
1541
|
}
|
|
1542
|
+
progressPrefix() {
|
|
1543
|
+
if (this.totalTodos === 0)
|
|
1544
|
+
return "";
|
|
1545
|
+
return pc2.dim(`[${this.completedTodos}/${this.totalTodos}]`) + " ";
|
|
1546
|
+
}
|
|
1547
|
+
recomputeCounts() {
|
|
1548
|
+
let total = 0;
|
|
1549
|
+
let completed = 0;
|
|
1550
|
+
for (const status of this.lastStatusByKey.values()) {
|
|
1551
|
+
if (status !== "cancelled")
|
|
1552
|
+
total++;
|
|
1553
|
+
if (status === "completed")
|
|
1554
|
+
completed++;
|
|
1555
|
+
}
|
|
1556
|
+
this.totalTodos = total;
|
|
1557
|
+
this.completedTodos = completed;
|
|
1558
|
+
}
|
|
1559
|
+
updateSpinnerText(contextLabel) {
|
|
1560
|
+
const prefix = this.progressPrefix();
|
|
1561
|
+
const base = this.baseSpinnerText ?? "";
|
|
1562
|
+
const ctx = contextLabel ? ` (${contextLabel})` : "";
|
|
1563
|
+
this.spinner.text = `${prefix}${base}${ctx}`;
|
|
1564
|
+
}
|
|
1536
1565
|
handleChunk(chunk) {
|
|
1566
|
+
const toolCalls = extractToolCallsFromStreamChunk(chunk);
|
|
1567
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
1568
|
+
for (const tc of toolCalls) {
|
|
1569
|
+
const signature = formatToolCall(tc);
|
|
1570
|
+
if (tc.name !== this.lastToolCallName) {
|
|
1571
|
+
this.lastToolCallName = tc.name;
|
|
1572
|
+
this.printHeaderOnce();
|
|
1573
|
+
this.spinner.stopAndPersist({
|
|
1574
|
+
symbol: " ",
|
|
1575
|
+
text: pc2.dim(` ↳ ${signature}`)
|
|
1576
|
+
});
|
|
1577
|
+
this.spinner.start();
|
|
1578
|
+
this.updateSpinnerText(this.currentInProgressContent);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1537
1582
|
const todos = extractTodosFromStreamChunk(chunk);
|
|
1538
1583
|
if (!todos)
|
|
1539
1584
|
return;
|
|
@@ -1543,16 +1588,22 @@ class TodoProgressRenderer {
|
|
|
1543
1588
|
const nextStatus = todo.status;
|
|
1544
1589
|
if (prevStatus !== nextStatus) {
|
|
1545
1590
|
this.lastStatusByKey.set(key, nextStatus);
|
|
1591
|
+
this.recomputeCounts();
|
|
1546
1592
|
if (nextStatus === "completed" && prevStatus !== "completed") {
|
|
1547
1593
|
this.printHeaderOnce();
|
|
1548
|
-
this.spinner.stopAndPersist({
|
|
1594
|
+
this.spinner.stopAndPersist({
|
|
1595
|
+
symbol: " ",
|
|
1596
|
+
text: ` ${this.progressPrefix()}${pc2.green("✓")} ${todo.content}`
|
|
1597
|
+
});
|
|
1549
1598
|
this.spinner.start();
|
|
1599
|
+
this.lastToolCallName = undefined;
|
|
1550
1600
|
}
|
|
1551
1601
|
if (nextStatus === "in_progress" && this.lastInProgressKey !== key) {
|
|
1552
1602
|
this.lastInProgressKey = key;
|
|
1603
|
+
this.currentInProgressContent = todo.content;
|
|
1553
1604
|
this.printHeaderOnce();
|
|
1554
|
-
|
|
1555
|
-
this.
|
|
1605
|
+
this.updateSpinnerText(todo.content);
|
|
1606
|
+
this.lastToolCallName = undefined;
|
|
1556
1607
|
}
|
|
1557
1608
|
}
|
|
1558
1609
|
}
|
|
@@ -1572,6 +1623,78 @@ function extractTodosFromStreamChunk(chunk) {
|
|
|
1572
1623
|
return nested.todos;
|
|
1573
1624
|
}
|
|
1574
1625
|
}
|
|
1626
|
+
function extractToolCallsFromStreamChunk(chunk) {
|
|
1627
|
+
if (!chunk || typeof chunk !== "object")
|
|
1628
|
+
return;
|
|
1629
|
+
const results = [];
|
|
1630
|
+
const extractFromMessage = (msg) => {
|
|
1631
|
+
if (!msg || typeof msg !== "object")
|
|
1632
|
+
return;
|
|
1633
|
+
const m = msg;
|
|
1634
|
+
if (!Array.isArray(m.tool_calls) || m.tool_calls.length === 0)
|
|
1635
|
+
return;
|
|
1636
|
+
for (const tc of m.tool_calls) {
|
|
1637
|
+
if (tc.name) {
|
|
1638
|
+
results.push({ name: tc.name, args: tc.args ?? {} });
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
const extractFromMessages = (messages) => {
|
|
1643
|
+
if (!Array.isArray(messages))
|
|
1644
|
+
return;
|
|
1645
|
+
const last = messages[messages.length - 1];
|
|
1646
|
+
extractFromMessage(last);
|
|
1647
|
+
};
|
|
1648
|
+
const direct = chunk;
|
|
1649
|
+
if (Array.isArray(direct.messages)) {
|
|
1650
|
+
extractFromMessages(direct.messages);
|
|
1651
|
+
if (results.length > 0)
|
|
1652
|
+
return results;
|
|
1653
|
+
}
|
|
1654
|
+
for (const value of Object.values(chunk)) {
|
|
1655
|
+
if (!value || typeof value !== "object")
|
|
1656
|
+
continue;
|
|
1657
|
+
const nested = value;
|
|
1658
|
+
if (Array.isArray(nested.messages)) {
|
|
1659
|
+
extractFromMessages(nested.messages);
|
|
1660
|
+
if (results.length > 0)
|
|
1661
|
+
return results;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
return results.length > 0 ? results : undefined;
|
|
1665
|
+
}
|
|
1666
|
+
function formatToolCall(tc) {
|
|
1667
|
+
const args = tc.args;
|
|
1668
|
+
const keys = Object.keys(args);
|
|
1669
|
+
if (keys.length === 0)
|
|
1670
|
+
return `${tc.name}()`;
|
|
1671
|
+
if (keys.length === 1) {
|
|
1672
|
+
const key = keys[0];
|
|
1673
|
+
const val = args[key];
|
|
1674
|
+
if (typeof val === "string" && val.length <= 80) {
|
|
1675
|
+
return `${tc.name}(${key}: ${JSON.stringify(val)})`;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
const parts = [];
|
|
1679
|
+
for (const key of keys.slice(0, 3)) {
|
|
1680
|
+
const val = args[key];
|
|
1681
|
+
parts.push(`${key}: ${truncateValue(val)}`);
|
|
1682
|
+
}
|
|
1683
|
+
if (keys.length > 3)
|
|
1684
|
+
parts.push("...");
|
|
1685
|
+
return `${tc.name}(${parts.join(", ")})`;
|
|
1686
|
+
}
|
|
1687
|
+
function truncateValue(val, maxLen = 40) {
|
|
1688
|
+
if (typeof val === "string") {
|
|
1689
|
+
return val.length > maxLen ? JSON.stringify(val.slice(0, maxLen - 3) + "...") : JSON.stringify(val);
|
|
1690
|
+
}
|
|
1691
|
+
if (typeof val === "number" || typeof val === "boolean")
|
|
1692
|
+
return String(val);
|
|
1693
|
+
const json = JSON.stringify(val);
|
|
1694
|
+
if (json && json.length > maxLen)
|
|
1695
|
+
return json.slice(0, maxLen - 3) + "...";
|
|
1696
|
+
return json ?? "undefined";
|
|
1697
|
+
}
|
|
1575
1698
|
|
|
1576
1699
|
// src/analysis/agent.ts
|
|
1577
1700
|
async function invokeWithTodoStreaming(agent, userMessage, spinner) {
|
|
@@ -2219,7 +2342,7 @@ function relativize(filePath, projectRoot) {
|
|
|
2219
2342
|
// package.json
|
|
2220
2343
|
var package_default = {
|
|
2221
2344
|
name: "zeitzeuge",
|
|
2222
|
-
version: "0.6.
|
|
2345
|
+
version: "0.6.4",
|
|
2223
2346
|
description: "A deepagent to witnessing slowdowns in your test runs.",
|
|
2224
2347
|
keywords: [
|
|
2225
2348
|
"analysis",
|