@elench/testkit 0.1.94 → 0.1.96
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/lib/cli/assistant/command-plan.mjs +227 -0
- package/lib/cli/assistant/context-pack.mjs +3 -3
- package/lib/cli/assistant/prompt-builder.mjs +4 -1
- package/lib/cli/assistant/tool-registry.mjs +24 -44
- 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
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
const TESTKIT_TYPES = new Set(["int", "e2e", "scenario", "dal", "load", "pw", "all"]);
|
|
2
|
+
const TESTKIT_DIR_COMMANDS = new Set(["run", "discover", "status", "doctor", "destroy", "cleanup", "typecheck"]);
|
|
3
|
+
const PACKAGE_RUNNERS = new Set(["npx", "pnpm", "npm", "yarn", "bun"]);
|
|
4
|
+
|
|
5
|
+
export function extractShellCommand(args = {}) {
|
|
6
|
+
if (!args || typeof args !== "object") return "";
|
|
7
|
+
const value =
|
|
8
|
+
args.command ??
|
|
9
|
+
args.cmd ??
|
|
10
|
+
args.commandString ??
|
|
11
|
+
args.shellCommand ??
|
|
12
|
+
args.input ??
|
|
13
|
+
args.script ??
|
|
14
|
+
"";
|
|
15
|
+
if (Array.isArray(value)) return value.map((part) => shellEscapeArg(part)).join(" ");
|
|
16
|
+
return String(value || "");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function planShellCommand(rawCommand) {
|
|
20
|
+
const raw = String(rawCommand || "").trim();
|
|
21
|
+
if (!raw) {
|
|
22
|
+
return {
|
|
23
|
+
executableCommand: "",
|
|
24
|
+
rawCommand: raw,
|
|
25
|
+
displayCommand: raw,
|
|
26
|
+
command: "",
|
|
27
|
+
title: "Shell command",
|
|
28
|
+
testkitRelated: false,
|
|
29
|
+
normalized: false,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const testkit = planTestkitCommand(raw);
|
|
34
|
+
if (testkit) return testkit;
|
|
35
|
+
|
|
36
|
+
const testkitScript = planTestkitPackageScript(raw);
|
|
37
|
+
if (testkitScript) return testkitScript;
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
executableCommand: raw,
|
|
41
|
+
rawCommand: raw,
|
|
42
|
+
displayCommand: raw,
|
|
43
|
+
command: firstCommandToken(raw),
|
|
44
|
+
title: "Shell command",
|
|
45
|
+
testkitRelated: false,
|
|
46
|
+
normalized: false,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function planTestkitPackageScript(raw) {
|
|
51
|
+
if (containsShellControl(raw)) return null;
|
|
52
|
+
const tokens = tokenizeShellWords(raw);
|
|
53
|
+
if (!tokens || tokens.length < 3) return null;
|
|
54
|
+
if (tokens[0] !== "npm" || tokens[1] !== "run") return null;
|
|
55
|
+
if (tokens[2] !== "testkit" && !tokens[2].startsWith("testkit:")) return null;
|
|
56
|
+
return {
|
|
57
|
+
executableCommand: raw,
|
|
58
|
+
rawCommand: raw,
|
|
59
|
+
displayCommand: raw,
|
|
60
|
+
command: "npm run testkit",
|
|
61
|
+
title: "npm testkit script",
|
|
62
|
+
testkitRelated: true,
|
|
63
|
+
normalized: false,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function planTestkitCommand(raw) {
|
|
68
|
+
if (containsShellControl(raw)) return null;
|
|
69
|
+
const tokens = tokenizeShellWords(raw);
|
|
70
|
+
if (!tokens || tokens.length === 0) return null;
|
|
71
|
+
|
|
72
|
+
const extracted = extractTestkitInvocation(tokens);
|
|
73
|
+
if (!extracted) return null;
|
|
74
|
+
|
|
75
|
+
const canonicalArgs = canonicalizeTestkitArgs(extracted.args);
|
|
76
|
+
const executableCommand = ["testkit", ...canonicalArgs].map(shellEscapeArg).join(" ");
|
|
77
|
+
const wasNormalized = executableCommand !== raw;
|
|
78
|
+
return {
|
|
79
|
+
executableCommand,
|
|
80
|
+
rawCommand: raw,
|
|
81
|
+
displayCommand: executableCommand,
|
|
82
|
+
command: "testkit",
|
|
83
|
+
title: "testkit command",
|
|
84
|
+
testkitRelated: true,
|
|
85
|
+
normalized: wasNormalized,
|
|
86
|
+
normalizationReason: wasNormalized ? extracted.reason : null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function extractTestkitInvocation(tokens) {
|
|
91
|
+
if (tokens[0] === "testkit") {
|
|
92
|
+
return {
|
|
93
|
+
args: tokens.slice(1),
|
|
94
|
+
reason: "canonicalized local testkit invocation",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!PACKAGE_RUNNERS.has(tokens[0])) return null;
|
|
99
|
+
|
|
100
|
+
if (tokens[0] === "npm" && ["exec", "x"].includes(tokens[1])) {
|
|
101
|
+
const index = findPackageTarget(tokens, 2);
|
|
102
|
+
if (index >= 0) return { args: tokens.slice(index + 1), reason: "replaced npm exec testkit with local testkit" };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (tokens[0] === "npx") {
|
|
106
|
+
const index = findPackageTarget(tokens, 1);
|
|
107
|
+
if (index >= 0) return { args: tokens.slice(index + 1), reason: "replaced npx testkit with local testkit" };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (tokens[0] === "pnpm" && ["exec", "dlx"].includes(tokens[1])) {
|
|
111
|
+
const index = findPackageTarget(tokens, 2);
|
|
112
|
+
if (index >= 0) return { args: tokens.slice(index + 1), reason: "replaced pnpm testkit launcher with local testkit" };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (tokens[0] === "yarn" && tokens[1] === "testkit") {
|
|
116
|
+
return { args: tokens.slice(2), reason: "replaced yarn testkit launcher with local testkit" };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (tokens[0] === "bun" && ["x", "run"].includes(tokens[1])) {
|
|
120
|
+
const index = findPackageTarget(tokens, 2);
|
|
121
|
+
if (index >= 0) return { args: tokens.slice(index + 1), reason: "replaced bun testkit launcher with local testkit" };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function canonicalizeTestkitArgs(inputArgs) {
|
|
128
|
+
const args = [...inputArgs];
|
|
129
|
+
if (args.length === 0) return [];
|
|
130
|
+
|
|
131
|
+
if (TESTKIT_TYPES.has(args[0])) {
|
|
132
|
+
return withDir(["run", "--type", args[0], ...args.slice(1)]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!TESTKIT_DIR_COMMANDS.has(args[0])) {
|
|
136
|
+
return args;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (args[0] === "run") {
|
|
140
|
+
const runArgs = [...args];
|
|
141
|
+
if (TESTKIT_TYPES.has(runArgs[1])) {
|
|
142
|
+
const type = runArgs.splice(1, 1)[0];
|
|
143
|
+
if (!hasFlag(runArgs, "--type", "-t")) runArgs.splice(1, 0, "--type", type);
|
|
144
|
+
}
|
|
145
|
+
return withDir(runArgs);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return withDir(args);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function withDir(args) {
|
|
152
|
+
if (hasFlag(args, "--dir", "-d") || args.includes("--help") || args.includes("-h")) return args;
|
|
153
|
+
const [command, ...rest] = args;
|
|
154
|
+
return [command, "--dir", ".", ...rest];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function hasFlag(args, longFlag, shortFlag) {
|
|
158
|
+
return args.some((arg) => arg === longFlag || arg.startsWith(`${longFlag}=`) || arg === shortFlag);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function findPackageTarget(tokens, startIndex) {
|
|
162
|
+
for (let index = startIndex; index < tokens.length; index += 1) {
|
|
163
|
+
const token = tokens[index];
|
|
164
|
+
if (token === "--") continue;
|
|
165
|
+
if (token === "testkit" || token === "@elench/testkit") return index;
|
|
166
|
+
if (!token.startsWith("-")) return -1;
|
|
167
|
+
}
|
|
168
|
+
return -1;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function firstCommandToken(command) {
|
|
172
|
+
const tokens = tokenizeShellWords(command);
|
|
173
|
+
return tokens?.[0] || command.split(/\s+/)[0] || "command";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function containsShellControl(command) {
|
|
177
|
+
return /[\n;&|<>`]/.test(command);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function tokenizeShellWords(command) {
|
|
181
|
+
const words = [];
|
|
182
|
+
let current = "";
|
|
183
|
+
let quote = null;
|
|
184
|
+
let escaping = false;
|
|
185
|
+
|
|
186
|
+
for (const char of String(command)) {
|
|
187
|
+
if (escaping) {
|
|
188
|
+
current += char;
|
|
189
|
+
escaping = false;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (char === "\\") {
|
|
193
|
+
escaping = true;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (quote) {
|
|
197
|
+
if (char === quote) {
|
|
198
|
+
quote = null;
|
|
199
|
+
} else {
|
|
200
|
+
current += char;
|
|
201
|
+
}
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (char === "'" || char === '"') {
|
|
205
|
+
quote = char;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (/\s/.test(char)) {
|
|
209
|
+
if (current) {
|
|
210
|
+
words.push(current);
|
|
211
|
+
current = "";
|
|
212
|
+
}
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
current += char;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (escaping || quote) return null;
|
|
219
|
+
if (current) words.push(current);
|
|
220
|
+
return words;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function shellEscapeArg(value) {
|
|
224
|
+
const stringValue = String(value);
|
|
225
|
+
if (/^[a-zA-Z0-9._:@/%+=,-]+$/.test(stringValue)) return stringValue;
|
|
226
|
+
return `'${stringValue.replace(/'/g, `'\\''`)}'`;
|
|
227
|
+
}
|
|
@@ -160,7 +160,9 @@ function buildContextMarkdown(productDir, snapshot, paths) {
|
|
|
160
160
|
lines.push(
|
|
161
161
|
"",
|
|
162
162
|
"## Guidance",
|
|
163
|
-
"- Use
|
|
163
|
+
"- Use the local `testkit` command directly when you need to execute or inspect tests.",
|
|
164
|
+
"- Preferred commands: `testkit run --dir . --type <type>`, `testkit discover --dir .`, `testkit status --dir .`, and `testkit doctor --dir .`.",
|
|
165
|
+
"- Do not launch testkit through pnpm, npm, yarn, bun, or npx unless the user explicitly asks for that exact package-manager command.",
|
|
164
166
|
"- Use the command log and focused context files before rereading artifacts manually.",
|
|
165
167
|
"- Prefer repo-local commands over guessing project-specific wrappers.",
|
|
166
168
|
""
|
|
@@ -180,8 +182,6 @@ function buildCommandsMarkdown() {
|
|
|
180
182
|
"- `testkit status --dir .`",
|
|
181
183
|
"- `testkit doctor --dir .`",
|
|
182
184
|
"- `testkit destroy --dir .`",
|
|
183
|
-
"- `npm run testkit`",
|
|
184
|
-
"- `npx testkit --dir . --type e2e`",
|
|
185
185
|
"",
|
|
186
186
|
].join("\n");
|
|
187
187
|
}
|
|
@@ -16,8 +16,11 @@ export function buildAssistantPrompt({
|
|
|
16
16
|
"You are Testkit Assistant.",
|
|
17
17
|
"You help users run tests, inspect failures, read logs and artifacts, and navigate the current local test state.",
|
|
18
18
|
"All user natural-language requests must be handled through your own reasoning plus the available tools.",
|
|
19
|
-
"
|
|
19
|
+
"Use shell_exec when the user asks to run tests or inspect the working repo.",
|
|
20
|
+
"For testkit work, invoke the local `testkit` command directly, for example `testkit run --dir . --type e2e` or `testkit discover --dir .`.",
|
|
21
|
+
"Do not wrap testkit with pnpm, npm, yarn, bun, or npx unless the user explicitly asks for that exact package-manager command.",
|
|
20
22
|
"Use read_context before repeating artifact/log inspection work, and use read_file/search_repo when you need codebase context.",
|
|
23
|
+
"After a tool result, describe only what the tool result actually says. Do not invent filesystem, sandbox, package-manager, or permission errors.",
|
|
21
24
|
buildAssistantResponseContract({ tools }),
|
|
22
25
|
"",
|
|
23
26
|
"Current run summary:",
|
|
@@ -5,6 +5,7 @@ import { loadCurrentRunArtifact, loadLatestRunArtifact, resolveFileSubject } fro
|
|
|
5
5
|
import {
|
|
6
6
|
readContextContent,
|
|
7
7
|
} from "../context-resources.mjs";
|
|
8
|
+
import { extractShellCommand, planShellCommand } from "./command-plan.mjs";
|
|
8
9
|
|
|
9
10
|
const COMMAND_OUTPUT_LIMIT = 14_000;
|
|
10
11
|
const COMMAND_LINE_LIMIT = 220;
|
|
@@ -14,7 +15,7 @@ export function listAssistantTools() {
|
|
|
14
15
|
return [
|
|
15
16
|
{
|
|
16
17
|
name: "shell_exec",
|
|
17
|
-
description: "Execute a shell command inside the repository.
|
|
18
|
+
description: "Execute a shell command inside the repository. Use local testkit commands for testkit work.",
|
|
18
19
|
},
|
|
19
20
|
{
|
|
20
21
|
name: "read_context",
|
|
@@ -48,10 +49,10 @@ export async function executeAssistantTool(name, argumentsObject, context) {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
async function shellExecTool(args, context) {
|
|
51
|
-
const command =
|
|
52
|
+
const command = extractShellCommand(args).trim();
|
|
52
53
|
if (!command) throw new Error("shell_exec requires a command string");
|
|
53
54
|
|
|
54
|
-
const shellCommand =
|
|
55
|
+
const shellCommand = planShellCommand(command);
|
|
55
56
|
const commandId = `cmd-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
56
57
|
context.commandLog?.appendCommandLog({
|
|
57
58
|
type: "command_start",
|
|
@@ -59,17 +60,22 @@ async function shellExecTool(args, context) {
|
|
|
59
60
|
commandId,
|
|
60
61
|
cwd: context.productDir,
|
|
61
62
|
raw: command,
|
|
63
|
+
executable: shellCommand.executableCommand,
|
|
64
|
+
normalized: shellCommand.normalized,
|
|
62
65
|
});
|
|
63
66
|
context.onEvent?.({
|
|
64
67
|
type: "tool-start",
|
|
65
68
|
tool: "shell_exec",
|
|
66
|
-
command,
|
|
69
|
+
command: shellCommand.executableCommand,
|
|
70
|
+
rawCommand: command,
|
|
67
71
|
title: shellCommand.title,
|
|
68
72
|
testkitRelated: shellCommand.testkitRelated,
|
|
69
|
-
message:
|
|
73
|
+
message: shellCommand.normalized
|
|
74
|
+
? `Running ${shellCommand.displayCommand} (${shellCommand.normalizationReason})`
|
|
75
|
+
: `Running ${shellCommand.displayCommand}`,
|
|
70
76
|
});
|
|
71
77
|
|
|
72
|
-
const result = await execaCommand(
|
|
78
|
+
const result = await execaCommand(shellCommand.executableCommand, {
|
|
73
79
|
cwd: context.productDir,
|
|
74
80
|
reject: false,
|
|
75
81
|
shell: true,
|
|
@@ -87,18 +93,21 @@ async function shellExecTool(args, context) {
|
|
|
87
93
|
commandId,
|
|
88
94
|
cwd: context.productDir,
|
|
89
95
|
raw: command,
|
|
96
|
+
executable: shellCommand.executableCommand,
|
|
97
|
+
normalized: shellCommand.normalized,
|
|
90
98
|
code: result.exitCode ?? 0,
|
|
91
99
|
signal: result.signal ?? null,
|
|
92
100
|
});
|
|
93
101
|
context.onEvent?.({
|
|
94
102
|
type: "tool-exit",
|
|
95
103
|
tool: "shell_exec",
|
|
96
|
-
command,
|
|
104
|
+
command: shellCommand.executableCommand,
|
|
105
|
+
rawCommand: command,
|
|
97
106
|
title: shellCommand.title,
|
|
98
107
|
testkitRelated: shellCommand.testkitRelated,
|
|
99
108
|
code: result.exitCode ?? 0,
|
|
100
109
|
signal: result.signal ?? null,
|
|
101
|
-
message: `${shellCommand.
|
|
110
|
+
message: `${shellCommand.displayCommand} exited ${result.exitCode ?? 0}`,
|
|
102
111
|
});
|
|
103
112
|
|
|
104
113
|
if (shellCommand.testkitRelated) {
|
|
@@ -106,13 +115,15 @@ async function shellExecTool(args, context) {
|
|
|
106
115
|
}
|
|
107
116
|
context.commandLog?.refresh?.();
|
|
108
117
|
|
|
109
|
-
const lines = formatCommandResult(
|
|
118
|
+
const lines = formatCommandResult(result, shellCommand);
|
|
110
119
|
return {
|
|
111
120
|
ok: (result.exitCode ?? 0) === 0,
|
|
112
121
|
title: shellCommand.title,
|
|
113
122
|
text: lines.join("\n"),
|
|
114
123
|
data: {
|
|
115
124
|
command,
|
|
125
|
+
executableCommand: shellCommand.executableCommand,
|
|
126
|
+
normalizedCommand: shellCommand.normalized,
|
|
116
127
|
stdout: result.stdout || "",
|
|
117
128
|
stderr: result.stderr || "",
|
|
118
129
|
exitCode: result.exitCode ?? 0,
|
|
@@ -216,42 +227,11 @@ async function searchRepoTool(args, context) {
|
|
|
216
227
|
};
|
|
217
228
|
}
|
|
218
229
|
|
|
219
|
-
function
|
|
220
|
-
const
|
|
221
|
-
if (
|
|
222
|
-
|
|
223
|
-
command: "testkit",
|
|
224
|
-
display: normalized,
|
|
225
|
-
title: "testkit command",
|
|
226
|
-
testkitRelated: true,
|
|
227
|
-
};
|
|
230
|
+
function formatCommandResult(result, shellCommand) {
|
|
231
|
+
const lines = [`$ ${shellCommand.displayCommand}`];
|
|
232
|
+
if (shellCommand.normalized) {
|
|
233
|
+
lines.push(`normalized from: ${shellCommand.rawCommand}`);
|
|
228
234
|
}
|
|
229
|
-
if (/^(npx)\s+testkit\b/.test(normalized)) {
|
|
230
|
-
return {
|
|
231
|
-
command: "npx testkit",
|
|
232
|
-
display: normalized,
|
|
233
|
-
title: "npx testkit",
|
|
234
|
-
testkitRelated: true,
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
if (/^(npm)\s+run\s+testkit\b/.test(normalized) || /^(npm)\s+run\s+testkit:/.test(normalized)) {
|
|
238
|
-
return {
|
|
239
|
-
command: "npm run testkit",
|
|
240
|
-
display: normalized,
|
|
241
|
-
title: "npm testkit script",
|
|
242
|
-
testkitRelated: true,
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
return {
|
|
246
|
-
command: normalized.split(/\s+/)[0] || "command",
|
|
247
|
-
display: normalized,
|
|
248
|
-
title: "Shell command",
|
|
249
|
-
testkitRelated: false,
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function formatCommandResult(command, result, shellCommand) {
|
|
254
|
-
const lines = [`$ ${command}`];
|
|
255
235
|
const stdout = (result.stdout || "").trim();
|
|
256
236
|
const stderr = (result.stderr || "").trim();
|
|
257
237
|
const merged = [];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.96",
|
|
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.
|
|
25
|
+
"@elench/testkit-protocol": "0.1.96"
|
|
26
26
|
},
|
|
27
27
|
"private": false
|
|
28
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.96",
|
|
4
4
|
"description": "Assistant-first CLI for running, inspecting, and debugging local testkit suites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -83,10 +83,10 @@
|
|
|
83
83
|
},
|
|
84
84
|
"dependencies": {
|
|
85
85
|
"@babel/code-frame": "^7.29.0",
|
|
86
|
-
"@elench/next-analysis": "0.1.
|
|
87
|
-
"@elench/testkit-bridge": "0.1.
|
|
88
|
-
"@elench/testkit-protocol": "0.1.
|
|
89
|
-
"@elench/ts-analysis": "0.1.
|
|
86
|
+
"@elench/next-analysis": "0.1.96",
|
|
87
|
+
"@elench/testkit-bridge": "0.1.96",
|
|
88
|
+
"@elench/testkit-protocol": "0.1.96",
|
|
89
|
+
"@elench/ts-analysis": "0.1.96",
|
|
90
90
|
"@oclif/core": "^4.10.6",
|
|
91
91
|
"esbuild": "^0.25.11",
|
|
92
92
|
"execa": "^9.5.0",
|