zeitzeuge 0.6.4 → 0.6.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/analysis/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EAGrB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAC;AACjF,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAM/B,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAwC/D,6DAA6D;AAC7D,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,WAAW,CAAC;CAC1B;AA6DD;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,GAAG,EACZ,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,OAAO,EAAE,CAAC,CAwBpB;AAED,0DAA0D;AAC1D,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,eAAe,EAAE,OAAO,CAAC;IACzB,mBAAmB,EAAE,OAAO,CAAC;CAC9B;AAkDD;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,GAAG,EACZ,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,OAAO,EAAE,CAAC,CAwBpB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIjD"}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/analysis/agent.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,eAAe,EAGrB,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAC;AACjF,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAM/B,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAyE/D,6DAA6D;AAC7D,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,WAAW,CAAC;CAC1B;AA6DD;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,GAAG,EACZ,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,OAAO,EAAE,CAAC,CAwBpB;AAED,0DAA0D;AAC1D,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,kBAAkB,CAAC;IAC5B,eAAe,EAAE,OAAO,CAAC;IACzB,mBAAmB,EAAE,OAAO,CAAC;CAC9B;AAkDD;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,GAAG,EACZ,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,OAAO,EAAE,CAAC,CAwBpB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIjD"}
package/dist/cli.js CHANGED
@@ -779,6 +779,7 @@ function parseSnapshot(rawSnapshot) {
779
779
  }
780
780
 
781
781
  // src/analysis/agent.ts
782
+ import { setMaxListeners } from "node:events";
782
783
  import {
783
784
  createDeepAgent
784
785
  } from "deepagents";
@@ -1210,12 +1211,19 @@ var FindingsSchema = z.object({
1210
1211
  });
1211
1212
 
1212
1213
  // src/output/progress.ts
