skrypt-ai 0.1.3 → 0.1.4

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 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.3';
8
+ const VERSION = '0.1.4';
9
9
  async function checkForUpdates() {
10
10
  try {
11
11
  const res = await fetch('https://registry.npmjs.org/skrypt-ai/latest', {
@@ -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;
@@ -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).push(doc);
48
+ const topicDocList = topicDocs.get(topicId);
49
+ if (topicDocList)
50
+ topicDocList.push(doc);
49
51
  }
50
52
  // Build topic objects
51
53
  const topics = [];
@@ -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).push(doc);
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).push(doc);
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,
@@ -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
  /**
@@ -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
- const files = await findFiles(dir, include, exclude);
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,4 @@
1
+ /**
2
+ * Integration tests for the full scanning pipeline
3
+ */
4
+ export {};
@@ -0,0 +1,181 @@
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 allElements = result.files.flatMap(f => f.elements);
95
+ const languages = new Set(result.files.map(f => f.language));
96
+ expect(languages.has('typescript')).toBe(true);
97
+ expect(languages.has('python')).toBe(true);
98
+ expect(languages.has('go')).toBe(true);
99
+ expect(languages.has('rust')).toBe(true);
100
+ });
101
+ it('respects exclude patterns', async () => {
102
+ const result = await scanDirectory(TEST_DIR, {
103
+ exclude: ['**/utils.py']
104
+ });
105
+ // Should not find the utils.py file
106
+ const fileNames = result.files.map(f => f.filePath);
107
+ const hasPython = fileNames.some(f => f.includes('utils.py'));
108
+ expect(hasPython).toBe(false);
109
+ });
110
+ it('respects include patterns', async () => {
111
+ const result = await scanDirectory(TEST_DIR, {
112
+ include: ['**/*.ts']
113
+ });
114
+ expect(result.files.length).toBe(1);
115
+ expect(result.files[0].language).toBe('typescript');
116
+ });
117
+ it('calls progress callback', async () => {
118
+ const progress = [];
119
+ await scanDirectory(TEST_DIR, {
120
+ onProgress: (current, total) => {
121
+ progress.push({ current, total });
122
+ }
123
+ });
124
+ expect(progress.length).toBeGreaterThan(0);
125
+ expect(progress[0].current).toBe(1);
126
+ });
127
+ });
128
+ describe('scanFile', () => {
129
+ it('scans a single TypeScript file', async () => {
130
+ const result = await scanFile(join(TEST_DIR, 'sample.ts'));
131
+ expect(result.errors).toHaveLength(0);
132
+ expect(result.language).toBe('typescript');
133
+ expect(result.elements.length).toBeGreaterThanOrEqual(2);
134
+ const functionNames = result.elements.map(e => e.name);
135
+ expect(functionNames).toContain('add');
136
+ expect(functionNames).toContain('Calculator');
137
+ });
138
+ it('scans a single Python file', async () => {
139
+ const result = await scanFile(join(TEST_DIR, 'utils.py'));
140
+ expect(result.errors).toHaveLength(0);
141
+ expect(result.language).toBe('python');
142
+ const names = result.elements.map(e => e.name);
143
+ expect(names).toContain('greet');
144
+ expect(names).toContain('Helper');
145
+ });
146
+ it('returns error for unsupported file type', async () => {
147
+ const result = await scanFile(join(TEST_DIR, 'unknown.xyz'));
148
+ expect(result.errors.length).toBeGreaterThan(0);
149
+ expect(result.elements).toHaveLength(0);
150
+ });
151
+ });
152
+ describe('single file via scanDirectory', () => {
153
+ it('accepts a single file path', async () => {
154
+ const result = await scanDirectory(join(TEST_DIR, 'sample.ts'));
155
+ expect(result.errors).toHaveLength(0);
156
+ expect(result.files.length).toBe(1);
157
+ expect(result.files[0].language).toBe('typescript');
158
+ });
159
+ });
160
+ describe('element extraction', () => {
161
+ it('extracts function parameters', async () => {
162
+ const result = await scanFile(join(TEST_DIR, 'sample.ts'));
163
+ const addFn = result.elements.find(e => e.name === 'add');
164
+ expect(addFn).toBeDefined();
165
+ expect(addFn?.parameters).toHaveLength(2);
166
+ expect(addFn?.parameters[0].name).toBe('a');
167
+ expect(addFn?.parameters[0].type).toBe('number');
168
+ });
169
+ it('extracts docstrings', async () => {
170
+ const result = await scanFile(join(TEST_DIR, 'sample.ts'));
171
+ const addFn = result.elements.find(e => e.name === 'add');
172
+ expect(addFn?.docstring).toContain('Add two numbers');
173
+ });
174
+ it('extracts class methods', async () => {
175
+ const result = await scanFile(join(TEST_DIR, 'sample.ts'));
176
+ const addMethod = result.elements.find(e => e.name === 'add' && e.kind === 'method');
177
+ expect(addMethod).toBeDefined();
178
+ expect(addMethod?.parentClass).toBe('Calculator');
179
+ });
180
+ });
181
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skrypt-ai",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "AI-powered documentation generator with code examples",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",