@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.
Files changed (80) hide show
  1. package/README.md +8 -8
  2. package/lib/app/browser-bridge.mjs +1 -1
  3. package/lib/cli/assistant/actions.mjs +333 -0
  4. package/lib/cli/assistant/app.mjs +25 -1
  5. package/lib/cli/assistant/command-observer.mjs +110 -0
  6. package/lib/cli/assistant/command-results.mjs +167 -0
  7. package/lib/cli/assistant/composer.mjs +1 -1
  8. package/lib/cli/assistant/context-pack.mjs +73 -6
  9. package/lib/cli/assistant/interactive.mjs +1 -1
  10. package/lib/cli/assistant/prompt-builder.mjs +15 -8
  11. package/lib/cli/{agents → assistant}/providers/claude.mjs +2 -3
  12. package/lib/cli/{agents → assistant}/providers/codex.mjs +2 -6
  13. package/lib/cli/{agents → assistant/providers}/index.mjs +5 -5
  14. package/lib/cli/assistant/session.mjs +36 -94
  15. package/lib/cli/assistant/slash-commands.mjs +22 -1
  16. package/lib/cli/assistant/state.mjs +187 -100
  17. package/lib/cli/assistant/view-model.mjs +1 -1
  18. package/lib/cli/command-flags.mjs +61 -0
  19. package/lib/cli/commands/assistant.mjs +4 -3
  20. package/lib/cli/commands/browser/serve.mjs +5 -23
  21. package/lib/cli/commands/cleanup.mjs +8 -2
  22. package/lib/cli/commands/db/snapshot/capture.mjs +8 -4
  23. package/lib/cli/commands/destroy.mjs +8 -2
  24. package/lib/cli/commands/discover.mjs +13 -32
  25. package/lib/cli/commands/doctor.mjs +17 -14
  26. package/lib/cli/commands/run.mjs +14 -3
  27. package/lib/cli/commands/status.mjs +14 -3
  28. package/lib/cli/commands/typecheck.mjs +12 -9
  29. package/lib/cli/{tui/inspect-app.mjs → components/blocks/run-tree.mjs} +29 -54
  30. package/lib/cli/{tui → components/primitives}/filter-bar.mjs +1 -1
  31. package/lib/cli/{presentation → components/primitives}/summary-box.mjs +1 -1
  32. package/lib/cli/config.mjs +63 -0
  33. package/lib/cli/entrypoint.mjs +14 -5
  34. package/lib/cli/operations/browser/serve/operation.mjs +23 -0
  35. package/lib/cli/operations/cleanup/operation.mjs +8 -0
  36. package/lib/cli/{db.mjs → operations/db/snapshot/capture/operation.mjs} +15 -9
  37. package/lib/cli/operations/destroy/operation.mjs +12 -0
  38. package/lib/cli/operations/discover/operation.mjs +32 -0
  39. package/lib/cli/operations/doctor/operation.mjs +5 -0
  40. package/lib/cli/operations/run/operation.mjs +129 -0
  41. package/lib/cli/operations/status/operation.mjs +7 -0
  42. package/lib/cli/operations/typecheck/operation.mjs +5 -0
  43. package/lib/cli/renderers/browser-serve/text.mjs +6 -0
  44. package/lib/cli/renderers/cleanup/text.mjs +3 -0
  45. package/lib/cli/renderers/db-snapshot-capture/text.mjs +3 -0
  46. package/lib/cli/renderers/destroy/text.mjs +3 -0
  47. package/lib/cli/{presentation/discovery-reporter.mjs → renderers/discover/report.mjs} +3 -3
  48. package/lib/cli/renderers/discover/text.mjs +7 -0
  49. package/lib/cli/renderers/doctor/text.mjs +7 -0
  50. package/lib/cli/{presentation/failure-presentation.mjs → renderers/run/failure.mjs} +6 -6
  51. package/lib/cli/renderers/run/interactive.mjs +119 -0
  52. package/lib/cli/{presentation/run-reporter.mjs → renderers/run/text-reporter.mjs} +5 -5
  53. package/lib/cli/renderers/status/text.mjs +7 -0
  54. package/lib/cli/renderers/typecheck/text.mjs +7 -0
  55. package/lib/cli/{tui/inspect-model.mjs → state/run/model.mjs} +11 -26
  56. package/lib/cli/{tui/inspect-state.mjs → state/run/state.mjs} +11 -18
  57. package/lib/cli/{tui → state/tree}/fuzzy-match.mjs +1 -1
  58. package/lib/cli/terminal/capabilities.mjs +33 -0
  59. package/lib/database/index.mjs +9 -21
  60. package/lib/{cli/viewer.mjs → results/artifacts.mjs} +1 -1
  61. package/lib/{cli/context-resources.mjs → results/context.mjs} +1 -1
  62. package/lib/runner/maintenance.mjs +25 -14
  63. package/lib/runner/readiness.mjs +5 -4
  64. package/lib/runner/state-io.mjs +10 -4
  65. package/node_modules/@elench/next-analysis/package.json +1 -1
  66. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  67. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  68. package/node_modules/@elench/ts-analysis/package.json +1 -1
  69. package/package.json +7 -8
  70. package/lib/cli/assistant/protocol.mjs +0 -67
  71. package/lib/cli/assistant/tool-registry.mjs +0 -318
  72. package/lib/cli/command-helpers.mjs +0 -191
  73. package/lib/cli/presentation/tree-reporter.mjs +0 -96
  74. package/lib/cli/tui/inspect-artifact-adapter.mjs +0 -3
  75. package/lib/cli/tui/inspect-live-adapter.mjs +0 -15
  76. /package/lib/cli/{agents → assistant}/providers/shared.mjs +0 -0
  77. /package/lib/cli/{presentation/events-reporter.mjs → renderers/run/events.mjs} +0 -0
  78. /package/lib/cli/{presentation → terminal}/colors.mjs +0 -0
  79. /package/lib/cli/{presentation/terminal-layout.mjs → terminal/layout.mjs} +0 -0
  80. /package/lib/{cli/presentation → results}/code-frames.mjs +0 -0
