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.
- package/dist/auth/index.d.ts +13 -3
- package/dist/auth/index.js +101 -9
- package/dist/auth/keychain.d.ts +5 -0
- package/dist/auth/keychain.js +82 -0
- package/dist/auth/notices.d.ts +3 -0
- package/dist/auth/notices.js +42 -0
- package/dist/autofix/index.d.ts +0 -4
- package/dist/autofix/index.js +10 -24
- package/dist/capture/browser.d.ts +11 -0
- package/dist/capture/browser.js +173 -0
- package/dist/capture/diff.d.ts +23 -0
- package/dist/capture/diff.js +52 -0
- package/dist/capture/index.d.ts +23 -0
- package/dist/capture/index.js +210 -0
- package/dist/capture/naming.d.ts +17 -0
- package/dist/capture/naming.js +45 -0
- package/dist/capture/parser.d.ts +15 -0
- package/dist/capture/parser.js +80 -0
- package/dist/capture/types.d.ts +57 -0
- package/dist/capture/types.js +1 -0
- package/dist/cli.js +20 -3
- package/dist/commands/autofix.js +136 -120
- package/dist/commands/cron.js +58 -47
- package/dist/commands/deploy.js +123 -102
- package/dist/commands/generate.js +125 -7
- package/dist/commands/heal.d.ts +10 -0
- package/dist/commands/heal.js +201 -0
- package/dist/commands/i18n.js +146 -111
- package/dist/commands/import.d.ts +2 -0
- package/dist/commands/import.js +157 -0
- package/dist/commands/init.js +19 -7
- package/dist/commands/lint.js +50 -44
- package/dist/commands/llms-txt.js +59 -49
- package/dist/commands/login.js +63 -34
- package/dist/commands/mcp.js +6 -0
- package/dist/commands/monitor.js +13 -8
- package/dist/commands/qa.d.ts +2 -0
- package/dist/commands/qa.js +43 -0
- package/dist/commands/review-pr.js +108 -92
- package/dist/commands/sdk.js +128 -122
- package/dist/commands/security.d.ts +2 -0
- package/dist/commands/security.js +109 -0
- package/dist/commands/test.js +91 -92
- package/dist/commands/version.js +104 -75
- package/dist/commands/watch.js +130 -114
- package/dist/config/types.js +2 -2
- package/dist/context-hub/index.d.ts +23 -0
- package/dist/context-hub/index.js +179 -0
- package/dist/context-hub/mappings.d.ts +8 -0
- package/dist/context-hub/mappings.js +55 -0
- package/dist/context-hub/types.d.ts +33 -0
- package/dist/context-hub/types.js +1 -0
- package/dist/generator/generator.js +39 -6
- package/dist/generator/types.d.ts +7 -0
- package/dist/generator/writer.d.ts +3 -1
- package/dist/generator/writer.js +36 -7
- package/dist/importers/confluence.d.ts +5 -0
- package/dist/importers/confluence.js +137 -0
- package/dist/importers/detect.d.ts +20 -0
- package/dist/importers/detect.js +121 -0
- package/dist/importers/docusaurus.d.ts +5 -0
- package/dist/importers/docusaurus.js +279 -0
- package/dist/importers/gitbook.d.ts +5 -0
- package/dist/importers/gitbook.js +189 -0
- package/dist/importers/github.d.ts +8 -0
- package/dist/importers/github.js +99 -0
- package/dist/importers/index.d.ts +15 -0
- package/dist/importers/index.js +30 -0
- package/dist/importers/markdown.d.ts +6 -0
- package/dist/importers/markdown.js +105 -0
- package/dist/importers/mintlify.d.ts +5 -0
- package/dist/importers/mintlify.js +172 -0
- package/dist/importers/notion.d.ts +5 -0
- package/dist/importers/notion.js +174 -0
- package/dist/importers/readme.d.ts +5 -0
- package/dist/importers/readme.js +184 -0
- package/dist/importers/transform.d.ts +90 -0
- package/dist/importers/transform.js +457 -0
- package/dist/importers/types.d.ts +37 -0
- package/dist/importers/types.js +1 -0
- package/dist/llm/anthropic-client.d.ts +1 -0
- package/dist/llm/anthropic-client.js +3 -1
- package/dist/llm/index.d.ts +6 -4
- package/dist/llm/index.js +76 -261
- package/dist/llm/openai-client.d.ts +1 -0
- package/dist/llm/openai-client.js +7 -2
- package/dist/plugins/index.js +7 -0
- package/dist/qa/checks.d.ts +10 -0
- package/dist/qa/checks.js +492 -0
- package/dist/qa/fixes.d.ts +30 -0
- package/dist/qa/fixes.js +277 -0
- package/dist/qa/index.d.ts +29 -0
- package/dist/qa/index.js +187 -0
- package/dist/qa/types.d.ts +24 -0
- package/dist/qa/types.js +1 -0
- package/dist/scanner/csharp.d.ts +23 -0
- package/dist/scanner/csharp.js +421 -0
- package/dist/scanner/index.js +53 -26
- package/dist/scanner/java.d.ts +39 -0
- package/dist/scanner/java.js +318 -0
- package/dist/scanner/kotlin.d.ts +23 -0
- package/dist/scanner/kotlin.js +389 -0
- package/dist/scanner/php.d.ts +57 -0
- package/dist/scanner/php.js +351 -0
- package/dist/scanner/python.js +17 -0
- package/dist/scanner/ruby.d.ts +36 -0
- package/dist/scanner/ruby.js +431 -0
- package/dist/scanner/swift.d.ts +25 -0
- package/dist/scanner/swift.js +392 -0
- package/dist/scanner/types.d.ts +1 -1
- package/dist/template/content/docs/_navigation.json +46 -0
- package/dist/template/content/docs/_sidebars.json +684 -0
- package/dist/template/content/docs/core.md +4544 -0
- package/dist/template/content/docs/index.mdx +89 -0
- package/dist/template/content/docs/integrations.md +1158 -0
- package/dist/template/content/docs/llms-full.md +403 -0
- package/dist/template/content/docs/llms.txt +4588 -0
- package/dist/template/content/docs/other.md +10379 -0
- package/dist/template/content/docs/tools.md +746 -0
- package/dist/template/content/docs/types.md +531 -0
- package/dist/template/docs.json +13 -11
- package/dist/template/mdx-components.tsx +27 -2
- package/dist/template/package.json +6 -0
- package/dist/template/public/search-index.json +1 -1
- package/dist/template/scripts/build-search-index.mjs +149 -13
- package/dist/template/src/app/api/chat/route.ts +83 -128
- package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
- package/dist/template/src/app/docs/llms-full.md +151 -4
- package/dist/template/src/app/docs/llms.txt +2464 -847
- package/dist/template/src/app/docs/page.mdx +48 -38
- package/dist/template/src/app/layout.tsx +3 -1
- package/dist/template/src/app/page.tsx +22 -8
- package/dist/template/src/components/ai-chat.tsx +73 -64
- package/dist/template/src/components/breadcrumbs.tsx +21 -23
- package/dist/template/src/components/copy-button.tsx +13 -9
- package/dist/template/src/components/copy-page-button.tsx +54 -0
- package/dist/template/src/components/docs-layout.tsx +37 -25
- package/dist/template/src/components/header.tsx +51 -10
- package/dist/template/src/components/mdx/card.tsx +17 -3
- package/dist/template/src/components/mdx/code-block.tsx +13 -9
- package/dist/template/src/components/mdx/code-group.tsx +13 -8
- package/dist/template/src/components/mdx/heading.tsx +15 -2
- package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
- package/dist/template/src/components/mdx/index.tsx +2 -0
- package/dist/template/src/components/mdx/mermaid.tsx +110 -0
- package/dist/template/src/components/mdx/screenshot.tsx +150 -0
- package/dist/template/src/components/scroll-to-hash.tsx +48 -0
- package/dist/template/src/components/sidebar.tsx +12 -18
- package/dist/template/src/components/table-of-contents.tsx +9 -0
- package/dist/template/src/lib/highlight.ts +3 -88
- package/dist/template/src/lib/navigation.ts +159 -0
- package/dist/template/src/lib/search-types.ts +4 -1
- package/dist/template/src/lib/search.ts +30 -7
- package/dist/template/src/styles/globals.css +17 -6
- package/dist/utils/files.d.ts +9 -1
- package/dist/utils/files.js +59 -10
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.js +0 -26
- 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
|
|
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
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
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
|
+
});
|