@elench/testkit 0.1.89 → 0.1.91

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 (31) hide show
  1. package/README.md +14 -7
  2. package/lib/cli/agents/index.mjs +27 -19
  3. package/lib/cli/agents/providers/claude.mjs +3 -3
  4. package/lib/cli/agents/providers/codex.mjs +3 -3
  5. package/lib/cli/assistant/app.mjs +210 -0
  6. package/lib/cli/assistant/context-pack.mjs +191 -0
  7. package/lib/cli/assistant/interactive.mjs +53 -0
  8. package/lib/cli/assistant/prompt-builder.mjs +7 -9
  9. package/lib/cli/assistant/session.mjs +6 -1
  10. package/lib/cli/assistant/state.mjs +134 -46
  11. package/lib/cli/assistant/tool-registry.mjs +220 -230
  12. package/lib/cli/commands/assistant.mjs +50 -34
  13. package/lib/cli/{tui/detail-pane.mjs → context-resources.mjs} +81 -21
  14. package/lib/cli/entrypoint.mjs +12 -4
  15. package/lib/cli/presentation/tree-reporter.mjs +0 -101
  16. package/lib/cli/tui/inspect-app.mjs +7 -88
  17. package/lib/cli/tui/inspect-state.mjs +0 -117
  18. package/node_modules/@elench/next-analysis/package.json +1 -1
  19. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  20. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  21. package/node_modules/@elench/ts-analysis/package.json +1 -1
  22. package/package.json +5 -5
  23. package/lib/cli/agents/investigate.mjs +0 -75
  24. package/lib/cli/agents/investigation-context.mjs +0 -102
  25. package/lib/cli/agents/investigation-interpreter.mjs +0 -320
  26. package/lib/cli/agents/investigation-log.mjs +0 -37
  27. package/lib/cli/agents/prompt-builder.mjs +0 -25
  28. package/lib/cli/assistant/content.mjs +0 -60
  29. package/lib/cli/assistant/tool-run-reporter.mjs +0 -80
  30. package/lib/cli/tui/assistant-app.mjs +0 -82
  31. package/lib/cli/tui/assistant-render.mjs +0 -99
