@opensip-tools/contracts 1.0.4

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 (86) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-typecheck.log +4 -0
  3. package/LICENSE +21 -0
  4. package/dist/__tests__/dashboard.test.d.ts +2 -0
  5. package/dist/__tests__/dashboard.test.d.ts.map +1 -0
  6. package/dist/__tests__/dashboard.test.js +85 -0
  7. package/dist/__tests__/dashboard.test.js.map +1 -0
  8. package/dist/__tests__/exit-codes.test.d.ts +2 -0
  9. package/dist/__tests__/exit-codes.test.d.ts.map +1 -0
  10. package/dist/__tests__/exit-codes.test.js +73 -0
  11. package/dist/__tests__/exit-codes.test.js.map +1 -0
  12. package/dist/__tests__/store.test.d.ts +2 -0
  13. package/dist/__tests__/store.test.d.ts.map +1 -0
  14. package/dist/__tests__/store.test.js +169 -0
  15. package/dist/__tests__/store.test.js.map +1 -0
  16. package/dist/exit-codes.d.ts +14 -0
  17. package/dist/exit-codes.d.ts.map +1 -0
  18. package/dist/exit-codes.js +61 -0
  19. package/dist/exit-codes.js.map +1 -0
  20. package/dist/index.d.ts +21 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +20 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/persistence/dashboard/checks.d.ts +7 -0
  25. package/dist/persistence/dashboard/checks.d.ts.map +1 -0
  26. package/dist/persistence/dashboard/checks.js +279 -0
  27. package/dist/persistence/dashboard/checks.js.map +1 -0
  28. package/dist/persistence/dashboard/css.d.ts +6 -0
  29. package/dist/persistence/dashboard/css.d.ts.map +1 -0
  30. package/dist/persistence/dashboard/css.js +141 -0
  31. package/dist/persistence/dashboard/css.js.map +1 -0
  32. package/dist/persistence/dashboard/generator.d.ts +9 -0
  33. package/dist/persistence/dashboard/generator.d.ts.map +1 -0
  34. package/dist/persistence/dashboard/generator.js +79 -0
  35. package/dist/persistence/dashboard/generator.js.map +1 -0
  36. package/dist/persistence/dashboard/index.d.ts +5 -0
  37. package/dist/persistence/dashboard/index.d.ts.map +1 -0
  38. package/dist/persistence/dashboard/index.js +5 -0
  39. package/dist/persistence/dashboard/index.js.map +1 -0
  40. package/dist/persistence/dashboard/overview.d.ts +6 -0
  41. package/dist/persistence/dashboard/overview.d.ts.map +1 -0
  42. package/dist/persistence/dashboard/overview.js +65 -0
  43. package/dist/persistence/dashboard/overview.js.map +1 -0
  44. package/dist/persistence/dashboard/recipes.d.ts +6 -0
  45. package/dist/persistence/dashboard/recipes.d.ts.map +1 -0
  46. package/dist/persistence/dashboard/recipes.js +68 -0
  47. package/dist/persistence/dashboard/recipes.js.map +1 -0
  48. package/dist/persistence/dashboard/sessions.d.ts +6 -0
  49. package/dist/persistence/dashboard/sessions.d.ts.map +1 -0
  50. package/dist/persistence/dashboard/sessions.js +205 -0
  51. package/dist/persistence/dashboard/sessions.js.map +1 -0
  52. package/dist/persistence/dashboard/shared.d.ts +6 -0
  53. package/dist/persistence/dashboard/shared.d.ts.map +1 -0
  54. package/dist/persistence/dashboard/shared.js +211 -0
  55. package/dist/persistence/dashboard/shared.js.map +1 -0
  56. package/dist/persistence/dashboard/tool-tabs.d.ts +6 -0
  57. package/dist/persistence/dashboard/tool-tabs.d.ts.map +1 -0
  58. package/dist/persistence/dashboard/tool-tabs.js +102 -0
  59. package/dist/persistence/dashboard/tool-tabs.js.map +1 -0
  60. package/dist/persistence/store.d.ts +103 -0
  61. package/dist/persistence/store.d.ts.map +1 -0
  62. package/dist/persistence/store.js +156 -0
  63. package/dist/persistence/store.js.map +1 -0
  64. package/dist/types.d.ts +279 -0
  65. package/dist/types.d.ts.map +1 -0
  66. package/dist/types.js +2 -0
  67. package/dist/types.js.map +1 -0
  68. package/package.json +35 -0
  69. package/src/__tests__/dashboard.test.ts +102 -0
  70. package/src/__tests__/exit-codes.test.ts +87 -0
  71. package/src/__tests__/store.test.ts +213 -0
  72. package/src/exit-codes.ts +74 -0
  73. package/src/index.ts +71 -0
  74. package/src/persistence/dashboard/checks.ts +279 -0
  75. package/src/persistence/dashboard/css.ts +141 -0
  76. package/src/persistence/dashboard/generator.ts +89 -0
  77. package/src/persistence/dashboard/index.ts +5 -0
  78. package/src/persistence/dashboard/overview.ts +65 -0
  79. package/src/persistence/dashboard/recipes.ts +68 -0
  80. package/src/persistence/dashboard/sessions.ts +205 -0
  81. package/src/persistence/dashboard/shared.ts +211 -0
  82. package/src/persistence/dashboard/tool-tabs.ts +102 -0
  83. package/src/persistence/store.ts +233 -0
  84. package/src/types.ts +306 -0
  85. package/tsconfig.json +8 -0
  86. package/vitest.config.ts +16 -0
