@elench/testkit 0.1.97 → 0.1.99
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 +8 -8
- package/lib/app/browser-bridge.mjs +1 -1
- package/lib/cli/assistant/actions.mjs +333 -0
- package/lib/cli/assistant/app.mjs +25 -1
- package/lib/cli/assistant/command-observer.mjs +110 -0
- package/lib/cli/assistant/command-results.mjs +167 -0
- package/lib/cli/assistant/composer.mjs +1 -1
- package/lib/cli/assistant/context-pack.mjs +73 -6
- package/lib/cli/assistant/interactive.mjs +1 -1
- package/lib/cli/assistant/prompt-builder.mjs +15 -8
- package/lib/cli/{agents → assistant}/providers/claude.mjs +2 -3
- package/lib/cli/{agents → assistant}/providers/codex.mjs +2 -6
- package/lib/cli/{agents → assistant/providers}/index.mjs +5 -5
- package/lib/cli/assistant/session.mjs +36 -94
- package/lib/cli/assistant/slash-commands.mjs +22 -1
- package/lib/cli/assistant/state.mjs +187 -100
- package/lib/cli/assistant/view-model.mjs +1 -1
- package/lib/cli/command-flags.mjs +61 -0
- package/lib/cli/commands/assistant.mjs +4 -3
- package/lib/cli/commands/browser/serve.mjs +5 -23
- package/lib/cli/commands/cleanup.mjs +8 -2
- package/lib/cli/commands/db/snapshot/capture.mjs +8 -4
- package/lib/cli/commands/destroy.mjs +8 -2
- package/lib/cli/commands/discover.mjs +13 -32
- package/lib/cli/commands/doctor.mjs +17 -14
- package/lib/cli/commands/run.mjs +14 -3
- package/lib/cli/commands/status.mjs +14 -3
- package/lib/cli/commands/typecheck.mjs +12 -9
- package/lib/cli/{tui/inspect-app.mjs → components/blocks/run-tree.mjs} +29 -54
- package/lib/cli/{tui → components/primitives}/filter-bar.mjs +1 -1
- package/lib/cli/{presentation → components/primitives}/summary-box.mjs +1 -1
- package/lib/cli/config.mjs +63 -0
- package/lib/cli/entrypoint.mjs +14 -5
- package/lib/cli/operations/browser/serve/operation.mjs +23 -0
- package/lib/cli/operations/cleanup/operation.mjs +8 -0
- package/lib/cli/{db.mjs → operations/db/snapshot/capture/operation.mjs} +15 -9
- package/lib/cli/operations/destroy/operation.mjs +12 -0
- package/lib/cli/operations/discover/operation.mjs +32 -0
- package/lib/cli/operations/doctor/operation.mjs +5 -0
- package/lib/cli/operations/run/operation.mjs +129 -0
- package/lib/cli/operations/status/operation.mjs +7 -0
- package/lib/cli/operations/typecheck/operation.mjs +5 -0
- package/lib/cli/renderers/browser-serve/text.mjs +6 -0
- package/lib/cli/renderers/cleanup/text.mjs +3 -0
- package/lib/cli/renderers/db-snapshot-capture/text.mjs +3 -0
- package/lib/cli/renderers/destroy/text.mjs +3 -0
- package/lib/cli/{presentation/discovery-reporter.mjs → renderers/discover/report.mjs} +3 -3
- package/lib/cli/renderers/discover/text.mjs +7 -0
- package/lib/cli/renderers/doctor/text.mjs +7 -0
- package/lib/cli/{presentation/failure-presentation.mjs → renderers/run/failure.mjs} +6 -6
- package/lib/cli/renderers/run/interactive.mjs +119 -0
- package/lib/cli/{presentation/run-reporter.mjs → renderers/run/text-reporter.mjs} +5 -5
- package/lib/cli/renderers/status/text.mjs +7 -0
- package/lib/cli/renderers/typecheck/text.mjs +7 -0
- package/lib/cli/{tui/inspect-model.mjs → state/run/model.mjs} +11 -26
- package/lib/cli/{tui/inspect-state.mjs → state/run/state.mjs} +11 -18
- package/lib/cli/{tui → state/tree}/fuzzy-match.mjs +1 -1
- package/lib/cli/terminal/capabilities.mjs +33 -0
- package/lib/database/index.mjs +9 -21
- package/lib/{cli/viewer.mjs → results/artifacts.mjs} +1 -1
- package/lib/{cli/context-resources.mjs → results/context.mjs} +1 -1
- package/lib/runner/maintenance.mjs +25 -14
- package/lib/runner/readiness.mjs +5 -4
- package/lib/runner/state-io.mjs +10 -4
- 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 +7 -8
- package/lib/cli/assistant/protocol.mjs +0 -67
- package/lib/cli/assistant/tool-registry.mjs +0 -318
- package/lib/cli/command-helpers.mjs +0 -191
- package/lib/cli/presentation/tree-reporter.mjs +0 -96
- package/lib/cli/tui/inspect-artifact-adapter.mjs +0 -3
- package/lib/cli/tui/inspect-live-adapter.mjs +0 -15
- /package/lib/cli/{agents → assistant}/providers/shared.mjs +0 -0
- /package/lib/cli/{presentation/events-reporter.mjs → renderers/run/events.mjs} +0 -0
- /package/lib/cli/{presentation → terminal}/colors.mjs +0 -0
- /package/lib/cli/{presentation/terminal-layout.mjs → terminal/layout.mjs} +0 -0
- /package/lib/{cli/presentation → results}/code-frames.mjs +0 -0
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import { loadCurrentRunArtifact, loadLatestRunArtifact } from "
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { loadCurrentRunArtifact, loadLatestRunArtifact } from "../../results/artifacts.mjs";
|
|
2
|
+
import {
|
|
3
|
+
formatCliConfig,
|
|
4
|
+
loadCliConfig,
|
|
5
|
+
mergeCliConfig,
|
|
6
|
+
resetCliConfig,
|
|
7
|
+
saveCliConfig,
|
|
8
|
+
} from "../config.mjs";
|
|
9
|
+
import { createRunState } from "../state/run/state.mjs";
|
|
10
|
+
import { buildContextSelection } from "../../results/context.mjs";
|
|
11
|
+
import { isProviderInstalled } from "./providers/index.mjs";
|
|
5
12
|
import { parseSlashCommand, formatSlashHelpLines } from "./slash-commands.mjs";
|
|
6
|
-
import {
|
|
13
|
+
import { executeAssistantAction } from "./actions.mjs";
|
|
7
14
|
import { runAssistantConversationTurn } from "./session.mjs";
|
|
8
15
|
import { prepareAssistantContextPack } from "./context-pack.mjs";
|
|
9
16
|
import {
|
|
@@ -41,10 +48,12 @@ export function createAssistantState({
|
|
|
41
48
|
configs = [],
|
|
42
49
|
env = process.env,
|
|
43
50
|
} = {}) {
|
|
44
|
-
const
|
|
51
|
+
const runState = createRunState({ dataSource });
|
|
52
|
+
let cliConfig = loadCliConfig(productDir);
|
|
53
|
+
runState.setAutoCollapsePassedTreeBranches(cliConfig.autoCollapsePassedTreeBranches);
|
|
45
54
|
const commandLog = prepareAssistantContextPack({
|
|
46
55
|
productDir,
|
|
47
|
-
|
|
56
|
+
runState,
|
|
48
57
|
});
|
|
49
58
|
|
|
50
59
|
const listeners = new Set();
|
|
@@ -76,8 +85,11 @@ export function createAssistantState({
|
|
|
76
85
|
model: settings.model,
|
|
77
86
|
prompt: "",
|
|
78
87
|
});
|
|
88
|
+
let liveRunSession = null;
|
|
89
|
+
let lastRunSession = null;
|
|
90
|
+
let liveRunSessionUnsubscribe = null;
|
|
79
91
|
|
|
80
|
-
|
|
92
|
+
runState.subscribe(() => {
|
|
81
93
|
commandLog.refresh();
|
|
82
94
|
notify();
|
|
83
95
|
});
|
|
@@ -104,13 +116,46 @@ export function createAssistantState({
|
|
|
104
116
|
commandLog.refresh();
|
|
105
117
|
}
|
|
106
118
|
|
|
119
|
+
function attachRunSession(session, { active = true } = {}) {
|
|
120
|
+
if (liveRunSessionUnsubscribe) {
|
|
121
|
+
liveRunSessionUnsubscribe();
|
|
122
|
+
liveRunSessionUnsubscribe = null;
|
|
123
|
+
}
|
|
124
|
+
if (!session) {
|
|
125
|
+
if (!active) notify();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
lastRunSession = session;
|
|
129
|
+
if (active) {
|
|
130
|
+
liveRunSession = session;
|
|
131
|
+
liveRunSessionUnsubscribe = session.runState.subscribe(() => {
|
|
132
|
+
notify();
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
notify();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function completeRunSession(session) {
|
|
139
|
+
if (liveRunSession === session) {
|
|
140
|
+
if (liveRunSessionUnsubscribe) {
|
|
141
|
+
liveRunSessionUnsubscribe();
|
|
142
|
+
liveRunSessionUnsubscribe = null;
|
|
143
|
+
}
|
|
144
|
+
liveRunSession = null;
|
|
145
|
+
}
|
|
146
|
+
if (session) lastRunSession = session;
|
|
147
|
+
notify();
|
|
148
|
+
}
|
|
149
|
+
|
|
107
150
|
const state = {
|
|
108
|
-
|
|
151
|
+
runState,
|
|
109
152
|
commandLog,
|
|
153
|
+
attachRunSession,
|
|
154
|
+
completeRunSession,
|
|
110
155
|
|
|
111
156
|
async loadLatestArtifact() {
|
|
112
157
|
try {
|
|
113
|
-
|
|
158
|
+
runState.hydrateFromArtifact(loadLatestRunArtifact(productDir));
|
|
114
159
|
} catch {
|
|
115
160
|
// No artifact yet.
|
|
116
161
|
}
|
|
@@ -119,7 +164,7 @@ export function createAssistantState({
|
|
|
119
164
|
|
|
120
165
|
async loadCurrentArtifact() {
|
|
121
166
|
try {
|
|
122
|
-
|
|
167
|
+
runState.hydrateFromArtifact(loadCurrentRunArtifact(productDir));
|
|
123
168
|
} catch {
|
|
124
169
|
// No artifact yet.
|
|
125
170
|
}
|
|
@@ -127,13 +172,13 @@ export function createAssistantState({
|
|
|
127
172
|
},
|
|
128
173
|
|
|
129
174
|
revealFile(serviceName, filePath) {
|
|
130
|
-
const revealed =
|
|
175
|
+
const revealed = runState.revealFile(serviceName, filePath);
|
|
131
176
|
refreshContextPack();
|
|
132
177
|
return revealed;
|
|
133
178
|
},
|
|
134
179
|
|
|
135
180
|
revealService(serviceName) {
|
|
136
|
-
const revealed =
|
|
181
|
+
const revealed = runState.revealService(serviceName);
|
|
137
182
|
refreshContextPack();
|
|
138
183
|
return revealed;
|
|
139
184
|
},
|
|
@@ -228,6 +273,19 @@ export function createAssistantState({
|
|
|
228
273
|
notify();
|
|
229
274
|
},
|
|
230
275
|
|
|
276
|
+
setCliConfig(nextConfig) {
|
|
277
|
+
cliConfig = mergeCliConfig(cliConfig, nextConfig);
|
|
278
|
+
saveCliConfig(productDir, cliConfig);
|
|
279
|
+
runState.setAutoCollapsePassedTreeBranches(cliConfig.autoCollapsePassedTreeBranches);
|
|
280
|
+
notify();
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
resetCliConfig() {
|
|
284
|
+
cliConfig = resetCliConfig(productDir);
|
|
285
|
+
runState.setAutoCollapsePassedTreeBranches(cliConfig.autoCollapsePassedTreeBranches);
|
|
286
|
+
notify();
|
|
287
|
+
},
|
|
288
|
+
|
|
231
289
|
resetSettings() {
|
|
232
290
|
settings = resetAssistantSettings(productDir);
|
|
233
291
|
resolvedProviderName = null;
|
|
@@ -239,6 +297,14 @@ export function createAssistantState({
|
|
|
239
297
|
notify();
|
|
240
298
|
},
|
|
241
299
|
|
|
300
|
+
getLiveRunSession() {
|
|
301
|
+
return liveRunSession;
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
getLastRunSession() {
|
|
305
|
+
return lastRunSession;
|
|
306
|
+
},
|
|
307
|
+
|
|
242
308
|
async submitCurrentComposer() {
|
|
243
309
|
const value = composerState.text.trim();
|
|
244
310
|
composerState = createComposerState();
|
|
@@ -263,6 +329,7 @@ export function createAssistantState({
|
|
|
263
329
|
}
|
|
264
330
|
if (slash) {
|
|
265
331
|
try {
|
|
332
|
+
setBusy(true, `Running ${slash.type}...`);
|
|
266
333
|
await executeSlashCommand({
|
|
267
334
|
slash,
|
|
268
335
|
state,
|
|
@@ -277,6 +344,8 @@ export function createAssistantState({
|
|
|
277
344
|
role: "system",
|
|
278
345
|
text: error instanceof Error ? error.message : String(error),
|
|
279
346
|
});
|
|
347
|
+
} finally {
|
|
348
|
+
setBusy(false, null);
|
|
280
349
|
}
|
|
281
350
|
refreshContextPack();
|
|
282
351
|
notify();
|
|
@@ -286,6 +355,7 @@ export function createAssistantState({
|
|
|
286
355
|
const routedSlash = routeLocalIntent(trimmed);
|
|
287
356
|
if (routedSlash) {
|
|
288
357
|
try {
|
|
358
|
+
setBusy(true, `Running ${routedSlash.type}...`);
|
|
289
359
|
await executeSlashCommand({
|
|
290
360
|
slash: routedSlash,
|
|
291
361
|
state,
|
|
@@ -300,6 +370,8 @@ export function createAssistantState({
|
|
|
300
370
|
role: "system",
|
|
301
371
|
text: error instanceof Error ? error.message : String(error),
|
|
302
372
|
});
|
|
373
|
+
} finally {
|
|
374
|
+
setBusy(false, null);
|
|
303
375
|
}
|
|
304
376
|
refreshContextPack();
|
|
305
377
|
notify();
|
|
@@ -310,7 +382,7 @@ export function createAssistantState({
|
|
|
310
382
|
setBusy(true, `Thinking with ${settings.provider === "auto" ? "provider" : settings.provider}...`);
|
|
311
383
|
const emitted = await runAssistantConversationTurn({
|
|
312
384
|
productDir,
|
|
313
|
-
|
|
385
|
+
runState,
|
|
314
386
|
transcript: messages.map((entry) => ({ role: entry.role, text: entry.text })),
|
|
315
387
|
userMessage: trimmed,
|
|
316
388
|
settings,
|
|
@@ -334,7 +406,7 @@ export function createAssistantState({
|
|
|
334
406
|
notify();
|
|
335
407
|
},
|
|
336
408
|
onToolEvent(event) {
|
|
337
|
-
handleAssistantToolEvent(event, appendMessage);
|
|
409
|
+
handleAssistantToolEvent(state, event, appendMessage);
|
|
338
410
|
},
|
|
339
411
|
});
|
|
340
412
|
for (const message of emitted) appendMessage(message);
|
|
@@ -356,8 +428,8 @@ export function createAssistantState({
|
|
|
356
428
|
|
|
357
429
|
getSnapshot() {
|
|
358
430
|
return {
|
|
359
|
-
context: buildContextSelection(
|
|
360
|
-
|
|
431
|
+
context: buildContextSelection(runState.getSnapshot()),
|
|
432
|
+
run: runState.getSnapshot(),
|
|
361
433
|
productDir,
|
|
362
434
|
messages: [...messages],
|
|
363
435
|
composer: composerState.text,
|
|
@@ -369,8 +441,11 @@ export function createAssistantState({
|
|
|
369
441
|
model: settings.model,
|
|
370
442
|
effort: settings.effort,
|
|
371
443
|
providerArgs: [...settings.providerArgs],
|
|
444
|
+
cliConfig,
|
|
372
445
|
activeStatus,
|
|
373
446
|
contextUsage,
|
|
447
|
+
liveRunSession: serializeRunSession(liveRunSession),
|
|
448
|
+
lastRunSession: serializeRunSession(lastRunSession),
|
|
374
449
|
contextPaths: {
|
|
375
450
|
contextPath: commandLog.contextPath,
|
|
376
451
|
summaryPath: commandLog.summaryPath,
|
|
@@ -460,28 +535,32 @@ async function executeSlashCommand({
|
|
|
460
535
|
appendMessage({ role: "assistant", text: "Assistant settings reset." });
|
|
461
536
|
return;
|
|
462
537
|
}
|
|
538
|
+
if (slash.type === "config-show") {
|
|
539
|
+
appendMessage({ role: "assistant", text: formatCliConfig(state.getSnapshot().cliConfig) });
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (slash.type === "config-reset") {
|
|
543
|
+
state.resetCliConfig();
|
|
544
|
+
appendMessage({ role: "assistant", text: "CLI config reset." });
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (slash.type === "config-set-auto-collapse") {
|
|
548
|
+
state.setCliConfig({ autoCollapsePassedTreeBranches: slash.value });
|
|
549
|
+
appendMessage({
|
|
550
|
+
role: "assistant",
|
|
551
|
+
text: `autoCollapsePassedTreeBranches set to ${slash.value}.`,
|
|
552
|
+
});
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
463
555
|
|
|
464
556
|
const result = await executeSlashTool(slash, {
|
|
465
557
|
productDir,
|
|
466
|
-
|
|
558
|
+
runState: state.runState,
|
|
467
559
|
configs,
|
|
468
560
|
env,
|
|
469
561
|
commandLog: state.commandLog,
|
|
470
562
|
onEvent(event) {
|
|
471
|
-
|
|
472
|
-
appendMessage({
|
|
473
|
-
role: "tool",
|
|
474
|
-
status: "running",
|
|
475
|
-
title: event.title || event.tool || "Tool",
|
|
476
|
-
text: event.message,
|
|
477
|
-
data: {
|
|
478
|
-
command: event.command || null,
|
|
479
|
-
testkitRelated: Boolean(event.testkitRelated),
|
|
480
|
-
},
|
|
481
|
-
});
|
|
482
|
-
} else if (event.type === "tool-status") {
|
|
483
|
-
state.setNotice(event.message);
|
|
484
|
-
}
|
|
563
|
+
handleAssistantToolEvent(state, event, appendMessage);
|
|
485
564
|
},
|
|
486
565
|
provider: settings.provider,
|
|
487
566
|
});
|
|
@@ -494,18 +573,53 @@ async function executeSlashCommand({
|
|
|
494
573
|
});
|
|
495
574
|
}
|
|
496
575
|
|
|
497
|
-
function handleAssistantToolEvent(event, appendMessage) {
|
|
498
|
-
if (!event
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
576
|
+
function handleAssistantToolEvent(state, event, appendMessage) {
|
|
577
|
+
if (!event) return;
|
|
578
|
+
if (event.type === "tool-start") {
|
|
579
|
+
appendMessage({
|
|
580
|
+
role: "tool",
|
|
581
|
+
status: "running",
|
|
582
|
+
title: event.title || event.tool || "Tool",
|
|
583
|
+
text: event.message || "Running tool",
|
|
584
|
+
data: {
|
|
585
|
+
command: event.command || null,
|
|
586
|
+
testkitRelated: Boolean(event.testkitRelated),
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (event.type === "tool-status") {
|
|
592
|
+
state.setNotice(event.message);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (event.type === "run-session-start") {
|
|
596
|
+
state.attachRunSession(event.session, { active: true });
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (event.type === "run-session-end") {
|
|
600
|
+
state.completeRunSession(event.session);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (event.type === "observed-run-artifact") {
|
|
604
|
+
const observedSession = {
|
|
605
|
+
productDir: state.getSnapshot().productDir,
|
|
606
|
+
runState: state.runState,
|
|
607
|
+
getSnapshot() {
|
|
608
|
+
return state.runState.getSnapshot();
|
|
609
|
+
},
|
|
610
|
+
};
|
|
611
|
+
state.attachRunSession(observedSession, { active: false });
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (event.type === "observed-testkit-command") {
|
|
615
|
+
appendMessage({
|
|
616
|
+
role: "tool",
|
|
617
|
+
toolName: event.command?.kind || "testkit",
|
|
618
|
+
title: formatObservedCommandTitle(event.command),
|
|
619
|
+
text: formatObservedCommandText(event.command),
|
|
620
|
+
data: event.command || null,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
509
623
|
}
|
|
510
624
|
|
|
511
625
|
function formatSettings(snapshot) {
|
|
@@ -520,62 +634,7 @@ function formatSettings(snapshot) {
|
|
|
520
634
|
}
|
|
521
635
|
|
|
522
636
|
async function executeSlashTool(slash, context) {
|
|
523
|
-
|
|
524
|
-
case "inspect":
|
|
525
|
-
return executeAssistantTool(
|
|
526
|
-
"read_context",
|
|
527
|
-
{ file: slash.file || null, mode: "detail" },
|
|
528
|
-
context
|
|
529
|
-
);
|
|
530
|
-
case "logs":
|
|
531
|
-
return executeAssistantTool("read_context", { service: slash.service || null, mode: "logs" }, context);
|
|
532
|
-
case "artifacts":
|
|
533
|
-
return executeAssistantTool("read_context", { file: slash.file || null, mode: "artifacts" }, context);
|
|
534
|
-
case "setup":
|
|
535
|
-
return executeAssistantTool("read_context", { service: slash.service || null, mode: "setup" }, context);
|
|
536
|
-
case "file":
|
|
537
|
-
return executeAssistantTool("read_file", { path: slash.file }, context);
|
|
538
|
-
case "service":
|
|
539
|
-
return executeAssistantTool("read_context", { service: slash.service, mode: "detail" }, context);
|
|
540
|
-
case "status":
|
|
541
|
-
return executeAssistantTool("shell_exec", { command: "testkit status --dir ." }, context);
|
|
542
|
-
case "discover":
|
|
543
|
-
return executeAssistantTool("shell_exec", { command: "testkit discover --dir ." }, context);
|
|
544
|
-
case "doctor":
|
|
545
|
-
return executeAssistantTool("shell_exec", { command: "testkit doctor --dir ." }, context);
|
|
546
|
-
case "run":
|
|
547
|
-
return executeAssistantTool("shell_exec", { command: buildRunSlashCommand(slash.options) }, context);
|
|
548
|
-
default:
|
|
549
|
-
throw new Error(`Unsupported slash command "${slash.type}"`);
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
function buildRunSlashCommand(options = {}) {
|
|
554
|
-
const types = options.type || [];
|
|
555
|
-
const parts = ["testkit", "run"];
|
|
556
|
-
if (types.length === 1) {
|
|
557
|
-
parts.push(types[0]);
|
|
558
|
-
}
|
|
559
|
-
parts.push("--dir", ".");
|
|
560
|
-
if (types.length !== 1) {
|
|
561
|
-
for (const type of types) {
|
|
562
|
-
parts.push("--type", type);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
for (const suite of options.suite || []) {
|
|
566
|
-
parts.push("--suite", suite);
|
|
567
|
-
}
|
|
568
|
-
for (const file of options.file || []) {
|
|
569
|
-
parts.push("--file", file);
|
|
570
|
-
}
|
|
571
|
-
if (options.service) parts.push("--service", options.service);
|
|
572
|
-
return parts.map(shellEscapeArg).join(" ");
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
function shellEscapeArg(value) {
|
|
576
|
-
const stringValue = String(value);
|
|
577
|
-
if (/^[a-zA-Z0-9._:/-]+$/.test(stringValue)) return stringValue;
|
|
578
|
-
return `'${stringValue.replace(/'/g, `'\\''`)}'`;
|
|
637
|
+
return executeAssistantAction(slash.type, slash, context);
|
|
579
638
|
}
|
|
580
639
|
|
|
581
640
|
function parseSlashCommandSafe(input) {
|
|
@@ -619,3 +678,31 @@ function routeLocalIntent(input) {
|
|
|
619
678
|
if (/^list\s+test\s+files$/.test(normalized)) return { type: "discover" };
|
|
620
679
|
return null;
|
|
621
680
|
}
|
|
681
|
+
|
|
682
|
+
function serializeRunSession(session) {
|
|
683
|
+
if (!session) return null;
|
|
684
|
+
return {
|
|
685
|
+
productDir: session.productDir,
|
|
686
|
+
snapshot: session.getSnapshot(),
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function formatObservedCommandTitle(command) {
|
|
691
|
+
const kind = command?.kind || "command";
|
|
692
|
+
return `testkit ${kind}`;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function formatObservedCommandText(command) {
|
|
696
|
+
if (!command) return "Observed Testkit command.";
|
|
697
|
+
const lines = [];
|
|
698
|
+
lines.push(`$ testkit ${command.argv?.join(" ") || command.kind || ""}`.trim());
|
|
699
|
+
if (Number.isInteger(command.exitCode)) lines.push(`exit code: ${command.exitCode}`);
|
|
700
|
+
const rows = command.result?.runSession?.summaryData?.rows || command.result?.summaryRows || command.result?.runArtifact?.summaryRows || null;
|
|
701
|
+
if (Array.isArray(rows) && rows.length > 0) {
|
|
702
|
+
for (const [label, value] of rows) lines.push(`${label}: ${value}`);
|
|
703
|
+
} else if (command.kind === "run" && command.result?.runArtifact?.summary?.files) {
|
|
704
|
+
const files = command.result.runArtifact.summary.files;
|
|
705
|
+
lines.push(`Files: ${files.total || 0} total, ${files.passed || 0} passed, ${files.failed || 0} failed, ${files.skipped || 0} skipped`);
|
|
706
|
+
}
|
|
707
|
+
return lines.join("\n");
|
|
708
|
+
}
|
|
@@ -23,7 +23,7 @@ export function buildAssistantViewModel(snapshot, { cwd = process.cwd(), termina
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function buildWelcomeModel(snapshot, { cwd = process.cwd(), providerLabel = null } = {}) {
|
|
26
|
-
const summaryRows = snapshot?.
|
|
26
|
+
const summaryRows = snapshot?.run?.summaryData?.rows || snapshot?.summaryData?.rows || [];
|
|
27
27
|
const rowValue = (label) => summaryRows.find(([key]) => key === label)?.[1] || null;
|
|
28
28
|
const contextSelection = snapshot?.context?.selection || {};
|
|
29
29
|
const latestResult = rowValue("Result");
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Flags } from "@oclif/core";
|
|
2
|
+
|
|
3
|
+
export const sharedFlags = {
|
|
4
|
+
dir: Flags.string({
|
|
5
|
+
description: "Explicit product directory",
|
|
6
|
+
}),
|
|
7
|
+
service: Flags.string({
|
|
8
|
+
description: "Limit the operation or assistant context to one service",
|
|
9
|
+
}),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const runFlags = {
|
|
13
|
+
...sharedFlags,
|
|
14
|
+
type: Flags.string({
|
|
15
|
+
char: "t",
|
|
16
|
+
multiple: true,
|
|
17
|
+
description: "Run specific suite type(s): int, e2e, scenario, dal, load, pw, all",
|
|
18
|
+
}),
|
|
19
|
+
suite: Flags.string({
|
|
20
|
+
char: "s",
|
|
21
|
+
multiple: true,
|
|
22
|
+
description: "Run specific suite(s)",
|
|
23
|
+
}),
|
|
24
|
+
file: Flags.string({
|
|
25
|
+
char: "f",
|
|
26
|
+
multiple: true,
|
|
27
|
+
description: "Run specific file(s)",
|
|
28
|
+
}),
|
|
29
|
+
workers: Flags.string({
|
|
30
|
+
description: "Number of test executors for the whole run",
|
|
31
|
+
}),
|
|
32
|
+
"file-timeout-seconds": Flags.string({
|
|
33
|
+
description: "Per-file wall-clock timeout in seconds",
|
|
34
|
+
}),
|
|
35
|
+
shard: Flags.string({
|
|
36
|
+
description: "Run only shard i of n at suite granularity",
|
|
37
|
+
}),
|
|
38
|
+
seed: Flags.string({
|
|
39
|
+
description: "Deterministic seed for scenario suites",
|
|
40
|
+
}),
|
|
41
|
+
"write-status": Flags.boolean({
|
|
42
|
+
description: "Write a deterministic testkit.status.json snapshot",
|
|
43
|
+
default: false,
|
|
44
|
+
}),
|
|
45
|
+
"allow-partial-status": Flags.boolean({
|
|
46
|
+
description: "Allow --write-status for filtered runs",
|
|
47
|
+
default: false,
|
|
48
|
+
}),
|
|
49
|
+
"ignore-skip-rules": Flags.boolean({
|
|
50
|
+
description: "Run files even if testkit.config.ts marks them skipped",
|
|
51
|
+
default: false,
|
|
52
|
+
}),
|
|
53
|
+
"output-mode": Flags.string({
|
|
54
|
+
description: "Reporter mode",
|
|
55
|
+
options: ["compact", "debug", "events"],
|
|
56
|
+
}),
|
|
57
|
+
debug: Flags.boolean({
|
|
58
|
+
description: "Alias for --output-mode debug",
|
|
59
|
+
default: false,
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Command, Flags } from "@oclif/core";
|
|
2
|
-
import {
|
|
2
|
+
import { loadManagedConfigs } from "../../app/configs.mjs";
|
|
3
|
+
import { loadLatestRunArtifact, resolveFileSubject } from "../../results/artifacts.mjs";
|
|
4
|
+
import { sharedFlags } from "../command-flags.mjs";
|
|
3
5
|
import { createAssistantState } from "../assistant/state.mjs";
|
|
4
6
|
import { runInteractiveAssistant } from "../assistant/interactive.mjs";
|
|
5
7
|
|
|
@@ -44,7 +46,7 @@ export default class AssistantCommand extends Command {
|
|
|
44
46
|
if (flags.message && flags.prompt) {
|
|
45
47
|
this.error("Use either --message or --prompt, not both.");
|
|
46
48
|
}
|
|
47
|
-
const { allConfigs } = await
|
|
49
|
+
const { allConfigs } = await loadManagedConfigs({ dir: flags.dir, service: flags.service });
|
|
48
50
|
const productDir = allConfigs[0]?.productDir || process.cwd();
|
|
49
51
|
const interactive =
|
|
50
52
|
(process.stdout.isTTY || process.env.TESTKIT_FORCE_INTERACTIVE_ASSISTANT === "1") &&
|
|
@@ -70,7 +72,6 @@ export default class AssistantCommand extends Command {
|
|
|
70
72
|
await assistantState.loadLatestArtifact();
|
|
71
73
|
if (flags.file) {
|
|
72
74
|
try {
|
|
73
|
-
const { loadLatestRunArtifact, resolveFileSubject } = await import("../viewer.mjs");
|
|
74
75
|
const artifact = loadLatestRunArtifact(productDir);
|
|
75
76
|
const subject = resolveFileSubject(artifact, flags.file, flags.service || null);
|
|
76
77
|
assistantState.revealFile(subject.service.name, subject.file.path);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command, Flags } from "@oclif/core";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { executeBrowserServeOperation } from "../../operations/browser/serve/operation.mjs";
|
|
3
|
+
import { renderBrowserServeResult } from "../../renderers/browser-serve/text.mjs";
|
|
4
4
|
|
|
5
5
|
export default class BrowserServeCommand extends Command {
|
|
6
6
|
static summary = "Serve the local browser bridge for the current testkit product";
|
|
@@ -23,31 +23,13 @@ export default class BrowserServeCommand extends Command {
|
|
|
23
23
|
|
|
24
24
|
async run() {
|
|
25
25
|
const { flags } = await this.parse(BrowserServeCommand);
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const adapter = {
|
|
29
|
-
loadProductContext: async () => context,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const serverRef = await startBrowserBridgeServer(adapter, {
|
|
33
|
-
host: flags.host,
|
|
34
|
-
port: flags.port,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const payload = {
|
|
38
|
-
ok: true,
|
|
39
|
-
productDir,
|
|
40
|
-
host: serverRef.host,
|
|
41
|
-
port: serverRef.port,
|
|
42
|
-
url: serverRef.url,
|
|
43
|
-
};
|
|
26
|
+
const result = await executeBrowserServeOperation(flags);
|
|
44
27
|
|
|
45
28
|
if (!this.jsonEnabled()) {
|
|
46
|
-
|
|
47
|
-
this.log(`Listening on ${serverRef.url}`);
|
|
29
|
+
for (const line of renderBrowserServeResult(result)) this.log(line);
|
|
48
30
|
}
|
|
49
31
|
|
|
50
32
|
await new Promise(() => {});
|
|
51
|
-
return
|
|
33
|
+
return result;
|
|
52
34
|
}
|
|
53
35
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Command } from "@oclif/core";
|
|
2
|
-
import {
|
|
2
|
+
import { sharedFlags } from "../command-flags.mjs";
|
|
3
|
+
import { executeCleanupOperation } from "../operations/cleanup/operation.mjs";
|
|
4
|
+
import { renderCleanupResult } from "../renderers/cleanup/text.mjs";
|
|
3
5
|
|
|
4
6
|
export default class CleanupCommand extends Command {
|
|
5
7
|
static summary = "Clean stale local testkit state";
|
|
@@ -10,6 +12,10 @@ export default class CleanupCommand extends Command {
|
|
|
10
12
|
|
|
11
13
|
async run() {
|
|
12
14
|
const { flags } = await this.parse(CleanupCommand);
|
|
13
|
-
|
|
15
|
+
const result = await executeCleanupOperation(flags);
|
|
16
|
+
if (!this.jsonEnabled()) {
|
|
17
|
+
for (const line of renderCleanupResult(result)) this.log(line);
|
|
18
|
+
}
|
|
19
|
+
return { ok: true, ...result };
|
|
14
20
|
}
|
|
15
21
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command, Flags } from "@oclif/core";
|
|
2
|
-
import { sharedFlags } from "../../../command-
|
|
3
|
-
import {
|
|
2
|
+
import { sharedFlags } from "../../../command-flags.mjs";
|
|
3
|
+
import { executeDatabaseSnapshotCaptureOperation } from "../../../operations/db/snapshot/capture/operation.mjs";
|
|
4
|
+
import { renderDatabaseSnapshotCaptureResult } from "../../../renderers/db-snapshot-capture/text.mjs";
|
|
4
5
|
|
|
5
6
|
export default class DbSnapshotCaptureCommand extends Command {
|
|
6
7
|
static summary = "Capture a database schema snapshot";
|
|
@@ -16,7 +17,10 @@ export default class DbSnapshotCaptureCommand extends Command {
|
|
|
16
17
|
|
|
17
18
|
async run() {
|
|
18
19
|
const { flags } = await this.parse(DbSnapshotCaptureCommand);
|
|
19
|
-
await
|
|
20
|
-
|
|
20
|
+
const result = await executeDatabaseSnapshotCaptureOperation(flags);
|
|
21
|
+
if (!this.jsonEnabled()) {
|
|
22
|
+
for (const line of renderDatabaseSnapshotCaptureResult(result)) this.log(line);
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
21
25
|
}
|
|
22
26
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Command } from "@oclif/core";
|
|
2
|
-
import {
|
|
2
|
+
import { sharedFlags } from "../command-flags.mjs";
|
|
3
|
+
import { executeDestroyOperation } from "../operations/destroy/operation.mjs";
|
|
4
|
+
import { renderDestroyResult } from "../renderers/destroy/text.mjs";
|
|
3
5
|
|
|
4
6
|
export default class DestroyCommand extends Command {
|
|
5
7
|
static summary = "Destroy local testkit state";
|
|
@@ -10,6 +12,10 @@ export default class DestroyCommand extends Command {
|
|
|
10
12
|
|
|
11
13
|
async run() {
|
|
12
14
|
const { flags } = await this.parse(DestroyCommand);
|
|
13
|
-
|
|
15
|
+
const results = await executeDestroyOperation(flags);
|
|
16
|
+
if (!this.jsonEnabled()) {
|
|
17
|
+
for (const line of renderDestroyResult(results)) this.log(line);
|
|
18
|
+
}
|
|
19
|
+
return { ok: true, results };
|
|
14
20
|
}
|
|
15
21
|
}
|