skrypt-ai 0.4.2 → 0.6.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 (159) hide show
  1. package/dist/auth/index.d.ts +13 -3
  2. package/dist/auth/index.js +101 -9
  3. package/dist/auth/keychain.d.ts +5 -0
  4. package/dist/auth/keychain.js +82 -0
  5. package/dist/auth/notices.d.ts +3 -0
  6. package/dist/auth/notices.js +42 -0
  7. package/dist/autofix/index.d.ts +0 -4
  8. package/dist/autofix/index.js +10 -24
  9. package/dist/capture/browser.d.ts +11 -0
  10. package/dist/capture/browser.js +173 -0
  11. package/dist/capture/diff.d.ts +23 -0
  12. package/dist/capture/diff.js +52 -0
  13. package/dist/capture/index.d.ts +23 -0
  14. package/dist/capture/index.js +210 -0
  15. package/dist/capture/naming.d.ts +17 -0
  16. package/dist/capture/naming.js +45 -0
  17. package/dist/capture/parser.d.ts +15 -0
  18. package/dist/capture/parser.js +80 -0
  19. package/dist/capture/types.d.ts +57 -0
  20. package/dist/capture/types.js +1 -0
  21. package/dist/cli.js +20 -3
  22. package/dist/commands/autofix.js +136 -120
  23. package/dist/commands/cron.js +58 -47
  24. package/dist/commands/deploy.js +123 -102
  25. package/dist/commands/generate.js +125 -7
  26. package/dist/commands/heal.d.ts +10 -0
  27. package/dist/commands/heal.js +201 -0
  28. package/dist/commands/i18n.js +146 -111
  29. package/dist/commands/import.d.ts +2 -0
  30. package/dist/commands/import.js +157 -0
  31. package/dist/commands/init.js +19 -7
  32. package/dist/commands/lint.js +50 -44
  33. package/dist/commands/llms-txt.js +59 -49
  34. package/dist/commands/login.js +63 -34
  35. package/dist/commands/mcp.js +6 -0
  36. package/dist/commands/monitor.js +13 -8
  37. package/dist/commands/qa.d.ts +2 -0
  38. package/dist/commands/qa.js +43 -0
  39. package/dist/commands/review-pr.js +108 -92
  40. package/dist/commands/sdk.js +128 -122
  41. package/dist/commands/security.d.ts +2 -0
  42. package/dist/commands/security.js +109 -0
  43. package/dist/commands/test.js +91 -92
  44. package/dist/commands/version.js +104 -75
  45. package/dist/commands/watch.js +130 -114
  46. package/dist/config/types.js +2 -2
  47. package/dist/context-hub/index.d.ts +23 -0
  48. package/dist/context-hub/index.js +179 -0
  49. package/dist/context-hub/mappings.d.ts +8 -0
  50. package/dist/context-hub/mappings.js +55 -0
  51. package/dist/context-hub/types.d.ts +33 -0
  52. package/dist/context-hub/types.js +1 -0
  53. package/dist/generator/generator.js +39 -6
  54. package/dist/generator/types.d.ts +7 -0
  55. package/dist/generator/writer.d.ts +3 -1
  56. package/dist/generator/writer.js +36 -7
  57. package/dist/importers/confluence.d.ts +5 -0
  58. package/dist/importers/confluence.js +137 -0
  59. package/dist/importers/detect.d.ts +20 -0
  60. package/dist/importers/detect.js +121 -0
  61. package/dist/importers/docusaurus.d.ts +5 -0
  62. package/dist/importers/docusaurus.js +279 -0
  63. package/dist/importers/gitbook.d.ts +5 -0
  64. package/dist/importers/gitbook.js +189 -0
  65. package/dist/importers/github.d.ts +8 -0
  66. package/dist/importers/github.js +99 -0
  67. package/dist/importers/index.d.ts +15 -0
  68. package/dist/importers/index.js +30 -0
  69. package/dist/importers/markdown.d.ts +6 -0
  70. package/dist/importers/markdown.js +105 -0
  71. package/dist/importers/mintlify.d.ts +5 -0
  72. package/dist/importers/mintlify.js +172 -0
  73. package/dist/importers/notion.d.ts +5 -0
  74. package/dist/importers/notion.js +174 -0
  75. package/dist/importers/readme.d.ts +5 -0
  76. package/dist/importers/readme.js +184 -0
  77. package/dist/importers/transform.d.ts +90 -0
  78. package/dist/importers/transform.js +457 -0
  79. package/dist/importers/types.d.ts +37 -0
  80. package/dist/importers/types.js +1 -0
  81. package/dist/llm/anthropic-client.d.ts +1 -0
  82. package/dist/llm/anthropic-client.js +3 -1
  83. package/dist/llm/index.d.ts +6 -4
  84. package/dist/llm/index.js +76 -261
  85. package/dist/llm/openai-client.d.ts +1 -0
  86. package/dist/llm/openai-client.js +7 -2
  87. package/dist/plugins/index.js +7 -0
  88. package/dist/qa/checks.d.ts +10 -0
  89. package/dist/qa/checks.js +492 -0
  90. package/dist/qa/fixes.d.ts +30 -0
  91. package/dist/qa/fixes.js +277 -0
  92. package/dist/qa/index.d.ts +29 -0
  93. package/dist/qa/index.js +187 -0
  94. package/dist/qa/types.d.ts +24 -0
  95. package/dist/qa/types.js +1 -0
  96. package/dist/scanner/csharp.d.ts +23 -0
  97. package/dist/scanner/csharp.js +421 -0
  98. package/dist/scanner/index.js +53 -26
  99. package/dist/scanner/java.d.ts +39 -0
  100. package/dist/scanner/java.js +318 -0
  101. package/dist/scanner/kotlin.d.ts +23 -0
  102. package/dist/scanner/kotlin.js +389 -0
  103. package/dist/scanner/php.d.ts +57 -0
  104. package/dist/scanner/php.js +351 -0
  105. package/dist/scanner/python.js +17 -0
  106. package/dist/scanner/ruby.d.ts +36 -0
  107. package/dist/scanner/ruby.js +431 -0
  108. package/dist/scanner/swift.d.ts +25 -0
  109. package/dist/scanner/swift.js +392 -0
  110. package/dist/scanner/types.d.ts +1 -1
  111. package/dist/template/content/docs/_navigation.json +46 -0
  112. package/dist/template/content/docs/_sidebars.json +684 -0
  113. package/dist/template/content/docs/core.md +4544 -0
  114. package/dist/template/content/docs/index.mdx +89 -0
  115. package/dist/template/content/docs/integrations.md +1158 -0
  116. package/dist/template/content/docs/llms-full.md +403 -0
  117. package/dist/template/content/docs/llms.txt +4588 -0
  118. package/dist/template/content/docs/other.md +10379 -0
  119. package/dist/template/content/docs/tools.md +746 -0
  120. package/dist/template/content/docs/types.md +531 -0
  121. package/dist/template/docs.json +13 -11
  122. package/dist/template/mdx-components.tsx +27 -2
  123. package/dist/template/package.json +6 -0
  124. package/dist/template/public/search-index.json +1 -1
  125. package/dist/template/scripts/build-search-index.mjs +149 -13
  126. package/dist/template/src/app/api/chat/route.ts +83 -128
  127. package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
  128. package/dist/template/src/app/docs/llms-full.md +151 -4
  129. package/dist/template/src/app/docs/llms.txt +2464 -847
  130. package/dist/template/src/app/docs/page.mdx +48 -38
  131. package/dist/template/src/app/layout.tsx +3 -1
  132. package/dist/template/src/app/page.tsx +22 -8
  133. package/dist/template/src/components/ai-chat.tsx +73 -64
  134. package/dist/template/src/components/breadcrumbs.tsx +21 -23
  135. package/dist/template/src/components/copy-button.tsx +13 -9
  136. package/dist/template/src/components/copy-page-button.tsx +54 -0
  137. package/dist/template/src/components/docs-layout.tsx +37 -25
  138. package/dist/template/src/components/header.tsx +51 -10
  139. package/dist/template/src/components/mdx/card.tsx +17 -3
  140. package/dist/template/src/components/mdx/code-block.tsx +13 -9
  141. package/dist/template/src/components/mdx/code-group.tsx +13 -8
  142. package/dist/template/src/components/mdx/heading.tsx +15 -2
  143. package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
  144. package/dist/template/src/components/mdx/index.tsx +2 -0
  145. package/dist/template/src/components/mdx/mermaid.tsx +110 -0
  146. package/dist/template/src/components/mdx/screenshot.tsx +150 -0
  147. package/dist/template/src/components/scroll-to-hash.tsx +48 -0
  148. package/dist/template/src/components/sidebar.tsx +12 -18
  149. package/dist/template/src/components/table-of-contents.tsx +9 -0
  150. package/dist/template/src/lib/highlight.ts +3 -88
  151. package/dist/template/src/lib/navigation.ts +159 -0
  152. package/dist/template/src/lib/search-types.ts +4 -1
  153. package/dist/template/src/lib/search.ts +30 -7
  154. package/dist/template/src/styles/globals.css +17 -6
  155. package/dist/utils/files.d.ts +9 -1
  156. package/dist/utils/files.js +59 -10
  157. package/dist/utils/validation.d.ts +0 -3
  158. package/dist/utils/validation.js +0 -26
  159. package/package.json +5 -1
