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
package/dist/config/loader.js
CHANGED
|
@@ -61,6 +61,7 @@ function mergeConfig(defaults, overrides) {
|
|
|
61
61
|
...defaults.source,
|
|
62
62
|
...overrides.source
|
|
63
63
|
},
|
|
64
|
+
sources: overrides.sources,
|
|
64
65
|
output: {
|
|
65
66
|
...defaults.output,
|
|
66
67
|
...overrides.output
|
|
@@ -74,10 +75,25 @@ function mergeConfig(defaults, overrides) {
|
|
|
74
75
|
}
|
|
75
76
|
export function validateConfig(config) {
|
|
76
77
|
const errors = [];
|
|
77
|
-
if (config.version !== 1) {
|
|
78
|
+
if (config.version !== 1 && config.version !== 2) {
|
|
78
79
|
errors.push(`Unsupported config version: ${config.version}`);
|
|
79
80
|
}
|
|
80
|
-
if (
|
|
81
|
+
// Validate sources array if present (v2)
|
|
82
|
+
if (config.sources && config.sources.length > 0) {
|
|
83
|
+
const labels = new Set();
|
|
84
|
+
for (const entry of config.sources) {
|
|
85
|
+
if (!entry.path) {
|
|
86
|
+
errors.push('Each source entry must have a path');
|
|
87
|
+
}
|
|
88
|
+
if (entry.label) {
|
|
89
|
+
if (labels.has(entry.label)) {
|
|
90
|
+
errors.push(`Duplicate source label: "${entry.label}"`);
|
|
91
|
+
}
|
|
92
|
+
labels.add(entry.label);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else if (!config.source.path) {
|
|
81
97
|
errors.push('source.path is required');
|
|
82
98
|
}
|
|
83
99
|
if (!config.output.path) {
|
|
@@ -88,6 +104,26 @@ export function validateConfig(config) {
|
|
|
88
104
|
}
|
|
89
105
|
return errors;
|
|
90
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Resolve v1 `source` or v2 `sources` into a normalized array of SourceEntry.
|
|
109
|
+
* Falls back to the single source config if no sources array is defined.
|
|
110
|
+
*/
|
|
111
|
+
export function resolveSourceEntries(config) {
|
|
112
|
+
if (config.sources && config.sources.length > 0) {
|
|
113
|
+
return config.sources.map(s => ({
|
|
114
|
+
path: s.path,
|
|
115
|
+
label: s.label,
|
|
116
|
+
include: s.include ?? config.source.include,
|
|
117
|
+
exclude: s.exclude ?? config.source.exclude,
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
// Backwards compat: single source
|
|
121
|
+
return [{
|
|
122
|
+
path: config.source.path,
|
|
123
|
+
include: config.source.include,
|
|
124
|
+
exclude: config.source.exclude,
|
|
125
|
+
}];
|
|
126
|
+
}
|
|
91
127
|
export function checkApiKey(provider) {
|
|
92
128
|
const envKey = PROVIDER_ENV_KEYS[provider];
|
|
93
129
|
// Ollama doesn't need an API key
|
package/dist/config/types.d.ts
CHANGED
|
@@ -3,6 +3,12 @@ export interface SourceConfig {
|
|
|
3
3
|
include?: string[];
|
|
4
4
|
exclude?: string[];
|
|
5
5
|
}
|
|
6
|
+
export interface SourceEntry {
|
|
7
|
+
path: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
include?: string[];
|
|
10
|
+
exclude?: string[];
|
|
11
|
+
}
|
|
6
12
|
export type LLMProvider = 'deepseek' | 'openai' | 'anthropic' | 'google' | 'ollama' | 'openrouter';
|
|
7
13
|
export interface LLMConfig {
|
|
8
14
|
provider: LLMProvider;
|
|
@@ -14,6 +20,7 @@ export declare const DEFAULT_MODELS: Record<LLMProvider, string>;
|
|
|
14
20
|
export interface Config {
|
|
15
21
|
version: number;
|
|
16
22
|
source: SourceConfig;
|
|
23
|
+
sources?: SourceEntry[];
|
|
17
24
|
output: {
|
|
18
25
|
path: string;
|
|
19
26
|
format: 'markdown' | 'mdx';
|
|
@@ -68,7 +68,8 @@ export async function generateForElement(element, client, options, onProgress) {
|
|
|
68
68
|
report('generating');
|
|
69
69
|
try {
|
|
70
70
|
const elementContext = buildElementContext(element, options.externalContext, options.projectContext);
|
|
71
|
-
const
|
|
71
|
+
const errorContext = options.previousErrors?.get(element.name);
|
|
72
|
+
const result = await generateDocumentation(client, elementContext, { multiLanguage: useMultiLang, verify: options.verify, previousError: errorContext });
|
|
72
73
|
report('done');
|
|
73
74
|
return {
|
|
74
75
|
element,
|
|
@@ -33,6 +33,9 @@ export interface GenerationOptions {
|
|
|
33
33
|
externalContext?: Map<string, string>;
|
|
34
34
|
maxTokens?: number;
|
|
35
35
|
projectContext?: string;
|
|
36
|
+
verify?: boolean;
|
|
37
|
+
/** Error context from previous verification failures, keyed by element name */
|
|
38
|
+
previousErrors?: Map<string, string>;
|
|
36
39
|
}
|
|
37
40
|
/**
|
|
38
41
|
* Result of generating docs for a file
|
package/dist/generator/writer.js
CHANGED
|
@@ -13,36 +13,45 @@ export async function writeLlmsTxt(docs, outputDir, options = {}) {
|
|
|
13
13
|
const description = options.description || 'API documentation generated by skrypt';
|
|
14
14
|
let content = `# ${projectName}\n\n`;
|
|
15
15
|
content += `> ${description}\n\n`;
|
|
16
|
-
//
|
|
17
|
-
const
|
|
16
|
+
// Check if docs have source labels (multi-source mode)
|
|
17
|
+
const hasSourceLabels = docs.some(d => d.element.sourceLabel);
|
|
18
|
+
// Group by source label then by file
|
|
19
|
+
const bySource = new Map();
|
|
18
20
|
for (const doc of docs) {
|
|
21
|
+
const sourceKey = doc.element.sourceLabel || '_default';
|
|
22
|
+
if (!bySource.has(sourceKey))
|
|
23
|
+
bySource.set(sourceKey, new Map());
|
|
24
|
+
const sourceMap = bySource.get(sourceKey);
|
|
19
25
|
const file = basename(doc.element.filePath).replace(/\.[^.]+$/, '');
|
|
20
|
-
if (!
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const fileDocs = byFile.get(file);
|
|
24
|
-
if (fileDocs)
|
|
25
|
-
fileDocs.push(doc);
|
|
26
|
+
if (!sourceMap.has(file))
|
|
27
|
+
sourceMap.set(file, []);
|
|
28
|
+
sourceMap.get(file).push(doc);
|
|
26
29
|
}
|
|
30
|
+
const totalModules = Array.from(bySource.values()).reduce((sum, m) => sum + m.size, 0);
|
|
27
31
|
// Summary section
|
|
28
32
|
content += `## Overview\n\n`;
|
|
29
|
-
content += `This project contains ${docs.length} documented API elements across ${
|
|
33
|
+
content += `This project contains ${docs.length} documented API elements across ${totalModules} modules.\n\n`;
|
|
30
34
|
// Quick reference
|
|
31
35
|
content += `## Quick Reference\n\n`;
|
|
32
|
-
for (const [
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
for (const [sourceLabel, byFile] of bySource) {
|
|
37
|
+
if (hasSourceLabels && sourceLabel !== '_default') {
|
|
38
|
+
content += `### ${sourceLabel}\n\n`;
|
|
39
|
+
}
|
|
40
|
+
for (const [file, fileDocs] of byFile) {
|
|
41
|
+
content += `${hasSourceLabels ? '####' : '###'} ${file}\n\n`;
|
|
42
|
+
for (const doc of fileDocs) {
|
|
43
|
+
content += `- \`${doc.element.name}\`: ${doc.element.kind}`;
|
|
44
|
+
if (doc.markdown) {
|
|
45
|
+
// Extract first sentence as summary
|
|
46
|
+
const firstSentence = doc.markdown.split(/\.\s/)[0]?.slice(0, 100);
|
|
47
|
+
if (firstSentence) {
|
|
48
|
+
content += ` - ${firstSentence}`;
|
|
49
|
+
}
|
|
41
50
|
}
|
|
51
|
+
content += '\n';
|
|
42
52
|
}
|
|
43
53
|
content += '\n';
|
|
44
54
|
}
|
|
45
|
-
content += '\n';
|
|
46
55
|
}
|
|
47
56
|
// Detailed API section
|
|
48
57
|
content += `## API Details\n\n`;
|
|
@@ -134,15 +143,38 @@ async function writeIndexFile(results, outputDir, sourceDir) {
|
|
|
134
143
|
const totalElements = results.reduce((sum, r) => sum + r.docs.length, 0);
|
|
135
144
|
content += '## Summary\n\n';
|
|
136
145
|
content += `- **Total elements:** ${totalElements}\n\n`;
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
+
// Check if any docs have source labels
|
|
147
|
+
const hasLabels = results.some(r => r.docs.some(d => d.element.sourceLabel));
|
|
148
|
+
if (hasLabels) {
|
|
149
|
+
// Group by source label
|
|
150
|
+
const byLabel = new Map();
|
|
151
|
+
for (const result of results) {
|
|
152
|
+
if (result.docs.length === 0)
|
|
153
|
+
continue;
|
|
154
|
+
const label = result.docs[0]?.element.sourceLabel || 'Other';
|
|
155
|
+
if (!byLabel.has(label))
|
|
156
|
+
byLabel.set(label, []);
|
|
157
|
+
byLabel.get(label).push(result);
|
|
158
|
+
}
|
|
159
|
+
for (const [label, labelResults] of byLabel) {
|
|
160
|
+
content += `## ${label}\n\n`;
|
|
161
|
+
for (const result of labelResults) {
|
|
162
|
+
const relPath = relative(sourceDir, result.filePath);
|
|
163
|
+
const docFileName = relPath.replace(/\.[^.]+$/, '.md');
|
|
164
|
+
content += `- [${relPath}](./${docFileName}) (${result.docs.length} elements)\n`;
|
|
165
|
+
}
|
|
166
|
+
content += '\n';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
content += '## Files\n\n';
|
|
171
|
+
for (const result of results) {
|
|
172
|
+
if (result.docs.length === 0)
|
|
173
|
+
continue;
|
|
174
|
+
const relPath = relative(sourceDir, result.filePath);
|
|
175
|
+
const docFileName = relPath.replace(/\.[^.]+$/, '.md');
|
|
176
|
+
content += `- [${relPath}](./${docFileName}) (${result.docs.length} elements)\n`;
|
|
177
|
+
}
|
|
146
178
|
}
|
|
147
179
|
await writeFile(join(outputDir, 'README.md'), content, 'utf-8');
|
|
148
180
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface DiscoveredRepo {
|
|
2
|
+
name: string;
|
|
3
|
+
full_name: string;
|
|
4
|
+
clone_url: string;
|
|
5
|
+
default_branch: string;
|
|
6
|
+
private: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Discover repositories in a GitHub organization.
|
|
10
|
+
* Returns up to MAX_REPOS repos sorted by most recently pushed.
|
|
11
|
+
*/
|
|
12
|
+
export declare function discoverOrgRepos(org: string, token: string): Promise<DiscoveredRepo[]>;
|
|
13
|
+
/**
|
|
14
|
+
* Shallow clone a repository to a temporary directory.
|
|
15
|
+
* Uses spawnSync with array args to prevent shell injection.
|
|
16
|
+
*/
|
|
17
|
+
export declare function cloneRepoToTemp(repo: DiscoveredRepo, token: string): Promise<string>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
import { mkdtempSync, rmSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
const MAX_REPOS = 50;
|
|
6
|
+
/**
|
|
7
|
+
* Discover repositories in a GitHub organization.
|
|
8
|
+
* Returns up to MAX_REPOS repos sorted by most recently pushed.
|
|
9
|
+
*/
|
|
10
|
+
export async function discoverOrgRepos(org, token) {
|
|
11
|
+
const repos = [];
|
|
12
|
+
let page = 1;
|
|
13
|
+
while (repos.length < MAX_REPOS) {
|
|
14
|
+
const url = `https://api.github.com/orgs/${encodeURIComponent(org)}/repos?per_page=100&sort=pushed&page=${page}`;
|
|
15
|
+
const response = await fetch(url, {
|
|
16
|
+
headers: {
|
|
17
|
+
'Authorization': `Bearer ${token}`,
|
|
18
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
19
|
+
'User-Agent': 'Skrypt-CLI',
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
if (response.status === 404) {
|
|
24
|
+
throw new Error(`Organization "${org}" not found`);
|
|
25
|
+
}
|
|
26
|
+
if (response.status === 401 || response.status === 403) {
|
|
27
|
+
throw new Error('GitHub token does not have access to this organization');
|
|
28
|
+
}
|
|
29
|
+
throw new Error(`GitHub API error: ${response.status}`);
|
|
30
|
+
}
|
|
31
|
+
const data = await response.json();
|
|
32
|
+
if (data.length === 0)
|
|
33
|
+
break;
|
|
34
|
+
for (const repo of data) {
|
|
35
|
+
if (repos.length >= MAX_REPOS)
|
|
36
|
+
break;
|
|
37
|
+
// Skip archived and forked repos
|
|
38
|
+
if (repo.archived || repo.fork)
|
|
39
|
+
continue;
|
|
40
|
+
repos.push({
|
|
41
|
+
name: repo.name,
|
|
42
|
+
full_name: repo.full_name,
|
|
43
|
+
clone_url: repo.clone_url,
|
|
44
|
+
default_branch: repo.default_branch,
|
|
45
|
+
private: repo.private,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
page++;
|
|
49
|
+
}
|
|
50
|
+
return repos;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Shallow clone a repository to a temporary directory.
|
|
54
|
+
* Uses spawnSync with array args to prevent shell injection.
|
|
55
|
+
*/
|
|
56
|
+
export async function cloneRepoToTemp(repo, token) {
|
|
57
|
+
const tempDir = mkdtempSync(join(tmpdir(), `skrypt-${repo.name}-`));
|
|
58
|
+
// Build authenticated clone URL
|
|
59
|
+
const cloneUrl = repo.clone_url.replace('https://', `https://x-access-token:${token}@`);
|
|
60
|
+
let result;
|
|
61
|
+
try {
|
|
62
|
+
result = spawnSync('git', [
|
|
63
|
+
'clone',
|
|
64
|
+
'--depth', '1',
|
|
65
|
+
'--single-branch',
|
|
66
|
+
'--branch', repo.default_branch,
|
|
67
|
+
cloneUrl,
|
|
68
|
+
tempDir,
|
|
69
|
+
], {
|
|
70
|
+
stdio: 'pipe',
|
|
71
|
+
timeout: 60_000,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
// Clean up temp dir on spawn failure
|
|
76
|
+
try {
|
|
77
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
catch { /* ignore */ }
|
|
80
|
+
throw new Error(`Failed to clone ${repo.full_name}: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
81
|
+
}
|
|
82
|
+
if (result.status !== 0) {
|
|
83
|
+
// Clean up temp dir on clone failure
|
|
84
|
+
try {
|
|
85
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
86
|
+
}
|
|
87
|
+
catch { /* ignore */ }
|
|
88
|
+
// Sanitize stderr to avoid leaking the token
|
|
89
|
+
const stderr = (result.stderr?.toString() || '').replace(/x-access-token:[^@]+@/g, 'x-access-token:***@');
|
|
90
|
+
throw new Error(`Failed to clone ${repo.full_name}: ${stderr}`);
|
|
91
|
+
}
|
|
92
|
+
return tempDir;
|
|
93
|
+
}
|
package/dist/llm/index.d.ts
CHANGED
package/dist/llm/index.js
CHANGED
|
@@ -31,7 +31,10 @@ export function createLLMClient(config) {
|
|
|
31
31
|
*/
|
|
32
32
|
export async function generateDocumentation(client, element, options) {
|
|
33
33
|
const useMultiLang = options?.multiLanguage ?? true;
|
|
34
|
-
|
|
34
|
+
let prompt = buildDocPrompt(element, useMultiLang, options?.verify);
|
|
35
|
+
if (options?.previousError) {
|
|
36
|
+
prompt += `\n\n⚠️ IMPORTANT: The previous code example for this element FAILED verification with the following error:\n\`\`\`\n${options.previousError}\n\`\`\`\nGenerate a DIFFERENT, working code example that avoids this error. Ensure the example is self-contained and runs without external dependencies unless specified.`;
|
|
37
|
+
}
|
|
35
38
|
const response = await client.complete({
|
|
36
39
|
messages: [
|
|
37
40
|
{
|
|
@@ -127,7 +130,7 @@ Generate ONE self-contained, executable example:
|
|
|
127
130
|
---CODE---
|
|
128
131
|
[Self-contained example — no markdown fences]
|
|
129
132
|
---END---`;
|
|
130
|
-
function buildDocPrompt(element, multiLanguage = false) {
|
|
133
|
+
function buildDocPrompt(element, multiLanguage = false, verify = false) {
|
|
131
134
|
let prompt = '';
|
|
132
135
|
// Project context first — gives the LLM the "big picture" for better explanations
|
|
133
136
|
if (element.projectContext) {
|
|
@@ -174,6 +177,9 @@ function buildDocPrompt(element, multiLanguage = false) {
|
|
|
174
177
|
if (multiLanguage) {
|
|
175
178
|
prompt += `\nGenerate BOTH TypeScript AND Python self-contained examples.`;
|
|
176
179
|
}
|
|
180
|
+
if (verify) {
|
|
181
|
+
prompt += `\n\nIMPORTANT: Include \`// Output: <expected>\` comments showing expected console output for every \`console.log\` call (or \`# Output: <expected>\` for Python \`print\` calls). These will be verified by running the code.`;
|
|
182
|
+
}
|
|
177
183
|
return prompt;
|
|
178
184
|
}
|
|
179
185
|
function parseDocResponse(content, elementName) {
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
function noWorkflowFile() {
|
|
3
|
+
return !existsSync('.github/workflows/skrypt.yml') &&
|
|
4
|
+
!existsSync('.github/workflows/skrypt.yaml');
|
|
5
|
+
}
|
|
6
|
+
export const ACTION_DEFINITIONS = [
|
|
7
|
+
// ── After init ──────────────────────────────────────────────
|
|
8
|
+
{
|
|
9
|
+
id: 'gen-init',
|
|
10
|
+
afterCommands: ['init'],
|
|
11
|
+
category: 'workflow',
|
|
12
|
+
message: 'Generate docs from your source code',
|
|
13
|
+
command: 'skrypt generate ./src -o ./content/docs',
|
|
14
|
+
priority: 100,
|
|
15
|
+
},
|
|
16
|
+
// ── After generate ──────────────────────────────────────────
|
|
17
|
+
{
|
|
18
|
+
id: 'test-gen',
|
|
19
|
+
afterCommands: ['generate'],
|
|
20
|
+
category: 'workflow',
|
|
21
|
+
message: 'Verify code snippets execute correctly',
|
|
22
|
+
command: 'skrypt test ./docs',
|
|
23
|
+
priority: 100,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'qa-gen',
|
|
27
|
+
afterCommands: ['generate'],
|
|
28
|
+
category: 'quality',
|
|
29
|
+
message: 'Run quality checks on generated docs',
|
|
30
|
+
command: 'skrypt qa ./docs',
|
|
31
|
+
priority: 80,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'audit-gen',
|
|
35
|
+
afterCommands: ['generate'],
|
|
36
|
+
category: 'advanced',
|
|
37
|
+
message: 'Check documentation coverage',
|
|
38
|
+
command: 'skrypt audit ./src --docs ./docs',
|
|
39
|
+
priority: 50,
|
|
40
|
+
},
|
|
41
|
+
// ── After test ──────────────────────────────────────────────
|
|
42
|
+
{
|
|
43
|
+
id: 'audit-test',
|
|
44
|
+
afterCommands: ['test'],
|
|
45
|
+
category: 'advanced',
|
|
46
|
+
message: 'Check documentation coverage',
|
|
47
|
+
command: 'skrypt audit ./src --docs ./docs',
|
|
48
|
+
priority: 60,
|
|
49
|
+
},
|
|
50
|
+
// ── After qa ────────────────────────────────────────────────
|
|
51
|
+
{
|
|
52
|
+
id: 'heal-qa',
|
|
53
|
+
afterCommands: ['qa'],
|
|
54
|
+
category: 'quality',
|
|
55
|
+
message: 'Auto-fix QA issues',
|
|
56
|
+
command: 'skrypt heal',
|
|
57
|
+
priority: 90,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'lint-qa',
|
|
61
|
+
afterCommands: ['qa'],
|
|
62
|
+
category: 'quality',
|
|
63
|
+
message: 'Lint markdown docs',
|
|
64
|
+
command: 'skrypt lint ./docs',
|
|
65
|
+
priority: 70,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: 'security-qa',
|
|
69
|
+
afterCommands: ['qa'],
|
|
70
|
+
category: 'quality',
|
|
71
|
+
message: 'Check for security issues',
|
|
72
|
+
command: 'skrypt security ./docs',
|
|
73
|
+
priority: 60,
|
|
74
|
+
},
|
|
75
|
+
// ── After audit ─────────────────────────────────────────────
|
|
76
|
+
{
|
|
77
|
+
id: 'refresh-audit',
|
|
78
|
+
afterCommands: ['audit'],
|
|
79
|
+
category: 'advanced',
|
|
80
|
+
message: 'Update stale docs based on code changes',
|
|
81
|
+
command: 'skrypt refresh ./src --docs ./docs',
|
|
82
|
+
priority: 80,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'review-audit',
|
|
86
|
+
afterCommands: ['audit'],
|
|
87
|
+
category: 'advanced',
|
|
88
|
+
message: 'Review docs quality with feedback',
|
|
89
|
+
command: 'skrypt review ./docs',
|
|
90
|
+
priority: 60,
|
|
91
|
+
},
|
|
92
|
+
// ── After refresh / review / heal / autofix ─────────────────
|
|
93
|
+
{
|
|
94
|
+
id: 'test-after-fix',
|
|
95
|
+
afterCommands: ['refresh', 'review', 'heal', 'autofix'],
|
|
96
|
+
category: 'workflow',
|
|
97
|
+
message: 'Verify updated docs pass tests',
|
|
98
|
+
command: 'skrypt test ./docs',
|
|
99
|
+
priority: 100,
|
|
100
|
+
},
|
|
101
|
+
// ── After deploy ────────────────────────────────────────────
|
|
102
|
+
{
|
|
103
|
+
id: 'watch-deploy',
|
|
104
|
+
afterCommands: ['deploy'],
|
|
105
|
+
category: 'workflow',
|
|
106
|
+
message: 'Watch for changes in development',
|
|
107
|
+
command: 'skrypt watch',
|
|
108
|
+
priority: 50,
|
|
109
|
+
},
|
|
110
|
+
// ── After import ────────────────────────────────────────────
|
|
111
|
+
{
|
|
112
|
+
id: 'qa-import',
|
|
113
|
+
afterCommands: ['import'],
|
|
114
|
+
category: 'quality',
|
|
115
|
+
message: 'Check imported docs quality',
|
|
116
|
+
command: 'skrypt qa ./docs',
|
|
117
|
+
priority: 90,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'gen-import',
|
|
121
|
+
afterCommands: ['import'],
|
|
122
|
+
category: 'workflow',
|
|
123
|
+
message: 'Generate docs to fill gaps',
|
|
124
|
+
command: 'skrypt generate ./src -o ./content/docs',
|
|
125
|
+
priority: 70,
|
|
126
|
+
},
|
|
127
|
+
// ── After login ─────────────────────────────────────────────
|
|
128
|
+
{
|
|
129
|
+
id: 'pro-login',
|
|
130
|
+
afterCommands: ['login'],
|
|
131
|
+
category: 'workflow',
|
|
132
|
+
message: 'Unlock Pro: test, heal, autofix, refresh',
|
|
133
|
+
command: 'skrypt test ./docs',
|
|
134
|
+
priority: 100,
|
|
135
|
+
},
|
|
136
|
+
// ── After gh-action ─────────────────────────────────────────
|
|
137
|
+
{
|
|
138
|
+
id: 'push-ci',
|
|
139
|
+
afterCommands: ['gh-action'],
|
|
140
|
+
category: 'cicd',
|
|
141
|
+
message: 'Push to GitHub to trigger CI',
|
|
142
|
+
command: 'git push',
|
|
143
|
+
priority: 100,
|
|
144
|
+
},
|
|
145
|
+
// ── CI/CD setup (cross-command) ─────────────────────────────
|
|
146
|
+
{
|
|
147
|
+
id: 'ci-setup',
|
|
148
|
+
afterCommands: ['generate', 'test', 'deploy'],
|
|
149
|
+
category: 'cicd',
|
|
150
|
+
message: 'Set up GitHub Actions for CI',
|
|
151
|
+
command: 'skrypt gh-action',
|
|
152
|
+
priority: 70,
|
|
153
|
+
condition: noWorkflowFile,
|
|
154
|
+
},
|
|
155
|
+
// ── Deploy (cross-command) ──────────────────────────────────
|
|
156
|
+
{
|
|
157
|
+
id: 'deploy-docs',
|
|
158
|
+
afterCommands: ['generate', 'test', 'heal', 'review', 'refresh', 'autofix'],
|
|
159
|
+
category: 'workflow',
|
|
160
|
+
message: 'Deploy docs to skrypt.sh',
|
|
161
|
+
command: 'skrypt deploy',
|
|
162
|
+
priority: 40,
|
|
163
|
+
},
|
|
164
|
+
// ── After lint / check-links / security ─────────────────────
|
|
165
|
+
{
|
|
166
|
+
id: 'deploy-quality',
|
|
167
|
+
afterCommands: ['lint', 'check-links', 'security'],
|
|
168
|
+
category: 'workflow',
|
|
169
|
+
message: 'Deploy docs to skrypt.sh',
|
|
170
|
+
command: 'skrypt deploy',
|
|
171
|
+
priority: 40,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: 'qa-quality',
|
|
175
|
+
afterCommands: ['lint', 'check-links', 'security'],
|
|
176
|
+
category: 'quality',
|
|
177
|
+
message: 'Run full quality checks',
|
|
178
|
+
command: 'skrypt qa ./docs',
|
|
179
|
+
priority: 60,
|
|
180
|
+
},
|
|
181
|
+
// ── After llms-txt ──────────────────────────────────────────
|
|
182
|
+
{
|
|
183
|
+
id: 'deploy-llms',
|
|
184
|
+
afterCommands: ['llms-txt'],
|
|
185
|
+
category: 'workflow',
|
|
186
|
+
message: 'Deploy docs with llms.txt',
|
|
187
|
+
command: 'skrypt deploy',
|
|
188
|
+
priority: 80,
|
|
189
|
+
},
|
|
190
|
+
];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { readPreferences, writePreferences, DEFAULT_PREFERENCES } from './state.js';
|
|
2
|
+
export { getSuggestions, printSuggestions } from './suggest.js';
|
|
3
|
+
export { runFirstTimeSetup } from './setup.js';
|
|
4
|
+
export { ACTION_DEFINITIONS } from './actions.js';
|
|
5
|
+
export type { NextActionPreferences, ProjectActionState, ActionDefinition, Suggestion } from './types.js';
|
|
6
|
+
export declare function handlePostAction(commandName: string): Promise<void>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { readPreferences, markCommandCompleted } from './state.js';
|
|
2
|
+
import { getSuggestions, printSuggestions } from './suggest.js';
|
|
3
|
+
import { runFirstTimeSetup } from './setup.js';
|
|
4
|
+
export { readPreferences, writePreferences, DEFAULT_PREFERENCES } from './state.js';
|
|
5
|
+
export { getSuggestions, printSuggestions } from './suggest.js';
|
|
6
|
+
export { runFirstTimeSetup } from './setup.js';
|
|
7
|
+
export { ACTION_DEFINITIONS } from './actions.js';
|
|
8
|
+
const SKIP_COMMANDS = new Set(['config', 'mcp', 'whoami', 'version', 'logout']);
|
|
9
|
+
export async function handlePostAction(commandName) {
|
|
10
|
+
try {
|
|
11
|
+
// Only in interactive mode
|
|
12
|
+
if (!process.stdin.isTTY)
|
|
13
|
+
return;
|
|
14
|
+
// Skip meta commands
|
|
15
|
+
if (SKIP_COMMANDS.has(commandName))
|
|
16
|
+
return;
|
|
17
|
+
let prefs = readPreferences();
|
|
18
|
+
// First-time setup
|
|
19
|
+
if (!prefs) {
|
|
20
|
+
try {
|
|
21
|
+
prefs = await runFirstTimeSetup();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Non-interactive or error — skip silently
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!prefs.enabled)
|
|
29
|
+
return;
|
|
30
|
+
// Track this command
|
|
31
|
+
markCommandCompleted(commandName);
|
|
32
|
+
// Show suggestions
|
|
33
|
+
const suggestions = getSuggestions(commandName, prefs);
|
|
34
|
+
printSuggestions(suggestions);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Never crash the CLI over suggestions
|
|
38
|
+
}
|
|
39
|
+
}
|