@@ -1,9 +1,16 @@
1
- import { loadCurrentRunArtifact, loadLatestRunArtifact } from "../viewer.mjs";
2
- import { createInspectState } from "../tui/inspect-state.mjs";
3
- import { buildContextSelection } from "../context-resources.mjs";
4
- import { isProviderInstalled } from "../agents/index.mjs";
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 { executeAssistantTool } from "./tool-registry.mjs";
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 inspectState = createInspectState({ dataSource });
51
+ const runState = createRunState({ dataSource });
52
+ let cliConfig = loadCliConfig(productDir);
53
+ runState.setAutoCollapsePassedTreeBranches(cliConfig.autoCollapsePassedTreeBranches);
45
54
  const commandLog = prepareAssistantContextPack({
46
55
  productDir,
47
- inspectState,
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
- inspectState.subscribe(() => {
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
- inspectState,
151
+ runState,
109
152
  commandLog,
153
+ attachRunSession,
154
+ completeRunSession,
110
155
 
111
156
  async loadLatestArtifact() {
112
157
  try {
113
- inspectState.hydrateFromArtifact(loadLatestRunArtifact(productDir));
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
- inspectState.hydrateFromArtifact(loadCurrentRunArtifact(productDir));
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 = inspectState.revealFile(serviceName, filePath);
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 = inspectState.revealService(serviceName);
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
- inspectState,
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(inspectState.getSnapshot()),
360
- inspect: inspectState.getSnapshot(),
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
- inspectState: state.inspectState,
558
+ runState: state.runState,
467
559
  configs,
468
560
  env,
469
561
  commandLog: state.commandLog,
470
562
  onEvent(event) {
471
- if (event.type === "tool-start") {
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 || event.type !== "tool-start") return;
499
- appendMessage({
500
- role: "tool",
501
- status: "running",
502
- title: event.title || event.tool || "Tool",
503
- text: event.message || "Running tool",
504
- data: {
505
- command: event.command || null,
506
- testkitRelated: Boolean(event.testkitRelated),
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
- switch (slash.type) {
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?.inspect?.summaryData?.rows || snapshot?.summaryData?.rows || [];
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 { sharedFlags, resolveConfigsForCommand } from "../command-helpers.mjs";
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 resolveConfigsForCommand(flags);
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 { startBrowserBridgeServer } from "@elench/testkit-bridge";
3
- import { loadBrowserBridgeContext } from "../../../app/browser-bridge.mjs";
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 { productDir, context } = await loadBrowserBridgeContext({ dir: flags.dir });
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
- this.log(`testkit browser bridge serving ${productDir}`);
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 payload;
33
+ return result;
52
34
  }
53
35
  }
@@ -1,5 +1,7 @@
1
1
  import { Command } from "@oclif/core";
2
- import { runStatusLike, sharedFlags } from "../command-helpers.mjs";
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
- return runStatusLike("cleanup", flags);
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-helpers.mjs";
3
- import { runDatabaseSnapshotCaptureCommand } from "../../../db.mjs";
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 runDatabaseSnapshotCaptureCommand(flags);
20
- return { ok: true };
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 { runStatusLike, sharedFlags } from "../command-helpers.mjs";
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
- return runStatusLike("destroy", flags);
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
  }