zeitzeuge 0.8.2 → 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 +672 -59
  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.
659
733
 
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.
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.
739
+
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
663
745
 
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)
746
+ When your analysis is complete, you MUST write ALL findings to a JSON file using write_file.
669
747
 
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.`;
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).
751
+
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.
757
+
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,6 +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
  };
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";
2005
+
2006
+ // ../utils/src/profiling/prompts/test-shared.ts
2007
+ var SEVERITY_RULES = `## Severity classification
2008
+
2009
+ Assign severity based on the nature and measured impact of the issue:
2010
+
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
+ }
2280
+ });
2281
+ }
2282
+ \`\`\`
2283
+
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
+ });
2297
+ }
2298
+ \`\`\`
2299
+
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) { /* ... */ }
2445
+ }
2446
+ }
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.
2478
+
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}`;
1909
2499
  // ../utils/src/output/terminal.ts
1910
2500
  import pc2 from "picocolors";
1911
2501
  import ora from "ora";
@@ -2230,8 +2820,7 @@ function generateMarkdown(options) {
2230
2820
  `);
2231
2821
  }
2232
2822
  // src/analysis/agent.ts
2233
- import { createDeepAgent } from "deepagents";
2234
- import { toolStrategy } from "langchain";
2823
+ import { createDeepAgent as createDeepAgent2 } from "deepagents";
2235
2824
 
2236
2825
  // src/analysis/prompts/shared.ts
2237
2826
  var BROWSER_TOOL_CALL_STRATEGY = `## CRITICAL: Tool call strategy — scripts first, source selectively
@@ -2274,7 +2863,7 @@ When source code is minified/compiled:
2274
2863
  Only provide \`afterCode\` when the source is clearly human-authored (readable
2275
2864
  variable names, formatting, comments) — e.g. inline scripts in HTML or
2276
2865
  un-minified CSS.`;
2277
- var SEVERITY_RULES = `## Severity classification
2866
+ var SEVERITY_RULES2 = `## Severity classification
2278
2867
 
2279
2868
  Assign severity based on measured impact — do NOT guess:
2280
2869
 
@@ -2429,14 +3018,14 @@ These are THREE separate findings, not one.
2429
3018
 
2430
3019
  ${BROWSER_TOOL_CALL_STRATEGY}
2431
3020
  ${VERIFICATION_RULES}
2432
- ${SEVERITY_RULES}
3021
+ ${SEVERITY_RULES2}
2433
3022
  ${FINDING_CATEGORIES}
2434
3023
  ${OUTPUT_FORMAT}
2435
3024
  ${STRUCTURED_OUTPUT_FIELDS}
2436
3025
  ${MINIFIED_SOURCE_HANDLING}
2437
3026
  ${CROSS_REFERENCING}
2438
3027
  ${IMPACT_ESTIMATION}
2439
- ${FULL_RESPONSE_REQUIREMENT}`;
3028
+ ${WRITE_FINDINGS_REQUIREMENT}`;
2440
3029
  // src/analysis/prompts/page-load.ts
2441
3030
  var PAGE_LOAD_PROMPT = `You are a specialist in analyzing page load performance, render-blocking resources, and network waterfall patterns.
2442
3031
 
@@ -2530,13 +3119,13 @@ Assets served without compression or with missing cache headers.
2530
3119
 
2531
3120
  ${BROWSER_TOOL_CALL_STRATEGY}
2532
3121
  ${VERIFICATION_RULES}
2533
- ${SEVERITY_RULES}
3122
+ ${SEVERITY_RULES2}
2534
3123
  ${FINDING_CATEGORIES}
2535
3124
  ${OUTPUT_FORMAT}
2536
3125
  ${STRUCTURED_OUTPUT_FIELDS}
2537
3126
  ${MINIFIED_SOURCE_HANDLING}
2538
3127
  ${IMPACT_ESTIMATION}
2539
- ${FULL_RESPONSE_REQUIREMENT}`;
3128
+ ${WRITE_FINDINGS_REQUIREMENT}`;
2540
3129
  // src/analysis/prompts/runtime-blocking.ts
2541
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.
2542
3131
 
@@ -2704,16 +3293,16 @@ window.addEventListener('scroll', () => {
2704
3293
 
2705
3294
  ${BROWSER_TOOL_CALL_STRATEGY}
2706
3295
  ${VERIFICATION_RULES}
2707
- ${SEVERITY_RULES}
3296
+ ${SEVERITY_RULES2}
2708
3297
  ${FINDING_CATEGORIES}
2709
3298
  ${OUTPUT_FORMAT}
2710
3299
  ${STRUCTURED_OUTPUT_FIELDS}
2711
3300
  ${MINIFIED_SOURCE_HANDLING}
2712
3301
  ${CROSS_REFERENCING}
2713
3302
  ${IMPACT_ESTIMATION}
2714
- ${FULL_RESPONSE_REQUIREMENT}`;
3303
+ ${WRITE_FINDINGS_REQUIREMENT}`;
2715
3304
  // src/analysis/prompts/code-pattern.ts
2716
- 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.
2717
3306
 
2718
3307
  You have access to a workspace with actual source files captured from a real page load.
2719
3308
 
@@ -2865,13 +3454,13 @@ point to as potentially problematic. A typical page has 3-8 issues.
2865
3454
 
2866
3455
  ${BROWSER_TOOL_CALL_STRATEGY}
2867
3456
  ${VERIFICATION_RULES}
2868
- ${SEVERITY_RULES}
3457
+ ${SEVERITY_RULES2}
2869
3458
  ${FINDING_CATEGORIES}
2870
3459
  ${OUTPUT_FORMAT}
2871
3460
  ${STRUCTURED_OUTPUT_FIELDS}
2872
3461
  ${MINIFIED_SOURCE_HANDLING}
2873
3462
  ${IMPACT_ESTIMATION}
2874
- ${FULL_RESPONSE_REQUIREMENT}`;
3463
+ ${WRITE_FINDINGS_REQUIREMENT}`;
2875
3464
 
2876
3465
  // src/analysis/prompts.ts
2877
3466
  var BROWSER_ORCHESTRATOR_PROMPT = `You are a performance analysis orchestrator.
@@ -2883,22 +3472,15 @@ var BROWSER_ORCHESTRATOR_PROMPT = `You are a performance analysis orchestrator.
2883
3472
  For each, set subagent_type and description EXACTLY as written in the
2884
3473
  user message. Copy the FULL multi-line description verbatim, including
2885
3474
  every file path listed.
2886
- 3. After all 4 subagents return, consolidate ALL findings into your
2887
- structured response.
2888
-
2889
- ## Consolidation rules — CRITICAL
2890
-
2891
- - Include EVERY finding from EVERY subagent in your structured response.
2892
- - Do NOT filter, drop, or summarize findings. Each distinct finding from each
2893
- subagent must appear as a separate entry.
2894
- - If a subagent reports 6 findings, your output must contain all 6.
2895
- - Preserve the exact sourceFile, category, severity, beforeCode, and afterCode
2896
- from each subagent finding. Do NOT rewrite or abbreviate them.
2897
- - If two subagents report findings for the same script but with DIFFERENT
2898
- categories (e.g., render-blocking vs frame-blocking-function), include BOTH
2899
- as separate findings.
2900
- - Do NOT add your own findings — only include what subagents reported.
2901
- - 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.`;
2902
3484
 
2903
3485
  // src/analysis/agent.ts
2904
3486
  function categoriseWorkspaceFiles(workspaceFiles) {
@@ -3030,7 +3612,7 @@ function buildSubagents(ctx) {
3030
3612
  {
3031
3613
  name: "code-pattern",
3032
3614
  description: "Detects frontend code anti-patterns: inline scripts, DOM manipulation in loops, missing event delegation, non-passive listeners.",
3033
- prompt: CODE_PATTERN_PROMPT
3615
+ prompt: CODE_PATTERN_PROMPT2
3034
3616
  }
3035
3617
  ];
3036
3618
  return agentDefs.map(({ name, description, prompt }) => {
@@ -3045,13 +3627,12 @@ function buildSubagents(ctx) {
3045
3627
  }
3046
3628
  async function analyze(model, backend, spinner, context, { animateProgress = true } = {}) {
3047
3629
  const subagents = buildSubagents(context);
3048
- const agent = createDeepAgent({
3630
+ const agent = createDeepAgent2({
3049
3631
  model,
3050
3632
  systemPrompt: BROWSER_ORCHESTRATOR_PROMPT,
3051
3633
  backend,
3052
3634
  subagents,
3053
- skills: ["skills/"],
3054
- responseFormat: toolStrategy(FindingsSchema)
3635
+ skills: ["skills/"]
3055
3636
  });
3056
3637
  const userMessage = context ? buildBrowserUserMessage(context) : [
3057
3638
  "Analyze the frontend performance data in this workspace.",
@@ -3060,10 +3641,10 @@ async function analyze(model, backend, spinner, context, { animateProgress = tru
3060
3641
  "to understand the overall picture, then explore source files to verify root causes."
3061
3642
  ].join(`
3062
3643
  `);
3063
- const result = await invokeWithTodoStreaming(agent, userMessage, spinner, { animateProgress });
3064
- const findings = result.structuredResponse?.findings;
3065
- if (!Array.isArray(findings)) {
3066
- 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");
3067
3648
  }
3068
3649
  const deduped = deduplicateFindings(findings);
3069
3650
  return rankFindings(deduped);
@@ -3982,7 +4563,11 @@ var argv = yargs(hideBin(process.argv)).scriptName("zeitzeuge").usage("Usage: $0
3982
4563
  alias: "o",
3983
4564
  type: "string",
3984
4565
  default: "zeitzeuge-report.md",
3985
- 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)"
3986
4571
  }).help("help", "Show help").alias("h", "help").version(VERSION).strict().parseSync();
3987
4572
  function validateUrl(url) {
3988
4573
  try {
@@ -4074,24 +4659,52 @@ async function main() {
4074
4659
  agentSpinner.succeed(`Analysis complete — ${findings.length} findings`);
4075
4660
  } catch (err) {
4076
4661
  agentSpinner.fail(`Analysis failed: ${err instanceof Error ? err.message : "Unknown error"}`);
4662
+ console.error(err);
4077
4663
  throw new Error(`LLM analysis failed. Check your API key and network connection.
4078
4664
  ` + (err instanceof Error ? ` Details: ${err.message}` : ""));
4079
4665
  } finally {
4080
4666
  workspace.cleanup();
4081
4667
  }
4082
- printFindings(findings);
4083
- printCaptureInfo(heapSummary, captureResult.trace);
4084
4668
  const outputPath = argv.output;
4085
- const reportPath = writeReport(resolve(outputPath), {
4086
- url,
4087
- version: VERSION,
4088
- findings,
4089
- heapSummary,
4090
- trace: captureResult.trace
4091
- });
4092
- 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(`
4093
4705
  \uD83D\uDCC4 Report written to ${reportPath}
4094
4706
  `);
4707
+ }
4095
4708
  } catch (err) {
4096
4709
  printError(err);
4097
4710
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeitzeuge",
3
- "version": "0.8.2",
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
  },