@elench/testkit 0.1.106 → 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.
@@ -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 || []) {
@@ -4,6 +4,7 @@ import { formatContextRemaining } from "./context-window.mjs";
4
4
  const PROVIDER_COMMAND_OUTPUT_PREVIEW_LINES = 12;
5
5
  const PROVIDER_FILE_READ_OUTPUT_PREVIEW_LINES = 8;
6
6
  const PROVIDER_DIFF_PREVIEW_LINES = 80;
7
+ const TESTKIT_COMMAND_OUTPUT_PREVIEW_LINES = 18;
7
8
 
8
9
  export function buildAssistantViewModel(snapshot, { cwd = process.cwd(), terminalWidth = 100 } = {}) {
9
10
  const providerLabel = buildProviderLabel(snapshot);
@@ -57,6 +58,7 @@ export function buildTranscriptBlocks(messages) {
57
58
  return (messages || []).map((message) => {
58
59
  const role = message.role || "system";
59
60
  if (role === "tool") {
61
+ const outputPreview = summarizeOutput(message.text || "", TESTKIT_COMMAND_OUTPUT_PREVIEW_LINES);
60
62
  return {
61
63
  id: message.id,
62
64
  kind: classifyToolBlock(message),
@@ -67,6 +69,9 @@ export function buildTranscriptBlocks(messages) {
67
69
  status: message.status || null,
68
70
  command: message.data?.command || null,
69
71
  exitCode: message.data?.exitCode ?? null,
72
+ outputPreview,
73
+ outputLineCount: countLines(message.text || ""),
74
+ omittedOutputLineCount: outputPreview.omittedLineCount,
70
75
  };
71
76
  }
72
77
  if (role === "provider-tool") {
@@ -159,7 +164,7 @@ function buildProviderCommandModel(message) {
159
164
  const output = event.output || event.data?.aggregated_output || event.data?.output || "";
160
165
  const exitCode = event.data?.exit_code ?? event.data?.exitCode ?? null;
161
166
  const status = message.status || providerCommandStatus(event);
162
- const diffText = extractProviderDiffText(event);
167
+ const diffText = extractProviderDiffText(event, rawCommand);
163
168
  const diffPreview = diffText ? summarizeOutput(diffText, PROVIDER_DIFF_PREVIEW_LINES) : null;
164
169
  const outputPreview = summarizeOutput(output, providerOutputPreviewLineLimit(event, rawCommand));
165
170
  return {
@@ -188,7 +193,7 @@ function isProviderFileReadCommand(event, rawCommand) {
188
193
  if (event.name && event.name !== "command") {
189
194
  return /^(read|view|cat)$/i.test(String(event.name));
190
195
  }
191
- const command = String(rawCommand || "").trim();
196
+ const command = unwrapShellCommand(rawCommand);
192
197
  if (!command) return false;
193
198
  if (/^(cat|sed|awk|head|tail|nl|less|more)\b/.test(command)) return true;
194
199
  if (/\b(rg|grep)\b[\s\S]*\b--files\b/.test(command)) return false;
@@ -205,24 +210,61 @@ function summarizeProviderEditCommand(event, rawCommand) {
205
210
  return command;
206
211
  }
207
212
 
208
- function extractProviderDiffText(event) {
209
- 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 = [
210
241
  event.input,
211
- event.detail,
212
- event.text,
213
- event.output,
214
242
  event.data?.arguments,
215
243
  event.data?.input,
216
244
  event.data?.patch,
217
245
  event.data?.diff,
218
- event.data?.aggregated_output,
219
- event.data?.output,
220
246
  ];
221
- for (const candidate of candidates) {
222
- const text = stringifyMaybe(candidate);
223
- 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
+ ];
224
254
  }
225
- 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();
226
268
  }
227
269
 
228
270
  function stringifyMaybe(value) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/testkit",
3
- "version": "0.1.106",
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": [