skrypt-ai 0.6.1 → 0.8.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 (180) 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/auth/index.js +6 -4
  11. package/dist/cli.js +12 -2
  12. package/dist/commands/audit.d.ts +2 -0
  13. package/dist/commands/audit.js +59 -0
  14. package/dist/commands/config.d.ts +2 -0
  15. package/dist/commands/config.js +73 -0
  16. package/dist/commands/{generate.d.ts → generate/index.d.ts} +1 -0
  17. package/dist/commands/generate/index.js +393 -0
  18. package/dist/commands/generate/scan.d.ts +41 -0
  19. package/dist/commands/generate/scan.js +256 -0
  20. package/dist/commands/generate/verify.d.ts +14 -0
  21. package/dist/commands/generate/verify.js +122 -0
  22. package/dist/commands/generate/write.d.ts +25 -0
  23. package/dist/commands/generate/write.js +120 -0
  24. package/dist/commands/import.js +4 -1
  25. package/dist/commands/llms-txt.js +6 -4
  26. package/dist/commands/refresh.d.ts +2 -0
  27. package/dist/commands/refresh.js +158 -0
  28. package/dist/commands/review.d.ts +2 -0
  29. package/dist/commands/review.js +110 -0
  30. package/dist/commands/test.js +177 -236
  31. package/dist/commands/watch.js +29 -20
  32. package/dist/config/loader.d.ts +6 -2
  33. package/dist/config/loader.js +39 -3
  34. package/dist/config/types.d.ts +7 -0
  35. package/dist/generator/agents-md.d.ts +25 -0
  36. package/dist/generator/agents-md.js +122 -0
  37. package/dist/generator/generator.js +2 -1
  38. package/dist/generator/index.d.ts +2 -0
  39. package/dist/generator/index.js +2 -0
  40. package/dist/generator/mdx-serializer.d.ts +11 -0
  41. package/dist/generator/mdx-serializer.js +135 -0
  42. package/dist/generator/organizer.d.ts +1 -16
  43. package/dist/generator/organizer.js +0 -38
  44. package/dist/generator/types.d.ts +3 -0
  45. package/dist/generator/writer.js +65 -32
  46. package/dist/github/org-discovery.d.ts +17 -0
  47. package/dist/github/org-discovery.js +93 -0
  48. package/dist/llm/index.d.ts +2 -0
  49. package/dist/llm/index.js +8 -2
  50. package/dist/llm/proxy-client.d.ts +32 -0
  51. package/dist/llm/proxy-client.js +103 -0
  52. package/dist/next-actions/actions.d.ts +2 -0
  53. package/dist/next-actions/actions.js +190 -0
  54. package/dist/next-actions/index.d.ts +6 -0
  55. package/dist/next-actions/index.js +39 -0
  56. package/dist/next-actions/setup.d.ts +2 -0
  57. package/dist/next-actions/setup.js +72 -0
  58. package/dist/next-actions/state.d.ts +7 -0
  59. package/dist/next-actions/state.js +68 -0
  60. package/dist/next-actions/suggest.d.ts +3 -0
  61. package/dist/next-actions/suggest.js +47 -0
  62. package/dist/next-actions/types.d.ts +26 -0
  63. package/dist/refresh/differ.d.ts +9 -0
  64. package/dist/refresh/differ.js +67 -0
  65. package/dist/refresh/index.d.ts +4 -0
  66. package/dist/refresh/index.js +4 -0
  67. package/dist/refresh/manifest.d.ts +18 -0
  68. package/dist/refresh/manifest.js +71 -0
  69. package/dist/refresh/splicer.d.ts +9 -0
  70. package/dist/refresh/splicer.js +50 -0
  71. package/dist/refresh/types.d.ts +37 -0
  72. package/dist/review/index.d.ts +8 -0
  73. package/dist/review/index.js +94 -0
  74. package/dist/review/parser.d.ts +16 -0
  75. package/dist/review/parser.js +95 -0
  76. package/dist/review/types.d.ts +18 -0
  77. package/dist/scanner/csharp.d.ts +0 -4
  78. package/dist/scanner/csharp.js +9 -49
  79. package/dist/scanner/go.d.ts +0 -3
  80. package/dist/scanner/go.js +8 -35
  81. package/dist/scanner/java.d.ts +0 -4
  82. package/dist/scanner/java.js +9 -49
  83. package/dist/scanner/kotlin.d.ts +0 -3
  84. package/dist/scanner/kotlin.js +6 -33
  85. package/dist/scanner/php.d.ts +0 -10
  86. package/dist/scanner/php.js +11 -55
  87. package/dist/scanner/ruby.d.ts +0 -3
  88. package/dist/scanner/ruby.js +8 -38
  89. package/dist/scanner/rust.d.ts +0 -3
  90. package/dist/scanner/rust.js +10 -37
  91. package/dist/scanner/swift.d.ts +0 -3
  92. package/dist/scanner/swift.js +8 -35
  93. package/dist/scanner/types.d.ts +2 -0
  94. package/dist/scanner/utils.d.ts +41 -0
  95. package/dist/scanner/utils.js +97 -0
  96. package/dist/structure/index.d.ts +19 -0
  97. package/dist/structure/index.js +92 -0
  98. package/dist/structure/planner.d.ts +8 -0
  99. package/dist/structure/planner.js +180 -0
  100. package/dist/structure/topology.d.ts +16 -0
  101. package/dist/structure/topology.js +49 -0
  102. package/dist/structure/types.d.ts +26 -0
  103. package/dist/template/docs.json +5 -2
  104. package/dist/template/next.config.mjs +31 -0
  105. package/dist/template/package.json +5 -3
  106. package/dist/template/src/app/layout.tsx +13 -13
  107. package/dist/template/src/app/llms-full.md/route.ts +29 -0
  108. package/dist/template/src/app/llms.txt/route.ts +29 -0
  109. package/dist/template/src/app/md/[...slug]/route.ts +174 -0
  110. package/dist/template/src/app/reference/route.ts +22 -18
  111. package/dist/template/src/app/sitemap.ts +1 -1
  112. package/dist/template/src/components/ai-chat-impl.tsx +206 -0
  113. package/dist/template/src/components/ai-chat.tsx +20 -193
  114. package/dist/template/src/components/mdx/index.tsx +27 -4
  115. package/dist/template/src/lib/fonts.ts +135 -0
  116. package/dist/template/src/middleware.ts +101 -0
  117. package/dist/template/src/styles/globals.css +28 -20
  118. package/dist/testing/comparator.d.ts +7 -0
  119. package/dist/testing/comparator.js +77 -0
  120. package/dist/testing/docker.d.ts +21 -0
  121. package/dist/testing/docker.js +234 -0
  122. package/dist/testing/env.d.ts +16 -0
  123. package/dist/testing/env.js +58 -0
  124. package/dist/testing/extractor.d.ts +9 -0
  125. package/dist/testing/extractor.js +195 -0
  126. package/dist/testing/index.d.ts +6 -0
  127. package/dist/testing/index.js +6 -0
  128. package/dist/testing/runner.d.ts +5 -0
  129. package/dist/testing/runner.js +225 -0
  130. package/dist/testing/types.d.ts +58 -0
  131. package/dist/utils/files.d.ts +0 -8
  132. package/dist/utils/files.js +0 -33
  133. package/package.json +1 -1
  134. package/dist/autofix/autofix.test.js +0 -487
  135. package/dist/commands/generate.js +0 -445
  136. package/dist/generator/generator.test.js +0 -259
  137. package/dist/generator/writer.test.js +0 -411
  138. package/dist/llm/llm.manual-test.js +0 -112
  139. package/dist/llm/llm.mock-test.d.ts +0 -4
  140. package/dist/llm/llm.mock-test.js +0 -79
  141. package/dist/plugins/index.d.ts +0 -47
  142. package/dist/plugins/index.js +0 -181
  143. package/dist/scanner/content-type.test.js +0 -231
  144. package/dist/scanner/integration.test.d.ts +0 -4
  145. package/dist/scanner/integration.test.js +0 -180
  146. package/dist/scanner/scanner.test.js +0 -210
  147. package/dist/scanner/typescript.manual-test.d.ts +0 -1
  148. package/dist/scanner/typescript.manual-test.js +0 -112
  149. package/dist/template/src/app/docs/auth/page.mdx +0 -589
  150. package/dist/template/src/app/docs/autofix/page.mdx +0 -624
  151. package/dist/template/src/app/docs/cli/page.mdx +0 -217
  152. package/dist/template/src/app/docs/config/page.mdx +0 -428
  153. package/dist/template/src/app/docs/configuration/page.mdx +0 -86
  154. package/dist/template/src/app/docs/deployment/page.mdx +0 -112
  155. package/dist/template/src/app/docs/generator/generator.md +0 -504
  156. package/dist/template/src/app/docs/generator/organizer.md +0 -779
  157. package/dist/template/src/app/docs/generator/page.mdx +0 -613
  158. package/dist/template/src/app/docs/github/page.mdx +0 -502
  159. package/dist/template/src/app/docs/llm/anthropic-client.md +0 -549
  160. package/dist/template/src/app/docs/llm/index.md +0 -471
  161. package/dist/template/src/app/docs/llm/page.mdx +0 -428
  162. package/dist/template/src/app/docs/plugins/page.mdx +0 -1793
  163. package/dist/template/src/app/docs/pro/page.mdx +0 -121
  164. package/dist/template/src/app/docs/quickstart/page.mdx +0 -93
  165. package/dist/template/src/app/docs/scanner/content-type.md +0 -599
  166. package/dist/template/src/app/docs/scanner/index.md +0 -212
  167. package/dist/template/src/app/docs/scanner/page.mdx +0 -307
  168. package/dist/template/src/app/docs/scanner/python.md +0 -469
  169. package/dist/template/src/app/docs/scanner/python_parser.md +0 -1056
  170. package/dist/template/src/app/docs/scanner/rust.md +0 -325
  171. package/dist/template/src/app/docs/scanner/typescript.md +0 -201
  172. package/dist/template/src/app/icon.tsx +0 -29
  173. package/dist/utils/validation.d.ts +0 -1
  174. package/dist/utils/validation.js +0 -12
  175. /package/dist/{autofix/autofix.test.d.ts → audit/types.js} +0 -0
  176. /package/dist/{generator/generator.test.d.ts → next-actions/types.js} +0 -0
  177. /package/dist/{generator/writer.test.d.ts → refresh/types.js} +0 -0
  178. /package/dist/{llm/llm.manual-test.d.ts → review/types.js} +0 -0
  179. /package/dist/{scanner/content-type.test.d.ts → structure/types.js} +0 -0
  180. /package/dist/{scanner/scanner.test.d.ts → testing/types.js} +0 -0
