skrypt-ai 0.6.0 → 0.7.0

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 (88) hide show
  1. package/dist/audit/doc-parser.d.ts +5 -0
  2. package/dist/audit/doc-parser.js +106 -0
  3. package/dist/audit/index.d.ts +4 -0
  4. package/dist/audit/index.js +4 -0
  5. package/dist/audit/matcher.d.ts +6 -0
  6. package/dist/audit/matcher.js +94 -0
  7. package/dist/audit/reporter.d.ts +9 -0
  8. package/dist/audit/reporter.js +106 -0
  9. package/dist/audit/types.d.ts +37 -0
  10. package/dist/audit/types.js +1 -0
  11. package/dist/auth/index.js +3 -1
  12. package/dist/cli.js +11 -1
  13. package/dist/commands/audit.d.ts +2 -0
  14. package/dist/commands/audit.js +59 -0
  15. package/dist/commands/config.d.ts +2 -0
  16. package/dist/commands/config.js +73 -0
  17. package/dist/commands/cron.js +4 -0
  18. package/dist/commands/generate.d.ts +7 -0
  19. package/dist/commands/generate.js +528 -234
  20. package/dist/commands/refresh.d.ts +2 -0
  21. package/dist/commands/refresh.js +158 -0
  22. package/dist/commands/review-pr.js +5 -0
  23. package/dist/commands/review.d.ts +2 -0
  24. package/dist/commands/review.js +110 -0
  25. package/dist/commands/test.js +177 -236
  26. package/dist/commands/watch.js +29 -20
  27. package/dist/config/loader.d.ts +6 -1
  28. package/dist/config/loader.js +38 -2
  29. package/dist/config/types.d.ts +7 -0
  30. package/dist/generator/generator.js +2 -1
  31. package/dist/generator/types.d.ts +3 -0
  32. package/dist/generator/writer.js +60 -28
  33. package/dist/github/org-discovery.d.ts +17 -0
  34. package/dist/github/org-discovery.js +93 -0
  35. package/dist/llm/index.d.ts +2 -0
  36. package/dist/llm/index.js +8 -2
  37. package/dist/next-actions/actions.d.ts +2 -0
  38. package/dist/next-actions/actions.js +190 -0
  39. package/dist/next-actions/index.d.ts +6 -0
  40. package/dist/next-actions/index.js +39 -0
  41. package/dist/next-actions/setup.d.ts +2 -0
  42. package/dist/next-actions/setup.js +72 -0
  43. package/dist/next-actions/state.d.ts +7 -0
  44. package/dist/next-actions/state.js +68 -0
  45. package/dist/next-actions/suggest.d.ts +3 -0
  46. package/dist/next-actions/suggest.js +47 -0
  47. package/dist/next-actions/types.d.ts +26 -0
  48. package/dist/next-actions/types.js +1 -0
  49. package/dist/refresh/differ.d.ts +9 -0
  50. package/dist/refresh/differ.js +67 -0
  51. package/dist/refresh/index.d.ts +4 -0
  52. package/dist/refresh/index.js +4 -0
  53. package/dist/refresh/manifest.d.ts +18 -0
  54. package/dist/refresh/manifest.js +71 -0
  55. package/dist/refresh/splicer.d.ts +9 -0
  56. package/dist/refresh/splicer.js +50 -0
  57. package/dist/refresh/types.d.ts +37 -0
  58. package/dist/refresh/types.js +1 -0
  59. package/dist/review/index.d.ts +8 -0
  60. package/dist/review/index.js +94 -0
  61. package/dist/review/parser.d.ts +16 -0
  62. package/dist/review/parser.js +95 -0
  63. package/dist/review/types.d.ts +18 -0
  64. package/dist/review/types.js +1 -0
  65. package/dist/scanner/types.d.ts +2 -0
  66. package/dist/structure/index.d.ts +19 -0
  67. package/dist/structure/index.js +92 -0
  68. package/dist/structure/planner.d.ts +8 -0
  69. package/dist/structure/planner.js +180 -0
  70. package/dist/structure/topology.d.ts +16 -0
  71. package/dist/structure/topology.js +49 -0
  72. package/dist/structure/types.d.ts +26 -0
  73. package/dist/structure/types.js +1 -0
  74. package/dist/testing/comparator.d.ts +7 -0
  75. package/dist/testing/comparator.js +77 -0
  76. package/dist/testing/docker.d.ts +21 -0
  77. package/dist/testing/docker.js +234 -0
  78. package/dist/testing/env.d.ts +16 -0
  79. package/dist/testing/env.js +58 -0
  80. package/dist/testing/extractor.d.ts +9 -0
  81. package/dist/testing/extractor.js +195 -0
  82. package/dist/testing/index.d.ts +6 -0
  83. package/dist/testing/index.js +6 -0
  84. package/dist/testing/runner.d.ts +5 -0
  85. package/dist/testing/runner.js +225 -0
  86. package/dist/testing/types.d.ts +58 -0
  87. package/dist/testing/types.js +1 -0
  88. package/package.json +1 -1
