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,106 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join, extname } from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Find all markdown files in a docs directory
|
|
5
|
+
*/
|
|
6
|
+
function findMarkdownFiles(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) === '.md' || extname(entry) === '.mdx')) {
|
|
17
|
+
files.push(fullPath);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
walk(dir);
|
|
22
|
+
return files;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Extract documented elements from markdown files
|
|
26
|
+
*/
|
|
27
|
+
export function parseDocumentedElements(docsDir) {
|
|
28
|
+
const files = findMarkdownFiles(docsDir);
|
|
29
|
+
const elements = [];
|
|
30
|
+
for (const filePath of files) {
|
|
31
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
32
|
+
const lines = content.split('\n');
|
|
33
|
+
let hasCodeBlock = false;
|
|
34
|
+
let currentElement = null;
|
|
35
|
+
for (let i = 0; i < lines.length; i++) {
|
|
36
|
+
const line = lines[i];
|
|
37
|
+
if (hasCodeBlock) {
|
|
38
|
+
if (line.trim().startsWith('```')) {
|
|
39
|
+
hasCodeBlock = false;
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
// Look for headings that indicate API elements
|
|
44
|
+
// Pattern: ### `functionName` or ## `ClassName`
|
|
45
|
+
const headingMatch = line.match(/^#{2,4}\s+`([^`]+)`/);
|
|
46
|
+
if (headingMatch) {
|
|
47
|
+
// Save previous element
|
|
48
|
+
if (currentElement?.name) {
|
|
49
|
+
elements.push(currentElement);
|
|
50
|
+
}
|
|
51
|
+
const name = headingMatch[1];
|
|
52
|
+
currentElement = {
|
|
53
|
+
name,
|
|
54
|
+
kind: 'unknown',
|
|
55
|
+
filePath,
|
|
56
|
+
lineNumber: i + 1,
|
|
57
|
+
hasExample: false,
|
|
58
|
+
};
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
// Look for signature code blocks right after element heading
|
|
62
|
+
// Must check before generic code block toggle
|
|
63
|
+
if (line.match(/^```\w+/)) {
|
|
64
|
+
hasCodeBlock = true;
|
|
65
|
+
if (currentElement) {
|
|
66
|
+
currentElement.hasExample = true;
|
|
67
|
+
if (!currentElement.signature && i + 1 < lines.length) {
|
|
68
|
+
const sigLines = [];
|
|
69
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
70
|
+
if (lines[j].trim() === '```')
|
|
71
|
+
break;
|
|
72
|
+
sigLines.push(lines[j]);
|
|
73
|
+
}
|
|
74
|
+
currentElement.signature = sigLines.join('\n').trim();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
// Plain ``` (closing or code block without language)
|
|
80
|
+
if (line.trim().startsWith('```')) {
|
|
81
|
+
hasCodeBlock = true;
|
|
82
|
+
if (currentElement) {
|
|
83
|
+
currentElement.hasExample = true;
|
|
84
|
+
}
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
// Detect kind from context
|
|
88
|
+
if (currentElement && currentElement.kind === 'unknown') {
|
|
89
|
+
if (/\bmethod\b/i.test(line)) {
|
|
90
|
+
currentElement.kind = 'method';
|
|
91
|
+
}
|
|
92
|
+
else if (/\bclass\b/i.test(line)) {
|
|
93
|
+
currentElement.kind = 'class';
|
|
94
|
+
}
|
|
95
|
+
else if (/\bfunction\b/i.test(line)) {
|
|
96
|
+
currentElement.kind = 'function';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Save last element
|
|
101
|
+
if (currentElement?.name) {
|
|
102
|
+
elements.push(currentElement);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return elements;
|
|
106
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { APIElement } from '../scanner/types.js';
|
|
2
|
+
import { DocumentedElement, AuditReport } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Cross-reference code elements with documented elements to produce an audit report
|
|
5
|
+
*/
|
|
6
|
+
export declare function crossReference(codeElements: APIElement[], docElements: DocumentedElement[]): AuditReport;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a name for fuzzy matching (lowercase, strip common prefixes)
|
|
3
|
+
*/
|
|
4
|
+
function normalizeName(name) {
|
|
5
|
+
return name
|
|
6
|
+
.replace(/^(async\s+)?/, '')
|
|
7
|
+
.replace(/\(.*\)$/, '')
|
|
8
|
+
.trim()
|
|
9
|
+
.toLowerCase();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Normalize a signature for comparison
|
|
13
|
+
*/
|
|
14
|
+
function normalizeSignature(sig) {
|
|
15
|
+
return sig
|
|
16
|
+
.replace(/\s+/g, ' ')
|
|
17
|
+
.replace(/\s*:\s*/g, ': ')
|
|
18
|
+
.replace(/\s*=\s*/g, ' = ')
|
|
19
|
+
.trim();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Cross-reference code elements with documented elements to produce an audit report
|
|
23
|
+
*/
|
|
24
|
+
export function crossReference(codeElements, docElements) {
|
|
25
|
+
const docNames = new Map();
|
|
26
|
+
// Index documented elements by normalized name
|
|
27
|
+
for (const doc of docElements) {
|
|
28
|
+
const key = normalizeName(doc.name);
|
|
29
|
+
if (!docNames.has(key))
|
|
30
|
+
docNames.set(key, []);
|
|
31
|
+
docNames.get(key).push(doc);
|
|
32
|
+
}
|
|
33
|
+
const matchedCodeNames = new Set();
|
|
34
|
+
const matchedDocNames = new Set();
|
|
35
|
+
const undocumented = [];
|
|
36
|
+
const signatureMismatches = [];
|
|
37
|
+
const missingExamples = [];
|
|
38
|
+
// Find undocumented code elements and signature mismatches
|
|
39
|
+
for (const codeEl of codeElements) {
|
|
40
|
+
const key = normalizeName(codeEl.name);
|
|
41
|
+
const docMatches = docNames.get(key);
|
|
42
|
+
if (!docMatches || docMatches.length === 0) {
|
|
43
|
+
undocumented.push(codeEl);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
matchedCodeNames.add(key);
|
|
47
|
+
for (const docEl of docMatches) {
|
|
48
|
+
matchedDocNames.add(normalizeName(docEl.name));
|
|
49
|
+
// Check for signature mismatch
|
|
50
|
+
if (docEl.signature && codeEl.signature) {
|
|
51
|
+
const normalizedDoc = normalizeSignature(docEl.signature);
|
|
52
|
+
const normalizedCode = normalizeSignature(codeEl.signature);
|
|
53
|
+
if (normalizedDoc !== normalizedCode) {
|
|
54
|
+
signatureMismatches.push({
|
|
55
|
+
element: codeEl,
|
|
56
|
+
docsSignature: docEl.signature,
|
|
57
|
+
codeSignature: codeEl.signature,
|
|
58
|
+
docFile: docEl.filePath,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Check for missing examples
|
|
63
|
+
if (!docEl.hasExample) {
|
|
64
|
+
missingExamples.push(docEl);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Find orphaned docs (documented but no matching code)
|
|
69
|
+
const orphaned = [];
|
|
70
|
+
for (const doc of docElements) {
|
|
71
|
+
const key = normalizeName(doc.name);
|
|
72
|
+
if (!matchedCodeNames.has(key)) {
|
|
73
|
+
orphaned.push({ name: doc.name, filePath: doc.filePath });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const documented = codeElements.length - undocumented.length;
|
|
77
|
+
const coveragePercent = codeElements.length > 0
|
|
78
|
+
? Math.round((documented / codeElements.length) * 100)
|
|
79
|
+
: 100;
|
|
80
|
+
// Staleness score: ratio of signature mismatches to documented elements
|
|
81
|
+
const stalenessScore = documented > 0
|
|
82
|
+
? Math.round((signatureMismatches.length / documented) * 100)
|
|
83
|
+
: 0;
|
|
84
|
+
return {
|
|
85
|
+
undocumented,
|
|
86
|
+
orphaned,
|
|
87
|
+
signatureMismatches,
|
|
88
|
+
missingExamples,
|
|
89
|
+
coveragePercent,
|
|
90
|
+
stalenessScore,
|
|
91
|
+
totalCodeElements: codeElements.length,
|
|
92
|
+
totalDocElements: docElements.length,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AuditReport } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Format audit report as human-readable text
|
|
4
|
+
*/
|
|
5
|
+
export declare function formatTextReport(report: AuditReport, basePath: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Format audit report as JSON
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatJsonReport(report: AuditReport): string;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { relative } from 'path';
|
|
2
|
+
/**
|
|
3
|
+
* Format audit report as human-readable text
|
|
4
|
+
*/
|
|
5
|
+
export function formatTextReport(report, basePath) {
|
|
6
|
+
const lines = [];
|
|
7
|
+
lines.push('skrypt audit\n');
|
|
8
|
+
lines.push(` Coverage: ${report.coveragePercent}% (${report.totalCodeElements - report.undocumented.length}/${report.totalCodeElements} elements documented)`);
|
|
9
|
+
if (report.stalenessScore > 0) {
|
|
10
|
+
lines.push(` Staleness: ${report.stalenessScore}% of documented elements have signature mismatches`);
|
|
11
|
+
}
|
|
12
|
+
lines.push('');
|
|
13
|
+
// Undocumented
|
|
14
|
+
if (report.undocumented.length > 0) {
|
|
15
|
+
lines.push(` UNDOCUMENTED (${report.undocumented.length})`);
|
|
16
|
+
// Group by file
|
|
17
|
+
const byFile = new Map();
|
|
18
|
+
for (const el of report.undocumented) {
|
|
19
|
+
const rel = relative(basePath, el.filePath);
|
|
20
|
+
if (!byFile.has(rel))
|
|
21
|
+
byFile.set(rel, []);
|
|
22
|
+
byFile.get(rel).push(`${el.name}()`);
|
|
23
|
+
}
|
|
24
|
+
for (const [file, names] of byFile) {
|
|
25
|
+
lines.push(` ${file} → ${names.join(', ')}`);
|
|
26
|
+
}
|
|
27
|
+
lines.push('');
|
|
28
|
+
}
|
|
29
|
+
// Signature mismatches (stale)
|
|
30
|
+
if (report.signatureMismatches.length > 0) {
|
|
31
|
+
lines.push(` STALE (${report.signatureMismatches.length})`);
|
|
32
|
+
for (const mismatch of report.signatureMismatches.slice(0, 20)) {
|
|
33
|
+
const docRel = relative(basePath, mismatch.docFile);
|
|
34
|
+
lines.push(` ${docRel} → ${mismatch.element.name}`);
|
|
35
|
+
lines.push(` docs: ${mismatch.docsSignature.slice(0, 80)}`);
|
|
36
|
+
lines.push(` code: ${mismatch.codeSignature.slice(0, 80)}`);
|
|
37
|
+
}
|
|
38
|
+
if (report.signatureMismatches.length > 20) {
|
|
39
|
+
lines.push(` ... and ${report.signatureMismatches.length - 20} more`);
|
|
40
|
+
}
|
|
41
|
+
lines.push('');
|
|
42
|
+
}
|
|
43
|
+
// Orphaned docs
|
|
44
|
+
if (report.orphaned.length > 0) {
|
|
45
|
+
lines.push(` ORPHANED (${report.orphaned.length})`);
|
|
46
|
+
for (const orphan of report.orphaned.slice(0, 20)) {
|
|
47
|
+
const rel = relative(basePath, orphan.filePath);
|
|
48
|
+
lines.push(` ${rel} — ${orphan.name} — no matching code`);
|
|
49
|
+
}
|
|
50
|
+
if (report.orphaned.length > 20) {
|
|
51
|
+
lines.push(` ... and ${report.orphaned.length - 20} more`);
|
|
52
|
+
}
|
|
53
|
+
lines.push('');
|
|
54
|
+
}
|
|
55
|
+
// Missing examples
|
|
56
|
+
if (report.missingExamples.length > 0) {
|
|
57
|
+
lines.push(` MISSING EXAMPLES (${report.missingExamples.length})`);
|
|
58
|
+
for (const el of report.missingExamples.slice(0, 20)) {
|
|
59
|
+
const rel = relative(basePath, el.filePath);
|
|
60
|
+
lines.push(` ${rel}:${el.lineNumber} — ${el.name}`);
|
|
61
|
+
}
|
|
62
|
+
if (report.missingExamples.length > 20) {
|
|
63
|
+
lines.push(` ... and ${report.missingExamples.length - 20} more`);
|
|
64
|
+
}
|
|
65
|
+
lines.push('');
|
|
66
|
+
}
|
|
67
|
+
if (report.undocumented.length === 0 && report.signatureMismatches.length === 0 &&
|
|
68
|
+
report.orphaned.length === 0 && report.missingExamples.length === 0) {
|
|
69
|
+
lines.push(' All elements documented with matching signatures and examples!');
|
|
70
|
+
}
|
|
71
|
+
return lines.join('\n');
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Format audit report as JSON
|
|
75
|
+
*/
|
|
76
|
+
export function formatJsonReport(report) {
|
|
77
|
+
return JSON.stringify({
|
|
78
|
+
coverage: {
|
|
79
|
+
percent: report.coveragePercent,
|
|
80
|
+
documented: report.totalCodeElements - report.undocumented.length,
|
|
81
|
+
total: report.totalCodeElements,
|
|
82
|
+
},
|
|
83
|
+
staleness: {
|
|
84
|
+
score: report.stalenessScore,
|
|
85
|
+
mismatches: report.signatureMismatches.length,
|
|
86
|
+
},
|
|
87
|
+
undocumented: report.undocumented.map(el => ({
|
|
88
|
+
name: el.name,
|
|
89
|
+
kind: el.kind,
|
|
90
|
+
file: el.filePath,
|
|
91
|
+
line: el.lineNumber,
|
|
92
|
+
})),
|
|
93
|
+
orphaned: report.orphaned,
|
|
94
|
+
signatureMismatches: report.signatureMismatches.map(m => ({
|
|
95
|
+
name: m.element.name,
|
|
96
|
+
docFile: m.docFile,
|
|
97
|
+
docsSignature: m.docsSignature,
|
|
98
|
+
codeSignature: m.codeSignature,
|
|
99
|
+
})),
|
|
100
|
+
missingExamples: report.missingExamples.map(el => ({
|
|
101
|
+
name: el.name,
|
|
102
|
+
file: el.filePath,
|
|
103
|
+
line: el.lineNumber,
|
|
104
|
+
})),
|
|
105
|
+
}, null, 2);
|
|
106
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { APIElement } from '../scanner/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* An element that has been documented (found in markdown)
|
|
4
|
+
*/
|
|
5
|
+
export interface DocumentedElement {
|
|
6
|
+
name: string;
|
|
7
|
+
kind: 'function' | 'class' | 'method' | 'unknown';
|
|
8
|
+
signature?: string;
|
|
9
|
+
filePath: string;
|
|
10
|
+
lineNumber: number;
|
|
11
|
+
hasExample: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A mismatch between code and docs signatures
|
|
15
|
+
*/
|
|
16
|
+
export interface SignatureMismatch {
|
|
17
|
+
element: APIElement;
|
|
18
|
+
docsSignature: string;
|
|
19
|
+
codeSignature: string;
|
|
20
|
+
docFile: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Full audit report
|
|
24
|
+
*/
|
|
25
|
+
export interface AuditReport {
|
|
26
|
+
undocumented: APIElement[];
|
|
27
|
+
orphaned: Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
filePath: string;
|
|
30
|
+
}>;
|
|
31
|
+
signatureMismatches: SignatureMismatch[];
|
|
32
|
+
missingExamples: DocumentedElement[];
|
|
33
|
+
coveragePercent: number;
|
|
34
|
+
stalenessScore: number;
|
|
35
|
+
totalCodeElements: number;
|
|
36
|
+
totalDocElements: number;
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/auth/index.js
CHANGED
package/dist/cli.js
CHANGED
|
@@ -22,6 +22,11 @@ import { testCommand } from './commands/test.js';
|
|
|
22
22
|
import { securityCommand } from './commands/security.js';
|
|
23
23
|
import { importCommand } from './commands/import.js';
|
|
24
24
|
import { healCommand } from './commands/heal.js';
|
|
25
|
+
import { auditCommand } from './commands/audit.js';
|
|
26
|
+
import { refreshCommand } from './commands/refresh.js';
|
|
27
|
+
import { reviewCommand } from './commands/review.js';
|
|
28
|
+
import { configCommand } from './commands/config.js';
|
|
29
|
+
import { handlePostAction } from './next-actions/index.js';
|
|
25
30
|
import { createRequire } from 'module';
|
|
26
31
|
process.on('uncaughtException', (err) => {
|
|
27
32
|
console.error('\x1b[31mFatal error:\x1b[0m', err.message);
|
|
@@ -70,7 +75,8 @@ program
|
|
|
70
75
|
.name('skrypt')
|
|
71
76
|
.description('AI-powered documentation generator with code examples')
|
|
72
77
|
.version(VERSION)
|
|
73
|
-
.hook('postAction', async () => {
|
|
78
|
+
.hook('postAction', async (_thisCommand, actionCommand) => {
|
|
79
|
+
await handlePostAction(actionCommand.name());
|
|
74
80
|
await checkForUpdates();
|
|
75
81
|
});
|
|
76
82
|
program.addCommand(initCommand);
|
|
@@ -97,4 +103,8 @@ program.addCommand(testCommand);
|
|
|
97
103
|
program.addCommand(securityCommand);
|
|
98
104
|
program.addCommand(importCommand);
|
|
99
105
|
program.addCommand(healCommand);
|
|
106
|
+
program.addCommand(auditCommand);
|
|
107
|
+
program.addCommand(refreshCommand);
|
|
108
|
+
program.addCommand(reviewCommand);
|
|
109
|
+
program.addCommand(configCommand);
|
|
100
110
|
program.parse();
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import { scanDirectory } from '../scanner/index.js';
|
|
5
|
+
import { parseDocumentedElements, crossReference, formatTextReport, formatJsonReport } from '../audit/index.js';
|
|
6
|
+
export const auditCommand = new Command('audit')
|
|
7
|
+
.description('Audit documentation coverage and staleness')
|
|
8
|
+
.argument('[source]', 'Source code directory to audit', '.')
|
|
9
|
+
.option('--docs <dir>', 'Documentation directory to compare against', './docs')
|
|
10
|
+
.option('--format <type>', 'Output format: text or json', 'text')
|
|
11
|
+
.option('--public-only', 'Only audit exported/public APIs')
|
|
12
|
+
.action(async (source, options) => {
|
|
13
|
+
try {
|
|
14
|
+
const sourcePath = resolve(source);
|
|
15
|
+
const docsPath = resolve(options.docs || './docs');
|
|
16
|
+
if (!existsSync(sourcePath)) {
|
|
17
|
+
console.error(`Error: Source directory not found: ${sourcePath}`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
if (!existsSync(docsPath)) {
|
|
21
|
+
console.error(`Error: Docs directory not found: ${docsPath}`);
|
|
22
|
+
console.error(' Specify with --docs <dir>');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
// Step 1: Scan source code
|
|
26
|
+
console.log('Scanning source code...');
|
|
27
|
+
const scanResult = await scanDirectory(sourcePath, {
|
|
28
|
+
onProgress: (current, total, file) => {
|
|
29
|
+
process.stdout.write(`\r [${current}/${total}] ${file.slice(-50).padStart(50)}`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
console.log('');
|
|
33
|
+
let codeElements = scanResult.files.flatMap(f => f.elements);
|
|
34
|
+
if (options.publicOnly) {
|
|
35
|
+
codeElements = codeElements.filter(el => el.isExported === true || el.isPublic === true);
|
|
36
|
+
}
|
|
37
|
+
// Step 2: Parse documented elements
|
|
38
|
+
console.log('Parsing documentation...');
|
|
39
|
+
const docElements = parseDocumentedElements(docsPath);
|
|
40
|
+
console.log(` Found ${docElements.length} documented elements\n`);
|
|
41
|
+
// Step 3: Cross-reference
|
|
42
|
+
const report = crossReference(codeElements, docElements);
|
|
43
|
+
// Step 4: Output
|
|
44
|
+
if (options.format === 'json') {
|
|
45
|
+
console.log(formatJsonReport(report));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
console.log(formatTextReport(report, sourcePath));
|
|
49
|
+
}
|
|
50
|
+
// Exit with error code if coverage is below threshold
|
|
51
|
+
if (report.undocumented.length > 0 || report.signatureMismatches.length > 0) {
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { readPreferences, writePreferences, runFirstTimeSetup } from '../next-actions/index.js';
|
|
3
|
+
export const configCommand = new Command('config')
|
|
4
|
+
.description('Configure Skrypt preferences')
|
|
5
|
+
.option('--suggestions', 'Configure next-step suggestions')
|
|
6
|
+
.option('--suggestions-on', 'Enable next-step suggestions')
|
|
7
|
+
.option('--suggestions-off', 'Disable next-step suggestions')
|
|
8
|
+
.action(async (options) => {
|
|
9
|
+
// Reject conflicting flags
|
|
10
|
+
const flagCount = [options.suggestions, options.suggestionsOn, options.suggestionsOff].filter(Boolean).length;
|
|
11
|
+
if (flagCount > 1) {
|
|
12
|
+
console.error('Error: Use only one of --suggestions, --suggestions-on, --suggestions-off');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
if (options.suggestionsOn) {
|
|
16
|
+
const prefs = readPreferences();
|
|
17
|
+
if (prefs) {
|
|
18
|
+
prefs.enabled = true;
|
|
19
|
+
writePreferences(prefs);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
writePreferences({
|
|
23
|
+
enabled: true,
|
|
24
|
+
categories: { workflow: true, quality: true, cicd: true, advanced: true },
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
console.log(' Next-step suggestions enabled.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (options.suggestionsOff) {
|
|
31
|
+
const prefs = readPreferences();
|
|
32
|
+
if (prefs) {
|
|
33
|
+
prefs.enabled = false;
|
|
34
|
+
writePreferences(prefs);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
writePreferences({
|
|
38
|
+
enabled: false,
|
|
39
|
+
categories: { workflow: false, quality: false, cicd: false, advanced: false },
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
console.log(' Next-step suggestions disabled.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (options.suggestions) {
|
|
46
|
+
await runFirstTimeSetup();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// No flags — show current config
|
|
50
|
+
const prefs = readPreferences();
|
|
51
|
+
console.log('skrypt config');
|
|
52
|
+
console.log('');
|
|
53
|
+
if (!prefs) {
|
|
54
|
+
console.log(' No preferences set yet. Run any command to configure.');
|
|
55
|
+
console.log('');
|
|
56
|
+
console.log(' Options:');
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.log(' Suggestions: ' + (prefs.enabled ? '\x1b[32menabled\x1b[0m' : '\x1b[90mdisabled\x1b[0m'));
|
|
60
|
+
if (prefs.enabled) {
|
|
61
|
+
console.log(' Categories:');
|
|
62
|
+
console.log(` workflow: ${prefs.categories.workflow ? '\x1b[32mon\x1b[0m' : '\x1b[90moff\x1b[0m'}`);
|
|
63
|
+
console.log(` quality: ${prefs.categories.quality ? '\x1b[32mon\x1b[0m' : '\x1b[90moff\x1b[0m'}`);
|
|
64
|
+
console.log(` cicd: ${prefs.categories.cicd ? '\x1b[32mon\x1b[0m' : '\x1b[90moff\x1b[0m'}`);
|
|
65
|
+
console.log(` advanced: ${prefs.categories.advanced ? '\x1b[32mon\x1b[0m' : '\x1b[90moff\x1b[0m'}`);
|
|
66
|
+
}
|
|
67
|
+
console.log('');
|
|
68
|
+
console.log(' Options:');
|
|
69
|
+
}
|
|
70
|
+
console.log(' --suggestions Reconfigure suggestion categories');
|
|
71
|
+
console.log(' --suggestions-on Enable suggestions');
|
|
72
|
+
console.log(' --suggestions-off Disable suggestions');
|
|
73
|
+
});
|
package/dist/commands/cron.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
3
3
|
import { resolve, join } from 'path';
|
|
4
|
+
import { requirePro } from '../auth/index.js';
|
|
4
5
|
const CRON_WORKFLOW = `name: Skrypt Auto-Update Docs
|
|
5
6
|
|
|
6
7
|
on:
|
|
@@ -54,6 +55,9 @@ export const cronCommand = new Command('cron')
|
|
|
54
55
|
.option('-s, --schedule <cron>', 'Cron schedule (default: daily at 2am UTC)', '0 2 * * *')
|
|
55
56
|
.option('-f, --force', 'Overwrite existing workflow')
|
|
56
57
|
.action(async (repoPath, options) => {
|
|
58
|
+
if (!await requirePro('cron')) {
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
57
61
|
try {
|
|
58
62
|
const resolvedPath = resolve(repoPath);
|
|
59
63
|
const workflowDir = join(resolvedPath, '.github', 'workflows');
|
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { SourceEntry } from '../config/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parse source arguments with optional labels.
|
|
5
|
+
* e.g. "./api:API" → { path: "./api", label: "API" }
|
|
6
|
+
* e.g. "./src" → { path: "./src" }
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseSourceArgs(args: string[]): SourceEntry[];
|
|
2
9
|
export declare const generateCommand: Command;
|