skrypt-ai 0.3.3 → 0.4.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/README.md +1 -1
- package/dist/auth/index.d.ts +0 -1
- package/dist/auth/index.js +3 -5
- package/dist/autofix/index.js +15 -3
- package/dist/cli.js +19 -4
- package/dist/commands/check-links.js +164 -174
- package/dist/commands/deploy.js +5 -2
- package/dist/commands/generate.js +206 -199
- package/dist/commands/i18n.js +3 -20
- package/dist/commands/init.js +47 -40
- package/dist/commands/lint.js +3 -20
- package/dist/commands/mcp.js +125 -122
- package/dist/commands/monitor.js +125 -108
- package/dist/commands/review-pr.js +1 -1
- package/dist/commands/sdk.js +1 -1
- package/dist/config/loader.js +21 -2
- package/dist/generator/organizer.d.ts +3 -0
- package/dist/generator/organizer.js +4 -9
- package/dist/generator/writer.js +2 -10
- package/dist/github/pr-comments.js +21 -8
- package/dist/plugins/index.js +1 -0
- package/dist/scanner/index.js +8 -2
- package/dist/template/docs.json +2 -1
- package/dist/template/next.config.mjs +3 -1
- package/dist/template/package.json +17 -14
- package/dist/template/public/favicon.svg +4 -0
- package/dist/template/public/search-index.json +1 -1
- package/dist/template/scripts/build-search-index.mjs +120 -25
- package/dist/template/src/app/api/chat/route.ts +11 -3
- package/dist/template/src/app/docs/README.md +28 -0
- package/dist/template/src/app/docs/[...slug]/page.tsx +141 -14
- package/dist/template/src/app/docs/auth/page.mdx +589 -0
- package/dist/template/src/app/docs/autofix/page.mdx +624 -0
- package/dist/template/src/app/docs/cli/page.mdx +217 -0
- package/dist/template/src/app/docs/config/page.mdx +428 -0
- package/dist/template/src/app/docs/configuration/page.mdx +86 -0
- package/dist/template/src/app/docs/deployment/page.mdx +112 -0
- package/dist/template/src/app/docs/error.tsx +20 -0
- package/dist/template/src/app/docs/generator/generator.md +504 -0
- package/dist/template/src/app/docs/generator/organizer.md +779 -0
- package/dist/template/src/app/docs/generator/page.mdx +613 -0
- package/dist/template/src/app/docs/github/page.mdx +502 -0
- package/dist/template/src/app/docs/llm/anthropic-client.md +549 -0
- package/dist/template/src/app/docs/llm/index.md +471 -0
- package/dist/template/src/app/docs/llm/page.mdx +428 -0
- package/dist/template/src/app/docs/llms-full.md +256 -0
- package/dist/template/src/app/docs/llms.txt +2971 -0
- package/dist/template/src/app/docs/not-found.tsx +23 -0
- package/dist/template/src/app/docs/page.mdx +0 -3
- package/dist/template/src/app/docs/plugins/page.mdx +1793 -0
- package/dist/template/src/app/docs/pro/page.mdx +121 -0
- package/dist/template/src/app/docs/quickstart/page.mdx +93 -0
- package/dist/template/src/app/docs/scanner/content-type.md +599 -0
- package/dist/template/src/app/docs/scanner/index.md +212 -0
- package/dist/template/src/app/docs/scanner/page.mdx +307 -0
- package/dist/template/src/app/docs/scanner/python.md +469 -0
- package/dist/template/src/app/docs/scanner/python_parser.md +1056 -0
- package/dist/template/src/app/docs/scanner/rust.md +325 -0
- package/dist/template/src/app/docs/scanner/typescript.md +201 -0
- package/dist/template/src/app/error.tsx +3 -3
- package/dist/template/src/app/icon.tsx +29 -0
- package/dist/template/src/app/layout.tsx +57 -7
- package/dist/template/src/app/not-found.tsx +35 -0
- package/dist/template/src/app/page.tsx +95 -11
- package/dist/template/src/components/ai-chat.tsx +26 -21
- package/dist/template/src/components/breadcrumbs.tsx +56 -12
- package/dist/template/src/components/copy-button.tsx +17 -3
- package/dist/template/src/components/docs-layout.tsx +202 -8
- package/dist/template/src/components/feedback.tsx +4 -2
- package/dist/template/src/components/footer.tsx +42 -0
- package/dist/template/src/components/header.tsx +56 -20
- package/dist/template/src/components/mdx/accordion.tsx +17 -13
- package/dist/template/src/components/mdx/callout.tsx +50 -37
- package/dist/template/src/components/mdx/card.tsx +24 -12
- package/dist/template/src/components/mdx/code-block.tsx +17 -3
- package/dist/template/src/components/mdx/code-group.tsx +78 -18
- package/dist/template/src/components/mdx/code-playground.tsx +3 -0
- package/dist/template/src/components/mdx/go-playground.tsx +3 -0
- package/dist/template/src/components/mdx/highlighted-code.tsx +178 -38
- package/dist/template/src/components/mdx/python-playground.tsx +2 -0
- package/dist/template/src/components/mdx/steps.tsx +6 -6
- package/dist/template/src/components/mdx/tabs.tsx +76 -8
- package/dist/template/src/components/page-header.tsx +19 -0
- package/dist/template/src/components/scroll-to-top.tsx +33 -0
- package/dist/template/src/components/search-dialog.tsx +251 -57
- package/dist/template/src/components/sidebar.tsx +137 -77
- package/dist/template/src/components/table-of-contents.tsx +29 -13
- package/dist/template/src/lib/highlight.ts +90 -31
- package/dist/template/src/lib/search.ts +14 -4
- package/dist/template/src/lib/theme-utils.ts +140 -0
- package/dist/template/src/styles/globals.css +397 -84
- package/dist/template/src/types/remark-gfm.d.ts +2 -0
- package/dist/utils/files.d.ts +9 -0
- package/dist/utils/files.js +33 -0
- package/dist/utils/validation.d.ts +4 -0
- package/dist/utils/validation.js +38 -0
- package/package.json +1 -4
package/dist/commands/mcp.js
CHANGED
|
@@ -1,26 +1,9 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { existsSync, readFileSync,
|
|
3
|
-
import { resolve,
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import { resolve, relative } from 'path';
|
|
4
|
+
import { findMdxFiles } from '../utils/files.js';
|
|
4
5
|
import { createServer } from 'http';
|
|
5
6
|
import { requirePro } from '../auth/index.js';
|
|
6
|
-
function findMdxFiles(dir) {
|
|
7
|
-
const files = [];
|
|
8
|
-
function walk(currentDir) {
|
|
9
|
-
const entries = readdirSync(currentDir);
|
|
10
|
-
for (const entry of entries) {
|
|
11
|
-
const fullPath = join(currentDir, entry);
|
|
12
|
-
const stat = statSync(fullPath);
|
|
13
|
-
if (stat.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules') {
|
|
14
|
-
walk(fullPath);
|
|
15
|
-
}
|
|
16
|
-
else if (stat.isFile() && (extname(entry) === '.mdx' || extname(entry) === '.md')) {
|
|
17
|
-
files.push(fullPath);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
walk(dir);
|
|
22
|
-
return files;
|
|
23
|
-
}
|
|
24
7
|
function parseDocFile(filePath, basePath) {
|
|
25
8
|
const content = readFileSync(filePath, 'utf-8');
|
|
26
9
|
const relPath = relative(basePath, filePath);
|
|
@@ -166,116 +149,136 @@ export const mcpCommand = new Command('mcp')
|
|
|
166
149
|
.option('-p, --port <port>', 'Server port', '3100')
|
|
167
150
|
.option('-h, --host <host>', 'Server host', 'localhost')
|
|
168
151
|
.option('-o, --output <file>', 'Output manifest to file instead of starting server')
|
|
152
|
+
.option('-t, --token <token>', 'Bearer token for authenticating MCP requests')
|
|
169
153
|
.action(async (path, options) => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const docs = files.map(f => parseDocFile(f, targetPath));
|
|
186
|
-
const projectName = targetPath.split('/').pop() || 'docs';
|
|
187
|
-
// Output mode: write manifest to file
|
|
188
|
-
if (options.output) {
|
|
189
|
-
const manifest = generateMcpManifest(docs, projectName);
|
|
190
|
-
const outputPath = resolve(options.output);
|
|
191
|
-
writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
|
|
192
|
-
console.log(`MCP manifest written to ${outputPath}`);
|
|
193
|
-
process.exit(0);
|
|
194
|
-
}
|
|
195
|
-
// Server mode
|
|
196
|
-
const port = parseInt(options.port) || 3100;
|
|
197
|
-
const host = options.host || 'localhost';
|
|
198
|
-
console.log('skrypt mcp');
|
|
199
|
-
console.log(` docs: ${files.length} files`);
|
|
200
|
-
console.log(` server: http://${host}:${port}`);
|
|
201
|
-
console.log('');
|
|
202
|
-
const manifest = generateMcpManifest(docs, projectName);
|
|
203
|
-
const server = createServer((req, res) => {
|
|
204
|
-
const url = new URL(req.url || '/', `http://${host}:${port}`);
|
|
205
|
-
// CORS headers
|
|
206
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
207
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
208
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
209
|
-
if (req.method === 'OPTIONS') {
|
|
210
|
-
res.writeHead(204);
|
|
211
|
-
res.end();
|
|
212
|
-
return;
|
|
154
|
+
try {
|
|
155
|
+
// Pro feature - requires subscription
|
|
156
|
+
if (!await requirePro('mcp')) {
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
const targetPath = resolve(path);
|
|
160
|
+
if (!existsSync(targetPath)) {
|
|
161
|
+
console.error(`Error: Path not found: ${targetPath}`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
// Find and parse all docs
|
|
165
|
+
const files = findMdxFiles(targetPath);
|
|
166
|
+
if (files.length === 0) {
|
|
167
|
+
console.error('No .md or .mdx files found.');
|
|
168
|
+
process.exit(1);
|
|
213
169
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
170
|
+
const docs = files.map(f => parseDocFile(f, targetPath));
|
|
171
|
+
const projectName = targetPath.split('/').pop() || 'docs';
|
|
172
|
+
// Output mode: write manifest to file
|
|
173
|
+
if (options.output) {
|
|
174
|
+
const manifest = generateMcpManifest(docs, projectName);
|
|
175
|
+
const outputPath = resolve(options.output);
|
|
176
|
+
writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
|
|
177
|
+
console.log(`MCP manifest written to ${outputPath}`);
|
|
178
|
+
process.exit(0);
|
|
219
179
|
}
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
180
|
+
// Server mode
|
|
181
|
+
const port = Number(options.port) || 3100;
|
|
182
|
+
const host = options.host || 'localhost';
|
|
183
|
+
console.log('skrypt mcp');
|
|
184
|
+
console.log(` docs: ${files.length} files`);
|
|
185
|
+
console.log(` server: http://${host}:${port}`);
|
|
186
|
+
if (!options.token) {
|
|
187
|
+
console.warn('Warning: MCP server running without authentication. Use --token to secure.');
|
|
188
|
+
}
|
|
189
|
+
console.log('');
|
|
190
|
+
const manifest = generateMcpManifest(docs, projectName);
|
|
191
|
+
const server = createServer((req, res) => {
|
|
192
|
+
const url = new URL(req.url || '/', `http://${host}:${port}`);
|
|
193
|
+
// CORS headers
|
|
194
|
+
res.setHeader('Access-Control-Allow-Origin', `http://localhost:${port}`);
|
|
195
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
196
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
197
|
+
if (req.method === 'OPTIONS') {
|
|
198
|
+
res.writeHead(204);
|
|
199
|
+
res.end();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// Token authentication
|
|
203
|
+
if (options.token) {
|
|
204
|
+
const authHeader = req.headers['authorization'];
|
|
205
|
+
if (authHeader !== `Bearer ${options.token}`) {
|
|
206
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
207
|
+
res.end(JSON.stringify({ error: 'Unauthorized' }));
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// MCP manifest
|
|
212
|
+
if (url.pathname === '/' || url.pathname === '/manifest') {
|
|
213
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
214
|
+
res.end(JSON.stringify(manifest, null, 2));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// List docs
|
|
218
|
+
if (url.pathname === '/docs' || url.pathname === '/list') {
|
|
219
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
220
|
+
res.end(JSON.stringify({
|
|
221
|
+
docs: docs.map(d => ({
|
|
222
|
+
path: d.path,
|
|
223
|
+
title: d.title,
|
|
224
|
+
description: d.description,
|
|
225
|
+
})),
|
|
226
|
+
}));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
// Get specific doc
|
|
230
|
+
if (url.pathname.startsWith('/doc/')) {
|
|
231
|
+
const docPath = decodeURIComponent(url.pathname.slice(5));
|
|
232
|
+
const doc = docs.find(d => d.path === docPath || d.path === docPath + '.mdx' || d.path === docPath + '.md');
|
|
233
|
+
if (doc) {
|
|
234
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
235
|
+
res.end(JSON.stringify(doc));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
239
|
+
res.end(JSON.stringify({ error: 'Document not found' }));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// Search docs
|
|
243
|
+
if (url.pathname === '/search') {
|
|
244
|
+
const query = url.searchParams.get('q')?.toLowerCase() || '';
|
|
245
|
+
const results = docs
|
|
246
|
+
.filter(d => d.title.toLowerCase().includes(query) ||
|
|
247
|
+
d.description.toLowerCase().includes(query) ||
|
|
248
|
+
d.content.toLowerCase().includes(query))
|
|
249
|
+
.map(d => ({
|
|
225
250
|
path: d.path,
|
|
226
251
|
title: d.title,
|
|
227
252
|
description: d.description,
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const docPath = decodeURIComponent(url.pathname.slice(5));
|
|
235
|
-
const doc = docs.find(d => d.path === docPath || d.path === docPath + '.mdx' || d.path === docPath + '.md');
|
|
236
|
-
if (doc) {
|
|
253
|
+
score: ((d.title.toLowerCase().includes(query) ? 10 : 0) +
|
|
254
|
+
(d.description.toLowerCase().includes(query) ? 5 : 0) +
|
|
255
|
+
(d.content.toLowerCase().includes(query) ? 1 : 0)),
|
|
256
|
+
}))
|
|
257
|
+
.sort((a, b) => b.score - a.score)
|
|
258
|
+
.slice(0, 10);
|
|
237
259
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
238
|
-
res.end(JSON.stringify(
|
|
260
|
+
res.end(JSON.stringify({ query, results }));
|
|
239
261
|
return;
|
|
240
262
|
}
|
|
263
|
+
// 404
|
|
241
264
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
242
|
-
res.end(JSON.stringify({ error: '
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
.slice(0, 10);
|
|
262
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
263
|
-
res.end(JSON.stringify({ query, results }));
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
// 404
|
|
267
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
268
|
-
res.end(JSON.stringify({ error: 'Not found' }));
|
|
269
|
-
});
|
|
270
|
-
server.listen(port, host, () => {
|
|
271
|
-
console.log(`MCP server running at http://${host}:${port}`);
|
|
272
|
-
console.log('');
|
|
273
|
-
console.log('Endpoints:');
|
|
274
|
-
console.log(` GET / - MCP manifest`);
|
|
275
|
-
console.log(` GET /docs - List all documents`);
|
|
276
|
-
console.log(` GET /doc/:path - Get document content`);
|
|
277
|
-
console.log(` GET /search?q= - Search documents`);
|
|
278
|
-
console.log('');
|
|
279
|
-
console.log('Press Ctrl+C to stop');
|
|
280
|
-
});
|
|
265
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
266
|
+
});
|
|
267
|
+
server.listen(port, host, () => {
|
|
268
|
+
console.log(`MCP server running at http://${host}:${port}`);
|
|
269
|
+
console.log('');
|
|
270
|
+
console.log('Endpoints:');
|
|
271
|
+
console.log(` GET / - MCP manifest`);
|
|
272
|
+
console.log(` GET /docs - List all documents`);
|
|
273
|
+
console.log(` GET /doc/:path - Get document content`);
|
|
274
|
+
console.log(` GET /search?q= - Search documents`);
|
|
275
|
+
console.log('');
|
|
276
|
+
console.log('Press Ctrl+C to stop');
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
281
|
+
console.error(`Error: ${message}`);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
281
284
|
});
|
package/dist/commands/monitor.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
2
|
+
import { execSync, spawnSync } from 'child_process';
|
|
3
3
|
import { existsSync, writeFileSync, readdirSync, statSync, mkdirSync } from 'fs';
|
|
4
4
|
import { resolve, join, dirname } from 'path';
|
|
5
5
|
import { createLLMClient } from '../llm/index.js';
|
|
@@ -9,7 +9,14 @@ function getGitChanges(since, repoPath) {
|
|
|
9
9
|
const changes = [];
|
|
10
10
|
try {
|
|
11
11
|
// Get list of changed files
|
|
12
|
-
const
|
|
12
|
+
const diffResult = spawnSync('git', ['diff', '--name-status', since], {
|
|
13
|
+
cwd: repoPath, encoding: 'utf-8'
|
|
14
|
+
});
|
|
15
|
+
if (diffResult.status !== 0) {
|
|
16
|
+
console.error('Failed to get git diff:', diffResult.stderr);
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
const diffFiles = diffResult.stdout.toString().trim();
|
|
13
20
|
if (!diffFiles)
|
|
14
21
|
return [];
|
|
15
22
|
for (const line of diffFiles.split('\n')) {
|
|
@@ -22,7 +29,10 @@ function getGitChanges(since, repoPath) {
|
|
|
22
29
|
// Get the actual diff
|
|
23
30
|
let diff = '';
|
|
24
31
|
try {
|
|
25
|
-
|
|
32
|
+
const fileDiffResult = spawnSync('git', ['diff', since, '--', file], {
|
|
33
|
+
cwd: repoPath, encoding: 'utf-8', maxBuffer: 1024 * 1024
|
|
34
|
+
});
|
|
35
|
+
diff = fileDiffResult.stdout.toString().slice(0, 5000); // Limit diff size
|
|
26
36
|
}
|
|
27
37
|
catch {
|
|
28
38
|
// File might be binary or too large
|
|
@@ -178,114 +188,121 @@ export const monitorCommand = new Command('monitor')
|
|
|
178
188
|
.option('--create-pr', 'Create a GitHub PR with changes')
|
|
179
189
|
.option('--dry-run', 'Show what would be done without making changes')
|
|
180
190
|
.action(async (repoPath, options) => {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// Load config
|
|
191
|
-
const config = loadConfig();
|
|
192
|
-
if (options.provider) {
|
|
193
|
-
config.llm.provider = options.provider;
|
|
194
|
-
}
|
|
195
|
-
// Check API key
|
|
196
|
-
const { ok, envKey } = checkApiKey(config.llm.provider);
|
|
197
|
-
if (!ok && envKey) {
|
|
198
|
-
console.error(`Error: ${envKey} required for ${config.llm.provider}`);
|
|
199
|
-
process.exit(1);
|
|
200
|
-
}
|
|
201
|
-
// Step 1: Get git changes
|
|
202
|
-
console.log('Step 1: Analyzing git changes...');
|
|
203
|
-
const changes = getGitChanges(options.since || 'HEAD~10', resolvedPath);
|
|
204
|
-
if (changes.length === 0) {
|
|
205
|
-
console.log(' No code changes found.');
|
|
206
|
-
process.exit(0);
|
|
207
|
-
}
|
|
208
|
-
const userFacing = changes.filter(c => c.isUserFacing);
|
|
209
|
-
console.log(` Found ${changes.length} changed files (${userFacing.length} user-facing)`);
|
|
210
|
-
if (userFacing.length === 0) {
|
|
211
|
-
console.log(' No user-facing changes detected. No documentation updates needed.');
|
|
212
|
-
process.exit(0);
|
|
213
|
-
}
|
|
214
|
-
// Step 2: Find existing docs
|
|
215
|
-
console.log('\nStep 2: Scanning existing documentation...');
|
|
216
|
-
const docsPath = join(resolvedPath, 'docs');
|
|
217
|
-
const existingDocs = findExistingDocs(docsPath);
|
|
218
|
-
console.log(` Found ${existingDocs.length} existing doc files`);
|
|
219
|
-
// Step 3: Analyze with AI
|
|
220
|
-
console.log('\nStep 3: Analyzing changes with AI...');
|
|
221
|
-
const client = createLLMClient({
|
|
222
|
-
provider: config.llm.provider,
|
|
223
|
-
model: config.llm.model,
|
|
224
|
-
});
|
|
225
|
-
const suggestions = await analyzeChangesWithAI(changes, existingDocs, client);
|
|
226
|
-
if (suggestions.length === 0) {
|
|
227
|
-
console.log(' No documentation updates suggested.');
|
|
228
|
-
process.exit(0);
|
|
229
|
-
}
|
|
230
|
-
console.log(`\n=== Documentation Suggestions (${suggestions.length}) ===\n`);
|
|
231
|
-
for (const s of suggestions) {
|
|
232
|
-
const icon = s.action === 'create' ? '➕' : s.action === 'update' ? '📝' : '🗑️';
|
|
233
|
-
const priority = s.priority === 'high' ? '🔴' : s.priority === 'medium' ? '🟡' : '🟢';
|
|
234
|
-
console.log(`${icon} ${priority} ${s.file}`);
|
|
235
|
-
console.log(` Action: ${s.action.toUpperCase()}`);
|
|
236
|
-
console.log(` Reason: ${s.reason}`);
|
|
191
|
+
try {
|
|
192
|
+
// Pro feature - requires subscription
|
|
193
|
+
if (!await requirePro('monitor')) {
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
const resolvedPath = resolve(repoPath);
|
|
197
|
+
console.log('skrypt monitor');
|
|
198
|
+
console.log(` repo: ${resolvedPath}`);
|
|
199
|
+
console.log(` since: ${options.since}`);
|
|
237
200
|
console.log('');
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
writeFileSync(outputPath, JSON.stringify({ suggestions, changes: userFacing }, null, 2));
|
|
243
|
-
console.log(`Suggestions saved to: ${outputPath}`);
|
|
244
|
-
}
|
|
245
|
-
// Auto-fix if requested
|
|
246
|
-
if (options.autoFix && !options.dryRun) {
|
|
247
|
-
console.log('\n=== Auto-generating documentation ===\n');
|
|
248
|
-
for (const suggestion of suggestions.filter(s => s.action !== 'delete')) {
|
|
249
|
-
const change = changes.find(c => suggestion.reason.includes(c.file));
|
|
250
|
-
if (!change)
|
|
251
|
-
continue;
|
|
252
|
-
console.log(`Generating: ${suggestion.file}...`);
|
|
253
|
-
const content = await generateDocContent(suggestion, change, client);
|
|
254
|
-
if (content) {
|
|
255
|
-
const docPath = join(docsPath, suggestion.file);
|
|
256
|
-
mkdirSync(dirname(docPath), { recursive: true });
|
|
257
|
-
writeFileSync(docPath, content);
|
|
258
|
-
console.log(` ✓ Written: ${docPath}`);
|
|
259
|
-
}
|
|
201
|
+
// Load config
|
|
202
|
+
const config = loadConfig();
|
|
203
|
+
if (options.provider) {
|
|
204
|
+
config.llm.provider = options.provider;
|
|
260
205
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
console.log(
|
|
206
|
+
// Check API key
|
|
207
|
+
const { ok, envKey } = checkApiKey(config.llm.provider);
|
|
208
|
+
if (!ok && envKey) {
|
|
209
|
+
console.error(`Error: ${envKey} required for ${config.llm.provider}`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
// Step 1: Get git changes
|
|
213
|
+
console.log('Step 1: Analyzing git changes...');
|
|
214
|
+
const changes = getGitChanges(options.since || 'HEAD~10', resolvedPath);
|
|
215
|
+
if (changes.length === 0) {
|
|
216
|
+
console.log(' No code changes found.');
|
|
217
|
+
process.exit(0);
|
|
218
|
+
}
|
|
219
|
+
const userFacing = changes.filter(c => c.isUserFacing);
|
|
220
|
+
console.log(` Found ${changes.length} changed files (${userFacing.length} user-facing)`);
|
|
221
|
+
if (userFacing.length === 0) {
|
|
222
|
+
console.log(' No user-facing changes detected. No documentation updates needed.');
|
|
223
|
+
process.exit(0);
|
|
224
|
+
}
|
|
225
|
+
// Step 2: Find existing docs
|
|
226
|
+
console.log('\nStep 2: Scanning existing documentation...');
|
|
227
|
+
const docsPath = join(resolvedPath, 'docs');
|
|
228
|
+
const existingDocs = findExistingDocs(docsPath);
|
|
229
|
+
console.log(` Found ${existingDocs.length} existing doc files`);
|
|
230
|
+
// Step 3: Analyze with AI
|
|
231
|
+
console.log('\nStep 3: Analyzing changes with AI...');
|
|
232
|
+
const client = createLLMClient({
|
|
233
|
+
provider: config.llm.provider,
|
|
234
|
+
model: config.llm.model,
|
|
235
|
+
});
|
|
236
|
+
const suggestions = await analyzeChangesWithAI(changes, existingDocs, client);
|
|
237
|
+
if (suggestions.length === 0) {
|
|
238
|
+
console.log(' No documentation updates suggested.');
|
|
239
|
+
process.exit(0);
|
|
240
|
+
}
|
|
241
|
+
console.log(`\n=== Documentation Suggestions (${suggestions.length}) ===\n`);
|
|
242
|
+
for (const s of suggestions) {
|
|
243
|
+
const icon = s.action === 'create' ? '➕' : s.action === 'update' ? '📝' : '🗑️';
|
|
244
|
+
const priority = s.priority === 'high' ? '🔴' : s.priority === 'medium' ? '🟡' : '🟢';
|
|
245
|
+
console.log(`${icon} ${priority} ${s.file}`);
|
|
246
|
+
console.log(` Action: ${s.action.toUpperCase()}`);
|
|
247
|
+
console.log(` Reason: ${s.reason}`);
|
|
248
|
+
console.log('');
|
|
249
|
+
}
|
|
250
|
+
// Output to file if requested
|
|
251
|
+
if (options.output) {
|
|
252
|
+
const outputPath = resolve(options.output);
|
|
253
|
+
writeFileSync(outputPath, JSON.stringify({ suggestions, changes: userFacing }, null, 2));
|
|
254
|
+
console.log(`Suggestions saved to: ${outputPath}`);
|
|
278
255
|
}
|
|
279
|
-
|
|
280
|
-
|
|
256
|
+
// Auto-fix if requested
|
|
257
|
+
if (options.autoFix && !options.dryRun) {
|
|
258
|
+
console.log('\n=== Auto-generating documentation ===\n');
|
|
259
|
+
for (const suggestion of suggestions.filter(s => s.action !== 'delete')) {
|
|
260
|
+
const change = changes.find(c => suggestion.reason.includes(c.file));
|
|
261
|
+
if (!change)
|
|
262
|
+
continue;
|
|
263
|
+
console.log(`Generating: ${suggestion.file}...`);
|
|
264
|
+
const content = await generateDocContent(suggestion, change, client);
|
|
265
|
+
if (content) {
|
|
266
|
+
const docPath = join(docsPath, suggestion.file);
|
|
267
|
+
mkdirSync(dirname(docPath), { recursive: true });
|
|
268
|
+
writeFileSync(docPath, content);
|
|
269
|
+
console.log(` ✓ Written: ${docPath}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// Create PR if requested
|
|
274
|
+
if (options.createPr && !options.dryRun) {
|
|
275
|
+
console.log('\n=== Creating GitHub PR ===\n');
|
|
276
|
+
try {
|
|
277
|
+
// Check if gh CLI is available
|
|
278
|
+
execSync('gh --version', { stdio: 'ignore' });
|
|
279
|
+
// Create branch
|
|
280
|
+
const branchName = `docs/auto-update-${Date.now()}`;
|
|
281
|
+
execSync(`git checkout -b ${branchName}`, { cwd: resolvedPath });
|
|
282
|
+
// Stage and commit
|
|
283
|
+
execSync('git add docs/', { cwd: resolvedPath });
|
|
284
|
+
execSync(`git commit -m "docs: auto-update documentation for recent changes"`, { cwd: resolvedPath });
|
|
285
|
+
// Push and create PR
|
|
286
|
+
execSync(`git push -u origin ${branchName}`, { cwd: resolvedPath });
|
|
287
|
+
const prUrl = execSync(`gh pr create --title "docs: Auto-update documentation" --body "This PR was automatically generated by Skrypt monitor.\n\n## Changes\n${suggestions.map(s => `- ${s.action}: ${s.file}`).join('\n')}"`, { cwd: resolvedPath, encoding: 'utf-8' }).trim();
|
|
288
|
+
console.log(`PR created: ${prUrl}`);
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
console.error('Failed to create PR:', err);
|
|
292
|
+
}
|
|
281
293
|
}
|
|
294
|
+
// Summary
|
|
295
|
+
console.log('\n=== Summary ===');
|
|
296
|
+
console.log(` Changes analyzed: ${changes.length}`);
|
|
297
|
+
console.log(` User-facing: ${userFacing.length}`);
|
|
298
|
+
console.log(` Suggestions: ${suggestions.length}`);
|
|
299
|
+
console.log(` Create: ${suggestions.filter(s => s.action === 'create').length}`);
|
|
300
|
+
console.log(` Update: ${suggestions.filter(s => s.action === 'update').length}`);
|
|
301
|
+
console.log(` Delete: ${suggestions.filter(s => s.action === 'delete').length}`);
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
305
|
+
console.error(`Error: ${message}`);
|
|
306
|
+
process.exit(1);
|
|
282
307
|
}
|
|
283
|
-
// Summary
|
|
284
|
-
console.log('\n=== Summary ===');
|
|
285
|
-
console.log(` Changes analyzed: ${changes.length}`);
|
|
286
|
-
console.log(` User-facing: ${userFacing.length}`);
|
|
287
|
-
console.log(` Suggestions: ${suggestions.length}`);
|
|
288
|
-
console.log(` Create: ${suggestions.filter(s => s.action === 'create').length}`);
|
|
289
|
-
console.log(` Update: ${suggestions.filter(s => s.action === 'update').length}`);
|
|
290
|
-
console.log(` Delete: ${suggestions.filter(s => s.action === 'delete').length}`);
|
|
291
308
|
});
|
|
@@ -8,7 +8,7 @@ export const reviewPRCommand = new Command('review-pr')
|
|
|
8
8
|
.option('--token <token>', 'GitHub token (or use GITHUB_TOKEN env var)')
|
|
9
9
|
.action(async (prUrl, options) => {
|
|
10
10
|
// Parse PR URL
|
|
11
|
-
const urlMatch = prUrl.match(
|
|
11
|
+
const urlMatch = prUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
|
|
12
12
|
if (!urlMatch) {
|
|
13
13
|
console.error('Error: Invalid PR URL format');
|
|
14
14
|
console.error('Expected: https://github.com/owner/repo/pull/123');
|
package/dist/commands/sdk.js
CHANGED
|
@@ -115,7 +115,7 @@ async function parseOpenAPISpec(content) {
|
|
|
115
115
|
// Try YAML - dynamic import to avoid require()
|
|
116
116
|
try {
|
|
117
117
|
const yaml = await import('js-yaml');
|
|
118
|
-
return yaml.load(content);
|
|
118
|
+
return yaml.load(content, { schema: yaml.JSON_SCHEMA });
|
|
119
119
|
}
|
|
120
120
|
catch {
|
|
121
121
|
throw new Error('Failed to parse spec. For YAML files, install js-yaml: npm i js-yaml');
|
package/dist/config/loader.js
CHANGED
|
@@ -30,8 +30,27 @@ export function loadConfig(configPath) {
|
|
|
30
30
|
return DEFAULT_CONFIG;
|
|
31
31
|
}
|
|
32
32
|
function parseConfigFile(filepath) {
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
let content;
|
|
34
|
+
try {
|
|
35
|
+
content = readFileSync(filepath, 'utf-8');
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
throw new Error(`Could not read config file: ${filepath}. ` +
|
|
39
|
+
(err instanceof Error ? err.message : String(err)));
|
|
40
|
+
}
|
|
41
|
+
let parsed;
|
|
42
|
+
try {
|
|
43
|
+
parsed = yaml.load(content, { schema: yaml.JSON_SCHEMA });
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
throw new Error(`Config file has invalid YAML: ${filepath}. ` +
|
|
47
|
+
`Please check the syntax and try again. ` +
|
|
48
|
+
(err instanceof Error ? err.message : String(err)));
|
|
49
|
+
}
|
|
50
|
+
if (parsed === null || parsed === undefined || typeof parsed !== 'object') {
|
|
51
|
+
throw new Error(`Config file is empty or not a valid YAML object: ${filepath}. ` +
|
|
52
|
+
`Run 'skrypt init' to generate a valid config.`);
|
|
53
|
+
}
|
|
35
54
|
// Merge with defaults
|
|
36
55
|
return mergeConfig(DEFAULT_CONFIG, parsed);
|
|
37
56
|
}
|
|
@@ -17,13 +17,16 @@ export declare function detectCrossReferences(docs: GeneratedDoc[]): CrossRefere
|
|
|
17
17
|
export declare function getCrossRefsForElement(elementName: string, allRefs: CrossReference[]): CrossReference[];
|
|
18
18
|
/**
|
|
19
19
|
* Build navigation structure from topics
|
|
20
|
+
* @internal
|
|
20
21
|
*/
|
|
21
22
|
export declare function buildNavigation(topics: Topic[]): NavigationItem[];
|
|
22
23
|
/**
|
|
23
24
|
* Generate a sidebar configuration (works with multiple doc platforms)
|
|
25
|
+
* @internal
|
|
24
26
|
*/
|
|
25
27
|
export declare function generateSidebarConfig(topics: Topic[]): object;
|
|
26
28
|
/**
|
|
27
29
|
* Merge user config with defaults
|
|
30
|
+
* @internal
|
|
28
31
|
*/
|
|
29
32
|
export declare function mergeTopicConfig(userConfig: Partial<TopicConfig>, defaults?: TopicConfig): TopicConfig;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { slugify } from '../utils/files.js';
|
|
1
2
|
/**
|
|
2
3
|
* Default topic configuration with common patterns
|
|
3
4
|
*/
|
|
@@ -173,6 +174,7 @@ export function getCrossRefsForElement(elementName, allRefs) {
|
|
|
173
174
|
}
|
|
174
175
|
/**
|
|
175
176
|
* Build navigation structure from topics
|
|
177
|
+
* @internal
|
|
176
178
|
*/
|
|
177
179
|
export function buildNavigation(topics) {
|
|
178
180
|
return topics.map(topic => ({
|
|
@@ -186,6 +188,7 @@ export function buildNavigation(topics) {
|
|
|
186
188
|
}
|
|
187
189
|
/**
|
|
188
190
|
* Generate a sidebar configuration (works with multiple doc platforms)
|
|
191
|
+
* @internal
|
|
189
192
|
*/
|
|
190
193
|
export function generateSidebarConfig(topics) {
|
|
191
194
|
return {
|
|
@@ -203,17 +206,9 @@ function titleCase(str) {
|
|
|
203
206
|
.replace(/-/g, ' ')
|
|
204
207
|
.replace(/\b\w/g, c => c.toUpperCase());
|
|
205
208
|
}
|
|
206
|
-
/**
|
|
207
|
-
* Convert string to URL-safe slug
|
|
208
|
-
*/
|
|
209
|
-
function slugify(str) {
|
|
210
|
-
return str
|
|
211
|
-
.toLowerCase()
|
|
212
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
213
|
-
.replace(/^-|-$/g, '');
|
|
214
|
-
}
|
|
215
209
|
/**
|
|
216
210
|
* Merge user config with defaults
|
|
211
|
+
* @internal
|
|
217
212
|
*/
|
|
218
213
|
export function mergeTopicConfig(userConfig, defaults = DEFAULT_TOPIC_CONFIG) {
|
|
219
214
|
return {
|