@@ -6,6 +6,10 @@ import { DEFAULT_MODELS } from '../config/types.js';
6
6
  import { scanDirectory } from '../scanner/index.js';
7
7
  import { createLLMClient } from '../llm/index.js';
8
8
  import { generateForElements, groupDocsByFile, writeDocsToDirectory, writeDocsByTopic, writeLlmsTxt } from '../generator/index.js';
9
+ import { showSecurityNotice } from '../auth/notices.js';
10
+ import { runQA, printQAReport, fixQAIssues, printFixReport } from '../qa/index.js';
11
+ import { extractDependencyIds, isChubInstalled, fetchContextHubDocs, exportToContextHub } from '../context-hub/index.js';
12
+ import * as readline from 'readline';
9
13
  /**
10
14
  * Read .skryptignore patterns from source directory
11
15
  */
@@ -60,9 +64,23 @@ function shouldExcludeElement(element, patterns) {
60
64
  // Match by name
61
65
  if (pattern.startsWith('name:')) {
62
66
  const namePattern = pattern.slice(5);
63
- if (element.name === namePattern || element.name.match(new RegExp(namePattern))) {
67
+ if (element.name === namePattern) {
64
68
  return true;
65
69
  }
70
+ // Only use regex if the pattern contains regex metacharacters
71
+ // Reject patterns with nested quantifiers to prevent catastrophic backtracking (ReDoS)
72
+ if (/[*+?{}()|[\]\\^$.]/.test(namePattern)) {
73
+ if (/(\+|\*|\?)\{|\(\?[^:)]|\(\.[*+].*\)\+|\([^)]*[+*][^)]*\)[+*]/.test(namePattern)) {
74
+ continue; // Skip patterns prone to catastrophic backtracking
75
+ }
76
+ try {
77
+ if (new RegExp(namePattern).test(element.name))
78
+ return true;
79
+ }
80
+ catch {
81
+ // Invalid regex — treat as literal match (already checked above)
82
+ }
83
+ }
66
84
  }
67
85
  // Match by file path
68
86
  else if (pattern.includes('/') || pattern.includes('*')) {
@@ -112,6 +130,11 @@ export const generateCommand = new Command('generate')
112
130
  if (options.output)
113
131
  config.output.path = options.output;
114
132
  if (options.provider) {
133
+ const validProviders = ['deepseek', 'openai', 'anthropic', 'google', 'ollama', 'openrouter'];
134
+ if (!validProviders.includes(options.provider)) {
135
+ console.error(`Error: Unknown provider "${options.provider}". Valid: ${validProviders.join(', ')}`);
136
+ process.exit(1);
137
+ }
115
138
  config.llm.provider = options.provider;
116
139
  // Use provider's default model unless explicitly specified
117
140
  if (!options.model) {
@@ -137,6 +160,8 @@ export const generateCommand = new Command('generate')
137
160
  process.exit(1);
138
161
  }
139
162
  }
163
+ // First-run security notice
164
+ showSecurityNotice();
140
165
  console.log('skrypt generate');
141
166
  console.log(` source: ${config.source.path}`);
142
167
  console.log(` output: ${config.output.path}`);
@@ -145,6 +170,20 @@ export const generateCommand = new Command('generate')
145
170
  if (config.llm.baseUrl) {
146
171
  console.log(` base url: ${config.llm.baseUrl}`);
147
172
  }
173
+ // Routing transparency
174
+ const providerEnvKeys = {
175
+ openai: 'OPENAI_API_KEY',
176
+ anthropic: 'ANTHROPIC_API_KEY',
177
+ google: 'GOOGLE_API_KEY',
178
+ deepseek: 'DEEPSEEK_API_KEY',
179
+ };
180
+ const providerKey = providerEnvKeys[config.llm.provider];
181
+ if (providerKey && process.env[providerKey]) {
182
+ console.log(` routing: direct to ${config.llm.provider} (BYOK — your key never touches Skrypt)`);
183
+ }
184
+ else if (config.llm.provider !== 'ollama') {
185
+ console.log(' routing: via Skrypt API proxy');
186
+ }
148
187
  console.log('');
149
188
  // Check source exists
150
189
  const sourcePath = resolve(config.source.path);
@@ -222,6 +261,53 @@ export const generateCommand = new Command('generate')
222
261
  console.log('\n[dry run - stopping before generation]');
223
262
  process.exit(0);
224
263
  }
264
+ // Auto-read project context from README for richer doc generation
265
+ let projectContext;
266
+ const readmeCandidates = ['README.md', 'README.mdx', 'readme.md', 'README.rst', 'README.txt'];
267
+ for (const candidate of readmeCandidates) {
268
+ const readmePath = join(sourcePath, candidate);
269
+ if (existsSync(readmePath)) {
270
+ try {
271
+ const raw = readFileSync(readmePath, 'utf-8');
272
+ // Take the first ~1500 chars (intro/description section, not the whole file)
273
+ projectContext = raw.slice(0, 1500);
274
+ console.log(` Project context: loaded from ${candidate}`);
275
+ }
276
+ catch {
277
+ // Skip if unreadable
278
+ }
279
+ break;
280
+ }
281
+ }
282
+ // Also check parent directory (common for monorepos where source is a subdirectory)
283
+ if (!projectContext) {
284
+ const parentReadme = join(sourcePath, '..', 'README.md');
285
+ if (existsSync(parentReadme)) {
286
+ try {
287
+ projectContext = readFileSync(parentReadme, 'utf-8').slice(0, 1500);
288
+ console.log(' Project context: loaded from parent README.md');
289
+ }
290
+ catch {
291
+ // Skip
292
+ }
293
+ }
294
+ }
295
+ // Context Hub enrichment: fetch third-party API docs if chub is installed
296
+ let externalContext;
297
+ const allImports = [];
298
+ for (const el of allElements) {
299
+ if (el.imports?.length) {
300
+ allImports.push(...el.imports);
301
+ }
302
+ }
303
+ const chubIds = extractDependencyIds(allImports);
304
+ if (chubIds.length > 0 && isChubInstalled()) {
305
+ console.log(`\n Context Hub: fetching docs for ${chubIds.length} dependencies...`);
306
+ externalContext = fetchContextHubDocs(chubIds);
307
+ if (externalContext.size > 0) {
308
+ console.log(` Context Hub: enriching with ${externalContext.size} API references`);
309
+ }
310
+ }
225
311
  // Step 2: Generate docs
226
312
  console.log('\nStep 2: Generating documentation...');
227
313
  const client = createLLMClient({
@@ -236,6 +322,8 @@ export const generateCommand = new Command('generate')
236
322
  }
237
323
  const docs = await generateForElements(allElements, client, {
238
324
  multiLanguage,
325
+ externalContext,
326
+ projectContext,
239
327
  onProgress: (progress) => {
240
328
  if (progress.element !== lastElement) {
241
329
  if (lastElement)
@@ -292,13 +380,43 @@ export const generateCommand = new Command('generate')
292
380
  console.log(`\n Warning: OpenAPI spec not found: ${specPath}`);
293
381
  }
294
382
  }
295
- // Generate llms.txt for AEO (Answer Engine Optimization)
296
- if (options.llmsTxt) {
297
- await writeLlmsTxt(docs, outputPath, {
298
- projectName: options.projectName,
299
- description: `API documentation for ${options.projectName || basename(sourcePath)}`
383
+ // Always generate llms.txt for AEO (Answer Engine Optimization)
384
+ await writeLlmsTxt(docs, outputPath, {
385
+ projectName: options.projectName,
386
+ description: `API documentation for ${options.projectName || basename(sourcePath)}`
387
+ });
388
+ console.log(`\n Generated llms.txt and llms-full.md for AEO`);
389
+ // Step 4: Embedded QA (auto-fix then check)
390
+ console.log('\nStep 4: Running QA checks...');
391
+ const fixReport = fixQAIssues(outputPath);
392
+ printFixReport(fixReport);
393
+ const qaReport = runQA(outputPath);
394
+ printQAReport(qaReport);
395
+ // Context Hub export prompt (TTY only — skip in CI/piped mode)
396
+ if (process.stdin.isTTY) {
397
+ console.log('');
398
+ console.log(' Context Hub: Make your docs discoverable by AI coding agents.');
399
+ console.log(' Context Hub is a curated registry by Andrew Ng (7K+ stars)');
400
+ console.log(' https://github.com/andrewyng/context-hub');
401
+ console.log('');
402
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
403
+ const answer = await new Promise((resolve) => {
404
+ rl.question(' Export for Context Hub? (y/N) ', (ans) => {
405
+ rl.close();
406
+ resolve(ans.trim().toLowerCase());
407
+ });
300
408
  });
301
- console.log(`\n Generated llms.txt and llms-full.md for AEO`);
409
+ if (answer === 'y' || answer === 'yes') {
410
+ const languages = multiLanguage ? ['typescript', 'python'] : ['typescript'];
411
+ const projName = options.projectName || basename(sourcePath);
412
+ const result = exportToContextHub(docs, outputPath, {
413
+ projectName: projName,
414
+ languages,
415
+ description: `API documentation for ${projName}`,
416
+ });
417
+ console.log(`\n Exported ${result.filesWritten} files to ${result.outputDir}`);
418
+ console.log(' See context-hub/README.md for submission instructions');
419
+ }
302
420
  }
303
421
  console.log('\n=== Summary ===');
304
422
  console.log(` Total elements: ${totalDocs}`);
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Self-healing command: skrypt heal
3
+ *
4
+ * Orchestrates 3 phases of documentation healing:
5
+ * Phase 1: QA fixes (frontmatter, syntax, code blocks, security)
6
+ * Phase 2: Code example fixes (LLM-powered)
7
+ * Phase 3: Screenshot capture (headless browser)
8
+ */
9
+ import { Command } from 'commander';
10
+ export declare const healCommand: Command;
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Self-healing command: skrypt heal
3
+ *
4
+ * Orchestrates 3 phases of documentation healing:
5
+ * Phase 1: QA fixes (frontmatter, syntax, code blocks, security)
6
+ * Phase 2: Code example fixes (LLM-powered)
7
+ * Phase 3: Screenshot capture (headless browser)
8
+ */
9
+ import { Command } from 'commander';
10
+ import { resolve } from 'path';
11
+ import { readFileSync, writeFileSync } from 'fs';
12
+ import { requirePro } from '../auth/index.js';
13
+ import { fixQAIssues, printFixReport, runQA, printQAReport } from '../qa/index.js';
14
+ import { loadConfig, checkApiKey } from '../config/index.js';
15
+ import { createLLMClient } from '../llm/index.js';
16
+ import { autoFixExample, createTypeScriptValidator, createPythonValidator } from '../autofix/index.js';
17
+ import { findMdxFiles } from '../utils/files.js';
18
+ import { runCapture, printCaptureReport } from '../capture/index.js';
19
+ export const healCommand = new Command('heal')
20
+ .description('Self-heal documentation: fix QA issues, broken code, and capture screenshots')
21
+ .argument('[docs-dir]', 'Documentation directory', '.')
22
+ .option('--base-url <url>', 'Base URL for screenshot capture', 'http://localhost:3000')
23
+ .option('--output <dir>', 'Screenshot output directory', 'public/screenshots')
24
+ .option('--viewport <size>', 'Viewport WxH', '1280x720')
25
+ .option('--dark', 'Also capture dark mode variants')
26
+ .option('--diff', 'Only re-capture changed screenshots')
27
+ .option('--screenshots', 'Run screenshot capture phase')
28
+ .option('--qa-only', 'Run only QA fix phase')
29
+ .option('--code-only', 'Run only code fix phase')
30
+ .option('--deep', 'Run all 3 phases')
31
+ .option('--dry-run', 'Show what would change without writing')
32
+ .option('--timeout <ms>', 'Page load timeout', '30000')
33
+ .option('--wait <ms>', 'Wait after page load', '1000')
34
+ .option('--device <name>', 'Emulate device (e.g. "iPhone 14")')
35
+ .option('--concurrency <n>', 'Parallel capture limit', '3')
36
+ .option('--provider <name>', 'LLM provider (for code fix phase)')
37
+ .option('--model <name>', 'LLM model (for code fix phase)')
38
+ .option('--max-iterations <n>', 'Max fix attempts per code example', '3')
39
+ .option('-c, --config <file>', 'Config file path')
40
+ .action(async (docsDir, options) => {
41
+ try {
42
+ // Pro feature
43
+ if (!await requirePro('heal')) {
44
+ process.exit(1);
45
+ }
46
+ const resolvedDir = resolve(docsDir);
47
+ const [vpWidth, vpHeight] = options.viewport.split('x').map(Number);
48
+ // Determine which phases to run
49
+ const hasPhaseFlag = options.qaOnly || options.codeOnly || options.screenshots;
50
+ const runPhase1 = options.deep || options.qaOnly || !hasPhaseFlag;
51
+ const runPhase2 = options.deep || options.codeOnly;
52
+ const runPhase3 = options.deep || options.screenshots;
53
+ console.log('skrypt heal');
54
+ console.log(` directory: ${resolvedDir}`);
55
+ const phases = [];
56
+ if (runPhase1)
57
+ phases.push('QA fixes');
58
+ if (runPhase2)
59
+ phases.push('code fixes');
60
+ if (runPhase3)
61
+ phases.push('screenshots');
62
+ console.log(` phases: ${phases.join(', ')}`);
63
+ if (options.dryRun)
64
+ console.log(' mode: dry run');
65
+ console.log('');
66
+ // ── Phase 1: QA Fixes ──
67
+ if (runPhase1) {
68
+ console.log(' ── Phase 1: QA Fixes ──');
69
+ if (options.dryRun) {
70
+ const qaReport = runQA(resolvedDir);
71
+ printQAReport(qaReport);
72
+ }
73
+ else {
74
+ const fixReport = fixQAIssues(resolvedDir);
75
+ printFixReport(fixReport);
76
+ if (fixReport.totalFixes > 0) {
77
+ const qaReport = runQA(resolvedDir);
78
+ printQAReport(qaReport);
79
+ }
80
+ else {
81
+ console.log(' No QA issues to fix.');
82
+ }
83
+ }
84
+ console.log('');
85
+ }
86
+ // ── Phase 2: Code Fixes ──
87
+ if (runPhase2) {
88
+ console.log(' ── Phase 2: Code Fixes ──');
89
+ const config = loadConfig(options.config);
90
+ if (options.provider)
91
+ config.llm.provider = options.provider;
92
+ if (options.model)
93
+ config.llm.model = options.model;
94
+ const { ok, envKey } = checkApiKey(config.llm.provider);
95
+ if (!ok && envKey) {
96
+ console.error(` Error: ${envKey} environment variable required for code fixes`);
97
+ console.error(' Skipping Phase 2.\n');
98
+ }
99
+ else {
100
+ const client = createLLMClient({
101
+ provider: config.llm.provider,
102
+ model: config.llm.model,
103
+ baseUrl: config.llm.baseUrl,
104
+ });
105
+ const maxIterations = parseInt(options.maxIterations, 10) || 3;
106
+ const validators = {
107
+ javascript: createTypeScriptValidator(),
108
+ typescript: createTypeScriptValidator(),
109
+ ts: createTypeScriptValidator(),
110
+ js: createTypeScriptValidator(),
111
+ python: createPythonValidator(),
112
+ py: createPythonValidator(),
113
+ };
114
+ const docFiles = findMdxFiles(resolvedDir);
115
+ let totalFixed = 0;
116
+ let totalFailed = 0;
117
+ for (const filePath of docFiles) {
118
+ let content;
119
+ try {
120
+ content = readFileSync(filePath, 'utf-8');
121
+ }
122
+ catch {
123
+ continue;
124
+ }
125
+ // Find code blocks
126
+ const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
127
+ const codeBlocks = [];
128
+ let match;
129
+ while ((match = codeBlockRegex.exec(content)) !== null) {
130
+ const code = match[2];
131
+ if (!code)
132
+ continue;
133
+ codeBlocks.push({
134
+ match: match[0],
135
+ language: match[1] || 'javascript',
136
+ code,
137
+ });
138
+ }
139
+ if (codeBlocks.length === 0)
140
+ continue;
141
+ let newContent = content;
142
+ let fileFixed = 0;
143
+ for (const block of codeBlocks) {
144
+ const validator = validators[block.language.toLowerCase()];
145
+ if (!validator)
146
+ continue;
147
+ const initial = await validator(block.code);
148
+ if (initial.valid)
149
+ continue;
150
+ const result = await autoFixExample({ code: block.code, language: block.language, context: `Code example from ${filePath}` }, client, { maxIterations, validateFn: validator, language: block.language });
151
+ if (result.success) {
152
+ const newBlock = '```' + block.language + '\n' + result.fixedCode + '```';
153
+ newContent = newContent.replace(block.match, newBlock);
154
+ fileFixed++;
155
+ totalFixed++;
156
+ }
157
+ else {
158
+ totalFailed++;
159
+ }
160
+ }
161
+ if (fileFixed > 0 && !options.dryRun) {
162
+ writeFileSync(filePath, newContent, 'utf-8');
163
+ }
164
+ }
165
+ if (totalFixed > 0 || totalFailed > 0) {
166
+ console.log(` Code fixes: ${totalFixed} fixed, ${totalFailed} failed`);
167
+ }
168
+ else {
169
+ console.log(' All code examples are valid.');
170
+ }
171
+ if (options.dryRun && totalFixed > 0) {
172
+ console.log(' [dry run — no changes written]');
173
+ }
174
+ }
175
+ console.log('');
176
+ }
177
+ // ── Phase 3: Screenshot Capture ──
178
+ if (runPhase3) {
179
+ console.log(' ── Phase 3: Screenshot Capture ──');
180
+ const captureOptions = {
181
+ baseUrl: options.baseUrl,
182
+ outputDir: options.output,
183
+ viewport: { width: vpWidth || 1280, height: vpHeight || 720 },
184
+ dark: options.dark || false,
185
+ diff: options.diff || false,
186
+ dryRun: options.dryRun || false,
187
+ timeout: parseInt(options.timeout, 10) || 30000,
188
+ wait: parseInt(options.wait, 10) || 1000,
189
+ device: options.device,
190
+ concurrency: parseInt(options.concurrency, 10) || 3,
191
+ };
192
+ const captureReport = await runCapture(resolvedDir, captureOptions);
193
+ printCaptureReport(captureReport);
194
+ }
195
+ console.log('Done!');
196
+ }
197
+ catch (err) {
198
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
199
+ process.exit(1);
200
+ }
201
+ });