zeitzeuge 0.8.1 → 0.9.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.
Files changed (2) hide show
  1. package/dist/cli.js +660 -88
  2. package/package.json +9 -8
package/dist/cli.js CHANGED
@@ -125,6 +125,7 @@ class TodoProgressRenderer {
125
125
  totalTodos = 0;
126
126
  completedTodos = 0;
127
127
  subagentTodos = new Map;
128
+ pendingAutoTasks = new Set;
128
129
  canAnimate;
129
130
  constructor(spinner, { animate = true } = {}) {
130
131
  this.spinner = spinner;
@@ -234,11 +235,13 @@ class TodoProgressRenderer {
234
235
  }
235
236
  const toolCalls = extractToolCallsFromStreamChunk(chunk);
236
237
  if (toolCalls && toolCalls.length > 0) {
238
+ const newlyDispatched = [];
237
239
  for (const tc of toolCalls) {
238
240
  if (!isSubagent && tc.name === "task") {
239
241
  const subagentType = tc.args.subagent_type;
240
242
  if (typeof subagentType === "string" && !this.dispatchedSubagents.includes(subagentType)) {
241
243
  this.dispatchedSubagents.push(subagentType);
244
+ newlyDispatched.push(subagentType);
242
245
  }
243
246
  }
244
247
  if (tc.name === "write_todos") {
@@ -247,6 +250,11 @@ class TodoProgressRenderer {
247
250
  if (Array.isArray(todos2)) {
248
251
  const displayName = this.resolveSubagentName(nsKey, meta?.namespace);
249
252
  this.handleSubagentTodos(todos2, nsKey, displayName);
253
+ const autoNsKey = `auto:${displayName}`;
254
+ if (this.subagentTodos.has(autoNsKey)) {
255
+ this.subagentTodos.delete(autoNsKey);
256
+ this.pendingAutoTasks.delete(displayName);
257
+ }
250
258
  }
251
259
  }
252
260
  continue;
@@ -276,6 +284,21 @@ class TodoProgressRenderer {
276
284
  this.persistLine(" ", pc.dim(label));
277
285
  }
278
286
  }
287
+ for (const name of newlyDispatched) {
288
+ const autoNsKey = `auto:${name}`;
289
+ this.handleSubagentTodos([{ content: "analyzing", status: "in_progress" }], autoNsKey, name);
290
+ this.pendingAutoTasks.add(name);
291
+ }
292
+ if (!isSubagent && this.pendingAutoTasks.size > 0) {
293
+ const hasNonTaskCalls = toolCalls.some((tc) => tc.name !== "task" && tc.name !== "write_todos");
294
+ if (hasNonTaskCalls) {
295
+ for (const name of this.pendingAutoTasks) {
296
+ const autoNsKey = `auto:${name}`;
297
+ this.handleSubagentTodos([{ content: "analyzing", status: "completed" }], autoNsKey, name);
298
+ }
299
+ this.pendingAutoTasks.clear();
300
+ }
301
+ }
279
302
  }
280
303
  if (isSubagent)
281
304
  return;
@@ -497,6 +520,49 @@ async function invokeWithTodoStreaming(agent, userMessage, spinner, { animatePro
497
520
  }
498
521
  return lastValues;
499
522
  }
523
+ // ../utils/src/analysis/merge-findings.ts
524
+ var FINDINGS_DIR = "/findings";
525
+ var MERGED_FILENAME = "merged.json";
526
+ function toAbsoluteFindingsPath(entryPath) {
527
+ const lastSlash = entryPath.lastIndexOf("/");
528
+ const filename = lastSlash >= 0 ? entryPath.slice(lastSlash + 1) : entryPath;
529
+ return `${FINDINGS_DIR}/${filename}`;
530
+ }
531
+ async function mergeFindings(backend) {
532
+ let entries;
533
+ try {
534
+ entries = await backend.lsInfo(FINDINGS_DIR);
535
+ } catch {
536
+ return [];
537
+ }
538
+ const jsonFiles = entries.filter((e) => e.path.endsWith(".json") && !e.path.endsWith(MERGED_FILENAME));
539
+ if (jsonFiles.length === 0) {
540
+ return [];
541
+ }
542
+ const allFindings = [];
543
+ for (const entry of jsonFiles) {
544
+ const filePath = toAbsoluteFindingsPath(entry.path);
545
+ try {
546
+ const fileData = await backend.readRaw(filePath);
547
+ const raw = fileData.content.join(`
548
+ `);
549
+ const parsed = JSON.parse(raw);
550
+ const validated = FindingsSchema.safeParse(parsed);
551
+ if (validated.success) {
552
+ allFindings.push(...validated.data.findings);
553
+ } else {
554
+ console.warn(`[merge-findings] Skipping ${filePath}: schema validation failed`);
555
+ }
556
+ } catch (err) {
557
+ console.warn(`[merge-findings] Skipping ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
558
+ }
559
+ }
560
+ try {
561
+ const mergedContent = JSON.stringify({ findings: allFindings }, null, 2);
562
+ await backend.write(`${FINDINGS_DIR}/${MERGED_FILENAME}`, mergedContent);
563
+ } catch {}
564
+ return allFindings;
565
+ }
500
566
  // ../utils/src/analysis/deduplication.ts
501
567
  function extractFunctionName(finding) {
502
568
  if (finding.hotFunction?.name) {
@@ -655,21 +721,47 @@ Each finding MUST use one of these EXACT category values — do NOT invent new c
655
721
 
656
722
  Prefer more specific categories (algorithm, serialization, allocation, event-handling,
657
723
  blocking-io, listener-leak, gc-pressure) over generic ones (hot-function, other).`;
658
- var FULL_RESPONSE_REQUIREMENT = `## CRITICAL Your response MUST contain ALL findings in full
724
+ var PARALLEL_TOOL_CALLS = `## CRITICAL: Tool call strategy scripts for data, read_file for source
725
+
726
+ Your FIRST turn MUST:
727
+ 1. Run analysis scripts (execute_command) to query the JSON data files.
728
+ Use pre-built helper scripts in skills/ or write your own using the
729
+ data-scripting skill.
730
+ 2. Call read_file for ALL application source files listed above.
731
+
732
+ Batch everything into ONE turn. Do NOT read data files one-at-a-time.
733
+
734
+ For data files: run a helper script or write a custom one. This is faster
735
+ and uses fewer tokens than reading raw JSON.
736
+
737
+ For source files: use read_file since you need to see the exact code for
738
+ beforeCode/afterCode suggestions.
659
739
 
660
- Your final response is the ONLY thing the orchestrator sees. If you write a short summary
661
- like "All N findings have been reported", the orchestrator CANNOT see your findings and
662
- they will be LOST.
740
+ FORBIDDEN actions:
741
+ - ls NEVER call ls. File paths are already listed above.
742
+ - glob NEVER call glob. File paths are already listed above.
743
+ - Reading JSON data files with read_file — use scripts instead.`;
744
+ var WRITE_FINDINGS_REQUIREMENT = `## CRITICAL — Persist your findings to a file
745
+
746
+ When your analysis is complete, you MUST write ALL findings to a JSON file using write_file.
747
+
748
+ 1. Call write_file with path: \`/findings/<YOUR_AGENT_NAME>.json\`
749
+ Use your agent name as the filename (e.g. memory-heap, page-load, runtime-blocking,
750
+ code-pattern, cpu-hotspot, listener-leak, memory-closure).
663
751
 
664
- You MUST include the COMPLETE analysis in your response text. For EVERY finding, write out:
665
- - Title, category, severity, sourceFile, lineNumber
666
- - Full description of the issue
667
- - Complete beforeCode (verbatim from the source file)
668
- - Complete afterCode (working drop-in replacement)
752
+ 2. The file content MUST be a JSON object with this exact structure:
753
+ { "findings": [ { "severity": "...", "title": "...", ... }, ... ] }
754
+ Each finding must include ALL required fields: severity, title, description,
755
+ category, sourceFile, lineNumber, suggestedFix, beforeCode, afterCode,
756
+ confidence, impactMs, estimatedSavingsMs.
669
757
 
670
- Do NOT abbreviate. Do NOT say "findings have been reported" without listing them.
671
- The orchestrator will extract findings from your response text — if a finding is not
672
- in your text, it does not exist.`;
758
+ 3. Write ALL findings in a SINGLE write_file call. Do NOT write findings one at a time.
759
+
760
+ 4. After writing the file, respond with ONLY a brief summary like:
761
+ "Found 4 issues: 2 critical, 1 warning, 1 info. Written to /findings/memory-heap.json"
762
+
763
+ The orchestrator reads findings from the file directly — your text response is only for
764
+ progress display. If a finding is not in the JSON file, it does not exist.`;
673
765
  var STRUCTURED_OUTPUT_FIELDS = `## Structured output fields — REQUIRED for every finding
674
766
 
675
767
  Every finding MUST include ALL of these fields:
@@ -1906,46 +1998,504 @@ var BROWSER_ANALYSIS_SKILL_FILES = {
1906
1998
  "skills/browser-analysis/helpers/analyze-heap.js": ANALYZE_HEAP_JS,
1907
1999
  "skills/browser-analysis/helpers/find-patterns.js": FIND_PATTERNS_JS
1908
2000
  };
1909
- // ../utils/src/skills/result-writer.ts
1910
- var MERGE_RESULTS_JS = `'use strict';
2001
+ // ../utils/src/profiling/profile-parser.ts
2002
+ var META_FUNCTIONS = new Set(["(root)", "(idle)", "(program)"]);
2003
+ // ../utils/src/profiling/agent.ts
2004
+ import { createDeepAgent } from "deepagents";
1911
2005
 
1912
- var fs = require('fs');
2006
+ // ../utils/src/profiling/prompts/test-shared.ts
2007
+ var SEVERITY_RULES = `## Severity classification
1913
2008
 
1914
- fs.mkdirSync('results', { recursive: true });
2009
+ Assign severity based on the nature and measured impact of the issue:
1915
2010
 
1916
- var resultFiles;
1917
- try {
1918
- resultFiles = fs.readdirSync('results').filter(function (f) {
1919
- return f.endsWith('.json') && f !== 'merged.json';
2011
+ - **critical** — Any of:
2012
+ - Synchronous blocking of the event loop (CPU-bound loops, sync crypto, sync I/O)
2013
+ - Functions that CALL blocking functions (compound blockers)
2014
+ - Listener exceedances (count exceeding maxListeners threshold)
2015
+ - GC overhead >10% of total profile duration
2016
+ - A single function consuming >15% of APPLICATION code self-time
2017
+ - **warning** — Any of:
2018
+ - Listener add/remove imbalance (addCount > 2× removeCount) without exceedance
2019
+ - O(n²) or worse algorithms on collections
2020
+ - Unnecessary serialization (JSON.parse/JSON.stringify) on hot paths
2021
+ - Closure-based memory leaks or unbounded data structures
2022
+ - A function consuming 5–15% of application self-time
2023
+ - **info** — Minor inefficiencies, small optimisation opportunities, per-call
2024
+ object allocation (TextEncoder, RegExp, DateTimeFormat), or patterns that only
2025
+ matter at scale
2026
+
2027
+ IMPORTANT: Blocking/event-loop-blocking operations are ALWAYS critical, regardless
2028
+ of measured self-time percentage. Even a short blocking call prevents the event loop
2029
+ from processing other work and is a correctness issue, not just a performance issue.`;
2030
+
2031
+ // ../utils/src/profiling/prompts/cpu-hotspot.ts
2032
+ var CPU_HOTSPOT_PROMPT = `You are a specialist in detecting CPU-blocking operations and excessive object instantiation in JavaScript/TypeScript code.
2033
+
2034
+ You have access to a workspace with V8 CPU profiling data from a Vitest test run.
2035
+
2036
+ ## Your focus areas
2037
+
2038
+ ### 1. Blocking / Event-Loop-Blocking Operations (HIGHEST PRIORITY)
2039
+
2040
+ Look for functions that block the event loop with synchronous CPU-intensive work:
2041
+ - Synchronous crypto operations (hashing, encryption) that should use async APIs
2042
+ - CPU-bound loops (e.g., manual hashing with many iterations, busy-waits)
2043
+ - Functions that CALL other blocking functions (compound blocking). Report the
2044
+ CALLER as a separate finding.
2045
+ - Synchronous file I/O in hot paths (readFileSync, writeFileSync, etc.)
2046
+ - Heavy computation without yielding (e.g., large matrix operations, parsing)
2047
+
2048
+ **How to detect:** Read hot-functions/application.json for functions with high selfTime.
2049
+ For each one with >= 1% selfPercent, read the source code and check for:
2050
+ - Loops with many iterations doing CPU work
2051
+ - Calls to other blocking functions (trace the call chain — read the callee's source!)
2052
+ - Missing async/await for operations that have async alternatives (e.g., crypto.pbkdf2 vs crypto.pbkdf2Sync)
2053
+
2054
+ **IMPORTANT — Compound blockers are SEPARATE findings:**
2055
+ If function A calls function B and B is blocking, you MUST report TWO findings:
2056
+ 1. Function B: the primary blocking operation
2057
+ 2. Function A: a "compound blocker" that calls B, inheriting and compounding B's cost
2058
+ Do NOT just report B and skip A. The developer needs to know both call sites.
2059
+
2060
+ ### 2. Excessive Object Instantiation (SECONDARY)
2061
+
2062
+ Look for functions creating stateless objects on every call that should be
2063
+ module-level singletons or hoisted out of loops:
2064
+ - \`new TextEncoder()\` / \`new TextDecoder()\` — stateless, should be module-level
2065
+ - \`new Intl.DateTimeFormat()\` / \`new Intl.NumberFormat()\` — locale-dependent but cacheable
2066
+ - \`new Map()\` / \`new Set()\` — if used as temporary lookup then discarded each call
2067
+ - \`new RegExp()\` — if the pattern is constant, compile once at module level
2068
+ - \`new Date()\` inside sort comparators — called O(n log n) times
2069
+ - Any constructor call inside a hot loop that produces a stateless, reusable object
2070
+
2071
+ **How to detect:** Read source files for hot functions and look for object
2072
+ construction inside function bodies that could be hoisted to module scope.
2073
+
2074
+ **IMPORTANT:** If a function has BOTH a blocking issue AND an instantiation issue,
2075
+ report them as TWO separate findings with different categories (blocking-io vs allocation).
2076
+ Do NOT skip the instantiation finding just because you already reported a blocking finding
2077
+ for the same function.
2078
+
2079
+ ## Your scope — categories YOU own
2080
+
2081
+ You are one of four parallel subagents. Use ONLY these categories:
2082
+ - **blocking-io** — for event-loop-blocking operations (sync crypto, CPU loops, sync I/O)
2083
+ - **allocation** — for per-call object instantiation (new TextEncoder, new Intl.DateTimeFormat, new Map per call)
2084
+
2085
+ Do NOT report findings with categories: algorithm, serialization, gc-pressure,
2086
+ listener-leak, event-handling, unnecessary-computation. Other subagents handle those.
2087
+ Do NOT report findings about test files (tests/*.ts) — only about src/ files.
2088
+
2089
+ ## Your workflow
2090
+
2091
+ 1. In your FIRST turn, do ALL of these in ONE batch:
2092
+ a. Run the workspace overview script:
2093
+ execute_command: node skills/profile-analysis/helpers/analyze-workspace.js
2094
+ b. Run the detailed hot functions script:
2095
+ execute_command: node skills/profile-analysis/helpers/analyze-hotfunctions.js
2096
+ c. Call read_file for EVERY src/ file listed in "FILES IN THIS WORKSPACE" above.
2097
+ Do NOT use ls or glob. Batch everything into ONE turn.
2098
+ 2. From the script outputs, identify hot functions (>= 1% selfPercent) and match
2099
+ them to the source code you read.
2100
+ 3. For EACH hot function, analyze its source for blocking patterns or unnecessary instantiation.
2101
+ 4. Check EVERY source file top-to-bottom, not just the hot ones.
2102
+ 5. For compound blockers, trace the call chain using the callerChain data from the script output.
2103
+
2104
+ ${PARALLEL_TOOL_CALLS}
2105
+ ${VERIFICATION_RULES}
2106
+ ${SEVERITY_RULES}
2107
+ ${FINDING_CATEGORIES}
2108
+ ${OUTPUT_FORMAT}
2109
+ ${STRUCTURED_OUTPUT_FIELDS}
2110
+ ${WRITE_FINDINGS_REQUIREMENT}`;
2111
+ // ../utils/src/profiling/prompts/listener-leak.ts
2112
+ var LISTENER_LEAK_PROMPT = `You are a specialist in detecting event listener leaks and event handling imbalances in JavaScript/TypeScript code.
2113
+
2114
+ You have access to a workspace with V8 CPU profiling data and event listener tracking from a Vitest test run.
2115
+
2116
+ ## Your SOLE focus: Event Listener Leaks
2117
+
2118
+ You look for ONE thing: code that registers event listeners without proper cleanup,
2119
+ causing listener accumulation, memory growth, and MaxListenersExceededWarning.
2120
+
2121
+ ### Pattern A — Listener accumulation per call
2122
+
2123
+ A function that adds a new listener EVERY TIME it is called, but never removes
2124
+ old ones. After N calls, there are N active listeners.
2125
+
2126
+ \`\`\`typescript
2127
+ // BAD: adds a new listener on every call
2128
+ function getData() {
2129
+ emitter.on('update', handler); // accumulates!
2130
+ }
2131
+ \`\`\`
2132
+
2133
+ **Correct fix pattern — use a guard flag + named handler:**
2134
+
2135
+ \`\`\`typescript
2136
+ // GOOD: register once, named handler, proper cleanup
2137
+ let registered = false;
2138
+ const onUpdate = () => { cache = null; };
2139
+
2140
+ function getData() {
2141
+ if (!registered) {
2142
+ emitter.on('update', onUpdate);
2143
+ registered = true;
2144
+ }
2145
+ // ... rest of function
2146
+ }
2147
+
2148
+ function reset() {
2149
+ if (registered) {
2150
+ emitter.off('update', onUpdate); // surgical removal
2151
+ registered = false;
2152
+ }
2153
+ }
2154
+ \`\`\`
2155
+
2156
+ CRITICAL for afterCode: always use a NAMED handler (const onUpdate = ...) so
2157
+ it can be removed with .off(). NEVER use anonymous functions with .on().
2158
+ If the file has an existing cleanup/reset function, update it to call
2159
+ .off(event, handler) and reset the guard flag.
2160
+
2161
+ ### Pattern B — Missing unsubscribe mechanism
2162
+
2163
+ A subscribe-style function that adds listeners but returns no way to remove them.
2164
+
2165
+ \`\`\`typescript
2166
+ // BAD: no way to unsubscribe
2167
+ function subscribe(channel) {
2168
+ emitter.on(channel, handler); // no return value, no cleanup
2169
+ }
2170
+ \`\`\`
2171
+
2172
+ **Correct fix pattern — return an unsubscribe function:**
2173
+
2174
+ \`\`\`typescript
2175
+ // GOOD: returns cleanup function
2176
+ function subscribe(channel, handler) {
2177
+ emitter.on(channel, handler);
2178
+ return () => { emitter.off(channel, handler); };
2179
+ }
2180
+ \`\`\`
2181
+
2182
+ ### Pattern C — MaxListeners exceeded (MUST report as a SEPARATE finding)
2183
+
2184
+ When listener counts exceed the default maxListeners threshold (10), this
2185
+ triggers a MaxListenersExceededWarning at runtime. Check listener-tracking.json
2186
+ for the "exceedances" array — each entry shows an event type where the listener
2187
+ count exceeded the threshold.
2188
+
2189
+ **This is a SEPARATE finding from Pattern A/B**, even if the same function causes
2190
+ both the accumulation AND the exceedance. You MUST report:
2191
+ 1. Pattern A or B finding: the code that adds listeners without cleanup
2192
+ 2. Pattern C finding: the maxListeners threshold being exceeded, with the
2193
+ specific count, threshold, and event name from the exceedance data
2194
+
2195
+ The Pattern C finding MUST have:
2196
+ - category: **"event-handling"** (do NOT use "listener-leak" — use a DIFFERENT
2197
+ category from Pattern A/B so both findings survive deduplication)
2198
+ - severity: "critical" (exceedances are always critical)
2199
+ - title: focus on the event name and threshold, e.g. "maxListeners threshold
2200
+ exceeded for task:changed event (11 listeners, threshold 10)"
2201
+ - description: MUST mention ALL of these terms: "maxListeners", "threshold",
2202
+ "exceeded", the event name (e.g. "task:changed"), and the numeric count
2203
+ (e.g. "11") and threshold (e.g. "10") from the tracking data
2204
+
2205
+ ## Your scope — categories YOU own
2206
+
2207
+ You are one of four parallel subagents. Use ONLY these categories:
2208
+ - **listener-leak** — for Pattern A (accumulation) and Pattern B (missing unsubscribe)
2209
+ - **event-handling** — for Pattern C (maxListeners exceeded)
2210
+
2211
+ Do NOT report findings with categories: blocking-io, allocation, algorithm,
2212
+ serialization, gc-pressure, unnecessary-computation. Other subagents handle those.
2213
+ Do NOT report findings about test files (tests/*.ts) — only about src/ files.
2214
+
2215
+ ## Your workflow (follow this EXACTLY)
2216
+
2217
+ 1. In your FIRST turn, do ALL of these in ONE batch:
2218
+ a. Run the workspace overview script:
2219
+ execute_command: node skills/profile-analysis/helpers/analyze-workspace.js
2220
+ b. Run the detailed listener analysis script:
2221
+ execute_command: node skills/profile-analysis/helpers/analyze-listeners.js
2222
+ c. Call read_file for EVERY src/ file listed in "FILES IN THIS WORKSPACE" above.
2223
+ Do NOT use ls or glob. Batch everything into ONE turn.
2224
+ 2. From the script outputs, identify:
2225
+ - exceedances (listenerCount > maxListeners threshold)
2226
+ - add/remove imbalances (addCount with zero removeCount = leak candidates)
2227
+ 3. In the source files you already read, find the .on() / .addEventListener() calls
2228
+ and check if corresponding removal exists.
2229
+ 4. For each issue found, provide before/after code.
2230
+
2231
+ ## Important: Report EACH pattern as a SEPARATE finding
2232
+
2233
+ - If a function adds a listener without removal → one finding about accumulation
2234
+ - If a subscribe function has no unsubscribe mechanism → a separate finding
2235
+ - If maxListeners is exceeded → a SEPARATE finding (cross-reference with the causal
2236
+ pattern above). This must be its own finding even if you already reported the
2237
+ listener accumulation that caused it. The developer needs to know BOTH that
2238
+ listeners accumulate AND that the threshold is exceeded.
2239
+
2240
+ ### Minimum expected findings
2241
+
2242
+ For a typical codebase with listener leaks, expect at least:
2243
+ 1. One finding per function that adds listeners without cleanup (Pattern A)
2244
+ 2. One finding per subscribe function without unsubscribe (Pattern B)
2245
+ 3. One finding per maxListeners exceedance from tracking data (Pattern C)
2246
+
2247
+ ${PARALLEL_TOOL_CALLS}
2248
+ ${VERIFICATION_RULES}
2249
+ ${SEVERITY_RULES}
2250
+ ${FINDING_CATEGORIES}
2251
+ ${OUTPUT_FORMAT}
2252
+ ${STRUCTURED_OUTPUT_FIELDS}
2253
+ ${WRITE_FINDINGS_REQUIREMENT}`;
2254
+ // ../utils/src/profiling/prompts/memory-closure.ts
2255
+ var MEMORY_CLOSURE_PROMPT = `You are a specialist in detecting memory leaks caused by closures, unbounded data structures, and missing cleanup/eviction in JavaScript/TypeScript code.
2256
+
2257
+ You have access to a workspace with V8 CPU profiling data from a Vitest test run.
2258
+
2259
+ ## Your SOLE focus: Closure & Memory Leak Patterns
2260
+
2261
+ You look for code where objects, closures, or data structures retain references
2262
+ longer than necessary, preventing garbage collection and causing continuous
2263
+ memory growth.
2264
+
2265
+ ### Pattern A — Closures capturing outer-scope data
2266
+
2267
+ Closures stored in long-lived data structures that capture variables from
2268
+ the enclosing scope — even after the captured data is conceptually stale.
2269
+
2270
+ \`\`\`typescript
2271
+ // BAD: closure captures 'value' and 'ctx' from enclosing scope
2272
+ set(key, value, ctx) {
2273
+ this.cache.set(key, {
2274
+ data: value,
2275
+ refresher: () => {
2276
+ // This closure captures 'value' and 'ctx' — they can
2277
+ // never be garbage collected while the cache entry exists
2278
+ return fetchFresh(key, ctx);
2279
+ }
1920
2280
  });
1921
- } catch (e) {
1922
- console.log('[]');
1923
- process.exit(0);
1924
2281
  }
2282
+ \`\`\`
1925
2283
 
1926
- if (resultFiles.length === 0) {
1927
- console.log('[]');
1928
- process.exit(0);
2284
+ ### Pattern B — Unbounded data structures (no eviction)
2285
+
2286
+ Arrays, Maps, or Sets that only grow — elements are added but never removed,
2287
+ cleared, or evicted. Over time, memory grows monotonically.
2288
+
2289
+ \`\`\`typescript
2290
+ // BAD: log grows without bound
2291
+ process(item) {
2292
+ this.log.push({
2293
+ item,
2294
+ timestamp: Date.now(),
2295
+ context: this.currentContext // retains reference forever
2296
+ });
1929
2297
  }
2298
+ \`\`\`
1930
2299
 
1931
- var allFindings = [];
1932
- for (var i = 0; i < resultFiles.length; i++) {
1933
- try {
1934
- var data = JSON.parse(fs.readFileSync('results/' + resultFiles[i], 'utf8'));
1935
- if (Array.isArray(data)) {
1936
- allFindings = allFindings.concat(data);
2300
+ ### Pattern C — Closures capturing request/response or transient objects
2301
+
2302
+ Code that stores closures capturing objects meant to be short-lived (e.g.
2303
+ request bodies, response objects, connection handles), preventing them from
2304
+ being freed after their lifecycle ends.
2305
+
2306
+ \`\`\`typescript
2307
+ // BAD: closure captures the full transient object forever
2308
+ record(obj) {
2309
+ this.entries.push({
2310
+ id: obj.id,
2311
+ timestamp: Date.now(),
2312
+ getDetails: () => ({
2313
+ payload: obj.payload, // captures obj.payload forever
2314
+ metadata: obj.metadata // captures obj.metadata forever
2315
+ })
2316
+ });
2317
+ }
2318
+ \`\`\`
2319
+
2320
+ ## Your scope — categories YOU own
2321
+
2322
+ You are one of four parallel subagents. Use ONLY this category:
2323
+ - **gc-pressure** — for closures capturing outer-scope data, unbounded data
2324
+ structures (Maps, arrays) without eviction, and closures retaining transient objects
2325
+
2326
+ Do NOT report findings with categories: blocking-io, allocation, algorithm,
2327
+ serialization, listener-leak, event-handling, unnecessary-computation. Other
2328
+ subagents handle those. Specifically:
2329
+ - Do NOT report event listener leaks (the listener-leak agent handles those)
2330
+ - Do NOT report blocking I/O or CPU loops (the cpu-hotspot agent handles those)
2331
+ - Do NOT report algorithmic inefficiencies (the code-pattern agent handles those)
2332
+ Do NOT report findings about test files (tests/*.ts) — only about src/ files.
2333
+
2334
+ ## Your workflow (follow this EXACTLY)
2335
+
2336
+ 1. In your FIRST turn, do ALL of these in ONE batch:
2337
+ a. Run the workspace overview script:
2338
+ execute_command: node skills/profile-analysis/helpers/analyze-workspace.js
2339
+ b. Run the leak finder script:
2340
+ execute_command: node skills/profile-analysis/helpers/find-leaks.js
2341
+ c. Call read_file for EVERY src/ file listed in "FILES IN THIS WORKSPACE" above.
2342
+ Do NOT use ls or glob. Batch everything into ONE turn.
2343
+ 2. From the script outputs, identify potential leak patterns and allocation hotspots.
2344
+ 3. For each source file you read, look for:
2345
+ - Module-level or class-level Maps, Sets, Arrays used as stores
2346
+ - Whether a corresponding removal mechanism exists
2347
+ - Closures stored as values that capture outer-scope variables
2348
+ 4. For each issue found, provide before/after code with proper cleanup.
2349
+
2350
+ ### CRITICAL: Report EVERY distinct issue, even in the same class
2351
+
2352
+ A single class or module can have multiple closure/memory issues. Report
2353
+ each as a SEPARATE finding. For example, a CacheService class might have:
2354
+ 1. A \`set()\` method with a closure that captures outer-scope data
2355
+ 2. An unbounded access log in \`get()\` that grows without eviction
2356
+ 3. A Map that stores entries without any TTL or maxSize
2357
+ These are THREE separate findings, not one.
2358
+
2359
+ ${PARALLEL_TOOL_CALLS}
2360
+ ${VERIFICATION_RULES}
2361
+ ${SEVERITY_RULES}
2362
+ ${FINDING_CATEGORIES}
2363
+ ${OUTPUT_FORMAT}
2364
+ ${STRUCTURED_OUTPUT_FIELDS}
2365
+ ${WRITE_FINDINGS_REQUIREMENT}`;
2366
+ // ../utils/src/profiling/prompts/code-pattern.ts
2367
+ var CODE_PATTERN_PROMPT = `You are a specialist in detecting algorithmic inefficiencies, unnecessary computation, and serialization overhead in JavaScript/TypeScript code.
2368
+
2369
+ You have access to a workspace with V8 CPU profiling data from a Vitest test run.
2370
+
2371
+ ## Your focus areas
2372
+
2373
+ ### 1. Quadratic or Worse Algorithms (HIGHEST PRIORITY)
2374
+
2375
+ Look for O(n²) or worse complexity patterns:
2376
+
2377
+ **Pattern A — Nested iteration over same collection:**
2378
+ \`\`\`typescript
2379
+ // BAD: O(n²) — filter inside a loop
2380
+ for (const item of items) {
2381
+ const dupes = items.filter(other => other.id === item.id);
2382
+ }
2383
+ \`\`\`
2384
+
2385
+ **Pattern B — Pairwise comparison:**
2386
+ \`\`\`typescript
2387
+ // BAD: O(n²) or worse — nested loops over the same or related collections
2388
+ for (const a of items) {
2389
+ for (const b of items) {
2390
+ // comparison or accumulation logic
2391
+ }
2392
+ }
2393
+ \`\`\`
2394
+
2395
+ **Pattern C — O(n²) duplicate detection:**
2396
+ \`\`\`typescript
2397
+ // BAD: filter().length for each element = O(n²)
2398
+ items.forEach(item => {
2399
+ if (items.filter(x => x === item).length > 1) { /* duplicate */ }
2400
+ });
2401
+ // FIX: Use a Set or Map for O(n)
2402
+ \`\`\`
2403
+
2404
+ ### 2. Unnecessary Serialization (SECONDARY)
2405
+
2406
+ \`\`\`typescript
2407
+ // BAD: deep clone via JSON roundtrip on every call
2408
+ return JSON.parse(JSON.stringify(data));
2409
+ // FIX: structuredClone(data) or spread operator for shallow copies
2410
+ \`\`\`
2411
+
2412
+ ### 3. Regex Recompilation
2413
+
2414
+ \`\`\`typescript
2415
+ // BAD: compiles regex on every call
2416
+ function validate(input) {
2417
+ const pattern = new RegExp('^[a-z]+$'); // recompiled every call!
2418
+ return pattern.test(input);
2419
+ }
2420
+ // FIX: const PATTERN = /^[a-z]+$/; at module level
2421
+ \`\`\`
2422
+
2423
+ ### 4. Expensive Sort Comparators
2424
+
2425
+ \`\`\`typescript
2426
+ // BAD: creates objects inside sort comparator (called O(n log n) times)
2427
+ items.sort((a, b) => {
2428
+ const dateA = new Date(a.createdAt); // new object per comparison!
2429
+ return dateA.getTime() - new Date(b.createdAt).getTime();
2430
+ });
2431
+ // FIX: pre-compute timestamps before sorting
2432
+ \`\`\`
2433
+
2434
+ Also check for **functions called FROM sort comparators**. If \`items.sort((a, b) => computeWeight(a) - computeWeight(b))\` calls a function that does expensive work (Date parsing, string operations, object creation), that function runs O(n log n) times per sort — report it as a separate finding.
2435
+
2436
+ ### 5. Pairwise Correlation / Tag Comparison (O(n² × m²))
2437
+
2438
+ Look for functions that compare every pair of items AND every pair of their sub-elements:
2439
+ \`\`\`typescript
2440
+ // BAD: O(n²×m²) — for each pair of tasks, compare all pairs of their tags
2441
+ for (const taskA of tasks) {
2442
+ for (const taskB of tasks) {
2443
+ for (const tagA of taskA.tags) {
2444
+ for (const tagB of taskB.tags) { /* ... */ }
1937
2445
  }
1938
- } catch (e) {
1939
- // skip malformed files
1940
2446
  }
1941
2447
  }
2448
+ \`\`\`
2449
+ Functions named like \`computeCorrelations\`, \`computeTagCorrelations\`, \`findPairs\`, etc. are prime suspects. Also look for \`.sort()\` and \`.join()\` inside inner loops.
2450
+
2451
+ ## How to detect
2452
+
2453
+ 1. Read hot-functions/application.json to identify which functions are CPU-hot
2454
+ 2. Read EVERY application source file — not just the hot ones
2455
+ 3. Go through EVERY FUNCTION in every file and check for the patterns above
2456
+ 4. Pay special attention to:
2457
+ - Functions that operate on arrays or collections
2458
+ - Any function containing nested loops or chained .filter/.map/.reduce calls
2459
+ - Functions that call JSON.parse, JSON.stringify, or new RegExp inside a loop or on every invocation
2460
+ - Sort comparators that create objects (new Date(), etc.) — the comparator runs O(n log n) times
2461
+ - Functions called from sort comparators (they inherit O(n log n) invocations)
2462
+ - Functions that do pairwise comparison of collection elements (O(n²) or O(n²×m²))
2463
+ - Duplicate detection using .filter() instead of Set (O(n²) vs O(n))
2464
+
2465
+ ## Your scope — categories YOU own
2466
+
2467
+ You are one of four parallel subagents. Use ONLY these categories:
2468
+ - **algorithm** — for O(n²) loops, brute-force, pairwise comparison, expensive sort comparators
2469
+ - **serialization** — for unnecessary JSON.parse/JSON.stringify roundtrips
2470
+ - **unnecessary-computation** — for regex recompilation with constant patterns
2471
+
2472
+ Do NOT report findings with categories: blocking-io, allocation, gc-pressure,
2473
+ listener-leak, event-handling. Other subagents handle those. Specifically:
2474
+ - Do NOT report per-call object instantiation (new TextEncoder, etc.) — the cpu-hotspot agent handles those
2475
+ - Do NOT report event listener leaks — the listener-leak agent handles those
2476
+ - Do NOT report closure/memory leaks — the memory-closure agent handles those
2477
+ Do NOT report findings about test files (tests/*.ts) — only about src/ files.
1942
2478
 
1943
- fs.writeFileSync('results/merged.json', JSON.stringify(allFindings, null, 2));
1944
- console.log(JSON.stringify(allFindings));
1945
- `;
1946
- var RESULT_WRITER_SKILL_FILES = {
1947
- "skills/result-writer/merge-results.js": MERGE_RESULTS_JS
1948
- };
2479
+ ## Your workflow
2480
+
2481
+ 1. In your FIRST turn, do ALL of these in ONE batch:
2482
+ a. Run the workspace overview script:
2483
+ execute_command: node skills/profile-analysis/helpers/analyze-workspace.js
2484
+ b. Call read_file for ALL of these in ONE batch:
2485
+ - scripts/application.json
2486
+ - EVERY src/ file listed in "FILES IN THIS WORKSPACE" above
2487
+ Do NOT use ls or glob.
2488
+ 2. From the script output, identify which functions are CPU-hot.
2489
+ 3. For EVERY function in EVERY source file, check for the patterns above.
2490
+ 4. Report each distinct pattern as a separate finding.
2491
+
2492
+ ${PARALLEL_TOOL_CALLS}
2493
+ ${VERIFICATION_RULES}
2494
+ ${SEVERITY_RULES}
2495
+ ${FINDING_CATEGORIES}
2496
+ ${OUTPUT_FORMAT}
2497
+ ${STRUCTURED_OUTPUT_FIELDS}
2498
+ ${WRITE_FINDINGS_REQUIREMENT}`;
1949
2499
  // ../utils/src/output/terminal.ts
1950
2500
  import pc2 from "picocolors";
1951
2501
  import ora from "ora";
@@ -2270,8 +2820,7 @@ function generateMarkdown(options) {
2270
2820
  `);
2271
2821
  }
2272
2822
  // src/analysis/agent.ts
2273
- import { createDeepAgent } from "deepagents";
2274
- import { toolStrategy } from "langchain";
2823
+ import { createDeepAgent as createDeepAgent2 } from "deepagents";
2275
2824
 
2276
2825
  // src/analysis/prompts/shared.ts
2277
2826
  var BROWSER_TOOL_CALL_STRATEGY = `## CRITICAL: Tool call strategy — scripts first, source selectively
@@ -2314,7 +2863,7 @@ When source code is minified/compiled:
2314
2863
  Only provide \`afterCode\` when the source is clearly human-authored (readable
2315
2864
  variable names, formatting, comments) — e.g. inline scripts in HTML or
2316
2865
  un-minified CSS.`;
2317
- var SEVERITY_RULES = `## Severity classification
2866
+ var SEVERITY_RULES2 = `## Severity classification
2318
2867
 
2319
2868
  Assign severity based on measured impact — do NOT guess:
2320
2869
 
@@ -2469,14 +3018,14 @@ These are THREE separate findings, not one.
2469
3018
 
2470
3019
  ${BROWSER_TOOL_CALL_STRATEGY}
2471
3020
  ${VERIFICATION_RULES}
2472
- ${SEVERITY_RULES}
3021
+ ${SEVERITY_RULES2}
2473
3022
  ${FINDING_CATEGORIES}
2474
3023
  ${OUTPUT_FORMAT}
2475
3024
  ${STRUCTURED_OUTPUT_FIELDS}
2476
3025
  ${MINIFIED_SOURCE_HANDLING}
2477
3026
  ${CROSS_REFERENCING}
2478
3027
  ${IMPACT_ESTIMATION}
2479
- ${FULL_RESPONSE_REQUIREMENT}`;
3028
+ ${WRITE_FINDINGS_REQUIREMENT}`;
2480
3029
  // src/analysis/prompts/page-load.ts
2481
3030
  var PAGE_LOAD_PROMPT = `You are a specialist in analyzing page load performance, render-blocking resources, and network waterfall patterns.
2482
3031
 
@@ -2570,13 +3119,13 @@ Assets served without compression or with missing cache headers.
2570
3119
 
2571
3120
  ${BROWSER_TOOL_CALL_STRATEGY}
2572
3121
  ${VERIFICATION_RULES}
2573
- ${SEVERITY_RULES}
3122
+ ${SEVERITY_RULES2}
2574
3123
  ${FINDING_CATEGORIES}
2575
3124
  ${OUTPUT_FORMAT}
2576
3125
  ${STRUCTURED_OUTPUT_FIELDS}
2577
3126
  ${MINIFIED_SOURCE_HANDLING}
2578
3127
  ${IMPACT_ESTIMATION}
2579
- ${FULL_RESPONSE_REQUIREMENT}`;
3128
+ ${WRITE_FINDINGS_REQUIREMENT}`;
2580
3129
  // src/analysis/prompts/runtime-blocking.ts
2581
3130
  var RUNTIME_BLOCKING_PROMPT = `You are a specialist in analyzing Chrome runtime traces to find main-thread blocking operations, event listener leaks, and layout performance issues.
2582
3131
 
@@ -2744,16 +3293,16 @@ window.addEventListener('scroll', () => {
2744
3293
 
2745
3294
  ${BROWSER_TOOL_CALL_STRATEGY}
2746
3295
  ${VERIFICATION_RULES}
2747
- ${SEVERITY_RULES}
3296
+ ${SEVERITY_RULES2}
2748
3297
  ${FINDING_CATEGORIES}
2749
3298
  ${OUTPUT_FORMAT}
2750
3299
  ${STRUCTURED_OUTPUT_FIELDS}
2751
3300
  ${MINIFIED_SOURCE_HANDLING}
2752
3301
  ${CROSS_REFERENCING}
2753
3302
  ${IMPACT_ESTIMATION}
2754
- ${FULL_RESPONSE_REQUIREMENT}`;
3303
+ ${WRITE_FINDINGS_REQUIREMENT}`;
2755
3304
  // src/analysis/prompts/code-pattern.ts
2756
- var CODE_PATTERN_PROMPT = `You are a specialist in detecting frontend performance anti-patterns in JavaScript, CSS, and HTML source code.
3305
+ var CODE_PATTERN_PROMPT2 = `You are a specialist in detecting frontend performance anti-patterns in JavaScript, CSS, and HTML source code.
2757
3306
 
2758
3307
  You have access to a workspace with actual source files captured from a real page load.
2759
3308
 
@@ -2905,13 +3454,13 @@ point to as potentially problematic. A typical page has 3-8 issues.
2905
3454
 
2906
3455
  ${BROWSER_TOOL_CALL_STRATEGY}
2907
3456
  ${VERIFICATION_RULES}
2908
- ${SEVERITY_RULES}
3457
+ ${SEVERITY_RULES2}
2909
3458
  ${FINDING_CATEGORIES}
2910
3459
  ${OUTPUT_FORMAT}
2911
3460
  ${STRUCTURED_OUTPUT_FIELDS}
2912
3461
  ${MINIFIED_SOURCE_HANDLING}
2913
3462
  ${IMPACT_ESTIMATION}
2914
- ${FULL_RESPONSE_REQUIREMENT}`;
3463
+ ${WRITE_FINDINGS_REQUIREMENT}`;
2915
3464
 
2916
3465
  // src/analysis/prompts.ts
2917
3466
  var BROWSER_ORCHESTRATOR_PROMPT = `You are a performance analysis orchestrator.
@@ -2923,22 +3472,15 @@ var BROWSER_ORCHESTRATOR_PROMPT = `You are a performance analysis orchestrator.
2923
3472
  For each, set subagent_type and description EXACTLY as written in the
2924
3473
  user message. Copy the FULL multi-line description verbatim, including
2925
3474
  every file path listed.
2926
- 3. After all 4 subagents return, consolidate ALL findings into your
2927
- structured response.
2928
-
2929
- ## Consolidation rules — CRITICAL
2930
-
2931
- - Include EVERY finding from EVERY subagent in your structured response.
2932
- - Do NOT filter, drop, or summarize findings. Each distinct finding from each
2933
- subagent must appear as a separate entry.
2934
- - If a subagent reports 6 findings, your output must contain all 6.
2935
- - Preserve the exact sourceFile, category, severity, beforeCode, and afterCode
2936
- from each subagent finding. Do NOT rewrite or abbreviate them.
2937
- - If two subagents report findings for the same script but with DIFFERENT
2938
- categories (e.g., render-blocking vs frame-blocking-function), include BOTH
2939
- as separate findings.
2940
- - Do NOT add your own findings — only include what subagents reported.
2941
- - Do NOT call read_file, grep, ls, or glob. All analysis is done by subagents.`;
3475
+ 3. After all 4 subagents return, respond with: "All subagents complete."
3476
+
3477
+ ## CRITICAL rules
3478
+
3479
+ - Do NOT consolidate, re-read, or re-serialize findings. Subagents write
3480
+ their findings to /findings/*.json files directly.
3481
+ - Do NOT add your own findings all analysis is done by subagents.
3482
+ - Do NOT call read_file, grep, ls, or glob.
3483
+ - Your response should be SHORT just confirm completion.`;
2942
3484
 
2943
3485
  // src/analysis/agent.ts
2944
3486
  function categoriseWorkspaceFiles(workspaceFiles) {
@@ -3070,7 +3612,7 @@ function buildSubagents(ctx) {
3070
3612
  {
3071
3613
  name: "code-pattern",
3072
3614
  description: "Detects frontend code anti-patterns: inline scripts, DOM manipulation in loops, missing event delegation, non-passive listeners.",
3073
- prompt: CODE_PATTERN_PROMPT
3615
+ prompt: CODE_PATTERN_PROMPT2
3074
3616
  }
3075
3617
  ];
3076
3618
  return agentDefs.map(({ name, description, prompt }) => {
@@ -3085,13 +3627,12 @@ function buildSubagents(ctx) {
3085
3627
  }
3086
3628
  async function analyze(model, backend, spinner, context, { animateProgress = true } = {}) {
3087
3629
  const subagents = buildSubagents(context);
3088
- const agent = createDeepAgent({
3630
+ const agent = createDeepAgent2({
3089
3631
  model,
3090
3632
  systemPrompt: BROWSER_ORCHESTRATOR_PROMPT,
3091
3633
  backend,
3092
3634
  subagents,
3093
- skills: ["skills/"],
3094
- responseFormat: toolStrategy(FindingsSchema)
3635
+ skills: ["skills/"]
3095
3636
  });
3096
3637
  const userMessage = context ? buildBrowserUserMessage(context) : [
3097
3638
  "Analyze the frontend performance data in this workspace.",
@@ -3100,10 +3641,10 @@ async function analyze(model, backend, spinner, context, { animateProgress = tru
3100
3641
  "to understand the overall picture, then explore source files to verify root causes."
3101
3642
  ].join(`
3102
3643
  `);
3103
- const result = await invokeWithTodoStreaming(agent, userMessage, spinner, { animateProgress });
3104
- const findings = result.structuredResponse?.findings;
3105
- if (!Array.isArray(findings)) {
3106
- throw new Error(`Agent did not return structured findings: ${result.messages.at(-1)?.text}`);
3644
+ await invokeWithTodoStreaming(agent, userMessage, spinner, { animateProgress });
3645
+ const findings = await mergeFindings(backend);
3646
+ if (findings.length === 0) {
3647
+ throw new Error("Subagents did not write any findings to /findings/*.json");
3107
3648
  }
3108
3649
  const deduped = deduplicateFindings(findings);
3109
3650
  return rankFindings(deduped);
@@ -3940,7 +4481,6 @@ async function createWorkspace(options) {
3940
4481
  }
3941
4482
  Object.assign(files, DATA_SCRIPTING_SKILL_FILES);
3942
4483
  Object.assign(files, BROWSER_ANALYSIS_SKILL_FILES);
3943
- Object.assign(files, RESULT_WRITER_SKILL_FILES);
3944
4484
  const result = await createWorkspaceFromFiles(files);
3945
4485
  return {
3946
4486
  backend: result.backend,
@@ -4023,7 +4563,11 @@ var argv = yargs(hideBin(process.argv)).scriptName("zeitzeuge").usage("Usage: $0
4023
4563
  alias: "o",
4024
4564
  type: "string",
4025
4565
  default: "zeitzeuge-report.md",
4026
- describe: "Output path for the Markdown report"
4566
+ describe: "Output path for the report (use .json extension for JSON output)"
4567
+ }).option("format", {
4568
+ type: "string",
4569
+ choices: ["markdown", "json"],
4570
+ describe: "Output format (auto-detected from --output extension if omitted)"
4027
4571
  }).help("help", "Show help").alias("h", "help").version(VERSION).strict().parseSync();
4028
4572
  function validateUrl(url) {
4029
4573
  try {
@@ -4115,24 +4659,52 @@ async function main() {
4115
4659
  agentSpinner.succeed(`Analysis complete — ${findings.length} findings`);
4116
4660
  } catch (err) {
4117
4661
  agentSpinner.fail(`Analysis failed: ${err instanceof Error ? err.message : "Unknown error"}`);
4662
+ console.error(err);
4118
4663
  throw new Error(`LLM analysis failed. Check your API key and network connection.
4119
4664
  ` + (err instanceof Error ? ` Details: ${err.message}` : ""));
4120
4665
  } finally {
4121
4666
  workspace.cleanup();
4122
4667
  }
4123
- printFindings(findings);
4124
- printCaptureInfo(heapSummary, captureResult.trace);
4125
4668
  const outputPath = argv.output;
4126
- const reportPath = writeReport(resolve(outputPath), {
4127
- url,
4128
- version: VERSION,
4129
- findings,
4130
- heapSummary,
4131
- trace: captureResult.trace
4132
- });
4133
- console.log(`
4669
+ const explicitFormat = argv.format;
4670
+ const outputFormat = explicitFormat ?? (outputPath.endsWith(".json") ? "json" : "markdown");
4671
+ if (outputFormat === "json") {
4672
+ const jsonReport = {
4673
+ url,
4674
+ version: VERSION,
4675
+ analyzedAt: new Date().toISOString(),
4676
+ findings,
4677
+ metrics: {
4678
+ loadComplete: captureResult.trace.metrics.loadComplete,
4679
+ firstContentfulPaint: captureResult.trace.metrics.firstContentfulPaint,
4680
+ largestContentfulPaint: captureResult.trace.metrics.largestContentfulPaint,
4681
+ totalBlockingTime: captureResult.trace.metrics.totalBlockingTime,
4682
+ heapTotalSize: heapSummary.metadata.totalSize,
4683
+ heapNodeCount: heapSummary.metadata.nodeCount,
4684
+ detachedDomNodes: heapSummary.detachedNodes.count,
4685
+ networkRequests: captureResult.trace.networkRequests.length,
4686
+ totalTransferSize: captureResult.trace.networkRequests.reduce((s, r) => s + r.encodedSize, 0)
4687
+ }
4688
+ };
4689
+ const { writeFileSync: writeFileSync2 } = await import("node:fs");
4690
+ writeFileSync2(resolve(outputPath), JSON.stringify(jsonReport, null, 2), "utf-8");
4691
+ console.log(`
4692
+ \uD83D\uDCC4 JSON report written to ${resolve(outputPath)}
4693
+ `);
4694
+ } else {
4695
+ printFindings(findings);
4696
+ printCaptureInfo(heapSummary, captureResult.trace);
4697
+ const reportPath = writeReport(resolve(outputPath), {
4698
+ url,
4699
+ version: VERSION,
4700
+ findings,
4701
+ heapSummary,
4702
+ trace: captureResult.trace
4703
+ });
4704
+ console.log(`
4134
4705
  \uD83D\uDCC4 Report written to ${reportPath}
4135
4706
  `);
4707
+ }
4136
4708
  } catch (err) {
4137
4709
  printError(err);
4138
4710
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeitzeuge",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "description": "A deepagent to witnessing slowdowns in your test runs.",
5
5
  "keywords": [
6
6
  "analysis",
@@ -38,15 +38,16 @@
38
38
  "test": "bun test"
39
39
  },
40
40
  "dependencies": {
41
- "@langchain/anthropic": "^1.3.18",
42
- "@langchain/core": "^1.1.25",
41
+ "@langchain/anthropic": "^1.3.20",
42
+ "@langchain/core": "^1.1.27",
43
43
  "@langchain/node-vfs": "^0.1.2",
44
- "@langchain/openai": "^1.2.8",
44
+ "@langchain/openai": "^1.2.9",
45
+ "chalk": "^5.6.2",
45
46
  "deepagents": "^1.8.0",
46
- "langchain": "^1.2.23",
47
+ "langchain": "^1.2.26",
47
48
  "ora": "^9.3.0",
48
49
  "picocolors": "^1.1.1",
49
- "puppeteer-core": "^24.37.2",
50
+ "puppeteer-core": "^24.37.5",
50
51
  "webdriverio": "^9.24.0",
51
52
  "yargs": "^18.0.0",
52
53
  "zod": "^4.3.6"
@@ -56,8 +57,8 @@
56
57
  "@types/yargs": "^17",
57
58
  "@zeitzeuge/utils": "workspace:*",
58
59
  "lint-staged": "^16.2.7",
59
- "oxfmt": "^0.32.0",
60
- "oxlint": "^1.47.0",
60
+ "oxfmt": "^0.35.0",
61
+ "oxlint": "^1.50.0",
61
62
  "simple-git-hooks": "^2.13.1",
62
63
  "typescript": "^5"
63
64
  },