skrypt-ai 0.1.3 → 0.1.5
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/cli.js +1 -1
- package/dist/config/loader.d.ts +0 -1
- package/dist/config/loader.js +0 -3
- package/dist/generator/organizer.js +3 -1
- package/dist/generator/writer.js +6 -2
- package/dist/scanner/content-type.js +6 -5
- package/dist/scanner/index.d.ts +1 -1
- package/dist/scanner/index.js +12 -3
- package/dist/scanner/integration.test.d.ts +4 -0
- package/dist/scanner/integration.test.js +180 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { initCommand } from './commands/init.js';
|
|
|
5
5
|
import { watchCommand } from './commands/watch.js';
|
|
6
6
|
import { autofixCommand } from './commands/autofix.js';
|
|
7
7
|
import { reviewPRCommand } from './commands/review-pr.js';
|
|
8
|
-
const VERSION = '0.1.
|
|
8
|
+
const VERSION = '0.1.5';
|
|
9
9
|
async function checkForUpdates() {
|
|
10
10
|
try {
|
|
11
11
|
const res = await fetch('https://registry.npmjs.org/skrypt-ai/latest', {
|
package/dist/config/loader.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { Config, LLMProvider } from './types.js';
|
|
|
2
2
|
export declare function findConfigFile(dir: string): string | null;
|
|
3
3
|
export declare function loadConfig(configPath?: string): Config;
|
|
4
4
|
export declare function validateConfig(config: Config): string[];
|
|
5
|
-
export declare function getRequiredEnvKey(provider: LLMProvider): string | null;
|
|
6
5
|
export declare function checkApiKey(provider: LLMProvider): {
|
|
7
6
|
ok: boolean;
|
|
8
7
|
envKey: string | null;
|
package/dist/config/loader.js
CHANGED
|
@@ -69,9 +69,6 @@ export function validateConfig(config) {
|
|
|
69
69
|
}
|
|
70
70
|
return errors;
|
|
71
71
|
}
|
|
72
|
-
export function getRequiredEnvKey(provider) {
|
|
73
|
-
return PROVIDER_ENV_KEYS[provider] || null;
|
|
74
|
-
}
|
|
75
72
|
export function checkApiKey(provider) {
|
|
76
73
|
const envKey = PROVIDER_ENV_KEYS[provider];
|
|
77
74
|
// Ollama doesn't need an API key
|
|
@@ -45,7 +45,9 @@ export function organizeByTopic(docs, config = DEFAULT_TOPIC_CONFIG) {
|
|
|
45
45
|
if (!topicDocs.has(topicId)) {
|
|
46
46
|
topicDocs.set(topicId, []);
|
|
47
47
|
}
|
|
48
|
-
topicDocs.get(topicId)
|
|
48
|
+
const topicDocList = topicDocs.get(topicId);
|
|
49
|
+
if (topicDocList)
|
|
50
|
+
topicDocList.push(doc);
|
|
49
51
|
}
|
|
50
52
|
// Build topic objects
|
|
51
53
|
const topics = [];
|
package/dist/generator/writer.js
CHANGED
|
@@ -18,7 +18,9 @@ export async function writeLlmsTxt(docs, outputDir, options = {}) {
|
|
|
18
18
|
if (!byFile.has(file)) {
|
|
19
19
|
byFile.set(file, []);
|
|
20
20
|
}
|
|
21
|
-
byFile.get(file)
|
|
21
|
+
const fileDocs = byFile.get(file);
|
|
22
|
+
if (fileDocs)
|
|
23
|
+
fileDocs.push(doc);
|
|
22
24
|
}
|
|
23
25
|
// Summary section
|
|
24
26
|
content += `## Overview\n\n`;
|
|
@@ -133,7 +135,9 @@ export function groupDocsByFile(docs) {
|
|
|
133
135
|
if (!byFile.has(file)) {
|
|
134
136
|
byFile.set(file, []);
|
|
135
137
|
}
|
|
136
|
-
byFile.get(file)
|
|
138
|
+
const fileDocs = byFile.get(file);
|
|
139
|
+
if (fileDocs)
|
|
140
|
+
fileDocs.push(doc);
|
|
137
141
|
}
|
|
138
142
|
return Array.from(byFile.entries()).map(([filePath, fileDocs]) => ({
|
|
139
143
|
filePath,
|
|
@@ -103,7 +103,8 @@ export function classifyElements(elements) {
|
|
|
103
103
|
for (const element of elements) {
|
|
104
104
|
const classification = classifyElement(element);
|
|
105
105
|
const group = groups.get(classification.type);
|
|
106
|
-
group
|
|
106
|
+
if (group)
|
|
107
|
+
group.push(element);
|
|
107
108
|
}
|
|
108
109
|
return groups;
|
|
109
110
|
}
|
|
@@ -115,7 +116,7 @@ export function getRecommendedStructure(elements) {
|
|
|
115
116
|
const sections = [];
|
|
116
117
|
const stats = { api: 0, guide: 0, tutorial: 0, overview: 0 };
|
|
117
118
|
// API Reference section
|
|
118
|
-
const apiElements = classified.get('api');
|
|
119
|
+
const apiElements = classified.get('api') ?? [];
|
|
119
120
|
if (apiElements.length > 0) {
|
|
120
121
|
sections.push({
|
|
121
122
|
name: 'API Reference',
|
|
@@ -125,7 +126,7 @@ export function getRecommendedStructure(elements) {
|
|
|
125
126
|
stats.api = apiElements.length;
|
|
126
127
|
}
|
|
127
128
|
// Guides section
|
|
128
|
-
const guideElements = classified.get('guide');
|
|
129
|
+
const guideElements = classified.get('guide') ?? [];
|
|
129
130
|
if (guideElements.length > 0) {
|
|
130
131
|
sections.push({
|
|
131
132
|
name: 'Guides',
|
|
@@ -135,7 +136,7 @@ export function getRecommendedStructure(elements) {
|
|
|
135
136
|
stats.guide = guideElements.length;
|
|
136
137
|
}
|
|
137
138
|
// Tutorials section
|
|
138
|
-
const tutorialElements = classified.get('tutorial');
|
|
139
|
+
const tutorialElements = classified.get('tutorial') ?? [];
|
|
139
140
|
if (tutorialElements.length > 0) {
|
|
140
141
|
sections.push({
|
|
141
142
|
name: 'Tutorials',
|
|
@@ -145,7 +146,7 @@ export function getRecommendedStructure(elements) {
|
|
|
145
146
|
stats.tutorial = tutorialElements.length;
|
|
146
147
|
}
|
|
147
148
|
// Overview/Other
|
|
148
|
-
const overviewElements = classified.get('overview');
|
|
149
|
+
const overviewElements = classified.get('overview') ?? [];
|
|
149
150
|
if (overviewElements.length > 0) {
|
|
150
151
|
sections.push({
|
|
151
152
|
name: 'Overview',
|
package/dist/scanner/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface ScanAllResult {
|
|
|
12
12
|
errors: string[];
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Scan a directory for all API elements
|
|
15
|
+
* Scan a directory (or single file) for all API elements
|
|
16
16
|
*/
|
|
17
17
|
export declare function scanDirectory(dir: string, options?: ScanOptions): Promise<ScanAllResult>;
|
|
18
18
|
/**
|
package/dist/scanner/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readdir } from 'fs/promises';
|
|
1
|
+
import { readdir, stat } from 'fs/promises';
|
|
2
2
|
import { join, extname } from 'path';
|
|
3
3
|
import { PythonScanner } from './python.js';
|
|
4
4
|
import { TypeScriptScanner } from './typescript.js';
|
|
@@ -89,12 +89,21 @@ async function findFiles(dir, include, exclude) {
|
|
|
89
89
|
return files;
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
92
|
-
* Scan a directory for all API elements
|
|
92
|
+
* Scan a directory (or single file) for all API elements
|
|
93
93
|
*/
|
|
94
94
|
export async function scanDirectory(dir, options = {}) {
|
|
95
95
|
const include = options.include || ['**/*.py', '**/*.ts', '**/*.js', '**/*.go', '**/*.rs'];
|
|
96
96
|
const exclude = options.exclude || ['**/node_modules/**', '**/__pycache__/**', '**/dist/**'];
|
|
97
|
-
|
|
97
|
+
// Check if input is a file or directory
|
|
98
|
+
const stats = await stat(dir);
|
|
99
|
+
let files;
|
|
100
|
+
if (stats.isFile()) {
|
|
101
|
+
// Single file - just use it if it has a scanner
|
|
102
|
+
files = getScannerForFile(dir) ? [dir] : [];
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
files = await findFiles(dir, include, exclude);
|
|
106
|
+
}
|
|
98
107
|
const results = [];
|
|
99
108
|
const allErrors = [];
|
|
100
109
|
let totalElements = 0;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for the full scanning pipeline
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
5
|
+
import { writeFileSync, mkdirSync, rmSync, existsSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { scanDirectory, scanFile } from './index.js';
|
|
8
|
+
const TEST_DIR = join(process.cwd(), 'test-fixtures');
|
|
9
|
+
describe('Scanner Integration', () => {
|
|
10
|
+
beforeAll(() => {
|
|
11
|
+
// Create test fixtures
|
|
12
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
13
|
+
// TypeScript file
|
|
14
|
+
writeFileSync(join(TEST_DIR, 'sample.ts'), `
|
|
15
|
+
/**
|
|
16
|
+
* Add two numbers
|
|
17
|
+
* @param a First number
|
|
18
|
+
* @param b Second number
|
|
19
|
+
*/
|
|
20
|
+
export function add(a: number, b: number): number {
|
|
21
|
+
return a + b;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class Calculator {
|
|
25
|
+
value: number = 0;
|
|
26
|
+
|
|
27
|
+
add(n: number): this {
|
|
28
|
+
this.value += n;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
`);
|
|
33
|
+
// Python file
|
|
34
|
+
writeFileSync(join(TEST_DIR, 'utils.py'), `
|
|
35
|
+
"""Utility functions."""
|
|
36
|
+
|
|
37
|
+
def greet(name: str) -> str:
|
|
38
|
+
"""Greet someone by name."""
|
|
39
|
+
return f"Hello, {name}!"
|
|
40
|
+
|
|
41
|
+
class Helper:
|
|
42
|
+
"""Helper class."""
|
|
43
|
+
|
|
44
|
+
def process(self, data: dict) -> dict:
|
|
45
|
+
"""Process data."""
|
|
46
|
+
return data
|
|
47
|
+
`);
|
|
48
|
+
// Go file
|
|
49
|
+
writeFileSync(join(TEST_DIR, 'handlers.go'), `
|
|
50
|
+
package handlers
|
|
51
|
+
|
|
52
|
+
// GetUser retrieves a user
|
|
53
|
+
func GetUser(id string) string {
|
|
54
|
+
return id
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type Service struct {
|
|
58
|
+
name string
|
|
59
|
+
}
|
|
60
|
+
`);
|
|
61
|
+
// Rust file
|
|
62
|
+
writeFileSync(join(TEST_DIR, 'lib.rs'), `
|
|
63
|
+
/// Configuration struct
|
|
64
|
+
pub struct Config {
|
|
65
|
+
pub name: String,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
impl Config {
|
|
69
|
+
/// Create new config
|
|
70
|
+
pub fn new() -> Self {
|
|
71
|
+
Config { name: String::new() }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Parse config from string
|
|
76
|
+
pub fn parse_config(s: &str) -> Config {
|
|
77
|
+
Config { name: s.to_string() }
|
|
78
|
+
}
|
|
79
|
+
`);
|
|
80
|
+
});
|
|
81
|
+
afterAll(() => {
|
|
82
|
+
// Cleanup test fixtures
|
|
83
|
+
if (existsSync(TEST_DIR)) {
|
|
84
|
+
rmSync(TEST_DIR, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
describe('scanDirectory', () => {
|
|
88
|
+
it('scans all supported languages', async () => {
|
|
89
|
+
const result = await scanDirectory(TEST_DIR);
|
|
90
|
+
expect(result.errors).toHaveLength(0);
|
|
91
|
+
expect(result.files.length).toBeGreaterThanOrEqual(4);
|
|
92
|
+
expect(result.totalElements).toBeGreaterThanOrEqual(8);
|
|
93
|
+
// Check we found elements from each language
|
|
94
|
+
const languages = new Set(result.files.map(f => f.language));
|
|
95
|
+
expect(languages.has('typescript')).toBe(true);
|
|
96
|
+
expect(languages.has('python')).toBe(true);
|
|
97
|
+
expect(languages.has('go')).toBe(true);
|
|
98
|
+
expect(languages.has('rust')).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
it('respects exclude patterns', async () => {
|
|
101
|
+
const result = await scanDirectory(TEST_DIR, {
|
|
102
|
+
exclude: ['**/utils.py']
|
|
103
|
+
});
|
|
104
|
+
// Should not find the utils.py file
|
|
105
|
+
const fileNames = result.files.map(f => f.filePath);
|
|
106
|
+
const hasPython = fileNames.some(f => f.includes('utils.py'));
|
|
107
|
+
expect(hasPython).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
it('respects include patterns', async () => {
|
|
110
|
+
const result = await scanDirectory(TEST_DIR, {
|
|
111
|
+
include: ['**/*.ts']
|
|
112
|
+
});
|
|
113
|
+
expect(result.files.length).toBe(1);
|
|
114
|
+
expect(result.files[0].language).toBe('typescript');
|
|
115
|
+
});
|
|
116
|
+
it('calls progress callback', async () => {
|
|
117
|
+
const progress = [];
|
|
118
|
+
await scanDirectory(TEST_DIR, {
|
|
119
|
+
onProgress: (current, total) => {
|
|
120
|
+
progress.push({ current, total });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
expect(progress.length).toBeGreaterThan(0);
|
|
124
|
+
expect(progress[0].current).toBe(1);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
describe('scanFile', () => {
|
|
128
|
+
it('scans a single TypeScript file', async () => {
|
|
129
|
+
const result = await scanFile(join(TEST_DIR, 'sample.ts'));
|
|
130
|
+
expect(result.errors).toHaveLength(0);
|
|
131
|
+
expect(result.language).toBe('typescript');
|
|
132
|
+
expect(result.elements.length).toBeGreaterThanOrEqual(2);
|
|
133
|
+
const functionNames = result.elements.map(e => e.name);
|
|
134
|
+
expect(functionNames).toContain('add');
|
|
135
|
+
expect(functionNames).toContain('Calculator');
|
|
136
|
+
});
|
|
137
|
+
it('scans a single Python file', async () => {
|
|
138
|
+
const result = await scanFile(join(TEST_DIR, 'utils.py'));
|
|
139
|
+
expect(result.errors).toHaveLength(0);
|
|
140
|
+
expect(result.language).toBe('python');
|
|
141
|
+
const names = result.elements.map(e => e.name);
|
|
142
|
+
expect(names).toContain('greet');
|
|
143
|
+
expect(names).toContain('Helper');
|
|
144
|
+
});
|
|
145
|
+
it('returns error for unsupported file type', async () => {
|
|
146
|
+
const result = await scanFile(join(TEST_DIR, 'unknown.xyz'));
|
|
147
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
148
|
+
expect(result.elements).toHaveLength(0);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
describe('single file via scanDirectory', () => {
|
|
152
|
+
it('accepts a single file path', async () => {
|
|
153
|
+
const result = await scanDirectory(join(TEST_DIR, 'sample.ts'));
|
|
154
|
+
expect(result.errors).toHaveLength(0);
|
|
155
|
+
expect(result.files.length).toBe(1);
|
|
156
|
+
expect(result.files[0].language).toBe('typescript');
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
describe('element extraction', () => {
|
|
160
|
+
it('extracts function parameters', async () => {
|
|
161
|
+
const result = await scanFile(join(TEST_DIR, 'sample.ts'));
|
|
162
|
+
const addFn = result.elements.find(e => e.name === 'add');
|
|
163
|
+
expect(addFn).toBeDefined();
|
|
164
|
+
expect(addFn?.parameters).toHaveLength(2);
|
|
165
|
+
expect(addFn?.parameters[0].name).toBe('a');
|
|
166
|
+
expect(addFn?.parameters[0].type).toBe('number');
|
|
167
|
+
});
|
|
168
|
+
it('extracts docstrings', async () => {
|
|
169
|
+
const result = await scanFile(join(TEST_DIR, 'sample.ts'));
|
|
170
|
+
const addFn = result.elements.find(e => e.name === 'add');
|
|
171
|
+
expect(addFn?.docstring).toContain('Add two numbers');
|
|
172
|
+
});
|
|
173
|
+
it('extracts class methods', async () => {
|
|
174
|
+
const result = await scanFile(join(TEST_DIR, 'sample.ts'));
|
|
175
|
+
const addMethod = result.elements.find(e => e.name === 'add' && e.kind === 'method');
|
|
176
|
+
expect(addMethod).toBeDefined();
|
|
177
|
+
expect(addMethod?.parentClass).toBe('Calculator');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|