@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.
- package/README.md +14 -7
- package/lib/cli/agents/index.mjs +27 -19
- package/lib/cli/agents/providers/claude.mjs +3 -3
- package/lib/cli/agents/providers/codex.mjs +3 -3
- package/lib/cli/assistant/app.mjs +210 -0
- package/lib/cli/assistant/context-pack.mjs +191 -0
- package/lib/cli/assistant/interactive.mjs +53 -0
- package/lib/cli/assistant/prompt-builder.mjs +7 -9
- package/lib/cli/assistant/session.mjs +6 -1
- package/lib/cli/assistant/state.mjs +134 -46
- package/lib/cli/assistant/tool-registry.mjs +220 -230
- package/lib/cli/commands/assistant.mjs +50 -34
- package/lib/cli/{tui/detail-pane.mjs → context-resources.mjs} +81 -21
- package/lib/cli/entrypoint.mjs +12 -4
- package/lib/cli/presentation/tree-reporter.mjs +0 -101
- package/lib/cli/tui/inspect-app.mjs +7 -88
- package/lib/cli/tui/inspect-state.mjs +0 -117
- package/node_modules/@elench/next-analysis/package.json +1 -1
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/ts-analysis/package.json +1 -1
- package/package.json +5 -5
- package/lib/cli/agents/investigate.mjs +0 -75
- package/lib/cli/agents/investigation-context.mjs +0 -102
- package/lib/cli/agents/investigation-interpreter.mjs +0 -320
- package/lib/cli/agents/investigation-log.mjs +0 -37
- package/lib/cli/agents/prompt-builder.mjs +0 -25
- package/lib/cli/assistant/content.mjs +0 -60
- package/lib/cli/assistant/tool-run-reporter.mjs +0 -80
- package/lib/cli/tui/assistant-app.mjs +0 -82
- 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
|
-
}
|