@@ -1,80 +0,0 @@
1
- import {
2
- applyReporterPlans,
3
- applyReporterRunSummary,
4
- applyReporterTaskFinished,
5
- applyReporterTaskStarted,
6
- } from "../tui/inspect-live-adapter.mjs";
7
- import { suiteSelectionType } from "../../runner/suite-selection.mjs";
8
-
9
- export function createAssistantRunReporter({ inspectState, onStatus } = {}) {
10
- return {
11
- reporter: {
12
- outputMode: "compact",
13
-
14
- setServicePlans(plans) {
15
- applyReporterPlans(inspectState, plans);
16
- onStatus?.("Planned run.");
17
- },
18
-
19
- setTotalFileCount(count) {
20
- inspectState.setTotalFileCount(count);
21
- },
22
-
23
- setRegressionCatalog(document) {
24
- inspectState.setRegressionCatalog(document);
25
- },
26
-
27
- serviceSkipped(config, reason) {
28
- inspectState.markServiceSkipped(config.name, reason);
29
- },
30
-
31
- plannedSkip(entry) {
32
- inspectState.markPlannedSkip(entry);
33
- },
34
-
35
- taskStarted(task) {
36
- const suiteKey = `${task.displayType || suiteSelectionType(task.type, task.framework)}:${task.suiteName}`;
37
- applyReporterTaskStarted(inspectState, task, suiteKey);
38
- },
39
-
40
- taskFinished(task, outcome) {
41
- applyReporterTaskFinished(inspectState, task, outcome);
42
- if (outcome.failed) {
43
- onStatus?.(`Failed ${task.file}`);
44
- }
45
- },
46
-
47
- runtimeError(task, message) {
48
- inspectState.markRuntimeError(task, message);
49
- },
50
-
51
- setupOperationFinished() {},
52
-
53
- phaseStarted(label) {
54
- inspectState.setPhase(label);
55
- onStatus?.(label);
56
- },
57
-
58
- toolchainResolved() {},
59
- localServiceStarting() {},
60
- writeLine() {},
61
- writeDebugLine() {},
62
- telemetry() {},
63
-
64
- runSummary(results, durationMs, regressionReport) {
65
- applyReporterRunSummary(inspectState, results, durationMs, regressionReport);
66
- onStatus?.("Run finished.");
67
- },
68
-
69
- error(message) {
70
- onStatus?.(String(message));
71
- },
72
- },
73
-
74
- finalize: Promise.resolve(),
75
-
76
- close() {
77
- // No long-lived UI resources to close.
78
- },
79
- };
80
- }
@@ -1,82 +0,0 @@
1
- import React, { createElement, useEffect, useState } from "react";
2
- import { Box, Text, useAnimation, useApp, useInput } from "ink";
3
- import { dim, yellow } from "../presentation/colors.mjs";
4
- import {
5
- buildAssistantComposerLines,
6
- buildAssistantHeader,
7
- buildAssistantTranscriptLines,
8
- getAssistantLayout,
9
- } from "./assistant-render.mjs";
10
-
11
- const SPINNER_FRAMES = ["|", "/", "-", "\\"];
12
-
13
- export function AssistantApp({ assistantState, stdout } = {}) {
14
- const { exit } = useApp();
15
- const [snapshot, setSnapshot] = useState(() => assistantState.getSnapshot());
16
- const { frame } = useAnimation({ interval: 80, isActive: snapshot.busy });
17
-
18
- useEffect(() => {
19
- const unsubscribe = assistantState.subscribe(() => {
20
- setSnapshot(assistantState.getSnapshot());
21
- });
22
- return unsubscribe;
23
- }, [assistantState]);
24
-
25
- useInput((input, key) => {
26
- if (key.ctrl && input === "c") {
27
- exit();
28
- return;
29
- }
30
- if (input === "q" && !snapshot.composer) {
31
- exit();
32
- return;
33
- }
34
- if (key.return) {
35
- void assistantState.submitCurrentComposer();
36
- return;
37
- }
38
- if (key.backspace || key.delete) {
39
- assistantState.backspaceComposer();
40
- return;
41
- }
42
- if (key.escape) {
43
- assistantState.setComposer("");
44
- return;
45
- }
46
- if (isPrintableInput(input, key)) {
47
- assistantState.appendComposer(input);
48
- }
49
- });
50
-
51
- const spinner = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
52
- const layout = getAssistantLayout(stdout);
53
- const transcriptLines = buildAssistantTranscriptLines(snapshot, {
54
- width: layout.width,
55
- maxLines: layout.transcriptLines,
56
- });
57
- const composerLines = buildAssistantComposerLines(snapshot, { width: layout.width });
58
-
59
- return createElement(
60
- Box,
61
- { flexDirection: "column" },
62
- createElement(Text, null, dim(buildAssistantHeader(snapshot, spinner))),
63
- snapshot.notice ? createElement(Text, null, yellow(snapshot.notice)) : null,
64
- createElement(Text, null, ""),
65
- ...transcriptLines.map((line, index) => createElement(Text, { key: `line-${index}` }, line)),
66
- createElement(Text, null, ""),
67
- ...composerLines.map((line, index) => createElement(Text, { key: `composer-${index}` }, line)),
68
- createElement(Text, null, dim("Enter send · Esc clear draft · q quit"))
69
- );
70
- }
71
-
72
- function isPrintableInput(input, key) {
73
- return Boolean(
74
- input &&
75
- input.length === 1 &&
76
- !key.ctrl &&
77
- !key.meta &&
78
- !key.return &&
79
- !key.escape &&
80
- !key.tab
81
- );
82
- }
@@ -1,99 +0,0 @@
1
- import { bold, cyan, dim, yellow } from "../presentation/colors.mjs";
2
- import {
3
- getTerminalWidth,
4
- measureWidth,
5
- padEndVisible,
6
- wrapText,
7
- } from "../presentation/terminal-layout.mjs";
8
-
9
- export function buildAssistantHeader(snapshot, spinner = "|") {
10
- const status = snapshot.busy ? `${spinner} ${snapshot.activeStatus || "working"}` : "ready";
11
- const focus = snapshot.context.selection?.filePath
12
- || snapshot.context.selection?.label
13
- || "no focus";
14
- return [`testkit assistant`, `provider ${snapshot.provider}`, status, focus].join(" · ");
15
- }
16
-
17
- export function buildAssistantTranscriptLines(snapshot, { width = 100, maxLines = 40 } = {}) {
18
- const blocks = [];
19
- for (const message of snapshot.messages) {
20
- blocks.push(...formatAssistantMessageBlock(message, width));
21
- blocks.push("");
22
- }
23
- if (snapshot.busy) {
24
- blocks.push(dim(`${snapshot.activeStatus || "Working"}...`));
25
- }
26
- return trimTranscript(blocks, maxLines);
27
- }
28
-
29
- export function buildAssistantComposerLines(snapshot, { width = 100 } = {}) {
30
- const boxWidth = Math.max(24, width);
31
- const innerWidth = Math.max(18, boxWidth - 4);
32
- const draft = snapshot.composer || "";
33
- const label = "Message";
34
- const promptPrefix = "> ";
35
- const contentLines = draft
36
- ? wrapText(draft, Math.max(8, innerWidth - measureWidth(promptPrefix)))
37
- : [dim("Ask testkit about failures, runs, logs, or artifacts")];
38
- const top = `┌${"─".repeat(Math.max(0, boxWidth - 2))}┐`;
39
- const labelLine = `│ ${padEndVisible(bold(label), innerWidth)} │`;
40
- const draftLines = contentLines.map((line, index) =>
41
- `│ ${padEndVisible(index === 0 ? `${promptPrefix}${line}` : ` ${line}`, innerWidth)} │`
42
- );
43
- const bottom = `└${"─".repeat(Math.max(0, boxWidth - 2))}┘`;
44
- return [top, labelLine, ...draftLines, bottom];
45
- }
46
-
47
- export function getAssistantLayout(stdout, { transcriptReserve = 9 } = {}) {
48
- const width = getTerminalWidth(stdout, 100);
49
- const rows = Number(stdout?.rows);
50
- const height = Number.isFinite(rows) && rows > 0 ? rows : 40;
51
- const transcriptLines = Math.max(10, height - transcriptReserve);
52
- return {
53
- width,
54
- transcriptLines,
55
- };
56
- }
57
-
58
- export function formatAssistantMessageBlock(message, width = 100) {
59
- const title = formatRoleTitle(message);
60
- const bodyWidth = Math.max(12, width - 2);
61
- const bodyLines = String(message.text || "")
62
- .split(/\r?\n/)
63
- .flatMap((line) => wrapText(line, bodyWidth));
64
- const rendered = [title];
65
- for (const line of bodyLines) {
66
- rendered.push(line ? ` ${line}` : "");
67
- }
68
- if (message.role === "tool" && message.toolName) {
69
- rendered[0] = cyan(title);
70
- } else if (message.role === "assistant") {
71
- rendered[0] = bold(title);
72
- } else if (message.role === "system") {
73
- rendered[0] = yellow(title);
74
- }
75
- return rendered;
76
- }
77
-
78
- function formatRoleTitle(message) {
79
- if (message.role === "user") return "You";
80
- if (message.role === "assistant") return "Testkit";
81
- if (message.role === "tool") return message.title || formatToolName(message.toolName);
82
- return "System";
83
- }
84
-
85
- function formatToolName(toolName) {
86
- const raw = String(toolName || "Tool").trim();
87
- if (!raw) return "Tool";
88
- return raw
89
- .split(/[_-]+/)
90
- .filter(Boolean)
91
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
92
- .join(" ");
93
- }
94
-
95
- function trimTranscript(lines, maxLines) {
96
- if (lines.length <= maxLines) return lines;
97
- const hidden = lines.length - maxLines;
98
- return [dim(`… ${hidden} earlier line${hidden === 1 ? "" : "s"} …`), ...lines.slice(-maxLines + 1)];
99
- }