skrypt-ai 0.5.0 → 0.6.1
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.js +8 -1
- package/dist/autofix/index.d.ts +0 -4
- package/dist/autofix/index.js +0 -21
- 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 +4 -0
- 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 +88 -6
- 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/lint.js +50 -44
- package/dist/commands/llms-txt.js +59 -49
- package/dist/commands/login.js +61 -43
- 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 +114 -103
- package/dist/commands/sdk.js +128 -122
- package/dist/commands/security.js +86 -80
- 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 +24 -4
- 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/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 +16 -2
- 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/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 +84 -6
- 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/styles/globals.css +17 -6
- package/dist/utils/validation.d.ts +0 -3
- package/dist/utils/validation.js +0 -26
- package/package.json +3 -2
package/dist/commands/deploy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { spawnSync, spawn } from 'child_process';
|
|
3
|
-
import { existsSync, readFileSync, statSync } from 'fs';
|
|
3
|
+
import { existsSync, readFileSync, statSync, unlinkSync } from 'fs';
|
|
4
4
|
import { resolve, join } from 'path';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
6
|
import { createHash } from 'crypto';
|
|
@@ -81,8 +81,7 @@ async function buildSite(docsPath) {
|
|
|
81
81
|
return new Promise((resolve, reject) => {
|
|
82
82
|
const child = spawn('npm', ['run', 'build'], {
|
|
83
83
|
cwd: docsPath,
|
|
84
|
-
stdio: ['inherit', 'pipe', 'pipe']
|
|
85
|
-
shell: true
|
|
84
|
+
stdio: ['inherit', 'pipe', 'pipe']
|
|
86
85
|
});
|
|
87
86
|
child.stdout?.on('data', (data) => {
|
|
88
87
|
process.stdout.write(` ${data.toString().trim()}\n`);
|
|
@@ -208,113 +207,135 @@ export const deployCommand = new Command('deploy')
|
|
|
208
207
|
.option('--project <slug>', 'Project slug (e.g., my-docs)')
|
|
209
208
|
.option('--token <key>', 'API token (or set SKRYPT_API_KEY env var)')
|
|
210
209
|
.action(async (directory, options) => {
|
|
211
|
-
|
|
212
|
-
const docsPath = resolve(directory);
|
|
213
|
-
console.log('skrypt deploy');
|
|
214
|
-
console.log(` directory: ${docsPath}`);
|
|
215
|
-
console.log('');
|
|
216
|
-
// Validate directory
|
|
217
|
-
if (!existsSync(docsPath)) {
|
|
218
|
-
console.error(`Error: Directory not found: ${docsPath}`);
|
|
219
|
-
process.exit(1);
|
|
220
|
-
}
|
|
221
|
-
// Check for package.json (Next.js project)
|
|
222
|
-
const packageJsonPath = join(docsPath, 'package.json');
|
|
223
|
-
if (!existsSync(packageJsonPath)) {
|
|
224
|
-
console.error('Error: No package.json found. Is this a Skrypt docs site?');
|
|
225
|
-
console.error(' Run: skrypt init <directory>');
|
|
226
|
-
process.exit(1);
|
|
227
|
-
}
|
|
228
|
-
// Get API token
|
|
229
|
-
const apiToken = getApiToken(options);
|
|
230
|
-
if (!apiToken) {
|
|
231
|
-
console.error('Error: No API token found.');
|
|
232
|
-
console.error('');
|
|
233
|
-
console.error(' Provide a token using one of:');
|
|
234
|
-
console.error(' --token <key>');
|
|
235
|
-
console.error(' SKRYPT_API_KEY environment variable');
|
|
236
|
-
console.error(' skrypt login');
|
|
237
|
-
console.error('');
|
|
238
|
-
process.exit(1);
|
|
239
|
-
}
|
|
240
|
-
// Get project slug
|
|
241
|
-
const projectSlug = getProjectSlug(options, docsPath);
|
|
242
|
-
if (!projectSlug) {
|
|
243
|
-
console.error('Error: No project slug found.');
|
|
244
|
-
console.error('');
|
|
245
|
-
console.error(' Provide a project slug using one of:');
|
|
246
|
-
console.error(' --project <slug>');
|
|
247
|
-
console.error(' "slug" field in docs.json');
|
|
248
|
-
console.error('');
|
|
249
|
-
process.exit(1);
|
|
250
|
-
}
|
|
251
|
-
console.log(` project: ${projectSlug}`);
|
|
252
|
-
console.log('');
|
|
253
|
-
// Step 1: Build the site
|
|
254
|
-
console.log('Step 1: Building documentation site...');
|
|
255
|
-
let outDir;
|
|
210
|
+
let bundlePath;
|
|
256
211
|
try {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
console.
|
|
261
|
-
console.
|
|
262
|
-
|
|
263
|
-
|
|
212
|
+
const startTime = Date.now();
|
|
213
|
+
const docsPath = resolve(directory);
|
|
214
|
+
console.log('skrypt deploy');
|
|
215
|
+
console.log(` directory: ${docsPath}`);
|
|
216
|
+
console.log('');
|
|
217
|
+
// Validate directory
|
|
218
|
+
if (!existsSync(docsPath)) {
|
|
219
|
+
console.error(`Error: Directory not found: ${docsPath}`);
|
|
220
|
+
process.exit(1);
|
|
264
221
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
222
|
+
// Check for package.json (Next.js project)
|
|
223
|
+
const packageJsonPath = join(docsPath, 'package.json');
|
|
224
|
+
if (!existsSync(packageJsonPath)) {
|
|
225
|
+
console.error('Error: No package.json found. Is this a Skrypt docs site?');
|
|
226
|
+
console.error(' Run: skrypt init <directory>');
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
// Get API token
|
|
230
|
+
const apiToken = getApiToken(options);
|
|
231
|
+
if (!apiToken) {
|
|
232
|
+
console.error('Error: No API token found.');
|
|
233
|
+
console.error('');
|
|
234
|
+
console.error(' Provide a token using one of:');
|
|
235
|
+
console.error(' --token <key>');
|
|
236
|
+
console.error(' SKRYPT_API_KEY environment variable');
|
|
237
|
+
console.error(' skrypt login');
|
|
272
238
|
console.error('');
|
|
273
|
-
console.error('Error: Static export not found.');
|
|
274
|
-
console.error(' Add to next.config.mjs: output: "export"');
|
|
275
239
|
process.exit(1);
|
|
276
240
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
241
|
+
// Get project slug
|
|
242
|
+
const projectSlug = getProjectSlug(options, docsPath);
|
|
243
|
+
if (!projectSlug) {
|
|
244
|
+
console.error('Error: No project slug found.');
|
|
245
|
+
console.error('');
|
|
246
|
+
console.error(' Provide a project slug using one of:');
|
|
247
|
+
console.error(' --project <slug>');
|
|
248
|
+
console.error(' "slug" field in docs.json');
|
|
249
|
+
console.error('');
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
console.log(` project: ${projectSlug}`);
|
|
253
|
+
console.log('');
|
|
254
|
+
// Step 1: Build the site
|
|
255
|
+
console.log('Step 1: Building documentation site...');
|
|
256
|
+
let outDir;
|
|
257
|
+
try {
|
|
258
|
+
outDir = await buildSite(docsPath);
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
console.error('');
|
|
262
|
+
console.error(`Error: Build failed`);
|
|
263
|
+
if (err instanceof Error) {
|
|
264
|
+
console.error(` ${err.message}`);
|
|
265
|
+
}
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
// Check build output exists
|
|
269
|
+
if (!existsSync(outDir)) {
|
|
270
|
+
// Try .next/static for non-static export
|
|
271
|
+
const nextDir = join(docsPath, '.next');
|
|
272
|
+
if (existsSync(nextDir)) {
|
|
273
|
+
console.error('');
|
|
274
|
+
console.error('Error: Static export not found.');
|
|
275
|
+
console.error(' Add to next.config.mjs: output: "export"');
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
console.error('');
|
|
279
|
+
console.error('Error: Build output not found.');
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
console.log(' Build completed successfully');
|
|
283
|
+
console.log('');
|
|
284
|
+
// Step 2: Bundle the output
|
|
285
|
+
console.log('Step 2: Creating deployment bundle...');
|
|
286
|
+
try {
|
|
287
|
+
bundlePath = await bundleOutput(outDir);
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
console.error('');
|
|
291
|
+
console.error('Error: Failed to create bundle');
|
|
292
|
+
if (err instanceof Error) {
|
|
293
|
+
console.error(` ${err.message}`);
|
|
294
|
+
}
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
console.log('');
|
|
298
|
+
// Step 3: Upload
|
|
299
|
+
console.log('Step 3: Deploying to Skrypt...');
|
|
300
|
+
const result = await uploadBundle(bundlePath, projectSlug, apiToken);
|
|
301
|
+
if (!result.success) {
|
|
302
|
+
console.error('');
|
|
303
|
+
console.error(`Error: Deployment failed`);
|
|
304
|
+
console.error(` ${result.error}`);
|
|
305
|
+
// Clean up temp bundle
|
|
306
|
+
try {
|
|
307
|
+
unlinkSync(bundlePath);
|
|
308
|
+
}
|
|
309
|
+
catch { /* ignore cleanup errors */ }
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
// Clean up temp bundle
|
|
313
|
+
try {
|
|
314
|
+
unlinkSync(bundlePath);
|
|
315
|
+
}
|
|
316
|
+
catch { /* ignore cleanup errors */ }
|
|
317
|
+
// Success
|
|
318
|
+
const duration = Math.round((Date.now() - startTime) / 1000);
|
|
319
|
+
console.log('');
|
|
320
|
+
console.log('=== Deployment Successful ===');
|
|
321
|
+
console.log('');
|
|
322
|
+
console.log(` URL: ${result.url}`);
|
|
323
|
+
if (result.customDomain) {
|
|
324
|
+
console.log(` Custom domain: ${result.customDomain}`);
|
|
325
|
+
}
|
|
326
|
+
console.log(` Duration: ${duration}s`);
|
|
327
|
+
console.log('');
|
|
328
|
+
console.log('Your documentation is now live!');
|
|
329
|
+
console.log('');
|
|
288
330
|
}
|
|
289
331
|
catch (err) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
332
|
+
if (bundlePath) {
|
|
333
|
+
try {
|
|
334
|
+
unlinkSync(bundlePath);
|
|
335
|
+
}
|
|
336
|
+
catch { /* ignore cleanup errors */ }
|
|
294
337
|
}
|
|
338
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
295
339
|
process.exit(1);
|
|
296
340
|
}
|
|
297
|
-
console.log('');
|
|
298
|
-
// Step 3: Upload
|
|
299
|
-
console.log('Step 3: Deploying to Skrypt...');
|
|
300
|
-
const result = await uploadBundle(bundlePath, projectSlug, apiToken);
|
|
301
|
-
if (!result.success) {
|
|
302
|
-
console.error('');
|
|
303
|
-
console.error(`Error: Deployment failed`);
|
|
304
|
-
console.error(` ${result.error}`);
|
|
305
|
-
process.exit(1);
|
|
306
|
-
}
|
|
307
|
-
// Success
|
|
308
|
-
const duration = Math.round((Date.now() - startTime) / 1000);
|
|
309
|
-
console.log('');
|
|
310
|
-
console.log('=== Deployment Successful ===');
|
|
311
|
-
console.log('');
|
|
312
|
-
console.log(` URL: ${result.url}`);
|
|
313
|
-
if (result.customDomain) {
|
|
314
|
-
console.log(` Custom domain: ${result.customDomain}`);
|
|
315
|
-
}
|
|
316
|
-
console.log(` Duration: ${duration}s`);
|
|
317
|
-
console.log('');
|
|
318
|
-
console.log('Your documentation is now live!');
|
|
319
|
-
console.log('');
|
|
320
341
|
});
|
|
@@ -7,6 +7,9 @@ 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
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';
|
|
10
13
|
/**
|
|
11
14
|
* Read .skryptignore patterns from source directory
|
|
12
15
|
*/
|
|
@@ -258,6 +261,53 @@ export const generateCommand = new Command('generate')
|
|
|
258
261
|
console.log('\n[dry run - stopping before generation]');
|
|
259
262
|
process.exit(0);
|
|
260
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
|
+
}
|
|
261
311
|
// Step 2: Generate docs
|
|
262
312
|
console.log('\nStep 2: Generating documentation...');
|
|
263
313
|
const client = createLLMClient({
|
|
@@ -272,6 +322,8 @@ export const generateCommand = new Command('generate')
|
|
|
272
322
|
}
|
|
273
323
|
const docs = await generateForElements(allElements, client, {
|
|
274
324
|
multiLanguage,
|
|
325
|
+
externalContext,
|
|
326
|
+
projectContext,
|
|
275
327
|
onProgress: (progress) => {
|
|
276
328
|
if (progress.element !== lastElement) {
|
|
277
329
|
if (lastElement)
|
|
@@ -328,13 +380,43 @@ export const generateCommand = new Command('generate')
|
|
|
328
380
|
console.log(`\n Warning: OpenAPI spec not found: ${specPath}`);
|
|
329
381
|
}
|
|
330
382
|
}
|
|
331
|
-
//
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
+
});
|
|
336
408
|
});
|
|
337
|
-
|
|
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
|
+
}
|
|
338
420
|
}
|
|
339
421
|
console.log('\n=== Summary ===');
|
|
340
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
|
+
});
|