zeitzeuge 0.6.3 → 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 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({ symbol: " ", text: ` ✓ ${todo.content}` });
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
- const base = this.baseSpinnerText ?? this.spinner.text;
1250
- this.spinner.text = base ? `${base} (${todo.content})` : todo.content;
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 pc from "picocolors";
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: pc.red("\uD83D\uDD34 CRITICAL"),
1743
- warning: pc.yellow("\uD83D\uDFE1 WARNING"),
1744
- info: pc.green("\uD83D\uDFE2 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: pc.red("CRITICAL"),
1748
- warning: pc.yellow("WARNING"),
1749
- info: pc.green("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(pc.cyan(`
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(pc.dim(`
1912
+ console.log(pc2.dim(`
1790
1913
  ` + "━".repeat(58) + `
1791
1914
  `));
1792
1915
  if (findings.length === 0) {
1793
- console.log(pc.green(` ✔ No significant performance issues found. Page looks healthy!
1916
+ console.log(pc2.green(` ✔ No significant performance issues found. Page looks healthy!
1794
1917
  `));
1795
- console.log(pc.dim("━".repeat(58)));
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}]: ${pc.bold(finding.title)}`);
1924
+ console.log(`${icon} [${categoryLabel}]: ${pc2.bold(finding.title)}`);
1802
1925
  if (finding.retainedSize != null) {
1803
- console.log(pc.dim(` Retained size: ${formatBytes2(finding.retainedSize)}`));
1926
+ console.log(pc2.dim(` Retained size: ${formatBytes2(finding.retainedSize)}`));
1804
1927
  }
1805
1928
  if (finding.impactMs != null) {
1806
- console.log(pc.dim(` Impact: ${finding.impactMs.toFixed(0)}ms`));
1929
+ console.log(pc2.dim(` Impact: ${finding.impactMs.toFixed(0)}ms`));
1807
1930
  }
1808
1931
  if (finding.resourceUrl) {
1809
- console.log(pc.dim(` Resource: ${finding.resourceUrl}`));
1932
+ console.log(pc2.dim(` Resource: ${finding.resourceUrl}`));
1810
1933
  }
1811
1934
  if (finding.retainerPath && finding.retainerPath.length > 0) {
1812
- console.log(pc.dim(` Path: ${finding.retainerPath.join(" → ")}`));
1935
+ console.log(pc2.dim(` Path: ${finding.retainerPath.join(" → ")}`));
1813
1936
  }
1814
1937
  if (finding.testFile) {
1815
- console.log(pc.dim(` Test file: ${finding.testFile}`));
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(pc.dim(` Function: ${hf.name} at ${hf.scriptUrl}:${hf.lineNumber} (selfTime: ${hf.selfTime.toFixed(0)}ms, ${hf.selfPercent.toFixed(1)}%)`));
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(pc.dim(" Suggested fix:"));
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(pc.dim(` ┌${"─".repeat(boxWidth)}┐`));
1952
+ console.log(pc2.dim(` ┌${"─".repeat(boxWidth)}┐`));
1830
1953
  for (const line of lines) {
1831
- console.log(pc.dim(" │ ") + pc.white(line.padEnd(boxWidth - 2)) + pc.dim(" │"));
1954
+ console.log(pc2.dim(" │ ") + pc2.white(line.padEnd(boxWidth - 2)) + pc2.dim(" │"));
1832
1955
  }
1833
- console.log(pc.dim(` └${"─".repeat(boxWidth)}┘`));
1956
+ console.log(pc2.dim(` └${"─".repeat(boxWidth)}┘`));
1834
1957
  }
1835
1958
  console.log();
1836
1959
  }
1837
- console.log(pc.dim("━".repeat(58)));
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: ${pc.red(`${counts.critical} critical`)}, ` + `${pc.yellow(`${counts.warning} warning`)}, ` + `${pc.green(`${counts.info} info`)}
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}${pc.green("✔")} No significant performance issues found.`);
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}]: ${pc.bold(finding.title)}`);
2004
+ console.log(`${indent}${severity} [${categoryLabel}]: ${pc2.bold(finding.title)}`);
1882
2005
  if (finding.testFile)
1883
- console.log(pc.dim(`${subIndent}Test file: ${finding.testFile}`));
2006
+ console.log(pc2.dim(`${subIndent}Test file: ${finding.testFile}`));
1884
2007
  if (finding.impactMs != null)
1885
- console.log(pc.dim(`${subIndent}Impact: ${finding.impactMs.toFixed(0)}ms`));
2008
+ console.log(pc2.dim(`${subIndent}Impact: ${finding.impactMs.toFixed(0)}ms`));
1886
2009
  if (finding.resourceUrl)
1887
- console.log(pc.dim(`${subIndent}Resource: ${finding.resourceUrl}`));
2010
+ console.log(pc2.dim(`${subIndent}Resource: ${finding.resourceUrl}`));
1888
2011
  if (finding.hotFunction) {
1889
2012
  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)}%)`));
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(pc.dim(`${subIndent}Suggested fix:`));
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}${pc.dim("Summary:")} ${pc.red(`${counts.critical} critical`)}, ${pc.yellow(`${counts.warning} warning`)}, ${pc.green(`${counts.info} info`)}`);
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}${pc.bold("Suite")}`);
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}${pc.bold("CPU Breakdown")}`);
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}${pc.bold("Top Hot Functions")}`);
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" ? pc.dim(` [${fn.sourceCategory}]`) : "";
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(pc.dim(`${indent} ${fn.scriptUrl}:${fn.lineNumber}`));
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}${pc.bold("Heap")}: ${formatBytes2(metrics.heap.totalAllocatedBytes)} allocated`);
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}${pc.bold("Event Listener Tracking")}`);
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} ${pc.red("⚠")} ${pc.red(`${exc.targetType}.${exc.eventType}`)}: ` + `${exc.listenerCount} listeners (max: ${exc.threshold})`);
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(pc.dim(`${indent} ${line}`));
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} ${pc.yellow("⚠")} ${entry.api} "${entry.type}": ` + `${entry.addCount} adds, ${entry.removeCount} removes ` + pc.yellow(`(${leaked} not cleaned up)`));
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(pc.dim(`Heap: ${formatBytes2(heapSummary.metadata.totalSize)} | ` + `Nodes: ${heapSummary.metadata.nodeCount.toLocaleString()} | ` + `Requests: ${trace.networkRequests.length} | ` + `Long tasks: ${trace.metrics.longTasks.length}`));
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(pc.red(`
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":"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,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"}
@@ -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({ symbol: " ", text: ` ✓ ${todo.content}` });
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
- const base = this.baseSpinnerText ?? this.spinner.text;
1555
- this.spinner.text = base ? `${base} (${todo.content})` : todo.content;
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.3-beta.3",
2345
+ version: "0.6.4",
2223
2346
  description: "A deepagent to witnessing slowdowns in your test runs.",
2224
2347
  keywords: [
2225
2348
  "analysis",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeitzeuge",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "A deepagent to witnessing slowdowns in your test runs.",
5
5
  "keywords": [
6
6
  "analysis",