@elench/testkit 0.1.111 → 0.1.112
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 +1 -1
- package/lib/cli/args.mjs +1 -1
- package/lib/cli/assistant/app.mjs +70 -20
- package/lib/cli/assistant/command-normalize.mjs +22 -0
- package/lib/cli/assistant/command-observer.mjs +49 -4
- package/lib/cli/assistant/command-results.mjs +10 -1
- package/lib/cli/assistant/context-pack.mjs +45 -15
- package/lib/cli/assistant/domain.d.mts +59 -0
- package/lib/cli/assistant/domain.d.mts.map +1 -0
- package/lib/cli/assistant/domain.mjs +2 -0
- package/lib/cli/assistant/domain.mjs.map +1 -0
- package/lib/cli/assistant/session.mjs +3 -1
- package/lib/cli/assistant/state.mjs +109 -2
- package/lib/cli/assistant/view-model.mjs +69 -9
- package/lib/cli/commands/run.mjs +1 -1
- package/lib/cli/components/blocks/run-tree.mjs +2 -1
- package/lib/cli/entrypoint.mjs +1 -1
- package/lib/config/discovery.mjs +0 -10
- package/lib/discovery/index.mjs +1 -1
- package/lib/domain/test-types.mjs +5 -14
- package/lib/runner/maintenance.mjs +1 -1
- package/lib/runner/provenance.mjs +4 -1
- package/lib/runner/status-model.mjs +15 -7
- package/lib/runner/suite-selection.mjs +2 -3
- 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/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.d.ts +0 -188
- package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.d.ts.map +0 -1
- package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.js +0 -293
- package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.js.map +0 -1
- package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/package.json +0 -25
|
@@ -17,6 +17,7 @@ import { parseSlashCommand, formatSlashHelpLines } from "./slash-commands.mjs";
|
|
|
17
17
|
import { executeAssistantAction } from "./actions.mjs";
|
|
18
18
|
import { runAssistantConversationTurn } from "./session.mjs";
|
|
19
19
|
import { prepareAssistantContextPack } from "./context-pack.mjs";
|
|
20
|
+
import { normalizeCommandLine, unwrapShellCommand } from "./command-normalize.mjs";
|
|
20
21
|
import {
|
|
21
22
|
discoverAssistantModels,
|
|
22
23
|
formatModelChoices,
|
|
@@ -88,6 +89,7 @@ export function createAssistantState({
|
|
|
88
89
|
let activeStatus = null;
|
|
89
90
|
let startupNoticeEmitted = false;
|
|
90
91
|
let lastTurnError = null;
|
|
92
|
+
let activeTurn = idleTurn();
|
|
91
93
|
let contextUsage = buildContextUsage({
|
|
92
94
|
provider: resolvedProviderName || settings.provider,
|
|
93
95
|
model: settings.model,
|
|
@@ -109,6 +111,7 @@ export function createAssistantState({
|
|
|
109
111
|
function appendMessage(message) {
|
|
110
112
|
const entry = {
|
|
111
113
|
id: `msg-${messages.length + 1}`,
|
|
114
|
+
...(activeTurn?.id && !message.turnId ? { turnId: activeTurn.id } : {}),
|
|
112
115
|
...message,
|
|
113
116
|
};
|
|
114
117
|
messages.push(entry);
|
|
@@ -182,6 +185,7 @@ export function createAssistantState({
|
|
|
182
185
|
commandLog,
|
|
183
186
|
attachRunSession,
|
|
184
187
|
completeRunSession,
|
|
188
|
+
updateMessage,
|
|
185
189
|
|
|
186
190
|
async loadLatestArtifact() {
|
|
187
191
|
try {
|
|
@@ -364,7 +368,8 @@ export function createAssistantState({
|
|
|
364
368
|
startupNoticeEmitted = true;
|
|
365
369
|
appendMessage({ role: "system", text: notice });
|
|
366
370
|
}
|
|
367
|
-
|
|
371
|
+
const turnId = createAssistantTurnId();
|
|
372
|
+
appendMessage({ role: "user", text: trimmed, turnId });
|
|
368
373
|
|
|
369
374
|
const slash = parseSlashCommandSafe(trimmed);
|
|
370
375
|
if (slash?.type === "__error__") {
|
|
@@ -373,6 +378,8 @@ export function createAssistantState({
|
|
|
373
378
|
}
|
|
374
379
|
if (slash) {
|
|
375
380
|
try {
|
|
381
|
+
activeTurn = { id: turnId, state: "slash_running", input: trimmed, startedAt: new Date().toISOString() };
|
|
382
|
+
commandLog.setActiveTurnId?.(turnId);
|
|
376
383
|
setBusy(true, `Running ${slash.type}...`);
|
|
377
384
|
await executeSlashCommand({
|
|
378
385
|
slash,
|
|
@@ -389,6 +396,14 @@ export function createAssistantState({
|
|
|
389
396
|
text: error instanceof Error ? error.message : String(error),
|
|
390
397
|
});
|
|
391
398
|
} finally {
|
|
399
|
+
commandLog.setActiveTurnId?.(null);
|
|
400
|
+
activeTurn = {
|
|
401
|
+
id: turnId,
|
|
402
|
+
state: "idle",
|
|
403
|
+
input: trimmed,
|
|
404
|
+
startedAt: activeTurn?.startedAt || new Date().toISOString(),
|
|
405
|
+
finishedAt: new Date().toISOString(),
|
|
406
|
+
};
|
|
392
407
|
setBusy(false, null);
|
|
393
408
|
}
|
|
394
409
|
refreshContextPack();
|
|
@@ -397,6 +412,8 @@ export function createAssistantState({
|
|
|
397
412
|
}
|
|
398
413
|
|
|
399
414
|
try {
|
|
415
|
+
activeTurn = { id: turnId, state: "provider_running", input: trimmed, startedAt: new Date().toISOString() };
|
|
416
|
+
commandLog.setActiveTurnId?.(turnId);
|
|
400
417
|
setBusy(true, `Thinking with ${settings.provider === "auto" ? "provider" : settings.provider}...`);
|
|
401
418
|
const providerTurn = createProviderTurnState();
|
|
402
419
|
await runAssistantConversationTurn({
|
|
@@ -405,6 +422,7 @@ export function createAssistantState({
|
|
|
405
422
|
transcript: buildConversationTranscript(messages),
|
|
406
423
|
userMessage: trimmed,
|
|
407
424
|
settings,
|
|
425
|
+
turnId,
|
|
408
426
|
env,
|
|
409
427
|
configs,
|
|
410
428
|
commandLog,
|
|
@@ -447,8 +465,26 @@ export function createAssistantState({
|
|
|
447
465
|
role: "system",
|
|
448
466
|
text: lastTurnError.message,
|
|
449
467
|
});
|
|
468
|
+
activeTurn = {
|
|
469
|
+
id: turnId,
|
|
470
|
+
state: "failed",
|
|
471
|
+
input: trimmed,
|
|
472
|
+
startedAt: activeTurn?.startedAt || new Date().toISOString(),
|
|
473
|
+
failedAt: new Date().toISOString(),
|
|
474
|
+
error: lastTurnError,
|
|
475
|
+
};
|
|
450
476
|
} finally {
|
|
477
|
+
commandLog.setActiveTurnId?.(null);
|
|
451
478
|
refreshContextPack();
|
|
479
|
+
if (activeTurn?.id === turnId && activeTurn.state !== "failed") {
|
|
480
|
+
activeTurn = {
|
|
481
|
+
id: turnId,
|
|
482
|
+
state: "idle",
|
|
483
|
+
input: trimmed,
|
|
484
|
+
startedAt: activeTurn?.startedAt || new Date().toISOString(),
|
|
485
|
+
finishedAt: new Date().toISOString(),
|
|
486
|
+
};
|
|
487
|
+
}
|
|
452
488
|
setBusy(false, null);
|
|
453
489
|
}
|
|
454
490
|
},
|
|
@@ -459,11 +495,13 @@ export function createAssistantState({
|
|
|
459
495
|
},
|
|
460
496
|
|
|
461
497
|
getSnapshot() {
|
|
498
|
+
const snapshotMessages = messages.map(serializeMessageForSnapshot);
|
|
462
499
|
return {
|
|
463
500
|
context: buildContextSelection(runState.getSnapshot()),
|
|
464
501
|
run: runState.getSnapshot(),
|
|
465
502
|
productDir,
|
|
466
|
-
messages:
|
|
503
|
+
messages: snapshotMessages,
|
|
504
|
+
activities: buildSnapshotActivities(snapshotMessages),
|
|
467
505
|
composer: composerState.text,
|
|
468
506
|
composerCursor: composerState.cursor,
|
|
469
507
|
notice,
|
|
@@ -475,6 +513,7 @@ export function createAssistantState({
|
|
|
475
513
|
providerArgs: [...settings.providerArgs],
|
|
476
514
|
cliConfig,
|
|
477
515
|
activeStatus,
|
|
516
|
+
turn: activeTurn,
|
|
478
517
|
lastTurnError,
|
|
479
518
|
diagnostics: [...diagnostics],
|
|
480
519
|
contextUsage,
|
|
@@ -508,6 +547,17 @@ function resolveInitialProvider(provider, env) {
|
|
|
508
547
|
return null;
|
|
509
548
|
}
|
|
510
549
|
|
|
550
|
+
function idleTurn() {
|
|
551
|
+
return {
|
|
552
|
+
id: null,
|
|
553
|
+
state: "idle",
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function createAssistantTurnId(now = Date.now(), random = Math.random) {
|
|
558
|
+
return `turn-${now}-${random().toString(36).slice(2, 10)}`;
|
|
559
|
+
}
|
|
560
|
+
|
|
511
561
|
async function executeSlashCommand({
|
|
512
562
|
slash,
|
|
513
563
|
state,
|
|
@@ -664,6 +714,7 @@ function handleAssistantToolEvent(state, event, appendMessage) {
|
|
|
664
714
|
return;
|
|
665
715
|
}
|
|
666
716
|
if (event.type === "observed-testkit-command") {
|
|
717
|
+
suppressMatchingProviderCommand(state, event.command);
|
|
667
718
|
appendMessage({
|
|
668
719
|
role: "tool",
|
|
669
720
|
toolName: event.command?.kind || "testkit",
|
|
@@ -679,6 +730,28 @@ function handleAssistantToolEvent(state, event, appendMessage) {
|
|
|
679
730
|
}
|
|
680
731
|
}
|
|
681
732
|
|
|
733
|
+
function suppressMatchingProviderCommand(state, command) {
|
|
734
|
+
const observed = normalizeCommandLine(formatObservedCommandLine(command));
|
|
735
|
+
if (!observed) return;
|
|
736
|
+
const snapshot = state.getSnapshot();
|
|
737
|
+
for (const message of snapshot.messages || []) {
|
|
738
|
+
if (message.role !== "provider-tool") continue;
|
|
739
|
+
const providerCommand = normalizeCommandLine(providerToolCommandLine(message.data));
|
|
740
|
+
if (!providerCommand || providerCommand !== observed) continue;
|
|
741
|
+
state.updateMessage?.(message.id, (current) => ({
|
|
742
|
+
data: {
|
|
743
|
+
...(current.data || {}),
|
|
744
|
+
supersededByTestkitCommand: command?.commandId || true,
|
|
745
|
+
},
|
|
746
|
+
}));
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function providerToolCommandLine(event) {
|
|
751
|
+
if (!event) return null;
|
|
752
|
+
return event.input || event.data?.command || event.data?.input || null;
|
|
753
|
+
}
|
|
754
|
+
|
|
682
755
|
function createProviderTurnState() {
|
|
683
756
|
return {
|
|
684
757
|
assistantMessageId: null,
|
|
@@ -940,6 +1013,40 @@ function serializeMessageForSnapshot(message) {
|
|
|
940
1013
|
};
|
|
941
1014
|
}
|
|
942
1015
|
|
|
1016
|
+
function buildSnapshotActivities(messages) {
|
|
1017
|
+
return (messages || []).map((message) => {
|
|
1018
|
+
const base = {
|
|
1019
|
+
id: message.id,
|
|
1020
|
+
turnId: message.turnId || null,
|
|
1021
|
+
title: message.title || null,
|
|
1022
|
+
text: message.text || "",
|
|
1023
|
+
status: message.status || null,
|
|
1024
|
+
data: message.data || null,
|
|
1025
|
+
};
|
|
1026
|
+
if (message.role === "user") return { ...base, kind: "user_message" };
|
|
1027
|
+
if (message.role === "assistant") return { ...base, kind: "assistant_message" };
|
|
1028
|
+
if (message.role === "provider-tool") {
|
|
1029
|
+
return {
|
|
1030
|
+
...base,
|
|
1031
|
+
kind: "provider_command",
|
|
1032
|
+
command: providerToolCommandLine(message.data),
|
|
1033
|
+
supersededBy: message.data?.supersededByTestkitCommand || null,
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
if (message.role === "provider-activity") return { ...base, kind: "provider_status" };
|
|
1037
|
+
if (message.role === "tool" && message.data?.testkitRelated) {
|
|
1038
|
+
const kind = message.data?.kind === "run" ? "testkit_run" : "testkit_command";
|
|
1039
|
+
return {
|
|
1040
|
+
...base,
|
|
1041
|
+
kind,
|
|
1042
|
+
command: message.data?.command || null,
|
|
1043
|
+
commandId: message.data?.commandId || null,
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
return { ...base, kind: "system_message" };
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
|
|
943
1050
|
function buildConversationTranscript(messages) {
|
|
944
1051
|
return (messages || [])
|
|
945
1052
|
.filter((entry) => !["provider-activity", "provider-tool", "provider-error"].includes(entry.role))
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import { formatContextRemaining } from "./context-window.mjs";
|
|
3
|
+
import { normalizeCommandLine, unwrapShellCommand } from "./command-normalize.mjs";
|
|
3
4
|
|
|
4
5
|
const PROVIDER_COMMAND_OUTPUT_PREVIEW_LINES = 12;
|
|
5
6
|
const PROVIDER_FILE_READ_OUTPUT_PREVIEW_LINES = 8;
|
|
@@ -13,7 +14,7 @@ export function buildAssistantViewModel(snapshot, { cwd = process.cwd(), termina
|
|
|
13
14
|
title: `testkit · ${repoName}`,
|
|
14
15
|
welcome: buildWelcomeModel(snapshot, { cwd, providerLabel }),
|
|
15
16
|
qualitySignal: buildQualitySignal(snapshot),
|
|
16
|
-
blocks: buildTranscriptBlocks(snapshot.messages || []),
|
|
17
|
+
blocks: snapshot.activities ? buildActivityBlocks(snapshot.activities) : buildTranscriptBlocks(snapshot.messages || []),
|
|
17
18
|
composer: {
|
|
18
19
|
text: snapshot.composer || "",
|
|
19
20
|
cursor: snapshot.composerCursor ?? 0,
|
|
@@ -134,7 +135,8 @@ export function buildWelcomeModel(snapshot, { cwd = process.cwd(), providerLabel
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
export function buildTranscriptBlocks(messages) {
|
|
137
|
-
|
|
138
|
+
const visibleMessages = filterSupersededProviderCommands(messages || []);
|
|
139
|
+
return visibleMessages.map((message) => {
|
|
138
140
|
const role = message.role || "system";
|
|
139
141
|
if (role === "tool") {
|
|
140
142
|
const outputPreview = summarizeOutput(message.text || "", TESTKIT_COMMAND_OUTPUT_PREVIEW_LINES);
|
|
@@ -204,6 +206,71 @@ export function buildTranscriptBlocks(messages) {
|
|
|
204
206
|
});
|
|
205
207
|
}
|
|
206
208
|
|
|
209
|
+
export function buildActivityBlocks(activities) {
|
|
210
|
+
const messages = (activities || [])
|
|
211
|
+
.filter((activity) => !activity.supersededBy)
|
|
212
|
+
.map((activity) => {
|
|
213
|
+
if (activity.kind === "user_message") return { id: activity.id, role: "user", text: activity.text || "" };
|
|
214
|
+
if (activity.kind === "assistant_message") return { id: activity.id, role: "assistant", text: activity.text || "" };
|
|
215
|
+
if (activity.kind === "provider_command") {
|
|
216
|
+
return {
|
|
217
|
+
id: activity.id,
|
|
218
|
+
role: "provider-tool",
|
|
219
|
+
title: activity.title || "provider command",
|
|
220
|
+
text: activity.text || "",
|
|
221
|
+
status: activity.status || null,
|
|
222
|
+
data: {
|
|
223
|
+
...(activity.data || {}),
|
|
224
|
+
input: activity.command || activity.data?.input || null,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
if (activity.kind === "provider_status") {
|
|
229
|
+
return {
|
|
230
|
+
id: activity.id,
|
|
231
|
+
role: "provider-activity",
|
|
232
|
+
title: activity.title || null,
|
|
233
|
+
text: activity.text || "",
|
|
234
|
+
status: activity.status || null,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (activity.kind === "testkit_command" || activity.kind === "testkit_run") {
|
|
238
|
+
return {
|
|
239
|
+
id: activity.id,
|
|
240
|
+
role: "tool",
|
|
241
|
+
title: activity.title || (activity.kind === "testkit_run" ? "testkit run" : "testkit command"),
|
|
242
|
+
text: activity.text || "",
|
|
243
|
+
status: activity.status || null,
|
|
244
|
+
data: {
|
|
245
|
+
...(activity.data || {}),
|
|
246
|
+
testkitRelated: true,
|
|
247
|
+
kind: activity.kind === "testkit_run" ? "run" : activity.data?.kind,
|
|
248
|
+
command: activity.command || activity.data?.command || null,
|
|
249
|
+
commandId: activity.commandId || activity.data?.commandId || null,
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return { id: activity.id, role: "system", title: activity.title || null, text: activity.text || "" };
|
|
254
|
+
});
|
|
255
|
+
return buildTranscriptBlocks(messages);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function filterSupersededProviderCommands(messages) {
|
|
259
|
+
const observedCommands = new Set(
|
|
260
|
+
(messages || [])
|
|
261
|
+
.filter((message) => message.role === "tool" && message.data?.testkitRelated)
|
|
262
|
+
.map((message) => normalizeCommandLine(message.data?.command))
|
|
263
|
+
.filter(Boolean)
|
|
264
|
+
);
|
|
265
|
+
return (messages || []).filter((message) => {
|
|
266
|
+
if (message.role !== "provider-tool") return true;
|
|
267
|
+
if (message.data?.supersededByTestkitCommand) return false;
|
|
268
|
+
const providerCommand = normalizeCommandLine(message.data?.input || message.data?.data?.command || message.data?.data?.input);
|
|
269
|
+
if (!providerCommand) return true;
|
|
270
|
+
return !observedCommands.has(providerCommand);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
207
274
|
export function buildStatusLine(snapshot, { cwd = process.cwd(), providerLabel = null } = {}) {
|
|
208
275
|
const context = formatContextRemaining(snapshot.contextUsage);
|
|
209
276
|
const provider = providerLabel || buildProviderLabel(snapshot);
|
|
@@ -339,13 +406,6 @@ function isDiffProducingShellCommand(command) {
|
|
|
339
406
|
return /^((git\s+diff|git\s+show|diff|apply_patch)\b|.*\bapply_patch\b)/.test(normalized);
|
|
340
407
|
}
|
|
341
408
|
|
|
342
|
-
function unwrapShellCommand(command) {
|
|
343
|
-
const text = String(command || "").trim();
|
|
344
|
-
const match = text.match(/(?:^|\s)(?:[^\s'"]*\/)?(?:bash|sh|zsh)\s+-lc\s+(['"])([\s\S]*)\1\s*$/);
|
|
345
|
-
if (!match) return text;
|
|
346
|
-
return match[2].replace(/\\(["'\\$`])/g, "$1").trim();
|
|
347
|
-
}
|
|
348
|
-
|
|
349
409
|
function stringifyMaybe(value) {
|
|
350
410
|
if (value == null) return "";
|
|
351
411
|
if (typeof value === "string") return value;
|
package/lib/cli/commands/run.mjs
CHANGED
|
@@ -14,7 +14,7 @@ export default class RunCommand extends Command {
|
|
|
14
14
|
type: Args.string({
|
|
15
15
|
description: `Optional suite type shortcut: ${publicTestTypeListText({ includeAll: true })}`,
|
|
16
16
|
required: false,
|
|
17
|
-
options: publicTestTypeList({ includeAll: true
|
|
17
|
+
options: publicTestTypeList({ includeAll: true }),
|
|
18
18
|
}),
|
|
19
19
|
};
|
|
20
20
|
|
|
@@ -25,8 +25,9 @@ export function RunTreeView({
|
|
|
25
25
|
interactive = true,
|
|
26
26
|
} = {}) {
|
|
27
27
|
const { exit } = useApp();
|
|
28
|
+
const controlledSnapshot = Boolean(snapshotOverride);
|
|
28
29
|
const [snapshot, setSnapshot] = useState(() => snapshotOverride || runState.getSnapshot());
|
|
29
|
-
const { frame } = useAnimation({ interval: 80, isActive: !snapshot.finished });
|
|
30
|
+
const { frame } = useAnimation({ interval: 80, isActive: !controlledSnapshot && !snapshot.finished });
|
|
30
31
|
const spinnerFrame = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
|
|
31
32
|
|
|
32
33
|
useEffect(() => {
|
package/lib/cli/entrypoint.mjs
CHANGED
|
@@ -16,7 +16,7 @@ export function normalizeCliArgs(argv) {
|
|
|
16
16
|
"browser",
|
|
17
17
|
"db",
|
|
18
18
|
]);
|
|
19
|
-
const runTypeShortcuts = new Set(publicTestTypeList({ includeAll: true
|
|
19
|
+
const runTypeShortcuts = new Set(publicTestTypeList({ includeAll: true }));
|
|
20
20
|
const valueFlags = new Set([
|
|
21
21
|
"--dir",
|
|
22
22
|
"--service",
|
package/lib/config/discovery.mjs
CHANGED
|
@@ -16,7 +16,6 @@ const DISCOVERY_RULES = [
|
|
|
16
16
|
{ suffix: ".dal.testkit.ts", type: "dal", framework: "k6" },
|
|
17
17
|
{ suffix: ".load.testkit.ts", type: "load", framework: "k6" },
|
|
18
18
|
{ suffix: ".ui.testkit.ts", type: "ui", framework: "playwright" },
|
|
19
|
-
{ suffix: ".pw.testkit.ts", type: "ui", framework: "playwright", legacySuffix: true },
|
|
20
19
|
];
|
|
21
20
|
|
|
22
21
|
export function discoverProject(productDir, explicitServices = {}, options = {}) {
|
|
@@ -31,15 +30,6 @@ export function discoverProject(productDir, explicitServices = {}, options = {})
|
|
|
31
30
|
for (const filePath of suiteFiles) {
|
|
32
31
|
const rule = inferRule(filePath);
|
|
33
32
|
if (!rule) continue;
|
|
34
|
-
if (rule.legacySuffix) {
|
|
35
|
-
diagnostics.push({
|
|
36
|
-
code: "legacy_ui_suffix",
|
|
37
|
-
severity: "warning",
|
|
38
|
-
message: `Legacy UI test suffix ".pw.testkit.ts" is deprecated. Rename to ".ui.testkit.ts": ${filePath}`,
|
|
39
|
-
path: filePath,
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
33
|
const owners = inferOwners(filePath, explicitServices, repoDiscovery);
|
|
44
34
|
if (owners === null) continue;
|
|
45
35
|
if (owners.length === 0) {
|
package/lib/discovery/index.mjs
CHANGED
|
@@ -489,7 +489,7 @@ function normalizePath(filePath) {
|
|
|
489
489
|
export function fileDisplayName(filePath) {
|
|
490
490
|
const base = path.posix
|
|
491
491
|
.basename(filePath)
|
|
492
|
-
.replace(/(\.int|\.e2e|\.scenario|\.dal|\.load|\.ui
|
|
492
|
+
.replace(/(\.int|\.e2e|\.scenario|\.dal|\.load|\.ui)\.testkit\.ts$/, "");
|
|
493
493
|
return formatDisplayName(base);
|
|
494
494
|
}
|
|
495
495
|
|
|
@@ -2,9 +2,6 @@ export const TEST_TYPE_ORDER = ["ui", "e2e", "scenario", "int", "dal", "load"];
|
|
|
2
2
|
export const TEST_TYPES = new Set(TEST_TYPE_ORDER);
|
|
3
3
|
export const RUN_TYPE_ORDER = [...TEST_TYPE_ORDER, "all"];
|
|
4
4
|
export const RUN_TYPES = new Set(RUN_TYPE_ORDER);
|
|
5
|
-
export const LEGACY_TEST_TYPE_ALIASES = new Map([
|
|
6
|
-
["pw", "ui"],
|
|
7
|
-
]);
|
|
8
5
|
|
|
9
6
|
const TEST_TYPE_LABELS = {
|
|
10
7
|
ui: "UI",
|
|
@@ -15,28 +12,22 @@ const TEST_TYPE_LABELS = {
|
|
|
15
12
|
load: "Load",
|
|
16
13
|
};
|
|
17
14
|
|
|
18
|
-
export function normalizePublicTestType(value) {
|
|
19
|
-
const normalized = String(value || "").trim();
|
|
20
|
-
return LEGACY_TEST_TYPE_ALIASES.get(normalized) || normalized;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
15
|
export function isPublicTestType(value) {
|
|
24
|
-
return TEST_TYPES.has(
|
|
16
|
+
return TEST_TYPES.has(String(value || "").trim());
|
|
25
17
|
}
|
|
26
18
|
|
|
27
19
|
export function isRunType(value) {
|
|
28
|
-
const normalized =
|
|
20
|
+
const normalized = String(value || "").trim();
|
|
29
21
|
return normalized === "all" || TEST_TYPES.has(normalized);
|
|
30
22
|
}
|
|
31
23
|
|
|
32
24
|
export function formatPublicTestType(value) {
|
|
33
|
-
const normalized =
|
|
25
|
+
const normalized = String(value || "").trim();
|
|
34
26
|
return TEST_TYPE_LABELS[normalized] || String(value || "");
|
|
35
27
|
}
|
|
36
28
|
|
|
37
|
-
export function publicTestTypeList({ includeAll = false
|
|
38
|
-
|
|
39
|
-
return includeLegacy ? [...values, "pw"] : [...values];
|
|
29
|
+
export function publicTestTypeList({ includeAll = false } = {}) {
|
|
30
|
+
return includeAll ? [...RUN_TYPE_ORDER] : [...TEST_TYPE_ORDER];
|
|
40
31
|
}
|
|
41
32
|
|
|
42
33
|
export function publicTestTypeListText(options = {}) {
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
const ASSISTANT_SESSION_ENV = "TESTKIT_ASSISTANT_SESSION_ID";
|
|
2
|
+
const ASSISTANT_TURN_ENV = "TESTKIT_ASSISTANT_TURN_ID";
|
|
2
3
|
const ASSISTANT_COMMAND_ID_ENV = "TESTKIT_ASSISTANT_COMMAND_ID";
|
|
3
4
|
|
|
4
5
|
export function buildRunProvenance(env = process.env) {
|
|
5
6
|
const sessionId = normalizeOptionalString(env?.[ASSISTANT_SESSION_ENV]);
|
|
7
|
+
const turnId = normalizeOptionalString(env?.[ASSISTANT_TURN_ENV]);
|
|
6
8
|
const commandId = normalizeOptionalString(env?.[ASSISTANT_COMMAND_ID_ENV]);
|
|
7
|
-
if (!sessionId && !commandId) return null;
|
|
9
|
+
if (!sessionId && !turnId && !commandId) return null;
|
|
8
10
|
return {
|
|
9
11
|
assistant: {
|
|
10
12
|
sessionId,
|
|
13
|
+
turnId,
|
|
11
14
|
commandId,
|
|
12
15
|
},
|
|
13
16
|
};
|
|
@@ -242,13 +242,21 @@ export function collectBundleCleanupTargets(productDir, { allConfigs = [], servi
|
|
|
242
242
|
export function collectAssistantCleanupTargets(productDir) {
|
|
243
243
|
const now = Date.now();
|
|
244
244
|
const dir = path.join(productDir, ".testkit", "assistant", "sessions");
|
|
245
|
-
return
|
|
246
|
-
.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
245
|
+
return listDirectories(dir)
|
|
246
|
+
.map((sessionDir) => {
|
|
247
|
+
const files = listFiles(sessionDir);
|
|
248
|
+
const sizeBytes = files.reduce((sum, file) => sum + file.size, 0);
|
|
249
|
+
const newestMtimeMs = files.reduce((latest, file) => Math.max(latest, file.mtimeMs), 0);
|
|
250
|
+
const expired = newestMtimeMs > 0 && now - newestMtimeMs >= ASSISTANT_RESULT_TTL_MS;
|
|
251
|
+
const large = sizeBytes >= ASSISTANT_LARGE_RESULT_BYTES;
|
|
252
|
+
if (!expired && !large) return null;
|
|
253
|
+
return {
|
|
254
|
+
path: sessionDir,
|
|
255
|
+
reason: large ? "large" : "expired",
|
|
256
|
+
sizeBytes,
|
|
257
|
+
};
|
|
258
|
+
})
|
|
259
|
+
.filter(Boolean);
|
|
252
260
|
}
|
|
253
261
|
|
|
254
262
|
function listDirectories(dir) {
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
RUN_TYPES,
|
|
4
4
|
TEST_TYPE_ORDER,
|
|
5
5
|
TEST_TYPES,
|
|
6
|
-
normalizePublicTestType,
|
|
7
6
|
publicTestTypeListText,
|
|
8
7
|
} from "../domain/test-types.mjs";
|
|
9
8
|
|
|
@@ -14,7 +13,7 @@ export function normalizeTypeValues(values = []) {
|
|
|
14
13
|
for (const part of String(rawValue).split(",")) {
|
|
15
14
|
const value = part.trim();
|
|
16
15
|
if (!value) continue;
|
|
17
|
-
const normalized =
|
|
16
|
+
const normalized = value;
|
|
18
17
|
if (!RUN_TYPES.has(normalized)) {
|
|
19
18
|
throw new Error(
|
|
20
19
|
`Unknown type "${value}". Expected one of: ${publicTestTypeListText({ includeAll: true })}.`
|
|
@@ -57,7 +56,7 @@ export function parseSuiteSelectors(values = []) {
|
|
|
57
56
|
|
|
58
57
|
const type = typeMatch[1];
|
|
59
58
|
const name = typeMatch[2].trim();
|
|
60
|
-
const normalizedType =
|
|
59
|
+
const normalizedType = type.trim();
|
|
61
60
|
if (!TEST_TYPES.has(normalizedType)) {
|
|
62
61
|
throw new Error(
|
|
63
62
|
`Unknown suite selector type "${type}". Expected one of: ${publicTestTypeListText()}.`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.112",
|
|
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.112"
|
|
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.112",
|
|
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.
|
|
94
|
-
"@elench/testkit-bridge": "0.1.
|
|
95
|
-
"@elench/testkit-protocol": "0.1.
|
|
96
|
-
"@elench/ts-analysis": "0.1.
|
|
93
|
+
"@elench/next-analysis": "0.1.112",
|
|
94
|
+
"@elench/testkit-bridge": "0.1.112",
|
|
95
|
+
"@elench/testkit-protocol": "0.1.112",
|
|
96
|
+
"@elench/ts-analysis": "0.1.112",
|
|
97
97
|
"@oclif/core": "^4.10.6",
|
|
98
98
|
"@playwright/test": "^1.52.0",
|
|
99
99
|
"esbuild": "^0.25.11",
|