@elench/testkit 0.1.105 → 0.1.107

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.
@@ -6,6 +6,10 @@ import { CodeBlock } from "./code-block.mjs";
6
6
  import { getComposerDisplayModel } from "./composer.mjs";
7
7
  import { MarkdownBlock } from "./markdown-block.mjs";
8
8
  import { buildAssistantViewModel } from "./view-model.mjs";
9
+ import { truncateText, wrapText } from "../terminal/layout.mjs";
10
+
11
+ const FALLBACK_COMMAND_BLOCK_WIDTH = 100;
12
+ const COMMAND_BLOCK_CHROME_WIDTH = 4;
9
13
 
10
14
  export function AssistantApp({
11
15
  assistantState,
@@ -180,13 +184,13 @@ function Transcript({ view }) {
180
184
  createElement(Text, null, dim(view.welcome.rows.find(([label]) => label === "Provider")?.[1] || "")),
181
185
  createElement(Text, null, ""),
182
186
  view.notice ? createElement(Text, null, yellow(view.notice)) : null,
183
- ...view.blocks.flatMap((block) => renderBlock(block))
187
+ ...view.blocks.flatMap((block) => renderBlock(block, view))
184
188
  );
185
189
  }
186
190
 
187
- function renderBlock(block) {
191
+ function renderBlock(block, view) {
188
192
  if (block.format === "markdown") return renderMarkdownBlock(block);
189
- if (block.format === "command") return renderCommandBlock(block);
193
+ if (block.format === "command") return renderCommandBlock(block, view);
190
194
  return renderPlainBlock(block);
191
195
  }
192
196
 
@@ -227,7 +231,7 @@ function renderPlainBlock(block) {
227
231
  return rendered;
228
232
  }
229
233
 
230
- function renderCommandBlock(block) {
234
+ function renderCommandBlock(block, view = {}) {
231
235
  const marker = colorMarker(block);
232
236
  const title = block.title ? bold(block.title) : bold("command");
233
237
  const command = formatCommandLine(block);
@@ -237,6 +241,14 @@ function renderCommandBlock(block) {
237
241
  const omitted = codeBlock
238
242
  ? codeBlock.omittedLineCount || 0
239
243
  : block.outputPreview?.omittedLineCount || block.omittedOutputLineCount || 0;
244
+ const blockWidth = Math.max(1, Number(view.terminalWidth) || FALLBACK_COMMAND_BLOCK_WIDTH);
245
+ const contentWidth = Math.max(1, blockWidth - COMMAND_BLOCK_CHROME_WIDTH);
246
+ const commandLines = command ? wrapText(`${dim("$")} ${command}`, contentWidth) : [];
247
+ const statusLine = status ? truncateText(status, contentWidth) : null;
248
+ const previewLines = outputLines.map((line) => truncateText(line, contentWidth));
249
+ const omittedLine = omitted > 0
250
+ ? truncateText(`… ${omitted} more line${omitted === 1 ? "" : "s"} omitted`, contentWidth)
251
+ : null;
240
252
 
241
253
  return [
242
254
  createElement(
@@ -248,18 +260,19 @@ function renderCommandBlock(block) {
248
260
  paddingLeft: 1,
249
261
  paddingRight: 1,
250
262
  marginBottom: 1,
263
+ width: blockWidth,
251
264
  },
252
265
  createElement(Text, { key: "title" }, `${marker} ${title}`),
253
- command ? createElement(Text, { key: "command" }, `${dim("$")} ${command}`) : null,
254
- status ? createElement(Text, { key: "status" }, colorCommandStatus(block, status)) : null,
266
+ ...commandLines.map((line, index) => createElement(Text, { key: `command-${index}` }, line)),
267
+ statusLine ? createElement(Text, { key: "status" }, colorCommandStatus(block, statusLine)) : null,
255
268
  codeBlock ? createElement(Text, { key: "code-gap" }, "") : null,
256
- ...(codeBlock ? CodeBlock({ lines: codeBlock.lines, language: codeBlock.language }) : []),
257
- ...outputLines.map((line, index) => (
269
+ ...(codeBlock ? CodeBlock({ lines: codeBlock.lines, language: codeBlock.language, width: contentWidth }) : []),
270
+ ...previewLines.map((line, index) => (
258
271
  createElement(Text, { key: `output-${index}` }, dim(line))
259
272
  )),
260
- omitted > 0 ? createElement(Text, { key: "omitted" }, dim(`… ${omitted} more line${omitted === 1 ? "" : "s"} omitted`)) : null,
273
+ omittedLine ? createElement(Text, { key: "omitted" }, dim(omittedLine)) : null,
261
274
  block.text && !command && outputLines.length === 0
262
- ? createElement(Text, { key: "text" }, colorBlockText(block, block.text))
275
+ ? createElement(Text, { key: "text" }, colorBlockText(block, truncateText(block.text, contentWidth)))
263
276
  : null
264
277
  ),
265
278
  ];
@@ -1,10 +1,11 @@
1
1
  import React, { createElement } from "react";
2
2
  import { Text } from "ink";
3
3
  import { dim, green, red, cyan } from "../terminal/colors.mjs";
4
+ import { truncateText } from "../terminal/layout.mjs";
4
5
 
5
- export function CodeBlock({ lines = [], language = "text" } = {}) {
6
+ export function CodeBlock({ lines = [], language = "text", width = null } = {}) {
6
7
  return lines.map((line, index) => (
7
- createElement(Text, { key: `code-${index}` }, colorCodeLine(line, language))
8
+ createElement(Text, { key: `code-${index}` }, colorCodeLine(width ? truncateText(line, width) : line, language))
8
9
  ));
9
10
  }
10
11
 
@@ -43,6 +43,8 @@ export async function runInteractiveAssistant({
43
43
  assistantState.revealService(service);
44
44
  }
45
45
 
46
+ clearInteractiveScreen({ stdout, env });
47
+
46
48
  const app = render(
47
49
  createElement(AssistantApp, {
48
50
  assistantState,
@@ -59,3 +61,9 @@ export async function runInteractiveAssistant({
59
61
 
60
62
  return app.waitUntilExit();
61
63
  }
64
+
65
+ export function clearInteractiveScreen({ stdout = process.stdout, env = process.env } = {}) {
66
+ if (env.TESTKIT_ASSISTANT_CLEAR_SCREEN === "0") return;
67
+ if (!stdout?.isTTY) return;
68
+ stdout.write("\x1b[2J\x1b[H");
69
+ }
@@ -8,6 +8,10 @@ import {
8
8
  } from "../config.mjs";
9
9
  import { createRunState } from "../state/run/state.mjs";
10
10
  import { buildContextSelection } from "../../results/context.mjs";
11
+ import { renderDiscoverResult } from "../renderers/discover/text.mjs";
12
+ import { renderDoctorResult } from "../renderers/doctor/text.mjs";
13
+ import { renderStatusResult } from "../renderers/status/text.mjs";
14
+ import { renderTypecheckResult } from "../renderers/typecheck/text.mjs";
11
15
  import { isProviderInstalled } from "./providers/index.mjs";
12
16
  import { parseSlashCommand, formatSlashHelpLines } from "./slash-commands.mjs";
13
17
  import { executeAssistantAction } from "./actions.mjs";
@@ -627,7 +631,12 @@ function handleAssistantToolEvent(state, event, appendMessage) {
627
631
  toolName: event.command?.kind || "testkit",
628
632
  title: formatObservedCommandTitle(event.command),
629
633
  text: formatObservedCommandText(event.command),
630
- data: event.command || null,
634
+ data: {
635
+ ...(event.command || {}),
636
+ command: formatObservedCommandLine(event.command),
637
+ exitCode: event.command?.exitCode ?? null,
638
+ testkitRelated: true,
639
+ },
631
640
  });
632
641
  }
633
642
  }
@@ -893,11 +902,18 @@ function formatObservedCommandTitle(command) {
893
902
  return `testkit ${kind}`;
894
903
  }
895
904
 
905
+ function formatObservedCommandLine(command) {
906
+ if (!command) return "testkit";
907
+ const argv = Array.isArray(command.argv) ? command.argv : [];
908
+ return `testkit ${argv.join(" ") || command.kind || ""}`.trim();
909
+ }
910
+
896
911
  function formatObservedCommandText(command) {
897
912
  if (!command) return "Observed Testkit command.";
913
+ const rendered = renderObservedCommandResult(command);
914
+ if (rendered.length > 0) return rendered.join("\n");
915
+
898
916
  const lines = [];
899
- lines.push(`$ testkit ${command.argv?.join(" ") || command.kind || ""}`.trim());
900
- if (Number.isInteger(command.exitCode)) lines.push(`exit code: ${command.exitCode}`);
901
917
  if (command.kind === "run" && command.result?.runArtifact) {
902
918
  for (const file of collectRunArtifactFiles(command.result.runArtifact)) {
903
919
  lines.push(`${formatObservedFileStatus(file.status)} ${file.serviceName} ${file.type} ${file.path}`);
@@ -913,6 +929,23 @@ function formatObservedCommandText(command) {
913
929
  return lines.join("\n");
914
930
  }
915
931
 
932
+ function renderObservedCommandResult(command) {
933
+ const result = command?.result;
934
+ if (!result) return [];
935
+ if (command.kind === "discover") return normalizeRenderedLines(renderDiscoverResult(result, { outputMode: "compact" }));
936
+ if (command.kind === "status") {
937
+ return normalizeRenderedLines((result.results || []).flatMap((entry) => renderStatusResult(entry)));
938
+ }
939
+ if (command.kind === "doctor") return normalizeRenderedLines(renderDoctorResult(result));
940
+ if (command.kind === "typecheck") return normalizeRenderedLines(renderTypecheckResult(result));
941
+ return [];
942
+ }
943
+
944
+ function normalizeRenderedLines(lines) {
945
+ return (Array.isArray(lines) ? lines : [])
946
+ .flatMap((line) => String(line ?? "").replace(/\r/g, "").split("\n"));
947
+ }
948
+
916
949
  function collectRunArtifactFiles(artifact) {
917
950
  const files = [];
918
951
  for (const service of artifact?.services || []) {
@@ -1,15 +1,16 @@
1
1
  import stripAnsi from "strip-ansi";
2
+ import { truncateText, wrapText } from "../terminal/layout.mjs";
2
3
  import { buildAssistantViewModel } from "./view-model.mjs";
3
4
  import { renderCodeBlockText } from "./code-block.mjs";
4
5
  import { renderMarkdownToAnsi } from "./markdown-block.mjs";
5
6
 
6
- export function renderAssistantSnapshotText(snapshot, { cwd = process.cwd(), ansi = false } = {}) {
7
- const view = buildAssistantViewModel(snapshot || {}, { cwd });
7
+ export function renderAssistantSnapshotText(snapshot, { cwd = process.cwd(), ansi = false, width = 100 } = {}) {
8
+ const view = buildAssistantViewModel(snapshot || {}, { cwd, terminalWidth: width });
8
9
  const lines = [view.title, view.welcome.rows.find(([label]) => label === "Provider")?.[1] || ""]
9
10
  .filter(Boolean);
10
11
  for (const block of view.blocks || []) {
11
12
  lines.push("");
12
- lines.push(...renderBlockLines(block, { ansi }));
13
+ lines.push(...renderBlockLines(block, { ansi, width: view.terminalWidth || width }));
13
14
  }
14
15
  lines.push("");
15
16
  lines.push(view.statusLine);
@@ -17,7 +18,7 @@ export function renderAssistantSnapshotText(snapshot, { cwd = process.cwd(), ans
17
18
  return ansi ? text : stripAnsi(text);
18
19
  }
19
20
 
20
- function renderBlockLines(block, { ansi = false } = {}) {
21
+ function renderBlockLines(block, { ansi = false, width = 100 } = {}) {
21
22
  const marker = block.marker || "";
22
23
  const title = block.title ? ` ${block.title}` : "";
23
24
  const text = String(block.text || "").trimEnd();
@@ -27,28 +28,29 @@ function renderBlockLines(block, { ansi = false } = {}) {
27
28
  return prefixLines(`${marker}${title}`, normalized);
28
29
  }
29
30
  if (block.format === "command") {
30
- return renderCommandBlockLines(block);
31
+ return renderCommandBlockLines(block, { width });
31
32
  }
32
33
  return prefixLines(`${marker}${title}`, text);
33
34
  }
34
35
 
35
- function renderCommandBlockLines(block) {
36
+ function renderCommandBlockLines(block, { width = 100 } = {}) {
36
37
  const header = `${block.marker || ""}${block.title ? ` ${block.title}` : ""}`.trim();
37
38
  const body = [];
39
+ const innerWidth = Math.max(1, width - 4);
38
40
  const command = formatCommandLine(block.command);
39
41
  const status = formatCommandStatus(block);
40
- if (command) body.push(`$ ${command}`);
41
- if (status) body.push(status);
42
+ if (command) body.push(...wrapText(`$ ${command}`, innerWidth));
43
+ if (status) body.push(truncateText(status, innerWidth));
42
44
  if (block.codeBlock) {
43
45
  body.push("");
44
- body.push(...renderCodeBlockText(block.codeBlock));
46
+ body.push(...renderCodeBlockText(block.codeBlock).map((line) => truncateText(line, innerWidth)));
45
47
  } else {
46
- for (const line of block.outputPreview?.lines || []) body.push(String(line));
48
+ for (const line of block.outputPreview?.lines || []) body.push(truncateText(line, innerWidth));
47
49
  const omitted = block.outputPreview?.omittedLineCount || block.omittedOutputLineCount || 0;
48
- if (omitted > 0) body.push(`... ${omitted} more line${omitted === 1 ? "" : "s"} omitted`);
50
+ if (omitted > 0) body.push(truncateText(`... ${omitted} more line${omitted === 1 ? "" : "s"} omitted`, innerWidth));
49
51
  }
50
- if (!command && body.length === 0 && block.text) body.push(String(block.text));
51
- return boxLines([header, ...body].filter(Boolean));
52
+ if (!command && body.length === 0 && block.text) body.push(truncateText(block.text, innerWidth));
53
+ return boxLines([header, ...body].filter(Boolean), { width });
52
54
  }
53
55
 
54
56
  function formatCommandLine(command) {
@@ -70,13 +72,13 @@ function formatCommandStatus(block) {
70
72
  return null;
71
73
  }
72
74
 
73
- function boxLines(lines) {
74
- const width = Math.max(1, ...lines.map((line) => visibleWidth(line)));
75
- const top = `╭${"─".repeat(width + 2)}╮`;
76
- const bottom = `╰${"─".repeat(width + 2)}╯`;
75
+ function boxLines(lines, { width = null } = {}) {
76
+ const contentWidth = width ? Math.max(1, width - 4) : Math.max(1, ...lines.map((line) => visibleWidth(line)));
77
+ const top = `╭${"─".repeat(contentWidth + 2)}╮`;
78
+ const bottom = `╰${"─".repeat(contentWidth + 2)}╯`;
77
79
  return [
78
80
  top,
79
- ...lines.map((line) => `│ ${line}${" ".repeat(width - visibleWidth(line))} │`),
81
+ ...lines.map((line) => `│ ${line}${" ".repeat(contentWidth - visibleWidth(line))} │`),
80
82
  bottom,
81
83
  ];
82
84
  }
@@ -2,7 +2,9 @@ import path from "path";
2
2
  import { formatContextRemaining } from "./context-window.mjs";
3
3
 
4
4
  const PROVIDER_COMMAND_OUTPUT_PREVIEW_LINES = 12;
5
+ const PROVIDER_FILE_READ_OUTPUT_PREVIEW_LINES = 8;
5
6
  const PROVIDER_DIFF_PREVIEW_LINES = 80;
7
+ const TESTKIT_COMMAND_OUTPUT_PREVIEW_LINES = 18;
6
8
 
7
9
  export function buildAssistantViewModel(snapshot, { cwd = process.cwd(), terminalWidth = 100 } = {}) {
8
10
  const providerLabel = buildProviderLabel(snapshot);
@@ -56,6 +58,7 @@ export function buildTranscriptBlocks(messages) {
56
58
  return (messages || []).map((message) => {
57
59
  const role = message.role || "system";
58
60
  if (role === "tool") {
61
+ const outputPreview = summarizeOutput(message.text || "", TESTKIT_COMMAND_OUTPUT_PREVIEW_LINES);
59
62
  return {
60
63
  id: message.id,
61
64
  kind: classifyToolBlock(message),
@@ -66,6 +69,9 @@ export function buildTranscriptBlocks(messages) {
66
69
  status: message.status || null,
67
70
  command: message.data?.command || null,
68
71
  exitCode: message.data?.exitCode ?? null,
72
+ outputPreview,
73
+ outputLineCount: countLines(message.text || ""),
74
+ omittedOutputLineCount: outputPreview.omittedLineCount,
69
75
  };
70
76
  }
71
77
  if (role === "provider-tool") {
@@ -158,9 +164,9 @@ function buildProviderCommandModel(message) {
158
164
  const output = event.output || event.data?.aggregated_output || event.data?.output || "";
159
165
  const exitCode = event.data?.exit_code ?? event.data?.exitCode ?? null;
160
166
  const status = message.status || providerCommandStatus(event);
161
- const diffText = extractProviderDiffText(event);
167
+ const diffText = extractProviderDiffText(event, rawCommand);
162
168
  const diffPreview = diffText ? summarizeOutput(diffText, PROVIDER_DIFF_PREVIEW_LINES) : null;
163
- const outputPreview = summarizeOutput(output, PROVIDER_COMMAND_OUTPUT_PREVIEW_LINES);
169
+ const outputPreview = summarizeOutput(output, providerOutputPreviewLineLimit(event, rawCommand));
164
170
  return {
165
171
  command: diffPreview ? summarizeProviderEditCommand(event, rawCommand) : rawCommand,
166
172
  exitCode,
@@ -178,6 +184,23 @@ function buildProviderCommandModel(message) {
178
184
  };
179
185
  }
180
186
 
187
+ function providerOutputPreviewLineLimit(event, rawCommand) {
188
+ if (isProviderFileReadCommand(event, rawCommand)) return PROVIDER_FILE_READ_OUTPUT_PREVIEW_LINES;
189
+ return PROVIDER_COMMAND_OUTPUT_PREVIEW_LINES;
190
+ }
191
+
192
+ function isProviderFileReadCommand(event, rawCommand) {
193
+ if (event.name && event.name !== "command") {
194
+ return /^(read|view|cat)$/i.test(String(event.name));
195
+ }
196
+ const command = unwrapShellCommand(rawCommand);
197
+ if (!command) return false;
198
+ if (/^(cat|sed|awk|head|tail|nl|less|more)\b/.test(command)) return true;
199
+ if (/\b(rg|grep)\b[\s\S]*\b--files\b/.test(command)) return false;
200
+ if (/^(rg|grep)\b/.test(command) && !/\s(-n|--line-number)\b/.test(command)) return false;
201
+ return /(\bsed\s+-n\b|\bhead\b|\btail\b|\bnl\b|\bcat\b)/.test(command);
202
+ }
203
+
181
204
  function summarizeProviderEditCommand(event, rawCommand) {
182
205
  const name = event.name ? String(event.name) : "edit";
183
206
  if (name && name !== "command") return name;
@@ -187,24 +210,61 @@ function summarizeProviderEditCommand(event, rawCommand) {
187
210
  return command;
188
211
  }
189
212
 
190
- function extractProviderDiffText(event) {
191
- const candidates = [
213
+ function extractProviderDiffText(event, rawCommand) {
214
+ const candidates = providerDiffCandidates(event, rawCommand);
215
+ for (const candidate of candidates) {
216
+ const text = stringifyMaybe(candidate);
217
+ if (looksLikeDiff(text)) return text;
218
+ }
219
+ return null;
220
+ }
221
+
222
+ function providerDiffCandidates(event, rawCommand) {
223
+ const name = String(event.name || "");
224
+ if (name && name !== "command") {
225
+ return [
226
+ event.input,
227
+ event.detail,
228
+ event.text,
229
+ event.output,
230
+ event.data?.arguments,
231
+ event.data?.input,
232
+ event.data?.patch,
233
+ event.data?.diff,
234
+ event.data?.aggregated_output,
235
+ event.data?.output,
236
+ ];
237
+ }
238
+
239
+ const command = String(rawCommand || event.data?.command || event.data?.input || "").trim();
240
+ const inputCandidates = [
192
241
  event.input,
193
- event.detail,
194
- event.text,
195
- event.output,
196
242
  event.data?.arguments,
197
243
  event.data?.input,
198
244
  event.data?.patch,
199
245
  event.data?.diff,
200
- event.data?.aggregated_output,
201
- event.data?.output,
202
246
  ];
203
- for (const candidate of candidates) {
204
- const text = stringifyMaybe(candidate);
205
- if (looksLikeDiff(text)) return text;
247
+ if (isDiffProducingShellCommand(command)) {
248
+ return [
249
+ ...inputCandidates,
250
+ event.output,
251
+ event.data?.aggregated_output,
252
+ event.data?.output,
253
+ ];
206
254
  }
207
- return null;
255
+ return inputCandidates;
256
+ }
257
+
258
+ function isDiffProducingShellCommand(command) {
259
+ const normalized = unwrapShellCommand(command);
260
+ return /^((git\s+diff|git\s+show|diff|apply_patch)\b|.*\bapply_patch\b)/.test(normalized);
261
+ }
262
+
263
+ function unwrapShellCommand(command) {
264
+ const text = String(command || "").trim();
265
+ const match = text.match(/(?:^|\s)(?:[^\s'"]*\/)?(?:bash|sh|zsh)\s+-lc\s+(['"])([\s\S]*)\1\s*$/);
266
+ if (!match) return text;
267
+ return match[2].replace(/\\(["'\\$`])/g, "$1").trim();
208
268
  }
209
269
 
210
270
  function stringifyMaybe(value) {
@@ -32,6 +32,25 @@ export function wrapText(text, width) {
32
32
  }).split("\n");
33
33
  }
34
34
 
35
+ export function truncateText(text, width, { ellipsis = "…" } = {}) {
36
+ const normalized = String(text ?? "");
37
+ if (width <= 0) return "";
38
+ if (measureWidth(normalized) <= width) return normalized;
39
+ const ellipsisWidth = measureWidth(ellipsis);
40
+ if (width <= ellipsisWidth) return ellipsis.slice(0, width);
41
+
42
+ const targetWidth = width - ellipsisWidth;
43
+ let rendered = "";
44
+ let renderedWidth = 0;
45
+ for (const char of normalized) {
46
+ const charWidth = measureWidth(char);
47
+ if (renderedWidth + charWidth > targetWidth) break;
48
+ rendered += char;
49
+ renderedWidth += charWidth;
50
+ }
51
+ return `${rendered}${ellipsis}`;
52
+ }
53
+
35
54
  export function renderIndentedBlock(text, { width, indent = " " } = {}) {
36
55
  const visibleIndent = measureWidth(indent);
37
56
  const contentWidth = Math.max(12, width - visibleIndent);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/next-analysis",
3
- "version": "0.1.105",
3
+ "version": "0.1.106",
4
4
  "description": "SWC-backed Next.js source analysis primitives for Erench tools",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/testkit-bridge",
3
- "version": "0.1.105",
3
+ "version": "0.1.106",
4
4
  "description": "Browser bridge helpers for testkit",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,7 +22,7 @@
22
22
  "typecheck": "tsc -p tsconfig.json --noEmit"
23
23
  },
24
24
  "dependencies": {
25
- "@elench/testkit-protocol": "0.1.105"
25
+ "@elench/testkit-protocol": "0.1.106"
26
26
  },
27
27
  "private": false
28
28
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/testkit-protocol",
3
- "version": "0.1.105",
3
+ "version": "0.1.106",
4
4
  "description": "Shared browser protocol for testkit bridge and extension consumers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/ts-analysis",
3
- "version": "0.1.105",
3
+ "version": "0.1.106",
4
4
  "description": "TypeScript compiler-backed source analysis primitives for Erench tools",
5
5
  "type": "module",
6
6
  "exports": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/testkit",
3
- "version": "0.1.105",
3
+ "version": "0.1.107",
4
4
  "description": "Assistant-first CLI for running, inspecting, and debugging local testkit suites",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -90,10 +90,10 @@
90
90
  },
91
91
  "dependencies": {
92
92
  "@babel/code-frame": "^7.29.0",
93
- "@elench/next-analysis": "0.1.105",
94
- "@elench/testkit-bridge": "0.1.105",
95
- "@elench/testkit-protocol": "0.1.105",
96
- "@elench/ts-analysis": "0.1.105",
93
+ "@elench/next-analysis": "0.1.106",
94
+ "@elench/testkit-bridge": "0.1.106",
95
+ "@elench/testkit-protocol": "0.1.106",
96
+ "@elench/ts-analysis": "0.1.106",
97
97
  "@oclif/core": "^4.10.6",
98
98
  "esbuild": "^0.25.11",
99
99
  "execa": "^9.5.0",