@@ -0,0 +1,234 @@
1
+ import { spawnSync, spawn } from 'child_process';
2
+ import { writeFileSync, mkdirSync, rmSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { tmpdir } from 'os';
5
+ import { randomUUID } from 'crypto';
6
+ import { compareOutput } from './comparator.js';
7
+ import { checkRequiredEnv } from './env.js';
8
+ /**
9
+ * Available Docker environments for multi-env testing
10
+ */
11
+ export const DOCKER_ENVIRONMENTS = [
12
+ { name: 'node-20', image: 'node:20-slim', command: 'node', languages: ['typescript', 'ts', 'javascript', 'js'] },
13
+ { name: 'node-18', image: 'node:18-slim', command: 'node', languages: ['typescript', 'ts', 'javascript', 'js'] },
14
+ { name: 'python-3.12', image: 'python:3.12-slim', command: 'python3', languages: ['python', 'py'] },
15
+ { name: 'python-3.10', image: 'python:3.10-slim', command: 'python3', languages: ['python', 'py'] },
16
+ { name: 'bun', image: 'oven/bun:latest', command: 'bun', languages: ['typescript', 'ts', 'javascript', 'js'] },
17
+ { name: 'deno', image: 'denoland/deno:latest', command: 'deno', languages: ['typescript', 'ts', 'javascript', 'js'] },
18
+ ];
19
+ /**
20
+ * Check if Docker is available on this machine
21
+ */
22
+ export function isDockerAvailable() {
23
+ try {
24
+ const result = spawnSync('docker', ['info'], {
25
+ stdio: 'pipe',
26
+ timeout: 5000,
27
+ });
28
+ return result.status === 0;
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
34
+ /**
35
+ * Parse a comma-separated environments string into DockerEnvironment objects
36
+ */
37
+ export function parseEnvironments(envString) {
38
+ const names = envString.split(',').map(s => s.trim()).filter(Boolean);
39
+ const envs = [];
40
+ for (const name of names) {
41
+ const env = DOCKER_ENVIRONMENTS.find(e => e.name === name);
42
+ if (env) {
43
+ envs.push(env);
44
+ }
45
+ else {
46
+ console.warn(` Warning: Unknown environment "${name}". Available: ${DOCKER_ENVIRONMENTS.map(e => e.name).join(', ')}`);
47
+ }
48
+ }
49
+ return envs;
50
+ }
51
+ /**
52
+ * Get compatible Docker environments for a given language
53
+ */
54
+ export function getCompatibleEnvironments(language, environments) {
55
+ return environments.filter(env => env.languages.includes(language));
56
+ }
57
+ /**
58
+ * Run a snippet in a Docker container
59
+ */
60
+ export async function runInDocker(snippet, environment, config) {
61
+ const startTime = Date.now();
62
+ const envLabel = `docker:${environment.name}`;
63
+ // Check for skip directive
64
+ if (snippet.skipReason) {
65
+ return {
66
+ snippet,
67
+ status: 'skip',
68
+ stdout: '',
69
+ stderr: `Skipped: ${snippet.skipReason}`,
70
+ exitCode: 0,
71
+ duration: 0,
72
+ environment: envLabel,
73
+ };
74
+ }
75
+ // Check required env vars
76
+ if (snippet.requiredEnv && snippet.requiredEnv.length > 0) {
77
+ const { ok, missing } = checkRequiredEnv(snippet.requiredEnv, config.envVars);
78
+ if (!ok) {
79
+ return {
80
+ snippet,
81
+ status: 'skip',
82
+ stdout: '',
83
+ stderr: `Missing required env vars: ${missing.join(', ')}`,
84
+ exitCode: 0,
85
+ duration: 0,
86
+ environment: envLabel,
87
+ };
88
+ }
89
+ }
90
+ const tempDir = join(tmpdir(), `skrypt-docker-${randomUUID()}`);
91
+ mkdirSync(tempDir, { recursive: true });
92
+ try {
93
+ const isTS = ['typescript', 'ts'].includes(snippet.language);
94
+ const isPy = ['python', 'py'].includes(snippet.language);
95
+ let filename;
96
+ let containerCmd;
97
+ if (isPy) {
98
+ filename = 'test.py';
99
+ containerCmd = ['python3', `/work/${filename}`];
100
+ }
101
+ else if (isTS && environment.name === 'bun') {
102
+ filename = 'test.ts';
103
+ containerCmd = ['bun', 'run', `/work/${filename}`];
104
+ }
105
+ else if (isTS && environment.name === 'deno') {
106
+ filename = 'test.ts';
107
+ containerCmd = ['deno', 'run', '--allow-all', `/work/${filename}`];
108
+ }
109
+ else if (isTS) {
110
+ // Node needs tsx for TypeScript
111
+ filename = 'test.ts';
112
+ containerCmd = ['npx', '-y', 'tsx', `/work/${filename}`];
113
+ }
114
+ else {
115
+ filename = 'test.js';
116
+ containerCmd = ['node', `/work/${filename}`];
117
+ }
118
+ writeFileSync(join(tempDir, filename), snippet.code);
119
+ // Build Docker args
120
+ const dockerArgs = ['run', '--rm', '-v', `${tempDir}:/work`, '-w', '/work'];
121
+ // Inject env vars
122
+ for (const [key, value] of Object.entries(config.envVars)) {
123
+ dockerArgs.push('-e', `${key}=${value}`);
124
+ }
125
+ dockerArgs.push(environment.image, ...containerCmd);
126
+ const result = await executeDocker(dockerArgs, config.timeout);
127
+ const duration = Date.now() - startTime;
128
+ // Check output if expected
129
+ if (snippet.expectedOutput !== undefined) {
130
+ const comparison = compareOutput(result.stdout, snippet.expectedOutput, snippet.expectedOutputMode || 'exact');
131
+ if (!comparison.match) {
132
+ return {
133
+ snippet,
134
+ status: 'output_mismatch',
135
+ stdout: result.stdout,
136
+ stderr: result.stderr,
137
+ exitCode: result.exitCode,
138
+ duration,
139
+ environment: envLabel,
140
+ outputMatch: false,
141
+ diff: comparison.diff,
142
+ };
143
+ }
144
+ return {
145
+ snippet,
146
+ status: result.exitCode === 0 ? 'pass' : 'fail',
147
+ stdout: result.stdout,
148
+ stderr: result.stderr,
149
+ exitCode: result.exitCode,
150
+ duration,
151
+ environment: envLabel,
152
+ outputMatch: true,
153
+ };
154
+ }
155
+ return {
156
+ snippet,
157
+ status: result.timedOut ? 'timeout' : (result.exitCode === 0 ? 'pass' : 'fail'),
158
+ stdout: result.stdout,
159
+ stderr: result.timedOut
160
+ ? `Timeout: execution exceeded ${config.timeout}ms`
161
+ : result.stderr,
162
+ exitCode: result.exitCode,
163
+ duration,
164
+ environment: envLabel,
165
+ };
166
+ }
167
+ catch (err) {
168
+ const message = err instanceof Error ? err.message : String(err);
169
+ return {
170
+ snippet,
171
+ status: 'fail',
172
+ stdout: '',
173
+ stderr: message,
174
+ exitCode: 1,
175
+ duration: Date.now() - startTime,
176
+ environment: envLabel,
177
+ };
178
+ }
179
+ finally {
180
+ try {
181
+ rmSync(tempDir, { recursive: true, force: true });
182
+ }
183
+ catch {
184
+ // Ignore cleanup errors
185
+ }
186
+ }
187
+ }
188
+ function executeDocker(args, timeoutMs) {
189
+ return new Promise((resolve) => {
190
+ const proc = spawn('docker', args, {
191
+ stdio: ['pipe', 'pipe', 'pipe'],
192
+ });
193
+ let stdout = '';
194
+ let stderr = '';
195
+ let timedOut = false;
196
+ const MAX_BUFFER = 1024 * 1024; // 1MB cap to prevent OOM
197
+ const timeout = setTimeout(() => {
198
+ timedOut = true;
199
+ proc.kill('SIGKILL');
200
+ }, timeoutMs);
201
+ proc.stdout?.on('data', (data) => {
202
+ if (stdout.length < MAX_BUFFER) {
203
+ stdout += data.toString();
204
+ if (stdout.length > MAX_BUFFER)
205
+ stdout = stdout.slice(0, MAX_BUFFER);
206
+ }
207
+ });
208
+ proc.stderr?.on('data', (data) => {
209
+ if (stderr.length < MAX_BUFFER) {
210
+ stderr += data.toString();
211
+ if (stderr.length > MAX_BUFFER)
212
+ stderr = stderr.slice(0, MAX_BUFFER);
213
+ }
214
+ });
215
+ proc.on('close', (code) => {
216
+ clearTimeout(timeout);
217
+ resolve({
218
+ exitCode: timedOut ? 1 : (code ?? 1),
219
+ stdout,
220
+ stderr,
221
+ timedOut,
222
+ });
223
+ });
224
+ proc.on('error', (err) => {
225
+ clearTimeout(timeout);
226
+ resolve({
227
+ exitCode: 1,
228
+ stdout,
229
+ stderr: err.message,
230
+ timedOut: false,
231
+ });
232
+ });
233
+ });
234
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Load environment variables from a .env file
3
+ */
4
+ export declare function loadEnvFile(envFilePath: string): Record<string, string>;
5
+ /**
6
+ * Build a clean environment for snippet execution.
7
+ * Only passes PATH, HOME, and explicitly injected vars.
8
+ */
9
+ export declare function buildCleanEnv(injectedVars: Record<string, string>): Record<string, string>;
10
+ /**
11
+ * Check if all required env vars are present
12
+ */
13
+ export declare function checkRequiredEnv(required: string[], available: Record<string, string>): {
14
+ ok: boolean;
15
+ missing: string[];
16
+ };
@@ -0,0 +1,58 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ /**
3
+ * Load environment variables from a .env file
4
+ */
5
+ export function loadEnvFile(envFilePath) {
6
+ if (!existsSync(envFilePath)) {
7
+ throw new Error(`Env file not found: ${envFilePath}`);
8
+ }
9
+ const content = readFileSync(envFilePath, 'utf-8');
10
+ const vars = {};
11
+ for (const line of content.split('\n')) {
12
+ const trimmed = line.trim();
13
+ if (!trimmed || trimmed.startsWith('#'))
14
+ continue;
15
+ const eqIdx = trimmed.indexOf('=');
16
+ if (eqIdx === -1)
17
+ continue;
18
+ const key = trimmed.substring(0, eqIdx).trim();
19
+ let value = trimmed.substring(eqIdx + 1).trim();
20
+ // Strip surrounding quotes
21
+ if ((value.startsWith('"') && value.endsWith('"')) ||
22
+ (value.startsWith("'") && value.endsWith("'"))) {
23
+ value = value.slice(1, -1);
24
+ }
25
+ vars[key] = value;
26
+ }
27
+ return vars;
28
+ }
29
+ /**
30
+ * Build a clean environment for snippet execution.
31
+ * Only passes PATH, HOME, and explicitly injected vars.
32
+ */
33
+ export function buildCleanEnv(injectedVars) {
34
+ const clean = {
35
+ NODE_NO_WARNINGS: '1',
36
+ };
37
+ // Carry over PATH so runtimes can be found
38
+ if (process.env.PATH) {
39
+ clean.PATH = process.env.PATH;
40
+ }
41
+ // HOME / USERPROFILE for tools that need it
42
+ if (process.env.HOME) {
43
+ clean.HOME = process.env.HOME;
44
+ }
45
+ if (process.env.USERPROFILE) {
46
+ clean.USERPROFILE = process.env.USERPROFILE;
47
+ }
48
+ // Inject user-provided vars (API keys, etc.)
49
+ Object.assign(clean, injectedVars);
50
+ return clean;
51
+ }
52
+ /**
53
+ * Check if all required env vars are present
54
+ */
55
+ export function checkRequiredEnv(required, available) {
56
+ const missing = required.filter(key => !available[key]);
57
+ return { ok: missing.length === 0, missing };
58
+ }
@@ -0,0 +1,9 @@
1
+ import { ExtractedSnippet } from './types.js';
2
+ /**
3
+ * Find all .md/.mdx files in a directory recursively
4
+ */
5
+ export declare function findDocFiles(dir: string): string[];
6
+ /**
7
+ * Extract code blocks from a markdown/MDX file
8
+ */
9
+ export declare function extractSnippets(filePath: string, languageFilter?: string): ExtractedSnippet[];
@@ -0,0 +1,195 @@
1
+ import { readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join, extname } from 'path';
3
+ const SUPPORTED_LANGUAGES = ['typescript', 'ts', 'javascript', 'js', 'python', 'py'];
4
+ /**
5
+ * Normalize language to canonical form for comparison
6
+ */
7
+ const LANGUAGE_CANONICAL = {
8
+ typescript: 'typescript',
9
+ ts: 'typescript',
10
+ javascript: 'javascript',
11
+ js: 'javascript',
12
+ python: 'python',
13
+ py: 'python',
14
+ };
15
+ /**
16
+ * Find all .md/.mdx files in a directory recursively
17
+ */
18
+ export function findDocFiles(dir) {
19
+ const files = [];
20
+ function walk(currentDir) {
21
+ const entries = readdirSync(currentDir);
22
+ for (const entry of entries) {
23
+ const fullPath = join(currentDir, entry);
24
+ const stat = statSync(fullPath);
25
+ if (stat.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules') {
26
+ walk(fullPath);
27
+ }
28
+ else if (stat.isFile() && (extname(entry) === '.mdx' || extname(entry) === '.md')) {
29
+ files.push(fullPath);
30
+ }
31
+ }
32
+ }
33
+ walk(dir);
34
+ return files;
35
+ }
36
+ /**
37
+ * Parse a multi-line expected output block.
38
+ * Supports:
39
+ * // Output: single line
40
+ * // Output (contains): partial match
41
+ * // Output:
42
+ * // line1
43
+ * // line2
44
+ */
45
+ function parseExpectedOutput(code) {
46
+ const lines = code.split('\n');
47
+ let output;
48
+ let mode = 'exact';
49
+ for (let i = 0; i < lines.length; i++) {
50
+ const line = lines[i].trim();
51
+ // Check for "// Output (contains): ..."
52
+ const containsMatch = line.match(/^\/\/\s*Output\s*\(contains\)\s*:\s*(.*)$/);
53
+ if (containsMatch) {
54
+ mode = 'contains';
55
+ const value = containsMatch[1].trim();
56
+ if (value) {
57
+ output = value;
58
+ }
59
+ else {
60
+ // Multi-line: collect subsequent // prefixed lines
61
+ output = collectMultiLineOutput(lines, i + 1);
62
+ }
63
+ break;
64
+ }
65
+ // Check for "// Output: ..."
66
+ const exactMatch = line.match(/^\/\/\s*Output\s*:\s*(.*)$/);
67
+ if (exactMatch) {
68
+ const value = exactMatch[1].trim();
69
+ if (value) {
70
+ output = value;
71
+ }
72
+ else {
73
+ output = collectMultiLineOutput(lines, i + 1);
74
+ }
75
+ break;
76
+ }
77
+ // Python: # Output: ...
78
+ const pyContainsMatch = line.match(/^#\s*Output\s*\(contains\)\s*:\s*(.*)$/);
79
+ if (pyContainsMatch) {
80
+ mode = 'contains';
81
+ const value = pyContainsMatch[1].trim();
82
+ if (value) {
83
+ output = value;
84
+ }
85
+ else {
86
+ output = collectMultiLineOutput(lines, i + 1, '#');
87
+ }
88
+ break;
89
+ }
90
+ const pyMatch = line.match(/^#\s*Output\s*:\s*(.*)$/);
91
+ if (pyMatch) {
92
+ const value = pyMatch[1].trim();
93
+ if (value) {
94
+ output = value;
95
+ }
96
+ else {
97
+ output = collectMultiLineOutput(lines, i + 1, '#');
98
+ }
99
+ break;
100
+ }
101
+ }
102
+ return { output, mode };
103
+ }
104
+ function collectMultiLineOutput(lines, startIdx, commentChar = '//') {
105
+ const outputLines = [];
106
+ for (let j = startIdx; j < lines.length; j++) {
107
+ const l = lines[j].trim();
108
+ if (commentChar === '//') {
109
+ if (l.startsWith('//')) {
110
+ outputLines.push(l.replace(/^\/\/\s?/, ''));
111
+ }
112
+ else {
113
+ break;
114
+ }
115
+ }
116
+ else {
117
+ if (l.startsWith('#')) {
118
+ outputLines.push(l.replace(/^#\s?/, ''));
119
+ }
120
+ else {
121
+ break;
122
+ }
123
+ }
124
+ }
125
+ return outputLines.join('\n');
126
+ }
127
+ /**
128
+ * Parse directive comments from a code snippet
129
+ */
130
+ function parseDirectives(code, language) {
131
+ const commentPrefix = ['python', 'py'].includes(language) ? '#' : '//';
132
+ const lines = code.split('\n');
133
+ let requiredEnv;
134
+ let skipReason;
135
+ let dependencies;
136
+ for (const line of lines) {
137
+ const trimmed = line.trim();
138
+ // Requires: ENV_VAR1, ENV_VAR2
139
+ const reqMatch = trimmed.match(new RegExp(`^${commentPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*Requires:\\s*(.+)$`));
140
+ if (reqMatch) {
141
+ requiredEnv = reqMatch[1].split(',').map(s => s.trim()).filter(Boolean);
142
+ }
143
+ // Skip: reason
144
+ const skipMatch = trimmed.match(new RegExp(`^${commentPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*Skip:\\s*(.+)$`));
145
+ if (skipMatch) {
146
+ skipReason = skipMatch[1].trim();
147
+ }
148
+ // Deps: pkg1, pkg2
149
+ const depsMatch = trimmed.match(new RegExp(`^${commentPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*Deps:\\s*(.+)$`));
150
+ if (depsMatch) {
151
+ dependencies = depsMatch[1].split(',').map(s => s.trim()).filter(Boolean);
152
+ }
153
+ }
154
+ return { requiredEnv, skipReason, dependencies };
155
+ }
156
+ /**
157
+ * Extract code blocks from a markdown/MDX file
158
+ */
159
+ export function extractSnippets(filePath, languageFilter) {
160
+ const content = readFileSync(filePath, 'utf-8');
161
+ const snippets = [];
162
+ const codeBlockRegex = /```(\w+)?[^\n]*\n([\s\S]*?)```/g;
163
+ let match;
164
+ while ((match = codeBlockRegex.exec(content)) !== null) {
165
+ const language = (match[1] || '').toLowerCase();
166
+ const code = (match[2] || '').trim();
167
+ if (languageFilter) {
168
+ const filterCanonical = LANGUAGE_CANONICAL[languageFilter.toLowerCase()] || languageFilter.toLowerCase();
169
+ const langCanonical = LANGUAGE_CANONICAL[language] || language;
170
+ if (langCanonical !== filterCanonical) {
171
+ continue;
172
+ }
173
+ }
174
+ if (!SUPPORTED_LANGUAGES.includes(language)) {
175
+ continue;
176
+ }
177
+ const beforeMatch = content.substring(0, match.index);
178
+ const lineNumber = beforeMatch.split('\n').length;
179
+ const { output, mode } = parseExpectedOutput(code);
180
+ const directives = parseDirectives(code, language);
181
+ snippets.push({
182
+ code,
183
+ language,
184
+ filePath,
185
+ lineNumber,
186
+ index: snippets.length,
187
+ expectedOutput: output,
188
+ expectedOutputMode: mode,
189
+ requiredEnv: directives.requiredEnv,
190
+ skipReason: directives.skipReason,
191
+ dependencies: directives.dependencies,
192
+ });
193
+ }
194
+ return snippets;
195
+ }
@@ -0,0 +1,6 @@
1
+ export * from './types.js';
2
+ export * from './extractor.js';
3
+ export * from './comparator.js';
4
+ export * from './env.js';
5
+ export * from './runner.js';
6
+ export * from './docker.js';
@@ -0,0 +1,6 @@
1
+ export * from './types.js';
2
+ export * from './extractor.js';
3
+ export * from './comparator.js';
4
+ export * from './env.js';
5
+ export * from './runner.js';
6
+ export * from './docker.js';
@@ -0,0 +1,5 @@
1
+ import { ExtractedSnippet, TestResult, RunnerConfig } from './types.js';
2
+ /**
3
+ * Run a snippet locally in an isolated temp directory with a clean environment
4
+ */
5
+ export declare function runLocally(snippet: ExtractedSnippet, config: RunnerConfig): Promise<TestResult>;