1214
+ import pc from "picocolors";
1215
+
1213
1216
  class TodoProgressRenderer {
1214
1217
  spinner;
1215
1218
  lastStatusByKey = new Map;
1216
1219
  lastInProgressKey;
1217
1220
  baseSpinnerText;
1218
1221
  printedHeader = false;
1222
+ lastToolCallName;
1223
+ lastSubagentToolCallName;
1224
+ currentInProgressContent;
1225
+ totalTodos = 0;
1226
+ completedTodos = 0;
1219
1227
  constructor(spinner) {
1220
1228
  this.spinner = spinner;
1221
1229
  this.baseSpinnerText = spinner.text;
@@ -1228,7 +1236,54 @@ class TodoProgressRenderer {
1228
1236
  this.spinner.stopAndPersist({ symbol: " ", text: header });
1229
1237
  this.spinner.start();
1230
1238
  }
1231
- handleChunk(chunk) {
1239
+ progressPrefix() {
1240
+ if (this.totalTodos === 0)
1241
+ return "";
1242
+ return pc.dim(`[${this.completedTodos}/${this.totalTodos}]`) + " ";
1243
+ }
1244
+ recomputeCounts() {
1245
+ let total = 0;
1246
+ let completed = 0;
1247
+ for (const status of this.lastStatusByKey.values()) {
1248
+ if (status !== "cancelled")
1249
+ total++;
1250
+ if (status === "completed")
1251
+ completed++;
1252
+ }
1253
+ this.totalTodos = total;
1254
+ this.completedTodos = completed;
1255
+ }
1256
+ updateSpinnerText(contextLabel) {
1257
+ const prefix = this.progressPrefix();
1258
+ const base = this.baseSpinnerText ?? "";
1259
+ const ctx = contextLabel ? ` (${contextLabel})` : "";
1260
+ this.spinner.text = `${prefix}${base}${ctx}`;
1261
+ }
1262
+ handleChunk(chunk, meta) {
1263
+ const isSubagent = meta?.isSubagent === true;
1264
+ const toolCalls = extractToolCallsFromStreamChunk(chunk);
1265
+ if (toolCalls && toolCalls.length > 0) {
1266
+ for (const tc of toolCalls) {
1267
+ const signature = formatToolCall(tc);
1268
+ const lastName = isSubagent ? this.lastSubagentToolCallName : this.lastToolCallName;
1269
+ if (tc.name !== lastName) {
1270
+ if (isSubagent)
1271
+ this.lastSubagentToolCallName = tc.name;
1272
+ else
1273
+ this.lastToolCallName = tc.name;
1274
+ this.printHeaderOnce();
1275
+ const label = isSubagent ? ` ↳ ${pc.cyan("[subagent]")} ${signature}` : ` ↳ ${signature}`;
1276
+ this.spinner.stopAndPersist({
1277
+ symbol: " ",
1278
+ text: pc.dim(label)
1279
+ });
1280
+ this.spinner.start();
1281
+ this.updateSpinnerText(this.currentInProgressContent);
1282
+ }
1283
+ }
1284
+ }
1285
+ if (isSubagent)
1286
+ return;
1232
1287
  const todos = extractTodosFromStreamChunk(chunk);
1233
1288
  if (!todos)
1234
1289
  return;
@@ -1238,16 +1293,24 @@ class TodoProgressRenderer {
1238
1293
  const nextStatus = todo.status;
1239
1294
  if (prevStatus !== nextStatus) {
1240
1295
  this.lastStatusByKey.set(key, nextStatus);
1296
+ this.recomputeCounts();
1241
1297
  if (nextStatus === "completed" && prevStatus !== "completed") {
1242
1298
  this.printHeaderOnce();
1243
- this.spinner.stopAndPersist({ symbol: " ", text: ` ✓ ${todo.content}` });
1299
+ this.spinner.stopAndPersist({
1300
+ symbol: " ",
1301
+ text: ` ${this.progressPrefix()}${pc.green("✓")} ${todo.content}`
1302
+ });
1244
1303
  this.spinner.start();
1304
+ this.lastToolCallName = undefined;
1305
+ this.lastSubagentToolCallName = undefined;
1245
1306
  }
1246
1307
  if (nextStatus === "in_progress" && this.lastInProgressKey !== key) {
1247
1308
  this.lastInProgressKey = key;
1309
+ this.currentInProgressContent = todo.content;
1248
1310
  this.printHeaderOnce();
1249
- const base = this.baseSpinnerText ?? this.spinner.text;
1250
- this.spinner.text = base ? `${base} (${todo.content})` : todo.content;
1311
+ this.updateSpinnerText(todo.content);
1312
+ this.lastToolCallName = undefined;
1313
+ this.lastSubagentToolCallName = undefined;
1251
1314
  }
1252
1315
  }
1253
1316
  }
@@ -1267,23 +1330,114 @@ function extractTodosFromStreamChunk(chunk) {
1267
1330
  return nested.todos;
1268
1331
  }
1269
1332
  }
1333
+ function extractToolCallsFromStreamChunk(chunk) {
1334
+ if (!chunk || typeof chunk !== "object")
1335
+ return;
1336
+ const results = [];
1337
+ const extractFromMessage = (msg) => {
1338
+ if (!msg || typeof msg !== "object")
1339
+ return;
1340
+ const m = msg;
1341
+ if (!Array.isArray(m.tool_calls) || m.tool_calls.length === 0)
1342
+ return;
1343
+ for (const tc of m.tool_calls) {
1344
+ if (tc.name) {
1345
+ results.push({ name: tc.name, args: tc.args ?? {} });
1346
+ }
1347
+ }
1348
+ };
1349
+ const extractFromMessages = (messages) => {
1350
+ if (!Array.isArray(messages))
1351
+ return;
1352
+ const last = messages[messages.length - 1];
1353
+ extractFromMessage(last);
1354
+ };
1355
+ const direct = chunk;
1356
+ if (Array.isArray(direct.messages)) {
1357
+ extractFromMessages(direct.messages);
1358
+ if (results.length > 0)
1359
+ return results;
1360
+ }
1361
+ for (const value of Object.values(chunk)) {
1362
+ if (!value || typeof value !== "object")
1363
+ continue;
1364
+ const nested = value;
1365
+ if (Array.isArray(nested.messages)) {
1366
+ extractFromMessages(nested.messages);
1367
+ if (results.length > 0)
1368
+ return results;
1369
+ }
1370
+ }
1371
+ return results.length > 0 ? results : undefined;
1372
+ }
1373
+ function formatToolCall(tc) {
1374
+ const args = tc.args;
1375
+ const keys = Object.keys(args);
1376
+ if (keys.length === 0)
1377
+ return `${tc.name}()`;
1378
+ if (keys.length === 1) {
1379
+ const key = keys[0];
1380
+ const val = args[key];
1381
+ if (typeof val === "string" && val.length <= 80) {
1382
+ return `${tc.name}(${key}: ${JSON.stringify(val)})`;
1383
+ }
1384
+ }
1385
+ const parts = [];
1386
+ for (const key of keys.slice(0, 3)) {
1387
+ const val = args[key];
1388
+ parts.push(`${key}: ${truncateValue(val)}`);
1389
+ }
1390
+ if (keys.length > 3)
1391
+ parts.push("...");
1392
+ return `${tc.name}(${parts.join(", ")})`;
1393
+ }
1394
+ function truncateValue(val, maxLen = 40) {
1395
+ if (typeof val === "string") {
1396
+ return val.length > maxLen ? JSON.stringify(val.slice(0, maxLen - 3) + "...") : JSON.stringify(val);
1397
+ }
1398
+ if (typeof val === "number" || typeof val === "boolean")
1399
+ return String(val);
1400
+ const json = JSON.stringify(val);
1401
+ if (json && json.length > maxLen)
1402
+ return json.slice(0, maxLen - 3) + "...";
1403
+ return json ?? "undefined";
1404
+ }
1270
1405
 
1271
1406
  // src/analysis/agent.ts
1407
+ function isSubagentNamespace(ns) {
1408
+ if (typeof ns === "string")
1409
+ return ns.includes("tools:");
1410
+ if (Array.isArray(ns))
1411
+ return ns.some((s) => typeof s === "string" && s.includes("tools:"));
1412
+ return false;
1413
+ }
1272
1414
  async function invokeWithTodoStreaming(agent, userMessage, spinner) {
1273
1415
  const renderer = new TodoProgressRenderer(spinner);
1274
- const stream = await agent.stream({ messages: [{ role: "user", content: userMessage }] }, { streamMode: ["updates", "values"] });
1416
+ const controller = new AbortController;
1417
+ setMaxListeners(0, controller.signal);
1418
+ const stream = await agent.stream({ messages: [{ role: "user", content: userMessage }] }, { streamMode: ["updates", "values"], subgraphs: true, signal: controller.signal });
1275
1419
  let lastValues;
1276
1420
  for await (const item of stream) {
1277
- if (Array.isArray(item) && item.length === 2) {
1278
- const mode = item[0];
1279
- const chunk = item[1];
1421
+ if (!Array.isArray(item)) {
1422
+ renderer.handleChunk(item);
1423
+ lastValues = item;
1424
+ continue;
1425
+ }
1426
+ if (item.length === 3) {
1427
+ const [ns, mode, chunk] = item;
1428
+ const isSubagent = isSubagentNamespace(ns);
1429
+ renderer.handleChunk(chunk, { isSubagent });
1430
+ if (!isSubagent && mode === "values")
1431
+ lastValues = chunk;
1432
+ continue;
1433
+ }
1434
+ if (item.length === 2) {
1435
+ const [mode, chunk] = item;
1280
1436
  renderer.handleChunk(chunk);
1281
1437
  if (mode === "values")
1282
1438
  lastValues = chunk;
1283
1439
  continue;
1284
1440
  }
1285
- renderer.handleChunk(item);
1286
- lastValues = item;
1287
1441
  }
1288
1442
  return lastValues;
1289
1443
  }
@@ -1535,7 +1689,7 @@ function buildResourceBreakdown(requests) {
1535
1689
  }
1536
1690
 
1537
1691
  // src/output/terminal.ts
1538
- import pc from "picocolors";
1692
+ import pc2 from "picocolors";
1539
1693
  import ora from "ora";
1540
1694
 
1541
1695
  // src/vitest/listener-tracker.ts
@@ -1739,14 +1893,14 @@ function aggregateListenerTracking(entries) {
1739
1893
 
1740
1894
  // src/output/terminal.ts
1741
1895
  var SEVERITY_ICONS = {
1742
- critical: pc.red("\uD83D\uDD34 CRITICAL"),
1743
- warning: pc.yellow("\uD83D\uDFE1 WARNING"),
1744
- info: pc.green("\uD83D\uDFE2 INFO")
1896
+ critical: pc2.red("\uD83D\uDD34 CRITICAL"),
1897
+ warning: pc2.yellow("\uD83D\uDFE1 WARNING"),
1898
+ info: pc2.green("\uD83D\uDFE2 INFO")
1745
1899
  };
1746
1900
  var SEVERITY_LABELS = {
1747
- critical: pc.red("CRITICAL"),
1748
- warning: pc.yellow("WARNING"),
1749
- info: pc.green("INFO")
1901
+ critical: pc2.red("CRITICAL"),
1902
+ warning: pc2.yellow("WARNING"),
1903
+ info: pc2.green("INFO")
1750
1904
  };
1751
1905
  var CATEGORY_LABELS = {
1752
1906
  "memory-leak": "Memory Leak",
@@ -1775,7 +1929,7 @@ var CATEGORY_LABELS = {
1775
1929
  };
1776
1930
  function printHeader(url, version) {
1777
1931
  const urlDisplay = url.length > 44 ? url.slice(0, 41) + "..." : url;
1778
- console.log(pc.cyan(`
1932
+ console.log(pc2.cyan(`
1779
1933
  ┌${"─".repeat(57)}┐
1780
1934
  ` + `│ zeitzeuge v${version.padEnd(44)}│
1781
1935
  ` + `│ Analyzing: ${urlDisplay.padEnd(44)}│
@@ -1786,62 +1940,62 @@ function createSpinner(text) {
1786
1940
  return ora({ text, color: "cyan" }).start();
1787
1941
  }
1788
1942
  function printFindings(findings) {
1789
- console.log(pc.dim(`
1943
+ console.log(pc2.dim(`
1790
1944
  ` + "━".repeat(58) + `
1791
1945
  `));
1792
1946
  if (findings.length === 0) {
1793
- console.log(pc.green(` ✔ No significant performance issues found. Page looks healthy!
1947
+ console.log(pc2.green(` ✔ No significant performance issues found. Page looks healthy!
1794
1948
  `));
1795
- console.log(pc.dim("━".repeat(58)));
1949
+ console.log(pc2.dim("━".repeat(58)));
1796
1950
  return;
1797
1951
  }
1798
1952
  for (const finding of findings) {
1799
1953
  const icon = SEVERITY_ICONS[finding.severity];
1800
1954
  const categoryLabel = CATEGORY_LABELS[finding.category] ?? finding.category;
1801
- console.log(`${icon} [${categoryLabel}]: ${pc.bold(finding.title)}`);
1955
+ console.log(`${icon} [${categoryLabel}]: ${pc2.bold(finding.title)}`);
1802
1956
  if (finding.retainedSize != null) {
1803
- console.log(pc.dim(` Retained size: ${formatBytes2(finding.retainedSize)}`));
1957
+ console.log(pc2.dim(` Retained size: ${formatBytes2(finding.retainedSize)}`));
1804
1958
  }
1805
1959
  if (finding.impactMs != null) {
1806
- console.log(pc.dim(` Impact: ${finding.impactMs.toFixed(0)}ms`));
1960
+ console.log(pc2.dim(` Impact: ${finding.impactMs.toFixed(0)}ms`));
1807
1961
  }
1808
1962
  if (finding.resourceUrl) {
1809
- console.log(pc.dim(` Resource: ${finding.resourceUrl}`));
1963
+ console.log(pc2.dim(` Resource: ${finding.resourceUrl}`));
1810
1964
  }
1811
1965
  if (finding.retainerPath && finding.retainerPath.length > 0) {
1812
- console.log(pc.dim(` Path: ${finding.retainerPath.join(" → ")}`));
1966
+ console.log(pc2.dim(` Path: ${finding.retainerPath.join(" → ")}`));
1813
1967
  }
1814
1968
  if (finding.testFile) {
1815
- console.log(pc.dim(` Test file: ${finding.testFile}`));
1969
+ console.log(pc2.dim(` Test file: ${finding.testFile}`));
1816
1970
  }
1817
1971
  if (finding.hotFunction) {
1818
1972
  const hf = finding.hotFunction;
1819
- console.log(pc.dim(` Function: ${hf.name} at ${hf.scriptUrl}:${hf.lineNumber} (selfTime: ${hf.selfTime.toFixed(0)}ms, ${hf.selfPercent.toFixed(1)}%)`));
1973
+ console.log(pc2.dim(` Function: ${hf.name} at ${hf.scriptUrl}:${hf.lineNumber} (selfTime: ${hf.selfTime.toFixed(0)}ms, ${hf.selfPercent.toFixed(1)}%)`));
1820
1974
  }
1821
1975
  console.log(`
1822
1976
  ${finding.description}
1823
1977
  `);
1824
1978
  if (finding.suggestedFix) {
1825
- console.log(pc.dim(" Suggested fix:"));
1979
+ console.log(pc2.dim(" Suggested fix:"));
1826
1980
  const lines = finding.suggestedFix.split(`
1827
1981
  `);
1828
1982
  const boxWidth = Math.max(...lines.map((l) => l.length), 20) + 4;
1829
- console.log(pc.dim(` ┌${"─".repeat(boxWidth)}┐`));
1983
+ console.log(pc2.dim(` ┌${"─".repeat(boxWidth)}┐`));
1830
1984
  for (const line of lines) {
1831
- console.log(pc.dim(" │ ") + pc.white(line.padEnd(boxWidth - 2)) + pc.dim(" │"));
1985
+ console.log(pc2.dim(" │ ") + pc2.white(line.padEnd(boxWidth - 2)) + pc2.dim(" │"));
1832
1986
  }
1833
- console.log(pc.dim(` └${"─".repeat(boxWidth)}┘`));
1987
+ console.log(pc2.dim(` └${"─".repeat(boxWidth)}┘`));
1834
1988
  }
1835
1989
  console.log();
1836
1990
  }
1837
- console.log(pc.dim("━".repeat(58)));
1991
+ console.log(pc2.dim("━".repeat(58)));
1838
1992
  const counts = {
1839
1993
  critical: findings.filter((f) => f.severity === "critical").length,
1840
1994
  warning: findings.filter((f) => f.severity === "warning").length,
1841
1995
  info: findings.filter((f) => f.severity === "info").length
1842
1996
  };
1843
1997
  console.log(`
1844
- Summary: ${pc.red(`${counts.critical} critical`)}, ` + `${pc.yellow(`${counts.warning} warning`)}, ` + `${pc.green(`${counts.info} info`)}
1998
+ Summary: ${pc2.red(`${counts.critical} critical`)}, ` + `${pc2.yellow(`${counts.warning} warning`)}, ` + `${pc2.green(`${counts.info} info`)}
1845
1999
  `);
1846
2000
  }
1847
2001
  function wrapText(text, maxWidth) {
@@ -1872,28 +2026,28 @@ function printFindingsVitest(findings) {
1872
2026
  const indent = " ";
1873
2027
  const subIndent = indent + " ";
1874
2028
  if (findings.length === 0) {
1875
- console.log(`${indent}${pc.green("✔")} No significant performance issues found.`);
2029
+ console.log(`${indent}${pc2.green("✔")} No significant performance issues found.`);
1876
2030
  return;
1877
2031
  }
1878
2032
  for (const finding of findings) {
1879
2033
  const severity = SEVERITY_LABELS[finding.severity];
1880
2034
  const categoryLabel = CATEGORY_LABELS[finding.category] ?? finding.category;
1881
- console.log(`${indent}${severity} [${categoryLabel}]: ${pc.bold(finding.title)}`);
2035
+ console.log(`${indent}${severity} [${categoryLabel}]: ${pc2.bold(finding.title)}`);
1882
2036
  if (finding.testFile)
1883
- console.log(pc.dim(`${subIndent}Test file: ${finding.testFile}`));
2037
+ console.log(pc2.dim(`${subIndent}Test file: ${finding.testFile}`));
1884
2038
  if (finding.impactMs != null)
1885
- console.log(pc.dim(`${subIndent}Impact: ${finding.impactMs.toFixed(0)}ms`));
2039
+ console.log(pc2.dim(`${subIndent}Impact: ${finding.impactMs.toFixed(0)}ms`));
1886
2040
  if (finding.resourceUrl)
1887
- console.log(pc.dim(`${subIndent}Resource: ${finding.resourceUrl}`));
2041
+ console.log(pc2.dim(`${subIndent}Resource: ${finding.resourceUrl}`));
1888
2042
  if (finding.hotFunction) {
1889
2043
  const hf = finding.hotFunction;
1890
- console.log(pc.dim(`${subIndent}Function: ${hf.name} at ${hf.scriptUrl}:${hf.lineNumber} (selfTime: ${hf.selfTime.toFixed(0)}ms, ${hf.selfPercent.toFixed(1)}%)`));
2044
+ console.log(pc2.dim(`${subIndent}Function: ${hf.name} at ${hf.scriptUrl}:${hf.lineNumber} (selfTime: ${hf.selfTime.toFixed(0)}ms, ${hf.selfPercent.toFixed(1)}%)`));
1891
2045
  }
1892
2046
  for (const line of wrapText(finding.description, 100)) {
1893
2047
  console.log(`${subIndent}${line}`);
1894
2048
  }
1895
2049
  if (finding.suggestedFix) {
1896
- console.log(pc.dim(`${subIndent}Suggested fix:`));
2050
+ console.log(pc2.dim(`${subIndent}Suggested fix:`));
1897
2051
  for (const line of finding.suggestedFix.split(`
1898
2052
  `)) {
1899
2053
  console.log(`${subIndent} ${line}`);
@@ -1906,13 +2060,13 @@ function printFindingsVitest(findings) {
1906
2060
  warning: findings.filter((f) => f.severity === "warning").length,
1907
2061
  info: findings.filter((f) => f.severity === "info").length
1908
2062
  };
1909
- console.log(`${indent}${pc.dim("Summary:")} ${pc.red(`${counts.critical} critical`)}, ${pc.yellow(`${counts.warning} warning`)}, ${pc.green(`${counts.info} info`)}`);
2063
+ console.log(`${indent}${pc2.dim("Summary:")} ${pc2.red(`${counts.critical} critical`)}, ${pc2.yellow(`${counts.warning} warning`)}, ${pc2.green(`${counts.info} info`)}`);
1910
2064
  }
1911
2065
  function printMetricsSummary(metrics) {
1912
2066
  const indent = " ";
1913
2067
  const s = metrics.suite;
1914
2068
  const c = metrics.cpu;
1915
- console.log(`${indent}${pc.bold("Suite")}`);
2069
+ console.log(`${indent}${pc2.bold("Suite")}`);
1916
2070
  console.log(`${indent} Total: ${formatMs(s.totalDuration)} · ` + `${s.totalTests} tests (${s.passCount} pass, ${s.failCount} fail) · ` + `Setup: ${formatMs(s.totalSetupTime)}`);
1917
2071
  console.log(`${indent} Avg: ${formatMs(s.averageTestDuration)} · ` + `Median: ${formatMs(s.medianTestDuration)} · ` + `P95: ${formatMs(s.p95TestDuration)} · ` + `Slowest: ${formatMs(s.slowestTestDuration)}`);
1918
2072
  if (s.slowestFile) {
@@ -1920,37 +2074,37 @@ function printMetricsSummary(metrics) {
1920
2074
  }
1921
2075
  if (c.gcTime > 0 || c.applicationTime > 0) {
1922
2076
  console.log("");
1923
- console.log(`${indent}${pc.bold("CPU Breakdown")}`);
2077
+ console.log(`${indent}${pc2.bold("CPU Breakdown")}`);
1924
2078
  console.log(`${indent} Application: ${formatMs(c.applicationTime)} (${c.applicationPercent}%) · ` + `Dependencies: ${formatMs(c.dependencyTime)} (${c.dependencyPercent}%) · ` + `Test/Framework: ${formatMs(c.testFrameworkTime)} (${c.testFrameworkPercent}%)`);
1925
2079
  console.log(`${indent} GC: ${formatMs(c.gcTime)} (${c.gcPercentage}%) · ` + `Idle: ${formatMs(c.idleTime)} (${c.idlePercentage}%)`);
1926
2080
  }
1927
2081
  if (metrics.hotFunctions.length > 0) {
1928
2082
  console.log("");
1929
- console.log(`${indent}${pc.bold("Top Hot Functions")}`);
2083
+ console.log(`${indent}${pc2.bold("Top Hot Functions")}`);
1930
2084
  const top5 = metrics.hotFunctions.slice(0, 5);
1931
2085
  for (const fn of top5) {
1932
- const category = fn.sourceCategory !== "unknown" ? pc.dim(` [${fn.sourceCategory}]`) : "";
2086
+ const category = fn.sourceCategory !== "unknown" ? pc2.dim(` [${fn.sourceCategory}]`) : "";
1933
2087
  console.log(`${indent} ${formatMs(fn.selfTime)} (${fn.selfPercent}%) ${fn.functionName}${category}`);
1934
2088
  if (fn.scriptUrl) {
1935
- console.log(pc.dim(`${indent} ${fn.scriptUrl}:${fn.lineNumber}`));
2089
+ console.log(pc2.dim(`${indent} ${fn.scriptUrl}:${fn.lineNumber}`));
1936
2090
  }
1937
2091
  }
1938
2092
  }
1939
2093
  if (metrics.heap) {
1940
2094
  console.log("");
1941
- console.log(`${indent}${pc.bold("Heap")}: ${formatBytes2(metrics.heap.totalAllocatedBytes)} allocated`);
2095
+ console.log(`${indent}${pc2.bold("Heap")}: ${formatBytes2(metrics.heap.totalAllocatedBytes)} allocated`);
1942
2096
  }
1943
2097
  if (metrics.listenerTracking) {
1944
2098
  const lt = metrics.listenerTracking;
1945
2099
  console.log("");
1946
- console.log(`${indent}${pc.bold("Event Listener Tracking")}`);
2100
+ console.log(`${indent}${pc2.bold("Event Listener Tracking")}`);
1947
2101
  if (lt.exceedances.length > 0) {
1948
2102
  for (const exc of lt.exceedances) {
1949
- console.log(`${indent} ${pc.red("⚠")} ${pc.red(`${exc.targetType}.${exc.eventType}`)}: ` + `${exc.listenerCount} listeners (max: ${exc.threshold})`);
2103
+ console.log(`${indent} ${pc2.red("⚠")} ${pc2.red(`${exc.targetType}.${exc.eventType}`)}: ` + `${exc.listenerCount} listeners (max: ${exc.threshold})`);
1950
2104
  if (exc.stack) {
1951
2105
  for (const line of exc.stack.split(`
1952
2106
  `).slice(0, 2)) {
1953
- console.log(pc.dim(`${indent} ${line}`));
2107
+ console.log(pc2.dim(`${indent} ${line}`));
1954
2108
  }
1955
2109
  }
1956
2110
  }
@@ -1959,7 +2113,7 @@ function printMetricsSummary(metrics) {
1959
2113
  if (allImbalances.length > 0) {
1960
2114
  for (const entry of allImbalances.slice(0, 5)) {
1961
2115
  const leaked = entry.addCount - entry.removeCount;
1962
- console.log(`${indent} ${pc.yellow("⚠")} ${entry.api} "${entry.type}": ` + `${entry.addCount} adds, ${entry.removeCount} removes ` + pc.yellow(`(${leaked} not cleaned up)`));
2116
+ console.log(`${indent} ${pc2.yellow("⚠")} ${entry.api} "${entry.type}": ` + `${entry.addCount} adds, ${entry.removeCount} removes ` + pc2.yellow(`(${leaked} not cleaned up)`));
1963
2117
  }
1964
2118
  }
1965
2119
  }
@@ -1973,11 +2127,11 @@ function formatMs(ms) {
1973
2127
  return `${(ms / 1000).toFixed(2)}s`;
1974
2128
  }
1975
2129
  function printCaptureInfo(heapSummary, trace) {
1976
- console.log(pc.dim(`Heap: ${formatBytes2(heapSummary.metadata.totalSize)} | ` + `Nodes: ${heapSummary.metadata.nodeCount.toLocaleString()} | ` + `Requests: ${trace.networkRequests.length} | ` + `Long tasks: ${trace.metrics.longTasks.length}`));
2130
+ 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
2131
  }
1978
2132
  function printError(err) {
1979
2133
  const message = err instanceof Error ? err.message : String(err);
1980
- console.error(pc.red(`
2134
+ console.error(pc2.red(`
1981
2135
  ✖ Error: ${message}
1982
2136
  `));
1983
2137
  }
@@ -1,12 +1,30 @@
1
1
  import type { Ora } from 'ora';
2
+ /** Options passed alongside a chunk to provide context about its origin. */
3
+ export interface ChunkMeta {
4
+ /** True when the chunk originates from a subagent (subgraph). */
5
+ isSubagent?: boolean;
6
+ }
2
7
  export declare class TodoProgressRenderer {
3
8
  private spinner;
4
9
  private lastStatusByKey;
5
10
  private lastInProgressKey;
6
11
  private baseSpinnerText;
7
12
  private printedHeader;
13
+ /** Last tool call name for the main agent (dedup). */
14
+ private lastToolCallName;
15
+ /** Last tool call name for subagents (separate dedup). */
16
+ private lastSubagentToolCallName;
17
+ private currentInProgressContent;
18
+ private totalTodos;
19
+ private completedTodos;
8
20
  constructor(spinner: Ora);
9
21
  private printHeaderOnce;
10
- handleChunk(chunk: unknown): void;
22
+ /** Build a progress prefix like `[2/5]` from the current todo counts. */
23
+ private progressPrefix;
24
+ /** Recompute todo counts from the full status map. */
25
+ private recomputeCounts;
26
+ /** Update the spinner text with current progress & context. */
27
+ private updateSpinnerText;
28
+ handleChunk(chunk: unknown, meta?: ChunkMeta): void;
11
29
  }
12
30
  //# sourceMappingURL=progress.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/output/progress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAU/B,qBAAa,oBAAoB;IAMnB,OAAO,CAAC,OAAO;IAL3B,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,aAAa,CAAS;gBAEV,OAAO,EAAE,GAAG;IAIhC,OAAO,CAAC,eAAe;IASvB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;CA2BlC"}
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,4EAA4E;AAC5E,MAAM,WAAW,SAAS;IACxB,iEAAiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,qBAAa,oBAAoB;IAanB,OAAO,CAAC,OAAO;IAZ3B,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,aAAa,CAAS;IAC9B,sDAAsD;IACtD,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,0DAA0D;IAC1D,OAAO,CAAC,wBAAwB,CAAqB;IACrD,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,EAAE,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI;CAuEpD"}
@@ -1084,6 +1084,7 @@ function formatBytes(bytes) {
1084
1084
  }
1085
1085
 
1086
1086
  // src/analysis/agent.ts
1087
+ import { setMaxListeners } from "node:events";
1087
1088
  import {
1088
1089
  createDeepAgent
1089
1090
  } from "deepagents";
@@ -1515,12 +1516,19 @@ var FindingsSchema = z.object({
1515
1516
  });
1516
1517
 
1517
1518
  // src/output/progress.ts
1519
+ import pc2 from "picocolors";
1520
+
1518
1521
  class TodoProgressRenderer {
1519
1522
  spinner;
1520
1523
  lastStatusByKey = new Map;
1521
1524
  lastInProgressKey;
1522
1525
  baseSpinnerText;
1523
1526
  printedHeader = false;
1527
+ lastToolCallName;
1528
+ lastSubagentToolCallName;
1529
+ currentInProgressContent;
1530
+ totalTodos = 0;
1531
+ completedTodos = 0;
1524
1532
  constructor(spinner) {
1525
1533
  this.spinner = spinner;
1526
1534
  this.baseSpinnerText = spinner.text;
@@ -1533,7 +1541,54 @@ class TodoProgressRenderer {
1533
1541
  this.spinner.stopAndPersist({ symbol: " ", text: header });
1534
1542
  this.spinner.start();
1535
1543
  }
1536
- handleChunk(chunk) {
1544
+ progressPrefix() {
1545
+ if (this.totalTodos === 0)
1546
+ return "";
1547
+ return pc2.dim(`[${this.completedTodos}/${this.totalTodos}]`) + " ";
1548
+ }
1549
+ recomputeCounts() {
1550
+ let total = 0;
1551
+ let completed = 0;
1552
+ for (const status of this.lastStatusByKey.values()) {
1553
+ if (status !== "cancelled")
1554
+ total++;
1555
+ if (status === "completed")
1556
+ completed++;
1557
+ }
1558
+ this.totalTodos = total;
1559
+ this.completedTodos = completed;
1560
+ }
1561
+ updateSpinnerText(contextLabel) {
1562
+ const prefix = this.progressPrefix();
1563
+ const base = this.baseSpinnerText ?? "";
1564
+ const ctx = contextLabel ? ` (${contextLabel})` : "";
1565
+ this.spinner.text = `${prefix}${base}${ctx}`;
1566
+ }
1567
+ handleChunk(chunk, meta) {
1568
+ const isSubagent = meta?.isSubagent === true;
1569
+ const toolCalls = extractToolCallsFromStreamChunk(chunk);
1570
+ if (toolCalls && toolCalls.length > 0) {
1571
+ for (const tc of toolCalls) {
1572
+ const signature = formatToolCall(tc);
1573
+ const lastName = isSubagent ? this.lastSubagentToolCallName : this.lastToolCallName;
1574
+ if (tc.name !== lastName) {
1575
+ if (isSubagent)
1576
+ this.lastSubagentToolCallName = tc.name;
1577
+ else
1578
+ this.lastToolCallName = tc.name;
1579
+ this.printHeaderOnce();
1580
+ const label = isSubagent ? ` ↳ ${pc2.cyan("[subagent]")} ${signature}` : ` ↳ ${signature}`;
1581
+ this.spinner.stopAndPersist({
1582
+ symbol: " ",
1583
+ text: pc2.dim(label)
1584
+ });
1585
+ this.spinner.start();
1586
+ this.updateSpinnerText(this.currentInProgressContent);
1587
+ }
1588
+ }
1589
+ }
1590
+ if (isSubagent)
1591
+ return;
1537
1592
  const todos = extractTodosFromStreamChunk(chunk);
1538
1593
  if (!todos)
1539
1594
  return;
@@ -1543,16 +1598,24 @@ class TodoProgressRenderer {
1543
1598
  const nextStatus = todo.status;
1544
1599
  if (prevStatus !== nextStatus) {
1545
1600
  this.lastStatusByKey.set(key, nextStatus);
1601
+ this.recomputeCounts();
1546
1602
  if (nextStatus === "completed" && prevStatus !== "completed") {
1547
1603
  this.printHeaderOnce();
1548
- this.spinner.stopAndPersist({ symbol: " ", text: ` ✓ ${todo.content}` });
1604
+ this.spinner.stopAndPersist({
1605
+ symbol: " ",
1606
+ text: ` ${this.progressPrefix()}${pc2.green("✓")} ${todo.content}`
1607
+ });
1549
1608
  this.spinner.start();
1609
+ this.lastToolCallName = undefined;
1610
+ this.lastSubagentToolCallName = undefined;
1550
1611
  }
1551
1612
  if (nextStatus === "in_progress" && this.lastInProgressKey !== key) {
1552
1613
  this.lastInProgressKey = key;
1614
+ this.currentInProgressContent = todo.content;
1553
1615
  this.printHeaderOnce();
1554
- const base = this.baseSpinnerText ?? this.spinner.text;
1555
- this.spinner.text = base ? `${base} (${todo.content})` : todo.content;
1616
+ this.updateSpinnerText(todo.content);
1617
+ this.lastToolCallName = undefined;
1618
+ this.lastSubagentToolCallName = undefined;
1556
1619
  }
1557
1620
  }
1558
1621
  }
@@ -1572,23 +1635,114 @@ function extractTodosFromStreamChunk(chunk) {
1572
1635
  return nested.todos;
1573
1636
  }
1574
1637
  }
1638
+ function extractToolCallsFromStreamChunk(chunk) {
1639
+ if (!chunk || typeof chunk !== "object")
1640
+ return;
1641
+ const results = [];
1642
+ const extractFromMessage = (msg) => {
1643
+ if (!msg || typeof msg !== "object")
1644
+ return;
1645
+ const m = msg;
1646
+ if (!Array.isArray(m.tool_calls) || m.tool_calls.length === 0)
1647
+ return;
1648
+ for (const tc of m.tool_calls) {
1649
+ if (tc.name) {
1650
+ results.push({ name: tc.name, args: tc.args ?? {} });
1651
+ }
1652
+ }
1653
+ };
1654
+ const extractFromMessages = (messages) => {
1655
+ if (!Array.isArray(messages))
1656
+ return;
1657
+ const last = messages[messages.length - 1];
1658
+ extractFromMessage(last);
1659
+ };
1660
+ const direct = chunk;
1661
+ if (Array.isArray(direct.messages)) {
1662
+ extractFromMessages(direct.messages);
1663
+ if (results.length > 0)
1664
+ return results;
1665
+ }
1666
+ for (const value of Object.values(chunk)) {
1667
+ if (!value || typeof value !== "object")
1668
+ continue;
1669
+ const nested = value;
1670
+ if (Array.isArray(nested.messages)) {
1671
+ extractFromMessages(nested.messages);
1672
+ if (results.length > 0)
1673
+ return results;
1674
+ }
1675
+ }
1676
+ return results.length > 0 ? results : undefined;
1677
+ }
1678
+ function formatToolCall(tc) {
1679
+ const args = tc.args;
1680
+ const keys = Object.keys(args);
1681
+ if (keys.length === 0)
1682
+ return `${tc.name}()`;
1683
+ if (keys.length === 1) {
1684
+ const key = keys[0];
1685
+ const val = args[key];
1686
+ if (typeof val === "string" && val.length <= 80) {
1687
+ return `${tc.name}(${key}: ${JSON.stringify(val)})`;
1688
+ }
1689
+ }
1690
+ const parts = [];
1691
+ for (const key of keys.slice(0, 3)) {
1692
+ const val = args[key];
1693
+ parts.push(`${key}: ${truncateValue(val)}`);
1694
+ }
1695
+ if (keys.length > 3)
1696
+ parts.push("...");
1697
+ return `${tc.name}(${parts.join(", ")})`;
1698
+ }
1699
+ function truncateValue(val, maxLen = 40) {
1700
+ if (typeof val === "string") {
1701
+ return val.length > maxLen ? JSON.stringify(val.slice(0, maxLen - 3) + "...") : JSON.stringify(val);
1702
+ }
1703
+ if (typeof val === "number" || typeof val === "boolean")
1704
+ return String(val);
1705
+ const json = JSON.stringify(val);
1706
+ if (json && json.length > maxLen)
1707
+ return json.slice(0, maxLen - 3) + "...";
1708
+ return json ?? "undefined";
1709
+ }
1575
1710
 
1576
1711
  // src/analysis/agent.ts
1712
+ function isSubagentNamespace(ns) {
1713
+ if (typeof ns === "string")
1714
+ return ns.includes("tools:");
1715
+ if (Array.isArray(ns))
1716
+ return ns.some((s) => typeof s === "string" && s.includes("tools:"));
1717
+ return false;
1718
+ }
1577
1719
  async function invokeWithTodoStreaming(agent, userMessage, spinner) {
1578
1720
  const renderer = new TodoProgressRenderer(spinner);
1579
- const stream = await agent.stream({ messages: [{ role: "user", content: userMessage }] }, { streamMode: ["updates", "values"] });
1721
+ const controller = new AbortController;
1722
+ setMaxListeners(0, controller.signal);
1723
+ const stream = await agent.stream({ messages: [{ role: "user", content: userMessage }] }, { streamMode: ["updates", "values"], subgraphs: true, signal: controller.signal });
1580
1724
  let lastValues;
1581
1725
  for await (const item of stream) {
1582
- if (Array.isArray(item) && item.length === 2) {
1583
- const mode = item[0];
1584
- const chunk = item[1];
1726
+ if (!Array.isArray(item)) {
1727
+ renderer.handleChunk(item);
1728
+ lastValues = item;
1729
+ continue;
1730
+ }
1731
+ if (item.length === 3) {
1732
+ const [ns, mode, chunk] = item;
1733
+ const isSubagent = isSubagentNamespace(ns);
1734
+ renderer.handleChunk(chunk, { isSubagent });
1735
+ if (!isSubagent && mode === "values")
1736
+ lastValues = chunk;
1737
+ continue;
1738
+ }
1739
+ if (item.length === 2) {
1740
+ const [mode, chunk] = item;
1585
1741
  renderer.handleChunk(chunk);
1586
1742
  if (mode === "values")
1587
1743
  lastValues = chunk;
1588
1744
  continue;
1589
1745
  }
1590
- renderer.handleChunk(item);
1591
- lastValues = item;
1592
1746
  }
1593
1747
  return lastValues;
1594
1748
  }
@@ -2219,7 +2373,7 @@ function relativize(filePath, projectRoot) {
2219
2373
  // package.json
2220
2374
  var package_default = {
2221
2375
  name: "zeitzeuge",
2222
- version: "0.6.3-beta.3",
2376
+ version: "0.6.5",
2223
2377
  description: "A deepagent to witnessing slowdowns in your test runs.",
2224
2378
  keywords: [
2225
2379
  "analysis",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeitzeuge",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "description": "A deepagent to witnessing slowdowns in your test runs.",
5
5
  "keywords": [
6
6
  "analysis",