skrypt-ai 0.6.0 → 0.7.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/audit/doc-parser.d.ts +5 -0
- package/dist/audit/doc-parser.js +106 -0
- package/dist/audit/index.d.ts +4 -0
- package/dist/audit/index.js +4 -0
- package/dist/audit/matcher.d.ts +6 -0
- package/dist/audit/matcher.js +94 -0
- package/dist/audit/reporter.d.ts +9 -0
- package/dist/audit/reporter.js +106 -0
- package/dist/audit/types.d.ts +37 -0
- package/dist/audit/types.js +1 -0
- package/dist/auth/index.js +3 -1
- package/dist/cli.js +11 -1
- package/dist/commands/audit.d.ts +2 -0
- package/dist/commands/audit.js +59 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +73 -0
- package/dist/commands/cron.js +4 -0
- package/dist/commands/generate.d.ts +7 -0
- package/dist/commands/generate.js +528 -234
- package/dist/commands/refresh.d.ts +2 -0
- package/dist/commands/refresh.js +158 -0
- package/dist/commands/review-pr.js +5 -0
- package/dist/commands/review.d.ts +2 -0
- package/dist/commands/review.js +110 -0
- package/dist/commands/test.js +177 -236
- package/dist/commands/watch.js +29 -20
- package/dist/config/loader.d.ts +6 -1
- package/dist/config/loader.js +38 -2
- package/dist/config/types.d.ts +7 -0
- package/dist/generator/generator.js +2 -1
- package/dist/generator/types.d.ts +3 -0
- package/dist/generator/writer.js +60 -28
- package/dist/github/org-discovery.d.ts +17 -0
- package/dist/github/org-discovery.js +93 -0
- package/dist/llm/index.d.ts +2 -0
- package/dist/llm/index.js +8 -2
- package/dist/next-actions/actions.d.ts +2 -0
- package/dist/next-actions/actions.js +190 -0
- package/dist/next-actions/index.d.ts +6 -0
- package/dist/next-actions/index.js +39 -0
- package/dist/next-actions/setup.d.ts +2 -0
- package/dist/next-actions/setup.js +72 -0
- package/dist/next-actions/state.d.ts +7 -0
- package/dist/next-actions/state.js +68 -0
- package/dist/next-actions/suggest.d.ts +3 -0
- package/dist/next-actions/suggest.js +47 -0
- package/dist/next-actions/types.d.ts +26 -0
- package/dist/next-actions/types.js +1 -0
- package/dist/refresh/differ.d.ts +9 -0
- package/dist/refresh/differ.js +67 -0
- package/dist/refresh/index.d.ts +4 -0
- package/dist/refresh/index.js +4 -0
- package/dist/refresh/manifest.d.ts +18 -0
- package/dist/refresh/manifest.js +71 -0
- package/dist/refresh/splicer.d.ts +9 -0
- package/dist/refresh/splicer.js +50 -0
- package/dist/refresh/types.d.ts +37 -0
- package/dist/refresh/types.js +1 -0
- package/dist/review/index.d.ts +8 -0
- package/dist/review/index.js +94 -0
- package/dist/review/parser.d.ts +16 -0
- package/dist/review/parser.js +95 -0
- package/dist/review/types.d.ts +18 -0
- package/dist/review/types.js +1 -0
- package/dist/scanner/types.d.ts +2 -0
- package/dist/structure/index.d.ts +19 -0
- package/dist/structure/index.js +92 -0
- package/dist/structure/planner.d.ts +8 -0
- package/dist/structure/planner.js +180 -0
- package/dist/structure/topology.d.ts +16 -0
- package/dist/structure/topology.js +49 -0
- package/dist/structure/types.d.ts +26 -0
- package/dist/structure/types.js +1 -0
- package/dist/testing/comparator.d.ts +7 -0
- package/dist/testing/comparator.js +77 -0
- package/dist/testing/docker.d.ts +21 -0
- package/dist/testing/docker.js +234 -0
- package/dist/testing/env.d.ts +16 -0
- package/dist/testing/env.js +58 -0
- package/dist/testing/extractor.d.ts +9 -0
- package/dist/testing/extractor.js +195 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.js +6 -0
- package/dist/testing/runner.d.ts +5 -0
- package/dist/testing/runner.js +225 -0
- package/dist/testing/types.d.ts +58 -0
- package/dist/testing/types.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import { requirePro } from '../auth/index.js';
|
|
5
|
+
import { scanDirectory } from '../scanner/index.js';
|
|
6
|
+
import { createLLMClient } from '../llm/index.js';
|
|
7
|
+
import { generateForElement } from '../generator/generator.js';
|
|
8
|
+
import { loadConfig, checkApiKey } from '../config/index.js';
|
|
9
|
+
import { readManifest, writeManifest, buildManifestEntries } from '../refresh/manifest.js';
|
|
10
|
+
import { getChangedFiles, findStaleElements } from '../refresh/differ.js';
|
|
11
|
+
import { spliceDocSection, removeDocSection } from '../refresh/splicer.js';
|
|
12
|
+
export const refreshCommand = new Command('refresh')
|
|
13
|
+
.description('Refresh stale documentation based on code changes')
|
|
14
|
+
.argument('[source]', 'Source code directory', '.')
|
|
15
|
+
.option('--docs <dir>', 'Documentation directory', './docs')
|
|
16
|
+
.option('--since <ref>', 'Git ref to compare against (e.g. HEAD~3, 2026-03-01)', 'HEAD~1')
|
|
17
|
+
.option('-c, --config <file>', 'Config file path')
|
|
18
|
+
.option('--provider <name>', 'LLM provider')
|
|
19
|
+
.option('--model <name>', 'LLM model name')
|
|
20
|
+
.action(async (source, options) => {
|
|
21
|
+
try {
|
|
22
|
+
if (!await requirePro('refresh')) {
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const sourcePath = resolve(source);
|
|
26
|
+
const docsPath = resolve(options.docs || './docs');
|
|
27
|
+
if (!existsSync(sourcePath)) {
|
|
28
|
+
console.error(`Error: Source directory not found: ${sourcePath}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
if (!existsSync(docsPath)) {
|
|
32
|
+
console.error(`Error: Docs directory not found: ${docsPath}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
console.log('skrypt refresh');
|
|
36
|
+
console.log(` source: ${sourcePath}`);
|
|
37
|
+
console.log(` docs: ${docsPath}`);
|
|
38
|
+
console.log(` since: ${options.since || 'HEAD~1'}`);
|
|
39
|
+
console.log('');
|
|
40
|
+
// Read existing manifest
|
|
41
|
+
const manifest = readManifest(docsPath);
|
|
42
|
+
if (!manifest) {
|
|
43
|
+
console.log(' No manifest found. Run `skrypt generate` first to create a manifest.');
|
|
44
|
+
console.log(' The manifest tracks which code elements map to which doc files.');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
console.log(` Manifest: generated ${manifest.generatedAt} (${manifest.commitSha})`);
|
|
48
|
+
console.log(` Elements tracked: ${manifest.elements.length}`);
|
|
49
|
+
console.log('');
|
|
50
|
+
// Get changed files
|
|
51
|
+
console.log('Finding changed files...');
|
|
52
|
+
const since = options.since || 'HEAD~1';
|
|
53
|
+
let changedFiles;
|
|
54
|
+
try {
|
|
55
|
+
changedFiles = getChangedFiles(since);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (changedFiles.length === 0) {
|
|
63
|
+
console.log(' No source files changed since ' + since);
|
|
64
|
+
console.log('\nAll documentation is up to date!');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
console.log(` ${changedFiles.length} file(s) changed`);
|
|
68
|
+
// Find stale elements
|
|
69
|
+
console.log('\nChecking for stale documentation...');
|
|
70
|
+
const staleElements = await findStaleElements(manifest, changedFiles);
|
|
71
|
+
if (staleElements.length === 0) {
|
|
72
|
+
console.log(' No stale documentation found.');
|
|
73
|
+
console.log('\nAll documentation is up to date!');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
console.log(` ${staleElements.length} element(s) need updating`);
|
|
77
|
+
console.log('');
|
|
78
|
+
// Set up LLM client for regeneration
|
|
79
|
+
const config = loadConfig(options.config);
|
|
80
|
+
if (options.provider)
|
|
81
|
+
config.llm.provider = options.provider;
|
|
82
|
+
if (options.model)
|
|
83
|
+
config.llm.model = options.model;
|
|
84
|
+
const { ok, envKey } = checkApiKey(config.llm.provider);
|
|
85
|
+
if (!ok && envKey) {
|
|
86
|
+
console.error(`Error: ${envKey} required for ${config.llm.provider}`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
const client = createLLMClient({
|
|
90
|
+
provider: config.llm.provider,
|
|
91
|
+
model: config.llm.model,
|
|
92
|
+
});
|
|
93
|
+
// Regenerate stale elements
|
|
94
|
+
let regenerated = 0;
|
|
95
|
+
let removed = 0;
|
|
96
|
+
for (const stale of staleElements) {
|
|
97
|
+
if (stale.newSignature === '[REMOVED]') {
|
|
98
|
+
// Element was deleted from code
|
|
99
|
+
console.log(` \x1b[31m✗ removed\x1b[0m ${stale.entry.name}`);
|
|
100
|
+
removeDocSection(stale.entry.docFile, stale.entry.name);
|
|
101
|
+
removed++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
console.log(` \x1b[33m↻ updating\x1b[0m ${stale.entry.name}`);
|
|
105
|
+
// Re-scan to get the full element context
|
|
106
|
+
try {
|
|
107
|
+
const scanResult = await scanDirectory(stale.entry.sourceFile);
|
|
108
|
+
const element = scanResult.files
|
|
109
|
+
.flatMap(f => f.elements)
|
|
110
|
+
.find(el => el.name === stale.entry.name && el.kind === stale.entry.kind);
|
|
111
|
+
if (!element) {
|
|
112
|
+
console.log(` Could not find element in source — skipping`);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const doc = await generateForElement(element, client, {});
|
|
116
|
+
if (doc.error) {
|
|
117
|
+
console.log(` \x1b[31mGeneration failed: ${doc.error.slice(0, 60)}\x1b[0m`);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
// Build the new section content
|
|
121
|
+
const lang = doc.codeLanguage;
|
|
122
|
+
let section = `### \`${element.name}\`\n\n`;
|
|
123
|
+
section += `\`\`\`${lang}\n${element.signature}\n\`\`\`\n\n`;
|
|
124
|
+
if (doc.markdown)
|
|
125
|
+
section += doc.markdown + '\n\n';
|
|
126
|
+
if (doc.codeExample) {
|
|
127
|
+
section += '**Example:**\n\n';
|
|
128
|
+
section += `\`\`\`${lang}\n${doc.codeExample}\n\`\`\`\n\n`;
|
|
129
|
+
}
|
|
130
|
+
spliceDocSection(stale.entry.docFile, stale.entry.name, section);
|
|
131
|
+
regenerated++;
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
console.log(` Error: ${err instanceof Error ? err.message : err}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const unchanged = manifest.elements.length - regenerated - removed;
|
|
138
|
+
// Update manifest
|
|
139
|
+
console.log('\nUpdating manifest...');
|
|
140
|
+
const scanResult = await scanDirectory(sourcePath);
|
|
141
|
+
const allElements = scanResult.files.flatMap(f => f.elements);
|
|
142
|
+
const newEntries = buildManifestEntries(allElements, docsPath);
|
|
143
|
+
writeManifest(docsPath, newEntries);
|
|
144
|
+
console.log('\n=== Refresh Summary ===');
|
|
145
|
+
console.log(` Regenerated: ${regenerated}`);
|
|
146
|
+
console.log(` Removed: ${removed}`);
|
|
147
|
+
console.log(` Unchanged: ${unchanged}`);
|
|
148
|
+
console.log(` Total: ${manifest.elements.length}`);
|
|
149
|
+
const savings = manifest.elements.length > 0
|
|
150
|
+
? Math.round((unchanged / manifest.elements.length) * 100)
|
|
151
|
+
: 0;
|
|
152
|
+
console.log(`\n Saved ~${savings}% LLM cost vs full regeneration`);
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { analyzePRForDocs, postPRComment, postInlineComments } from '../github/pr-comments.js';
|
|
3
|
+
import { requirePro } from '../auth/index.js';
|
|
3
4
|
export const reviewPRCommand = new Command('review-pr')
|
|
4
5
|
.description('Review a GitHub PR for documentation issues')
|
|
5
6
|
.argument('<pr-url>', 'GitHub PR URL (e.g., https://github.com/owner/repo/pull/123)')
|
|
@@ -8,6 +9,10 @@ export const reviewPRCommand = new Command('review-pr')
|
|
|
8
9
|
.option('--token <token>', 'GitHub token (or use GITHUB_TOKEN env var)')
|
|
9
10
|
.action(async (prUrl, options) => {
|
|
10
11
|
try {
|
|
12
|
+
// Pro feature - requires subscription
|
|
13
|
+
if (!await requirePro('review-pr')) {
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
11
16
|
// Parse PR URL
|
|
12
17
|
const urlMatch = prUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
|
|
13
18
|
if (!urlMatch) {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { resolve, relative, isAbsolute } from 'path';
|
|
4
|
+
import { requirePro } from '../auth/index.js';
|
|
5
|
+
import { loadConfig, checkApiKey } from '../config/index.js';
|
|
6
|
+
import { createLLMClient } from '../llm/index.js';
|
|
7
|
+
import { parseFeedbackFile, parseInlineComments, applyFeedback } from '../review/index.js';
|
|
8
|
+
export const reviewCommand = new Command('review')
|
|
9
|
+
.description('Incorporate feedback into documentation')
|
|
10
|
+
.argument('[docs]', 'Documentation directory', './docs')
|
|
11
|
+
.option('--feedback <file>', 'Feedback file (markdown format)')
|
|
12
|
+
.option('--inline', 'Process <!-- FIXME: --> and <!-- TODO: --> comments in docs')
|
|
13
|
+
.option('-c, --config <file>', 'Config file path')
|
|
14
|
+
.option('--provider <name>', 'LLM provider')
|
|
15
|
+
.option('--model <name>', 'LLM model name')
|
|
16
|
+
.action(async (docs, options) => {
|
|
17
|
+
try {
|
|
18
|
+
if (!await requirePro('review')) {
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const docsPath = resolve(docs);
|
|
22
|
+
if (!existsSync(docsPath)) {
|
|
23
|
+
console.error(`Error: Docs directory not found: ${docsPath}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
if (!options.feedback && !options.inline) {
|
|
27
|
+
console.error('Error: Specify --feedback <file> or --inline');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
console.log('skrypt review');
|
|
31
|
+
console.log(` docs: ${docsPath}`);
|
|
32
|
+
if (options.feedback)
|
|
33
|
+
console.log(` feedback: ${options.feedback}`);
|
|
34
|
+
if (options.inline)
|
|
35
|
+
console.log(' mode: inline (FIXME/TODO comments)');
|
|
36
|
+
console.log('');
|
|
37
|
+
// Collect feedback items
|
|
38
|
+
const feedbackItems = [];
|
|
39
|
+
if (options.feedback) {
|
|
40
|
+
const feedbackPath = resolve(options.feedback);
|
|
41
|
+
if (!existsSync(feedbackPath)) {
|
|
42
|
+
console.error(`Error: Feedback file not found: ${feedbackPath}`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
const rawItems = parseFeedbackFile(feedbackPath);
|
|
46
|
+
// Resolve relative filePaths from feedback to absolute paths
|
|
47
|
+
for (const item of rawItems) {
|
|
48
|
+
if (!isAbsolute(item.filePath)) {
|
|
49
|
+
item.filePath = resolve(item.filePath);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
feedbackItems.push(...rawItems);
|
|
53
|
+
}
|
|
54
|
+
if (options.inline) {
|
|
55
|
+
feedbackItems.push(...parseInlineComments(docsPath));
|
|
56
|
+
}
|
|
57
|
+
if (feedbackItems.length === 0) {
|
|
58
|
+
console.log('No feedback items found.');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
console.log(`Found ${feedbackItems.length} feedback item(s)\n`);
|
|
62
|
+
// Set up LLM client
|
|
63
|
+
const config = loadConfig(options.config);
|
|
64
|
+
if (options.provider)
|
|
65
|
+
config.llm.provider = options.provider;
|
|
66
|
+
if (options.model)
|
|
67
|
+
config.llm.model = options.model;
|
|
68
|
+
const { ok, envKey } = checkApiKey(config.llm.provider);
|
|
69
|
+
if (!ok && envKey) {
|
|
70
|
+
console.error(`Error: ${envKey} required for ${config.llm.provider}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
const client = createLLMClient({
|
|
74
|
+
provider: config.llm.provider,
|
|
75
|
+
model: config.llm.model,
|
|
76
|
+
});
|
|
77
|
+
// Apply feedback
|
|
78
|
+
let applied = 0;
|
|
79
|
+
let failed = 0;
|
|
80
|
+
for (const item of feedbackItems) {
|
|
81
|
+
const location = item.lineNumber ? `:${item.lineNumber}` : '';
|
|
82
|
+
const element = item.elementName ? ` (${item.elementName})` : '';
|
|
83
|
+
const relPath = relative(docsPath, item.filePath);
|
|
84
|
+
const displayPath = relPath.startsWith('..') ? item.filePath : relPath;
|
|
85
|
+
process.stdout.write(` Processing ${displayPath}${location}${element}...`);
|
|
86
|
+
const result = await applyFeedback(item, client);
|
|
87
|
+
if (result.applied) {
|
|
88
|
+
console.log(' \x1b[32m✓\x1b[0m');
|
|
89
|
+
applied++;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
console.log(` \x1b[31m✗\x1b[0m ${result.error?.slice(0, 60) || 'unknown error'}`);
|
|
93
|
+
failed++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
console.log('\n=== Review Summary ===');
|
|
97
|
+
console.log(` Applied: ${applied}`);
|
|
98
|
+
if (failed > 0) {
|
|
99
|
+
console.log(` Failed: ${failed}`);
|
|
100
|
+
}
|
|
101
|
+
console.log(` Total: ${feedbackItems.length}`);
|
|
102
|
+
if (failed > 0) {
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
});
|