@@ -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>;
@@ -0,0 +1,225 @@
1
+ import { 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 { buildCleanEnv, checkRequiredEnv } from './env.js';
8
+ /**
9
+ * Run a snippet locally in an isolated temp directory with a clean environment
10
+ */
11
+ export async function runLocally(snippet, config) {
12
+ const startTime = Date.now();
13
+ // Check for skip directive
14
+ if (snippet.skipReason) {
15
+ return {
16
+ snippet,
17
+ status: 'skip',
18
+ stdout: '',
19
+ stderr: `Skipped: ${snippet.skipReason}`,
20
+ exitCode: 0,
21
+ duration: 0,
22
+ environment: 'local',
23
+ };
24
+ }
25
+ // Check required env vars
26
+ if (snippet.requiredEnv && snippet.requiredEnv.length > 0) {
27
+ const { ok, missing } = checkRequiredEnv(snippet.requiredEnv, config.envVars);
28
+ if (!ok) {
29
+ return {
30
+ snippet,
31
+ status: 'skip',
32
+ stdout: '',
33
+ stderr: `Missing required env vars: ${missing.join(', ')}`,
34
+ exitCode: 0,
35
+ duration: 0,
36
+ environment: 'local',
37
+ };
38
+ }
39
+ }
40
+ const tempDir = join(tmpdir(), `skrypt-test-${randomUUID()}`);
41
+ mkdirSync(tempDir, { recursive: true });
42
+ try {
43
+ // Install dependencies if needed
44
+ if (config.installDeps && snippet.dependencies && snippet.dependencies.length > 0) {
45
+ const isPython = ['python', 'py'].includes(snippet.language);
46
+ if (isPython) {
47
+ await installPythonDeps(snippet.dependencies, tempDir, config.envVars);
48
+ }
49
+ else {
50
+ await installNodeDeps(snippet.dependencies, tempDir, config.envVars);
51
+ }
52
+ }
53
+ const { command, args, tempFile } = prepareExecution(snippet, tempDir);
54
+ if (!command) {
55
+ return {
56
+ snippet,
57
+ status: 'fail',
58
+ stdout: '',
59
+ stderr: `Unsupported language: ${snippet.language}`,
60
+ exitCode: 1,
61
+ duration: Date.now() - startTime,
62
+ environment: 'local',
63
+ };
64
+ }
65
+ writeFileSync(tempFile, snippet.code);
66
+ const cleanEnv = buildCleanEnv(config.envVars);
67
+ // Set TMPDIR to the snippet's temp dir for extra isolation
68
+ cleanEnv.TMPDIR = tempDir;
69
+ const result = await executeWithTimeout(command, args, config.timeout, tempDir, cleanEnv);
70
+ const duration = Date.now() - startTime;
71
+ // Check output if expected
72
+ if (snippet.expectedOutput !== undefined) {
73
+ const comparison = compareOutput(result.stdout, snippet.expectedOutput, snippet.expectedOutputMode || 'exact');
74
+ if (!comparison.match) {
75
+ return {
76
+ snippet,
77
+ status: 'output_mismatch',
78
+ stdout: result.stdout,
79
+ stderr: result.stderr,
80
+ exitCode: result.exitCode,
81
+ duration,
82
+ environment: 'local',
83
+ outputMatch: false,
84
+ diff: comparison.diff,
85
+ };
86
+ }
87
+ return {
88
+ snippet,
89
+ status: result.exitCode === 0 ? 'pass' : 'fail',
90
+ stdout: result.stdout,
91
+ stderr: result.stderr,
92
+ exitCode: result.exitCode,
93
+ duration,
94
+ environment: 'local',
95
+ outputMatch: true,
96
+ };
97
+ }
98
+ return {
99
+ snippet,
100
+ status: result.timedOut ? 'timeout' : (result.exitCode === 0 ? 'pass' : 'fail'),
101
+ stdout: result.stdout,
102
+ stderr: result.timedOut
103
+ ? `Timeout: execution exceeded ${config.timeout}ms`
104
+ : result.stderr,
105
+ exitCode: result.exitCode,
106
+ duration,
107
+ environment: 'local',
108
+ };
109
+ }
110
+ catch (err) {
111
+ const message = err instanceof Error ? err.message : String(err);
112
+ return {
113
+ snippet,
114
+ status: 'fail',
115
+ stdout: '',
116
+ stderr: message,
117
+ exitCode: 1,
118
+ duration: Date.now() - startTime,
119
+ environment: 'local',
120
+ };
121
+ }
122
+ finally {
123
+ try {
124
+ rmSync(tempDir, { recursive: true, force: true });
125
+ }
126
+ catch {
127
+ // Ignore cleanup errors
128
+ }
129
+ }
130
+ }
131
+ /**
132
+ * Determine the command and args to execute a snippet
133
+ */
134
+ function prepareExecution(snippet, tempDir) {
135
+ const isTS = ['typescript', 'ts'].includes(snippet.language);
136
+ const isJS = ['javascript', 'js'].includes(snippet.language);
137
+ const isPy = ['python', 'py'].includes(snippet.language);
138
+ if (isTS) {
139
+ const tempFile = join(tempDir, 'test.ts');
140
+ return { command: 'npx', args: ['tsx', tempFile], tempFile };
141
+ }
142
+ if (isJS) {
143
+ const tempFile = join(tempDir, 'test.js');
144
+ return { command: 'node', args: [tempFile], tempFile };
145
+ }
146
+ if (isPy) {
147
+ const tempFile = join(tempDir, 'test.py');
148
+ return { command: 'python3', args: [tempFile], tempFile };
149
+ }
150
+ return { command: null, args: [], tempFile: join(tempDir, 'test.txt') };
151
+ }
152
+ /**
153
+ * Install Node.js dependencies in a temp directory
154
+ */
155
+ async function installNodeDeps(deps, tempDir, envVars) {
156
+ const pkg = { name: 'skrypt-test', private: true, dependencies: {} };
157
+ for (const dep of deps) {
158
+ pkg.dependencies[dep] = '*';
159
+ }
160
+ writeFileSync(join(tempDir, 'package.json'), JSON.stringify(pkg));
161
+ const env = buildCleanEnv(envVars);
162
+ await executeWithTimeout('npm', ['install', '--prefix', tempDir, '--no-audit', '--no-fund'], 30000, tempDir, env);
163
+ }
164
+ /**
165
+ * Install Python dependencies in a temp directory using pip
166
+ */
167
+ async function installPythonDeps(deps, tempDir, envVars) {
168
+ const env = buildCleanEnv(envVars);
169
+ env.PIP_TARGET = join(tempDir, 'pip_packages');
170
+ env.PYTHONPATH = join(tempDir, 'pip_packages');
171
+ mkdirSync(join(tempDir, 'pip_packages'), { recursive: true });
172
+ await executeWithTimeout('python3', ['-m', 'pip', 'install', '--target', join(tempDir, 'pip_packages'), '--quiet', ...deps], 60000, tempDir, env);
173
+ }
174
+ /**
175
+ * Execute a command with timeout
176
+ */
177
+ function executeWithTimeout(command, args, timeoutMs, cwd, env) {
178
+ return new Promise((resolve) => {
179
+ const proc = spawn(command, args, {
180
+ cwd,
181
+ stdio: ['pipe', 'pipe', 'pipe'],
182
+ env,
183
+ });
184
+ let stdout = '';
185
+ let stderr = '';
186
+ let timedOut = false;
187
+ const MAX_BUFFER = 1024 * 1024; // 1MB cap to prevent OOM
188
+ const timeout = setTimeout(() => {
189
+ timedOut = true;
190
+ proc.kill('SIGKILL');
191
+ }, timeoutMs);
192
+ proc.stdout?.on('data', (data) => {
193
+ if (stdout.length < MAX_BUFFER) {
194
+ stdout += data.toString();
195
+ if (stdout.length > MAX_BUFFER)
196
+ stdout = stdout.slice(0, MAX_BUFFER);
197
+ }
198
+ });
199
+ proc.stderr?.on('data', (data) => {
200
+ if (stderr.length < MAX_BUFFER) {
201
+ stderr += data.toString();
202
+ if (stderr.length > MAX_BUFFER)
203
+ stderr = stderr.slice(0, MAX_BUFFER);
204
+ }
205
+ });
206
+ proc.on('close', (code) => {
207
+ clearTimeout(timeout);
208
+ resolve({
209
+ exitCode: timedOut ? 1 : (code ?? 1),
210
+ stdout,
211
+ stderr,
212
+ timedOut,
213
+ });
214
+ });
215
+ proc.on('error', (err) => {
216
+ clearTimeout(timeout);
217
+ resolve({
218
+ exitCode: 1,
219
+ stdout,
220
+ stderr: err.message,
221
+ timedOut: false,
222
+ });
223
+ });
224
+ });
225
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * A code snippet extracted from a documentation file
3
+ */
4
+ export interface ExtractedSnippet {
5
+ code: string;
6
+ language: string;
7
+ filePath: string;
8
+ lineNumber: number;
9
+ index: number;
10
+ expectedOutput?: string;
11
+ expectedOutputMode?: 'exact' | 'contains';
12
+ requiredEnv?: string[];
13
+ skipReason?: string;
14
+ dependencies?: string[];
15
+ }
16
+ /**
17
+ * Result of running a single snippet
18
+ */
19
+ export interface TestResult {
20
+ snippet: ExtractedSnippet;
21
+ status: 'pass' | 'fail' | 'skip' | 'timeout' | 'output_mismatch';
22
+ stdout: string;
23
+ stderr: string;
24
+ exitCode: number;
25
+ duration: number;
26
+ environment: string;
27
+ outputMatch?: boolean;
28
+ diff?: string;
29
+ }
30
+ /**
31
+ * Aggregate report for a test run
32
+ */
33
+ export interface VerificationReport {
34
+ total: number;
35
+ passed: number;
36
+ failed: number;
37
+ skipped: number;
38
+ outputMismatches: number;
39
+ duration: number;
40
+ results: TestResult[];
41
+ }
42
+ /**
43
+ * Configuration for the local runner
44
+ */
45
+ export interface RunnerConfig {
46
+ timeout: number;
47
+ envVars: Record<string, string>;
48
+ installDeps: boolean;
49
+ }
50
+ /**
51
+ * Configuration for Docker-based multi-environment testing
52
+ */
53
+ export interface DockerEnvironment {
54
+ name: string;
55
+ image: string;
56
+ command: string;
57
+ languages: string[];
58
+ }
@@ -7,11 +7,3 @@ export declare function findMdxFiles(dir: string): string[];
7
7
  * Convert string to URL-safe slug
8
8
  */
9
9
  export declare function slugify(str: string): string;
10
- /**
11
- * Simple YAML frontmatter parser — splits on --- markers.
12
- * Returns parsed key-value data and remaining content body.
13
- */
14
- export declare function parseFrontmatter(content: string): {
15
- data: Record<string, unknown>;
16
- content: string;
17
- };
@@ -47,36 +47,3 @@ export function slugify(str) {
47
47
  .replace(/[^a-z0-9]+/g, '-')
48
48
  .replace(/^-|-$/g, '');
49
49
  }
50
- /**
51
- * Simple YAML frontmatter parser — splits on --- markers.
52
- * Returns parsed key-value data and remaining content body.
53
- */
54
- export function parseFrontmatter(content) {
55
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
56
- if (!match)
57
- return { data: {}, content };
58
- const yamlStr = match[1];
59
- const body = match[2];
60
- const data = {};
61
- for (const line of yamlStr.split('\n')) {
62
- const kvMatch = line.match(/^(\w[\w-]*)\s*:\s*(.*)$/);
63
- if (!kvMatch)
64
- continue;
65
- const key = kvMatch[1];
66
- let value = kvMatch[2].trim();
67
- if (typeof value === 'string') {
68
- if ((value.startsWith('"') && value.endsWith('"')) ||
69
- (value.startsWith("'") && value.endsWith("'"))) {
70
- value = value.slice(1, -1);
71
- }
72
- else if (value === 'true')
73
- value = true;
74
- else if (value === 'false')
75
- value = false;
76
- else if (/^\d+$/.test(value))
77
- value = parseInt(value, 10);
78
- }
79
- data[key] = value;
80
- }
81
- return { data, content: body };
82
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skrypt-ai",
3
- "version": "0.6.1",
3
+ "version": "0.8.0",
4
4
  "description": "AI-powered documentation generator with code examples",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",