@@ -0,0 +1,233 @@
1
+ /**
2
+ * JSON file persistence for opensip-tools results.
3
+ *
4
+ * Sessions land at `<project>/opensip-tools/.runtime/sessions/`
5
+ * (per-project). Each run creates one file:
6
+ * `{timestamp}-{tool}-{recipe}.json`.
7
+ *
8
+ * The CLI bootstrap calls `configurePersistencePaths(projectPaths)`
9
+ * once on startup with paths from `resolveProjectPaths(cwd)`. Until
10
+ * that call, the module falls back to a user-global location
11
+ * (`~/.opensip-tools/`) so any caller who imports persistence helpers
12
+ * before the CLI's preAction hook still gets a valid path. The
13
+ * fallback is also exercised by tests that don't bootstrap a CLI.
14
+ */
15
+
16
+ import { randomUUID } from 'node:crypto';
17
+ import { mkdirSync, readFileSync, readdirSync, writeFileSync, unlinkSync } from 'node:fs';
18
+ import { homedir } from 'node:os';
19
+ import { basename, join } from 'node:path';
20
+
21
+ import { logger } from '@opensip-tools/core';
22
+
23
+ import type { ProjectPaths } from '@opensip-tools/core';
24
+
25
+ export interface StoredSession {
26
+ readonly id: string;
27
+ readonly tool: 'fit' | 'sim';
28
+ readonly timestamp: string;
29
+ readonly cwd: string;
30
+ readonly recipe?: string;
31
+ readonly score: number;
32
+ readonly passed: boolean;
33
+ readonly summary: {
34
+ readonly total: number;
35
+ readonly passed: number;
36
+ readonly failed: number;
37
+ readonly errors: number;
38
+ readonly warnings: number;
39
+ };
40
+ readonly checks: readonly {
41
+ readonly checkSlug: string;
42
+ readonly passed: boolean;
43
+ readonly violationCount?: number;
44
+ readonly findings: readonly {
45
+ readonly ruleId: string;
46
+ readonly message: string;
47
+ readonly severity: string;
48
+ readonly filePath?: string;
49
+ readonly line?: number;
50
+ readonly column?: number;
51
+ readonly suggestion?: string;
52
+ readonly category?: string;
53
+ }[];
54
+ readonly durationMs: number;
55
+ }[];
56
+ readonly durationMs: number;
57
+ }
58
+
59
+ /** Check catalog entry for dashboard display */
60
+ export interface CheckCatalogEntry {
61
+ readonly slug: string;
62
+ readonly name: string;
63
+ readonly icon: string;
64
+ readonly description: string;
65
+ readonly longDescription?: string;
66
+ readonly tags: readonly string[];
67
+ readonly confidence: 'high' | 'medium' | 'low';
68
+ readonly source: 'built-in' | 'community';
69
+ }
70
+
71
+ /** Recipe catalog entry for dashboard display */
72
+ export interface RecipeCatalogEntry {
73
+ readonly name: string;
74
+ readonly displayName: string;
75
+ readonly description: string;
76
+ readonly tags: readonly string[];
77
+ readonly selectorType: string;
78
+ readonly mode: string;
79
+ readonly timeout: number;
80
+ }
81
+
82
+ /**
83
+ * Fallback path: user-global `~/.opensip-tools/`, used by tests and
84
+ * any code path that imports persistence helpers before the CLI has
85
+ * called `configurePersistencePaths`. New code should not rely on
86
+ * this fallback — call `configurePersistencePaths` first.
87
+ */
88
+ export const TOOLS_HOME = join(homedir(), '.opensip-tools');
89
+
90
+ /** Mutable per-process state — set by `configurePersistencePaths`. */
91
+ let storeDir: string = join(TOOLS_HOME, 'sessions');
92
+ let reportsDir: string = join(TOOLS_HOME, 'reports');
93
+ const MAX_SESSIONS = 100;
94
+
95
+ /**
96
+ * Configure where this module writes sessions and reports. Called
97
+ * once by the CLI bootstrap with the project paths. Idempotent and
98
+ * safe to call repeatedly (e.g. tests that switch project dirs).
99
+ */
100
+ export function configurePersistencePaths(paths: Pick<ProjectPaths, 'sessionsDir' | 'reportsDir'>): void {
101
+ storeDir = paths.sessionsDir;
102
+ reportsDir = paths.reportsDir;
103
+ }
104
+
105
+ /** Ensure directory exists — mkdirSync with recursive is idempotent */
106
+ function ensureDir(dir: string): void {
107
+ mkdirSync(dir, { recursive: true });
108
+ }
109
+
110
+ /** Sanitize a string for use in a filename — strip path separators and special chars */
111
+ export function sanitizeForFilename(s: string): string {
112
+ return s.replaceAll('..', '-').replaceAll(/[/\\:*?"<>|.]/g, '-');
113
+ }
114
+
115
+ /** Save a session result to disk */
116
+ export function saveSession(session: StoredSession): string {
117
+ ensureDir(storeDir);
118
+ const safeRecipe = session.recipe ? `-${sanitizeForFilename(session.recipe)}` : '';
119
+ const filename = `${session.timestamp.replaceAll(/[:.]/g, '-')}-${session.tool}${safeRecipe}.json`;
120
+ // Ensure filename stays within the sessions directory
121
+ const filepath = join(storeDir, basename(filename));
122
+ writeFileSync(filepath, JSON.stringify(session, null, 2), 'utf8');
123
+
124
+ pruneOldSessions();
125
+ return filepath;
126
+ }
127
+
128
+ /** Count session files in the store directory */
129
+ export function countSessions(): number {
130
+ ensureDir(storeDir);
131
+ return readdirSync(storeDir).filter(f => f.endsWith('.json')).length;
132
+ }
133
+
134
+ /** Delete all sessions. Returns the number of files deleted. */
135
+ export function clearAllSessions(): number {
136
+ ensureDir(storeDir);
137
+ const files = readdirSync(storeDir).filter(f => f.endsWith('.json'));
138
+ for (const file of files) {
139
+ unlinkSync(join(storeDir, file));
140
+ }
141
+ return files.length;
142
+ }
143
+
144
+ /** Delete sessions older than the given number of days. Returns the number of files deleted. */
145
+ export function clearSessionsOlderThan(days: number): number {
146
+ ensureDir(storeDir);
147
+ const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
148
+ const files = readdirSync(storeDir).filter(f => f.endsWith('.json'));
149
+ let deleted = 0;
150
+
151
+ for (const file of files) {
152
+ try {
153
+ const filepath = join(storeDir, file);
154
+ const raw = readFileSync(filepath, 'utf8');
155
+ const session = JSON.parse(raw) as { timestamp?: string };
156
+ if (session.timestamp) {
157
+ const sessionTime = new Date(session.timestamp).getTime();
158
+ if (!Number.isNaN(sessionTime) && sessionTime < cutoff) {
159
+ unlinkSync(filepath);
160
+ deleted++;
161
+ }
162
+ }
163
+ } catch {
164
+ // Skip files that can't be read/parsed
165
+ }
166
+ }
167
+
168
+ return deleted;
169
+ }
170
+
171
+ /** Load all sessions, newest first. Optional limit to avoid reading everything. */
172
+ export function loadSessions(limit?: number): StoredSession[] {
173
+ ensureDir(storeDir);
174
+ const files = readdirSync(storeDir)
175
+ .filter(f => f.endsWith('.json'))
176
+ .sort()
177
+ // eslint-disable-next-line unicorn/no-array-reverse -- target ES2022; Array#toReversed is ES2023 and not in the lib
178
+ .reverse();
179
+
180
+ const toRead = limit ? files.slice(0, limit) : files;
181
+ const sessions: StoredSession[] = [];
182
+ for (const file of toRead) {
183
+ try {
184
+ const raw = readFileSync(join(storeDir, file), 'utf8');
185
+ sessions.push(JSON.parse(raw) as StoredSession);
186
+ } catch {
187
+ // Warn about corrupted files — don't crash
188
+ logger.warn({ evt: 'cli.session.corrupted', module: 'cli:persistence', msg: `Skipping corrupted session file: ${file}`, file });
189
+ }
190
+ }
191
+ return sessions;
192
+ }
193
+
194
+ /** Load the most recent session */
195
+ export function loadLatestSession(): StoredSession | null {
196
+ const sessions = loadSessions(1);
197
+ return sessions[0] ?? null;
198
+ }
199
+
200
+ /** Prune sessions beyond the max count */
201
+ function pruneOldSessions(): void {
202
+ const files = readdirSync(storeDir)
203
+ .filter(f => f.endsWith('.json'))
204
+ .sort()
205
+ // eslint-disable-next-line unicorn/no-array-reverse -- target ES2022; Array#toReversed is ES2023 and not in the lib
206
+ .reverse();
207
+
208
+ if (files.length <= MAX_SESSIONS) return;
209
+
210
+ for (const file of files.slice(MAX_SESSIONS)) {
211
+ try {
212
+ unlinkSync(join(storeDir, file));
213
+ } catch {
214
+ // Best effort
215
+ }
216
+ }
217
+ }
218
+
219
+ /** Get the store directory path */
220
+ export function getStoreDir(): string {
221
+ return storeDir;
222
+ }
223
+
224
+ /** Get the reports directory path, creating it if needed */
225
+ export function getReportsDir(): string {
226
+ ensureDir(reportsDir);
227
+ return reportsDir;
228
+ }
229
+
230
+ /** Generate a unique session ID */
231
+ export function generateSessionId(): string {
232
+ return randomUUID();
233
+ }
package/src/types.ts ADDED
@@ -0,0 +1,306 @@
1
+ import type { StoredSession } from './persistence/store.js';
2
+
3
+ // =============================================================================
4
+ // CLI OPTIONS TYPES
5
+ // =============================================================================
6
+
7
+ /** Options for the `fit` subcommand (derived from Commander flags). */
8
+ export interface FitOptions {
9
+ recipe?: string;
10
+ check?: string;
11
+ tags?: string;
12
+ list: boolean;
13
+ recipes: boolean;
14
+ json: boolean;
15
+ verbose: boolean;
16
+ findings: boolean;
17
+ reportTo?: string;
18
+ apiKey?: string;
19
+ exclude: string[];
20
+ cwd: string;
21
+ /** Explicit path to opensip-tools.config.yml (overrides package.json pointer and default location). */
22
+ config?: string;
23
+ debug: boolean;
24
+ /** Architecture-gate: save the current run's findings as a baseline. Mutually exclusive with --gate-compare. */
25
+ gateSave?: boolean;
26
+ /** Architecture-gate: compare current findings against a saved baseline; exit 1 if regressions found. Mutually exclusive with --gate-save. */
27
+ gateCompare?: boolean;
28
+ /** Path to the baseline file used by --gate-save / --gate-compare. Default: opensip-tools/.runtime/baseline.sarif */
29
+ baseline?: string;
30
+ }
31
+
32
+ /** Options for the `init` subcommand. */
33
+ export interface InitOptions {
34
+ cwd: string;
35
+ json: boolean;
36
+ debug: boolean;
37
+ /**
38
+ * Comma-separated language list. When omitted, init detects the
39
+ * project's primary language(s) by inspecting filesystem markers
40
+ * (Cargo.toml, pyproject.toml, etc.) and exits 2 with a prompt if
41
+ * the result is ambiguous.
42
+ */
43
+ language?: string;
44
+ /**
45
+ * Overwrite an existing opensip-tools.config.yml or example files
46
+ * without prompting. Default false — the safe behavior is to refuse
47
+ * overwriting.
48
+ */
49
+ force: boolean;
50
+ }
51
+
52
+ /** Options for `sim` subcommand. */
53
+ export interface ToolOptions {
54
+ cwd: string;
55
+ json: boolean;
56
+ debug: boolean;
57
+ /** Recipe name to run. Defaults to the built-in `default` if omitted. */
58
+ recipe?: string;
59
+ /** Filter by scenario kind (load / chaos / invariant / fix-evaluation). */
60
+ kind?: string;
61
+ }
62
+
63
+ /**
64
+ * Backwards-compatible alias — commands that previously accepted CliArgs
65
+ * can accept this union instead. The shape covers all fields used by any command.
66
+ */
67
+ export interface CliArgs {
68
+ command: string;
69
+ json: boolean;
70
+ check?: string;
71
+ recipe?: string;
72
+ cwd: string;
73
+ help: boolean;
74
+ list: boolean;
75
+ listRecipes: boolean;
76
+ verbose: boolean;
77
+ reportTo?: string;
78
+ apiKey?: string;
79
+ exclude: string[];
80
+ findings: boolean;
81
+ tags?: string;
82
+ /** Suppress banner/boxes; show only the pass-fail summary line. */
83
+ quiet?: boolean;
84
+ /** Open the HTML dashboard in the default browser after a successful run. */
85
+ open?: boolean;
86
+ /** Explicit opensip-tools.config.yml path from --config flag. */
87
+ config?: string;
88
+ /** Architecture-gate flags — see FitOptions for details. */
89
+ gateSave?: boolean;
90
+ gateCompare?: boolean;
91
+ baseline?: string;
92
+ /**
93
+ * Sim-only: filter scenarios by kind.
94
+ * One of 'load' | 'chaos' | 'invariant' | 'fix-evaluation', or undefined for all.
95
+ */
96
+ kind?: string;
97
+ }
98
+
99
+ /** Structured JSON output format */
100
+ export interface CliOutput {
101
+ readonly version: '1.0';
102
+ readonly tool: 'fit' | 'sim';
103
+ readonly timestamp: string;
104
+ readonly recipe?: string;
105
+ readonly score: number;
106
+ readonly passed: boolean;
107
+ readonly summary: { total: number; passed: number; failed: number; errors: number; warnings: number };
108
+ readonly checks: readonly CheckOutput[];
109
+ readonly durationMs: number;
110
+ }
111
+
112
+ export interface CheckOutput {
113
+ readonly checkSlug: string;
114
+ readonly passed: boolean;
115
+ readonly violationCount?: number;
116
+ readonly findings: readonly FindingOutput[];
117
+ readonly durationMs: number;
118
+ }
119
+
120
+ export interface FindingOutput {
121
+ readonly ruleId: string;
122
+ readonly message: string;
123
+ readonly severity: 'error' | 'warning';
124
+ readonly filePath?: string;
125
+ readonly line?: number;
126
+ readonly column?: number;
127
+ readonly suggestion?: string;
128
+ }
129
+
130
+ export interface TableRow {
131
+ check: string;
132
+ status: 'PASS' | 'FAIL' | 'TIMEOUT';
133
+ errors: number;
134
+ warnings: number;
135
+ validated: string;
136
+ ignored: number;
137
+ duration: string;
138
+ durationMs: number;
139
+ }
140
+
141
+ export interface SummaryOptions {
142
+ passed: number;
143
+ failed: number;
144
+ totalErrors: number;
145
+ totalWarnings: number;
146
+ totalIgnored: number;
147
+ durationMs: number;
148
+ }
149
+
150
+ // =============================================================================
151
+ // CommandResult — union type for all command results
152
+ // =============================================================================
153
+
154
+ /** Union type for all command results — App.tsx dispatches on result.type */
155
+ export type CommandResult =
156
+ | FitDoneResult
157
+ | SimDoneResult
158
+ | ListChecksResult
159
+ | ListRecipesResult
160
+ | HistoryResult
161
+ | DashboardResult
162
+ | InitResult
163
+ | ExperimentalResult
164
+ | PluginResult
165
+ | ClearDoneResult
166
+ | HelpResult
167
+ | ErrorResult;
168
+
169
+ export interface ClearDoneResult {
170
+ type: 'clear-done';
171
+ action: 'done' | 'cancelled' | 'empty';
172
+ deletedCount: number;
173
+ sessionCount: number;
174
+ }
175
+
176
+ export interface FitDoneResult {
177
+ type: 'fit-done';
178
+ rows: TableRow[];
179
+ summary: SummaryOptions;
180
+ label: string;
181
+ cwd: string;
182
+ findings?: {
183
+ checks: {
184
+ checkSlug: string;
185
+ errorCount: number;
186
+ warningCount: number;
187
+ error?: string;
188
+ violations?: {
189
+ severity: 'error' | 'warning';
190
+ message: string;
191
+ file?: string;
192
+ line?: number;
193
+ suggestion?: string;
194
+ }[];
195
+ }[];
196
+ };
197
+ reportStatus?: {
198
+ url: string;
199
+ findingCount: number;
200
+ runCount: number;
201
+ success: boolean;
202
+ error?: string;
203
+ chunksTotal?: number;
204
+ chunksSucceeded?: number;
205
+ };
206
+ /** Whether the run should cause a non-zero exit code (based on failOnErrors/failOnWarnings config) */
207
+ shouldFail?: boolean;
208
+ /** Whether an opensip-tools.config.yml was found in the target directory */
209
+ configFound?: boolean;
210
+ }
211
+
212
+ export interface ListChecksResult {
213
+ type: 'list-checks';
214
+ checks: { slug: string; description: string; tags: string[] }[];
215
+ totalCount: number;
216
+ }
217
+
218
+ export interface ListRecipesResult {
219
+ type: 'list-recipes';
220
+ recipes: { name: string; description: string; checkCount: string }[];
221
+ }
222
+
223
+ export interface HistoryResult {
224
+ type: 'history';
225
+ sessions: StoredSession[];
226
+ }
227
+
228
+ export interface DashboardResult {
229
+ type: 'dashboard';
230
+ path: string;
231
+ opened: boolean;
232
+ }
233
+
234
+ export interface InitResult {
235
+ type: 'init';
236
+ created: boolean;
237
+ path: string;
238
+ alreadyExists: boolean;
239
+ cwd: string;
240
+ configFilename: string;
241
+ /** Languages selected for this scaffold (post-detection or from --language). */
242
+ languages?: readonly ('typescript' | 'rust' | 'python' | 'go' | 'java' | 'cpp')[];
243
+ /**
244
+ * Every file init created, in display order. Includes the config
245
+ * file plus example check / recipe / scenario scaffolds. Empty
246
+ * when alreadyExists is true (nothing was written).
247
+ */
248
+ createdFiles?: readonly string[];
249
+ /** True when init appended `opensip-tools/.runtime/` to .gitignore. */
250
+ gitignoreUpdated?: boolean;
251
+ /**
252
+ * When detection is ambiguous and --language wasn't passed, init
253
+ * exits without writing anything and surfaces this error so the
254
+ * user can re-invoke with --language <list>.
255
+ */
256
+ ambiguousLanguageError?: {
257
+ detected: readonly string[];
258
+ message: string;
259
+ };
260
+ }
261
+
262
+ export interface ExperimentalResult {
263
+ type: 'experimental';
264
+ tool: 'sim';
265
+ cwd: string;
266
+ /** Optional `--kind` filter (load / chaos / invariant / fix-evaluation). */
267
+ kind?: string;
268
+ }
269
+
270
+ /** Outcome of a `sim --recipe <name>` run. */
271
+ export interface SimDoneResult {
272
+ type: 'sim-done';
273
+ recipeName: string;
274
+ cwd: string;
275
+ totalScenarios: number;
276
+ passedScenarios: number;
277
+ failedScenarios: number;
278
+ scenarios: {
279
+ scenarioId: string;
280
+ scenarioName: string;
281
+ kind: 'load' | 'chaos' | 'invariant' | 'fix-evaluation';
282
+ passed: boolean;
283
+ durationMs: number;
284
+ error?: string;
285
+ }[];
286
+ durationMs: number;
287
+ /** Whether the run should cause a non-zero exit code (any scenario failed). */
288
+ shouldFail?: boolean;
289
+ }
290
+
291
+ export interface PluginResult {
292
+ type: 'plugin';
293
+ action: 'list' | 'install' | 'remove' | 'sync' | 'add';
294
+ [key: string]: unknown;
295
+ }
296
+
297
+ export interface HelpResult {
298
+ type: 'help';
299
+ }
300
+
301
+ export interface ErrorResult {
302
+ type: 'error';
303
+ message: string;
304
+ suggestion?: string;
305
+ exitCode: number;
306
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"]
8
+ }
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ export default defineConfig({
3
+ test: {
4
+ include: ['src/**/*.test.ts'],
5
+ passWithNoTests: true,
6
+ coverage: {
7
+ include: ['src/**'],
8
+ exclude: [
9
+ 'src/**/*.test.ts',
10
+ 'src/**/__tests__/**',
11
+ 'src/**/index.ts',
12
+ 'src/types.ts',
13
+ ],
14
+ },
15
+ },
16
+ });