skrypt-ai 0.1.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/LICENSE +21 -0
- package/README.md +200 -0
- package/dist/autofix/index.d.ts +46 -0
- package/dist/autofix/index.js +240 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +40 -0
- package/dist/commands/autofix.d.ts +2 -0
- package/dist/commands/autofix.js +143 -0
- package/dist/commands/generate.d.ts +2 -0
- package/dist/commands/generate.js +320 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +56 -0
- package/dist/commands/review-pr.d.ts +2 -0
- package/dist/commands/review-pr.js +117 -0
- package/dist/commands/watch.d.ts +2 -0
- package/dist/commands/watch.js +142 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +2 -0
- package/dist/config/loader.d.ts +9 -0
- package/dist/config/loader.js +82 -0
- package/dist/config/types.d.ts +24 -0
- package/dist/config/types.js +34 -0
- package/dist/generator/generator.d.ts +15 -0
- package/dist/generator/generator.js +144 -0
- package/dist/generator/index.d.ts +4 -0
- package/dist/generator/index.js +4 -0
- package/dist/generator/organizer.d.ts +29 -0
- package/dist/generator/organizer.js +222 -0
- package/dist/generator/types.d.ts +83 -0
- package/dist/generator/types.js +1 -0
- package/dist/generator/writer.d.ts +28 -0
- package/dist/generator/writer.js +320 -0
- package/dist/github/pr-comments.d.ts +40 -0
- package/dist/github/pr-comments.js +308 -0
- package/dist/llm/anthropic-client.d.ts +16 -0
- package/dist/llm/anthropic-client.js +92 -0
- package/dist/llm/index.d.ts +53 -0
- package/dist/llm/index.js +400 -0
- package/dist/llm/llm.manual-test.d.ts +1 -0
- package/dist/llm/llm.manual-test.js +112 -0
- package/dist/llm/llm.mock-test.d.ts +4 -0
- package/dist/llm/llm.mock-test.js +79 -0
- package/dist/llm/openai-client.d.ts +17 -0
- package/dist/llm/openai-client.js +90 -0
- package/dist/llm/types.d.ts +60 -0
- package/dist/llm/types.js +20 -0
- package/dist/scanner/content-type.d.ts +39 -0
- package/dist/scanner/content-type.js +194 -0
- package/dist/scanner/content-type.test.d.ts +1 -0
- package/dist/scanner/content-type.test.js +231 -0
- package/dist/scanner/go.d.ts +20 -0
- package/dist/scanner/go.js +269 -0
- package/dist/scanner/index.d.ts +21 -0
- package/dist/scanner/index.js +137 -0
- package/dist/scanner/python.d.ts +6 -0
- package/dist/scanner/python.js +57 -0
- package/dist/scanner/python_parser.py +230 -0
- package/dist/scanner/rust.d.ts +23 -0
- package/dist/scanner/rust.js +304 -0
- package/dist/scanner/scanner.test.d.ts +1 -0
- package/dist/scanner/scanner.test.js +210 -0
- package/dist/scanner/types.d.ts +50 -0
- package/dist/scanner/types.js +1 -0
- package/dist/scanner/typescript.d.ts +34 -0
- package/dist/scanner/typescript.js +327 -0
- package/dist/scanner/typescript.manual-test.d.ts +1 -0
- package/dist/scanner/typescript.manual-test.js +112 -0
- package/dist/template/docs.json +32 -0
- package/dist/template/mdx-components.tsx +62 -0
- package/dist/template/next-env.d.ts +6 -0
- package/dist/template/next.config.mjs +17 -0
- package/dist/template/package.json +39 -0
- package/dist/template/postcss.config.mjs +5 -0
- package/dist/template/public/search-index.json +1 -0
- package/dist/template/scripts/build-search-index.mjs +120 -0
- package/dist/template/src/app/api/mock/[...path]/route.ts +224 -0
- package/dist/template/src/app/api/openapi/route.ts +48 -0
- package/dist/template/src/app/api/rate-limit/route.ts +84 -0
- package/dist/template/src/app/docs/[...slug]/page.tsx +81 -0
- package/dist/template/src/app/docs/layout.tsx +9 -0
- package/dist/template/src/app/docs/page.mdx +67 -0
- package/dist/template/src/app/error.tsx +63 -0
- package/dist/template/src/app/layout.tsx +71 -0
- package/dist/template/src/app/page.tsx +18 -0
- package/dist/template/src/app/reference/route.ts +36 -0
- package/dist/template/src/app/robots.ts +14 -0
- package/dist/template/src/app/sitemap.ts +64 -0
- package/dist/template/src/components/breadcrumbs.tsx +41 -0
- package/dist/template/src/components/copy-button.tsx +29 -0
- package/dist/template/src/components/docs-layout.tsx +35 -0
- package/dist/template/src/components/edit-link.tsx +39 -0
- package/dist/template/src/components/feedback.tsx +52 -0
- package/dist/template/src/components/header.tsx +66 -0
- package/dist/template/src/components/mdx/accordion.tsx +48 -0
- package/dist/template/src/components/mdx/api-badge.tsx +57 -0
- package/dist/template/src/components/mdx/callout.tsx +111 -0
- package/dist/template/src/components/mdx/card.tsx +62 -0
- package/dist/template/src/components/mdx/changelog.tsx +57 -0
- package/dist/template/src/components/mdx/code-block.tsx +42 -0
- package/dist/template/src/components/mdx/code-group.tsx +125 -0
- package/dist/template/src/components/mdx/code-playground.tsx +322 -0
- package/dist/template/src/components/mdx/go-playground.tsx +235 -0
- package/dist/template/src/components/mdx/heading.tsx +37 -0
- package/dist/template/src/components/mdx/highlighted-code.tsx +89 -0
- package/dist/template/src/components/mdx/index.tsx +15 -0
- package/dist/template/src/components/mdx/param-table.tsx +71 -0
- package/dist/template/src/components/mdx/python-playground.tsx +293 -0
- package/dist/template/src/components/mdx/steps.tsx +43 -0
- package/dist/template/src/components/mdx/tabs.tsx +81 -0
- package/dist/template/src/components/rate-limit-display.tsx +183 -0
- package/dist/template/src/components/search-dialog.tsx +178 -0
- package/dist/template/src/components/sidebar.tsx +129 -0
- package/dist/template/src/components/syntax-theme-selector.tsx +50 -0
- package/dist/template/src/components/table-of-contents.tsx +84 -0
- package/dist/template/src/components/theme-toggle.tsx +46 -0
- package/dist/template/src/components/version-selector.tsx +61 -0
- package/dist/template/src/contexts/syntax-theme.tsx +52 -0
- package/dist/template/src/lib/highlight.ts +83 -0
- package/dist/template/src/lib/search-types.ts +37 -0
- package/dist/template/src/lib/search.ts +125 -0
- package/dist/template/src/lib/utils.ts +6 -0
- package/dist/template/src/styles/globals.css +152 -0
- package/dist/template/tsconfig.json +25 -0
- package/dist/template/tsconfig.tsbuildinfo +1 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 autodocs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# skrypt
|
|
2
|
+
|
|
3
|
+
AI-powered documentation generator with code examples.
|
|
4
|
+
|
|
5
|
+
Scans your codebase, extracts API signatures, and generates beautiful documentation with working code examples.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Multi-language scanning**: TypeScript, Python, Go, Rust
|
|
10
|
+
- **AI-generated docs**: Clear descriptions, parameters, return types
|
|
11
|
+
- **Code examples**: Working examples in TypeScript and Python
|
|
12
|
+
- **Topic organization**: Group elements by concept, not file
|
|
13
|
+
- **MDX output**: Works with any doc platform
|
|
14
|
+
- **Doc site template**: Beautiful, searchable documentation site
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g skrypt
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### 1. Initialize a documentation site
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
skrypt init my-docs
|
|
28
|
+
cd my-docs
|
|
29
|
+
npm install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. Generate API documentation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
skrypt generate ../my-project/src -o ./content/docs
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 3. Start the dev server
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm run dev
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Open http://localhost:3000 to see your docs.
|
|
45
|
+
|
|
46
|
+
## CLI Commands
|
|
47
|
+
|
|
48
|
+
### `skrypt init [directory]`
|
|
49
|
+
|
|
50
|
+
Initialize a new documentation site.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
skrypt init my-docs --name "My API Docs"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Options:
|
|
57
|
+
- `--name <name>` - Project name (default: "my-docs")
|
|
58
|
+
|
|
59
|
+
### `skrypt generate <source>`
|
|
60
|
+
|
|
61
|
+
Generate documentation from source code.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
skrypt generate ./src -o ./docs
|
|
65
|
+
|
|
66
|
+
# Multi-language examples
|
|
67
|
+
skrypt generate ./src -o ./docs --multi-lang
|
|
68
|
+
|
|
69
|
+
# Organize by topic
|
|
70
|
+
skrypt generate ./src -o ./docs --by-topic
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Options:
|
|
74
|
+
- `-o, --output <dir>` - Output directory
|
|
75
|
+
- `-c, --config <file>` - Config file path
|
|
76
|
+
- `--provider <name>` - LLM provider (deepseek, openai, anthropic, google, ollama, openrouter)
|
|
77
|
+
- `--model <name>` - LLM model name
|
|
78
|
+
- `--multi-lang` - Generate TypeScript + Python examples
|
|
79
|
+
- `--by-topic` - Organize by topic instead of file
|
|
80
|
+
- `--dry-run` - Scan only, don't generate
|
|
81
|
+
- `--public-only` - Only document exported/public APIs
|
|
82
|
+
- `--exclude <patterns...>` - Exclude patterns (files or `name:pattern`)
|
|
83
|
+
- `--llms-txt` - Generate llms.txt for Answer Engine Optimization
|
|
84
|
+
- `--project-name <name>` - Project name for llms.txt header
|
|
85
|
+
- `--openapi <file>` - Include OpenAPI spec for API Playground
|
|
86
|
+
|
|
87
|
+
## Privacy Controls
|
|
88
|
+
|
|
89
|
+
Prevent internal code from being documented:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Only document exported APIs
|
|
93
|
+
skrypt generate ./src --public-only
|
|
94
|
+
|
|
95
|
+
# Exclude specific patterns
|
|
96
|
+
skrypt generate ./src --exclude "**/internal/**" "name:_private*"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Create `.skryptignore` in your source directory:
|
|
100
|
+
|
|
101
|
+
```gitignore
|
|
102
|
+
# Exclude test files
|
|
103
|
+
**/*.test.ts
|
|
104
|
+
**/*.spec.ts
|
|
105
|
+
|
|
106
|
+
# Exclude internal modules
|
|
107
|
+
**/internal/**
|
|
108
|
+
|
|
109
|
+
# Exclude by function name (regex supported)
|
|
110
|
+
name:_privateHelper
|
|
111
|
+
name:__internal.*
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Answer Engine Optimization (AEO)
|
|
115
|
+
|
|
116
|
+
Generate `llms.txt` for AI search engines:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
skrypt generate ./src --llms-txt --project-name "My API"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
This creates:
|
|
123
|
+
- `llms.txt` - Condensed API summary for LLMs
|
|
124
|
+
- `llms-full.md` - Full API reference in LLM-friendly format
|
|
125
|
+
|
|
126
|
+
## Configuration
|
|
127
|
+
|
|
128
|
+
Create `.skrypt.yaml` in your project:
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
version: 1
|
|
132
|
+
|
|
133
|
+
source:
|
|
134
|
+
path: ./src
|
|
135
|
+
include: ["**/*.ts", "**/*.py"]
|
|
136
|
+
exclude: ["**/node_modules/**"]
|
|
137
|
+
|
|
138
|
+
output:
|
|
139
|
+
path: ./docs
|
|
140
|
+
format: mdx
|
|
141
|
+
|
|
142
|
+
llm:
|
|
143
|
+
provider: deepseek
|
|
144
|
+
model: deepseek-v3
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Doc Site Features
|
|
148
|
+
|
|
149
|
+
The generated documentation site includes:
|
|
150
|
+
|
|
151
|
+
- **Search**: Full-text search across all docs
|
|
152
|
+
- **Syntax highlighting**: Beautiful code blocks with Shiki
|
|
153
|
+
- **MDX components**: Cards, Tabs, CodeGroup, Callouts, Accordions, Steps
|
|
154
|
+
- **Dark mode**: Automatic dark/light theme switching
|
|
155
|
+
- **Responsive**: Works on all device sizes
|
|
156
|
+
|
|
157
|
+
## MDX Components
|
|
158
|
+
|
|
159
|
+
Use these components in your MDX files:
|
|
160
|
+
|
|
161
|
+
```mdx
|
|
162
|
+
<CardGroup cols={2}>
|
|
163
|
+
<Card title="Getting Started" icon="Zap" href="/docs/quickstart">
|
|
164
|
+
Get up and running in 5 minutes
|
|
165
|
+
</Card>
|
|
166
|
+
</CardGroup>
|
|
167
|
+
|
|
168
|
+
<Info title="Note">
|
|
169
|
+
Important information here.
|
|
170
|
+
</Info>
|
|
171
|
+
|
|
172
|
+
<CodeGroup>
|
|
173
|
+
\`\`\`typescript
|
|
174
|
+
const client = new Client()
|
|
175
|
+
\`\`\`
|
|
176
|
+
|
|
177
|
+
\`\`\`python
|
|
178
|
+
client = Client()
|
|
179
|
+
\`\`\`
|
|
180
|
+
</CodeGroup>
|
|
181
|
+
|
|
182
|
+
<Steps>
|
|
183
|
+
<Step title="Install">Install the package</Step>
|
|
184
|
+
<Step title="Configure">Set up your config</Step>
|
|
185
|
+
</Steps>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Environment Variables
|
|
189
|
+
|
|
190
|
+
Set API keys for your LLM provider:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
export DEEPSEEK_API_KEY=your-key
|
|
194
|
+
export OPENAI_API_KEY=your-key
|
|
195
|
+
export ANTHROPIC_API_KEY=your-key
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
MIT
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-fix Broken Code Examples
|
|
3
|
+
*
|
|
4
|
+
* Uses LLM to automatically fix code examples that fail validation.
|
|
5
|
+
* - Free tier: 3 fix attempts per example
|
|
6
|
+
* - Pro tier: 10 fix attempts per example
|
|
7
|
+
*/
|
|
8
|
+
import { LLMClient } from '../llm/index.js';
|
|
9
|
+
export interface CodeExample {
|
|
10
|
+
code: string;
|
|
11
|
+
language: string;
|
|
12
|
+
context: string;
|
|
13
|
+
}
|
|
14
|
+
export interface FixResult {
|
|
15
|
+
success: boolean;
|
|
16
|
+
fixedCode: string;
|
|
17
|
+
iterations: number;
|
|
18
|
+
errors: string[];
|
|
19
|
+
explanation?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface AutoFixOptions {
|
|
22
|
+
maxIterations?: number;
|
|
23
|
+
validateFn?: (code: string) => Promise<ValidationResult>;
|
|
24
|
+
language?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ValidationResult {
|
|
27
|
+
valid: boolean;
|
|
28
|
+
errors: string[];
|
|
29
|
+
output?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Auto-fix a code example using LLM
|
|
33
|
+
*/
|
|
34
|
+
export declare function autoFixExample(example: CodeExample, client: LLMClient, options?: AutoFixOptions): Promise<FixResult>;
|
|
35
|
+
/**
|
|
36
|
+
* Batch auto-fix multiple examples
|
|
37
|
+
*/
|
|
38
|
+
export declare function autoFixBatch(examples: CodeExample[], client: LLMClient, options?: AutoFixOptions): Promise<Map<number, FixResult>>;
|
|
39
|
+
/**
|
|
40
|
+
* Create a TypeScript validator using tsc
|
|
41
|
+
*/
|
|
42
|
+
export declare function createTypeScriptValidator(): (code: string) => Promise<ValidationResult>;
|
|
43
|
+
/**
|
|
44
|
+
* Create a Python validator (requires python3 in PATH)
|
|
45
|
+
*/
|
|
46
|
+
export declare function createPythonValidator(): (code: string) => Promise<ValidationResult>;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-fix Broken Code Examples
|
|
3
|
+
*
|
|
4
|
+
* Uses LLM to automatically fix code examples that fail validation.
|
|
5
|
+
* - Free tier: 3 fix attempts per example
|
|
6
|
+
* - Pro tier: 10 fix attempts per example
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Default TypeScript/JavaScript validator using eval
|
|
10
|
+
*/
|
|
11
|
+
async function defaultValidator(code) {
|
|
12
|
+
try {
|
|
13
|
+
// Basic syntax check - try to parse as function
|
|
14
|
+
new Function(code);
|
|
15
|
+
return { valid: true, errors: [] };
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
19
|
+
return {
|
|
20
|
+
valid: false,
|
|
21
|
+
errors: [message],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Auto-fix a code example using LLM
|
|
27
|
+
*/
|
|
28
|
+
export async function autoFixExample(example, client, options = {}) {
|
|
29
|
+
const maxIterations = options.maxIterations ?? 3;
|
|
30
|
+
const validateFn = options.validateFn ?? defaultValidator;
|
|
31
|
+
const language = options.language ?? example.language;
|
|
32
|
+
let currentCode = example.code;
|
|
33
|
+
const allErrors = [];
|
|
34
|
+
let iterations = 0;
|
|
35
|
+
// First, validate the original code
|
|
36
|
+
const initialValidation = await validateFn(currentCode);
|
|
37
|
+
if (initialValidation.valid) {
|
|
38
|
+
return {
|
|
39
|
+
success: true,
|
|
40
|
+
fixedCode: currentCode,
|
|
41
|
+
iterations: 0,
|
|
42
|
+
errors: [],
|
|
43
|
+
explanation: 'Original code is valid, no fixes needed.',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
allErrors.push(...initialValidation.errors);
|
|
47
|
+
// Iterate until fixed or max iterations reached
|
|
48
|
+
while (iterations < maxIterations) {
|
|
49
|
+
iterations++;
|
|
50
|
+
const prompt = buildFixPrompt(currentCode, language, example.context, allErrors);
|
|
51
|
+
const response = await client.complete({
|
|
52
|
+
messages: [
|
|
53
|
+
{ role: 'system', content: 'You are a code fixer. Return only the fixed code in a code block.' },
|
|
54
|
+
{ role: 'user', content: prompt }
|
|
55
|
+
],
|
|
56
|
+
temperature: 0,
|
|
57
|
+
maxTokens: 2048
|
|
58
|
+
});
|
|
59
|
+
// Extract code from response
|
|
60
|
+
const fixedCode = extractCode(response.content, language);
|
|
61
|
+
if (!fixedCode) {
|
|
62
|
+
allErrors.push(`Iteration ${iterations}: Could not extract code from LLM response`);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// Validate the fixed code
|
|
66
|
+
const validation = await validateFn(fixedCode);
|
|
67
|
+
if (validation.valid) {
|
|
68
|
+
return {
|
|
69
|
+
success: true,
|
|
70
|
+
fixedCode,
|
|
71
|
+
iterations,
|
|
72
|
+
errors: allErrors,
|
|
73
|
+
explanation: `Fixed after ${iterations} iteration(s).`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Add new errors and continue
|
|
77
|
+
allErrors.push(`Iteration ${iterations}: ${validation.errors.join(', ')}`);
|
|
78
|
+
currentCode = fixedCode;
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
fixedCode: currentCode,
|
|
83
|
+
iterations,
|
|
84
|
+
errors: allErrors,
|
|
85
|
+
explanation: `Could not fix code after ${maxIterations} iterations.`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Build the LLM prompt for fixing code
|
|
90
|
+
*/
|
|
91
|
+
function buildFixPrompt(code, language, context, errors) {
|
|
92
|
+
return `You are a code fixer. Fix the following ${language} code example.
|
|
93
|
+
|
|
94
|
+
## Context
|
|
95
|
+
${context}
|
|
96
|
+
|
|
97
|
+
## Current Code
|
|
98
|
+
\`\`\`${language}
|
|
99
|
+
${code}
|
|
100
|
+
\`\`\`
|
|
101
|
+
|
|
102
|
+
## Errors
|
|
103
|
+
${errors.map((e, i) => `${i + 1}. ${e}`).join('\n')}
|
|
104
|
+
|
|
105
|
+
## Instructions
|
|
106
|
+
1. Analyze the errors and understand what's wrong
|
|
107
|
+
2. Fix the code to make it work correctly
|
|
108
|
+
3. Keep the fix minimal - don't change working parts
|
|
109
|
+
4. Maintain the original intent and style
|
|
110
|
+
5. Return ONLY the fixed code in a code block
|
|
111
|
+
|
|
112
|
+
## Fixed Code
|
|
113
|
+
`;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Extract code from LLM response
|
|
117
|
+
*/
|
|
118
|
+
function extractCode(response, language) {
|
|
119
|
+
// Try to find code block with language
|
|
120
|
+
const langRegex = new RegExp(`\`\`\`${language}\\n([\\s\\S]*?)\\n\`\`\``, 'i');
|
|
121
|
+
const langMatch = response.match(langRegex);
|
|
122
|
+
if (langMatch?.[1])
|
|
123
|
+
return langMatch[1].trim();
|
|
124
|
+
// Try generic code block
|
|
125
|
+
const genericMatch = response.match(/```\w*\n([\s\S]*?)\n```/);
|
|
126
|
+
if (genericMatch?.[1])
|
|
127
|
+
return genericMatch[1].trim();
|
|
128
|
+
// Try to find code without block
|
|
129
|
+
const lines = response.split('\n');
|
|
130
|
+
const codeLines = lines.filter(line => {
|
|
131
|
+
const trimmed = line.trim();
|
|
132
|
+
return trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('//');
|
|
133
|
+
});
|
|
134
|
+
return codeLines.length > 0 ? codeLines.join('\n') : null;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Batch auto-fix multiple examples
|
|
138
|
+
*/
|
|
139
|
+
export async function autoFixBatch(examples, client, options = {}) {
|
|
140
|
+
const results = new Map();
|
|
141
|
+
for (let i = 0; i < examples.length; i++) {
|
|
142
|
+
const example = examples[i];
|
|
143
|
+
if (!example)
|
|
144
|
+
continue;
|
|
145
|
+
console.log(` [${i + 1}/${examples.length}] Fixing ${example.language} example...`);
|
|
146
|
+
const result = await autoFixExample(example, client, options);
|
|
147
|
+
results.set(i, result);
|
|
148
|
+
if (result.success) {
|
|
149
|
+
console.log(` ✓ Fixed in ${result.iterations} iteration(s)`);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.log(` ✗ Could not fix after ${result.iterations} iteration(s)`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return results;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Create a TypeScript validator using tsc
|
|
159
|
+
*/
|
|
160
|
+
export function createTypeScriptValidator() {
|
|
161
|
+
return async (code) => {
|
|
162
|
+
try {
|
|
163
|
+
const ts = await import('typescript');
|
|
164
|
+
const result = ts.transpileModule(code, {
|
|
165
|
+
compilerOptions: {
|
|
166
|
+
module: ts.ModuleKind.ESNext,
|
|
167
|
+
target: ts.ScriptTarget.ES2020,
|
|
168
|
+
strict: true,
|
|
169
|
+
noEmit: true,
|
|
170
|
+
},
|
|
171
|
+
reportDiagnostics: true,
|
|
172
|
+
});
|
|
173
|
+
if (result.diagnostics && result.diagnostics.length > 0) {
|
|
174
|
+
return {
|
|
175
|
+
valid: false,
|
|
176
|
+
errors: result.diagnostics.map(d => typeof d.messageText === 'string' ? d.messageText : d.messageText.messageText),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return { valid: true, errors: [] };
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
183
|
+
return {
|
|
184
|
+
valid: false,
|
|
185
|
+
errors: [message],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Create a Python validator (requires python3 in PATH)
|
|
192
|
+
*/
|
|
193
|
+
export function createPythonValidator() {
|
|
194
|
+
return async (code) => {
|
|
195
|
+
try {
|
|
196
|
+
const { spawnSync } = await import('child_process');
|
|
197
|
+
const { writeFileSync, unlinkSync } = await import('fs');
|
|
198
|
+
const { join } = await import('path');
|
|
199
|
+
const { tmpdir } = await import('os');
|
|
200
|
+
const { randomUUID } = await import('crypto');
|
|
201
|
+
// Use crypto.randomUUID() to avoid timestamp collisions
|
|
202
|
+
const tempFile = join(tmpdir(), `autofix_${randomUUID()}.py`);
|
|
203
|
+
writeFileSync(tempFile, code);
|
|
204
|
+
try {
|
|
205
|
+
// Use spawnSync with array args to avoid command injection
|
|
206
|
+
const result = spawnSync('python3', ['-m', 'py_compile', tempFile], {
|
|
207
|
+
encoding: 'utf-8',
|
|
208
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
209
|
+
});
|
|
210
|
+
if (result.status !== 0) {
|
|
211
|
+
throw { stderr: result.stderr, message: result.stderr };
|
|
212
|
+
}
|
|
213
|
+
return { valid: true, errors: [] };
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
const errObj = err;
|
|
217
|
+
const message = errObj.stderr || errObj.message || String(err);
|
|
218
|
+
return {
|
|
219
|
+
valid: false,
|
|
220
|
+
errors: [message],
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
try {
|
|
225
|
+
unlinkSync(tempFile);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
// Ignore cleanup errors
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
234
|
+
return {
|
|
235
|
+
valid: false,
|
|
236
|
+
errors: [message],
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { generateCommand } from './commands/generate.js';
|
|
4
|
+
import { initCommand } from './commands/init.js';
|
|
5
|
+
import { watchCommand } from './commands/watch.js';
|
|
6
|
+
import { autofixCommand } from './commands/autofix.js';
|
|
7
|
+
import { reviewPRCommand } from './commands/review-pr.js';
|
|
8
|
+
const VERSION = '0.1.0';
|
|
9
|
+
async function checkForUpdates() {
|
|
10
|
+
try {
|
|
11
|
+
const res = await fetch('https://registry.npmjs.org/skrypt-ai/latest', {
|
|
12
|
+
signal: AbortSignal.timeout(2000)
|
|
13
|
+
});
|
|
14
|
+
if (res.ok) {
|
|
15
|
+
const data = await res.json();
|
|
16
|
+
const latest = data.version;
|
|
17
|
+
if (latest && latest !== VERSION) {
|
|
18
|
+
console.log(`\n Update available: ${VERSION} → ${latest}`);
|
|
19
|
+
console.log(` Run: npm install -g skrypt-ai@latest\n`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Silently fail - don't block CLI
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const program = new Command();
|
|
28
|
+
program
|
|
29
|
+
.name('skrypt')
|
|
30
|
+
.description('AI-powered documentation generator with code examples')
|
|
31
|
+
.version(VERSION)
|
|
32
|
+
.hook('postAction', async () => {
|
|
33
|
+
await checkForUpdates();
|
|
34
|
+
});
|
|
35
|
+
program.addCommand(initCommand);
|
|
36
|
+
program.addCommand(generateCommand);
|
|
37
|
+
program.addCommand(watchCommand);
|
|
38
|
+
program.addCommand(autofixCommand);
|
|
39
|
+
program.addCommand(reviewPRCommand);
|
|
40
|
+
program.parse();
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import { loadConfig, checkApiKey } from '../config/index.js';
|
|
5
|
+
import { createLLMClient } from '../llm/index.js';
|
|
6
|
+
import { autoFixExample, createTypeScriptValidator, createPythonValidator } from '../autofix/index.js';
|
|
7
|
+
export const autofixCommand = new Command('autofix')
|
|
8
|
+
.description('Auto-fix broken code examples in documentation')
|
|
9
|
+
.argument('<file>', 'Documentation file to fix (markdown/mdx)')
|
|
10
|
+
.option('-c, --config <file>', 'Config file path')
|
|
11
|
+
.option('--provider <name>', 'LLM provider')
|
|
12
|
+
.option('--model <name>', 'LLM model')
|
|
13
|
+
.option('--max-iterations <n>', 'Max fix attempts per example', '3')
|
|
14
|
+
.option('--dry-run', 'Show fixes without writing')
|
|
15
|
+
.option('--language <lang>', 'Force language for validation')
|
|
16
|
+
.action(async (file, options) => {
|
|
17
|
+
const config = loadConfig(options.config);
|
|
18
|
+
if (options.provider)
|
|
19
|
+
config.llm.provider = options.provider;
|
|
20
|
+
if (options.model)
|
|
21
|
+
config.llm.model = options.model;
|
|
22
|
+
const { ok, envKey } = checkApiKey(config.llm.provider);
|
|
23
|
+
if (!ok && envKey) {
|
|
24
|
+
console.error(`Error: ${envKey} environment variable required`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
const filePath = resolve(file);
|
|
28
|
+
console.log(`skrypt autofix`);
|
|
29
|
+
console.log(` file: ${filePath}`);
|
|
30
|
+
console.log(` provider: ${config.llm.provider}`);
|
|
31
|
+
console.log(` max iterations: ${options.maxIterations}`);
|
|
32
|
+
console.log('');
|
|
33
|
+
// Read the file
|
|
34
|
+
let content;
|
|
35
|
+
try {
|
|
36
|
+
content = readFileSync(filePath, 'utf-8');
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
const errObj = err;
|
|
40
|
+
if (errObj.code === 'ENOENT') {
|
|
41
|
+
console.error(`Error: File not found: ${filePath}`);
|
|
42
|
+
}
|
|
43
|
+
else if (errObj.code === 'EACCES') {
|
|
44
|
+
console.error(`Error: Permission denied: ${filePath}`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
48
|
+
console.error(`Error: Could not read file: ${filePath} (${message})`);
|
|
49
|
+
}
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
// Find all code blocks
|
|
53
|
+
const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
|
|
54
|
+
const codeBlocks = [];
|
|
55
|
+
let match;
|
|
56
|
+
while ((match = codeBlockRegex.exec(content)) !== null) {
|
|
57
|
+
const code = match[2];
|
|
58
|
+
if (!code)
|
|
59
|
+
continue;
|
|
60
|
+
codeBlocks.push({
|
|
61
|
+
match: match[0],
|
|
62
|
+
language: options.language || match[1] || 'javascript',
|
|
63
|
+
code,
|
|
64
|
+
index: match.index,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (codeBlocks.length === 0) {
|
|
68
|
+
console.log('No code blocks found in file.');
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
console.log(`Found ${codeBlocks.length} code block(s)`);
|
|
72
|
+
console.log('');
|
|
73
|
+
const client = createLLMClient({
|
|
74
|
+
provider: config.llm.provider,
|
|
75
|
+
model: config.llm.model,
|
|
76
|
+
baseUrl: config.llm.baseUrl,
|
|
77
|
+
});
|
|
78
|
+
const maxIterations = parseInt(options.maxIterations);
|
|
79
|
+
// Create validators
|
|
80
|
+
const validators = {
|
|
81
|
+
javascript: createTypeScriptValidator(),
|
|
82
|
+
typescript: createTypeScriptValidator(),
|
|
83
|
+
ts: createTypeScriptValidator(),
|
|
84
|
+
js: createTypeScriptValidator(),
|
|
85
|
+
python: createPythonValidator(),
|
|
86
|
+
py: createPythonValidator(),
|
|
87
|
+
};
|
|
88
|
+
let fixedCount = 0;
|
|
89
|
+
let failedCount = 0;
|
|
90
|
+
let newContent = content;
|
|
91
|
+
for (let i = 0; i < codeBlocks.length; i++) {
|
|
92
|
+
const block = codeBlocks[i];
|
|
93
|
+
if (!block)
|
|
94
|
+
continue;
|
|
95
|
+
const validator = validators[block.language.toLowerCase()];
|
|
96
|
+
if (!validator) {
|
|
97
|
+
console.log(`[${i + 1}/${codeBlocks.length}] Skipping ${block.language} (no validator)`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
console.log(`[${i + 1}/${codeBlocks.length}] Checking ${block.language} example...`);
|
|
101
|
+
// First check if it's valid
|
|
102
|
+
const initial = await validator(block.code);
|
|
103
|
+
if (initial.valid) {
|
|
104
|
+
console.log(` ✓ Already valid`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
console.log(` ✗ Invalid: ${initial.errors[0]?.slice(0, 60)}...`);
|
|
108
|
+
console.log(` → Attempting to fix...`);
|
|
109
|
+
const result = await autoFixExample({
|
|
110
|
+
code: block.code,
|
|
111
|
+
language: block.language,
|
|
112
|
+
context: `Code example from documentation file: ${file}`,
|
|
113
|
+
}, client, {
|
|
114
|
+
maxIterations,
|
|
115
|
+
validateFn: validator,
|
|
116
|
+
language: block.language,
|
|
117
|
+
});
|
|
118
|
+
if (result.success) {
|
|
119
|
+
console.log(` ✓ Fixed in ${result.iterations} iteration(s)`);
|
|
120
|
+
fixedCount++;
|
|
121
|
+
// Replace in content
|
|
122
|
+
const newBlock = '```' + block.language + '\n' + result.fixedCode + '```';
|
|
123
|
+
newContent = newContent.replace(block.match, newBlock);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
console.log(` ✗ Could not fix after ${result.iterations} iterations`);
|
|
127
|
+
failedCount++;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
console.log('');
|
|
131
|
+
console.log('=== Summary ===');
|
|
132
|
+
console.log(` Checked: ${codeBlocks.length}`);
|
|
133
|
+
console.log(` Fixed: ${fixedCount}`);
|
|
134
|
+
console.log(` Failed: ${failedCount}`);
|
|
135
|
+
if (fixedCount > 0 && !options.dryRun) {
|
|
136
|
+
writeFileSync(filePath, newContent);
|
|
137
|
+
console.log(`\nWrote fixes to ${filePath}`);
|
|
138
|
+
}
|
|
139
|
+
else if (fixedCount > 0 && options.dryRun) {
|
|
140
|
+
console.log(`\n[dry run - no changes written]`);
|
|
141
|
+
}
|
|
142
|
+
console.log('\nDone!');
|
|
143
|
+
});
|