skrypt-ai 0.1.4 → 0.1.8

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,487 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { autoFixExample, autoFixBatch, createTypeScriptValidator, createPythonValidator } from './index.js';
3
+ // Create a mock LLM client
4
+ function createMockLLMClient(responses) {
5
+ let callCount = 0;
6
+ const defaultResponse = '```typescript\nconst x = 1\nconsole.log(x)\n```';
7
+ return {
8
+ provider: 'openai',
9
+ complete: vi.fn().mockImplementation(async () => {
10
+ const content = responses?.[callCount] ?? defaultResponse;
11
+ callCount++;
12
+ return {
13
+ content,
14
+ model: 'test-model',
15
+ usage: { inputTokens: 100, outputTokens: 200, totalTokens: 300 },
16
+ finishReason: 'stop'
17
+ };
18
+ }),
19
+ isConfigured: vi.fn().mockReturnValue(true)
20
+ };
21
+ }
22
+ // Mock validator that always passes
23
+ const passingValidator = async () => ({
24
+ valid: true,
25
+ errors: []
26
+ });
27
+ // Mock validator that always fails
28
+ const failingValidator = async (code) => ({
29
+ valid: false,
30
+ errors: [`Syntax error in: ${code.slice(0, 20)}...`]
31
+ });
32
+ // Mock validator that fails N times then passes
33
+ function createCountingValidator(failTimes) {
34
+ let count = 0;
35
+ return async () => {
36
+ count++;
37
+ if (count <= failTimes) {
38
+ return { valid: false, errors: [`Failure ${count}`] };
39
+ }
40
+ return { valid: true, errors: [] };
41
+ };
42
+ }
43
+ describe('Autofix', () => {
44
+ describe('Code Block Extraction', () => {
45
+ it('should extract code from language-specific code blocks', async () => {
46
+ const client = createMockLLMClient([
47
+ '```typescript\nconst result = "fixed"\n```'
48
+ ]);
49
+ const example = {
50
+ code: 'const result = broken',
51
+ language: 'typescript',
52
+ context: 'A simple variable assignment'
53
+ };
54
+ const result = await autoFixExample(example, client, {
55
+ validateFn: createCountingValidator(1),
56
+ maxIterations: 3
57
+ });
58
+ expect(result.success).toBe(true);
59
+ expect(result.fixedCode).toContain('const result');
60
+ });
61
+ it('should extract code from generic code blocks', async () => {
62
+ const client = createMockLLMClient([
63
+ '```\nconst generic = "code"\n```'
64
+ ]);
65
+ const example = {
66
+ code: 'broken code',
67
+ language: 'typescript',
68
+ context: 'Test'
69
+ };
70
+ const result = await autoFixExample(example, client, {
71
+ validateFn: createCountingValidator(1),
72
+ maxIterations: 3
73
+ });
74
+ expect(result.success).toBe(true);
75
+ expect(result.fixedCode).toContain('generic');
76
+ });
77
+ it('should extract code without code fences', async () => {
78
+ const client = createMockLLMClient([
79
+ 'const noFences = "extracted"'
80
+ ]);
81
+ const example = {
82
+ code: 'broken',
83
+ language: 'typescript',
84
+ context: 'Test'
85
+ };
86
+ const result = await autoFixExample(example, client, {
87
+ validateFn: createCountingValidator(1),
88
+ maxIterations: 3
89
+ });
90
+ expect(result.success).toBe(true);
91
+ expect(result.fixedCode).toContain('noFences');
92
+ });
93
+ it('should handle Python code blocks', async () => {
94
+ const client = createMockLLMClient([
95
+ '```python\ndef fixed():\n return True\n```'
96
+ ]);
97
+ const example = {
98
+ code: 'def broken():\n return',
99
+ language: 'python',
100
+ context: 'A Python function'
101
+ };
102
+ const result = await autoFixExample(example, client, {
103
+ validateFn: createCountingValidator(1),
104
+ maxIterations: 3
105
+ });
106
+ expect(result.success).toBe(true);
107
+ expect(result.fixedCode).toContain('def fixed');
108
+ });
109
+ });
110
+ describe('autoFixExample', () => {
111
+ let mockClient;
112
+ beforeEach(() => {
113
+ mockClient = createMockLLMClient();
114
+ });
115
+ it('should return immediately if code is already valid', async () => {
116
+ const example = {
117
+ code: 'const x = 1',
118
+ language: 'typescript',
119
+ context: 'Valid code'
120
+ };
121
+ const result = await autoFixExample(example, mockClient, {
122
+ validateFn: passingValidator
123
+ });
124
+ expect(result.success).toBe(true);
125
+ expect(result.iterations).toBe(0);
126
+ expect(result.fixedCode).toBe('const x = 1');
127
+ expect(result.explanation).toContain('no fixes needed');
128
+ expect(mockClient.complete).not.toHaveBeenCalled();
129
+ });
130
+ it('should fix code after one iteration', async () => {
131
+ const client = createMockLLMClient([
132
+ '```typescript\nconst fixed = "value"\n```'
133
+ ]);
134
+ const example = {
135
+ code: 'const broken =',
136
+ language: 'typescript',
137
+ context: 'Variable assignment'
138
+ };
139
+ const result = await autoFixExample(example, client, {
140
+ validateFn: createCountingValidator(1),
141
+ maxIterations: 3
142
+ });
143
+ expect(result.success).toBe(true);
144
+ expect(result.iterations).toBe(1);
145
+ });
146
+ it('should retry up to maxIterations', async () => {
147
+ const example = {
148
+ code: 'broken',
149
+ language: 'typescript',
150
+ context: 'Test'
151
+ };
152
+ const result = await autoFixExample(example, mockClient, {
153
+ validateFn: failingValidator,
154
+ maxIterations: 3
155
+ });
156
+ expect(result.success).toBe(false);
157
+ expect(result.iterations).toBe(3);
158
+ expect(mockClient.complete).toHaveBeenCalledTimes(3);
159
+ });
160
+ it('should use default max iterations of 3', async () => {
161
+ const example = {
162
+ code: 'broken',
163
+ language: 'typescript',
164
+ context: 'Test'
165
+ };
166
+ const result = await autoFixExample(example, mockClient, {
167
+ validateFn: failingValidator
168
+ });
169
+ expect(result.iterations).toBe(3);
170
+ });
171
+ it('should accumulate errors across iterations', async () => {
172
+ const example = {
173
+ code: 'broken',
174
+ language: 'typescript',
175
+ context: 'Test'
176
+ };
177
+ const result = await autoFixExample(example, mockClient, {
178
+ validateFn: failingValidator,
179
+ maxIterations: 2
180
+ });
181
+ expect(result.errors.length).toBeGreaterThan(1);
182
+ });
183
+ it('should fix code after multiple iterations', async () => {
184
+ const client = createMockLLMClient([
185
+ '```typescript\nstill broken\n```',
186
+ '```typescript\nconst fixed = true\n```'
187
+ ]);
188
+ const example = {
189
+ code: 'broken',
190
+ language: 'typescript',
191
+ context: 'Test'
192
+ };
193
+ const result = await autoFixExample(example, client, {
194
+ validateFn: createCountingValidator(2),
195
+ maxIterations: 5
196
+ });
197
+ expect(result.success).toBe(true);
198
+ expect(result.iterations).toBe(2);
199
+ });
200
+ it('should handle LLM returning empty response', async () => {
201
+ const client = createMockLLMClient(['', '', '']);
202
+ const example = {
203
+ code: 'broken',
204
+ language: 'typescript',
205
+ context: 'Test'
206
+ };
207
+ const result = await autoFixExample(example, client, {
208
+ validateFn: failingValidator,
209
+ maxIterations: 3
210
+ });
211
+ expect(result.success).toBe(false);
212
+ expect(result.errors.some(e => e.includes('Could not extract code'))).toBe(true);
213
+ });
214
+ it('should use custom language option', async () => {
215
+ const client = createMockLLMClient(['```python\nprint("fixed")\n```']);
216
+ const example = {
217
+ code: 'print(broken)',
218
+ language: 'python',
219
+ context: 'Python print'
220
+ };
221
+ const result = await autoFixExample(example, client, {
222
+ validateFn: createCountingValidator(1),
223
+ language: 'python'
224
+ });
225
+ expect(result.success).toBe(true);
226
+ expect(result.fixedCode).toContain('print');
227
+ });
228
+ });
229
+ describe('TypeScript Validator', () => {
230
+ it('should validate correct TypeScript code', async () => {
231
+ const validator = createTypeScriptValidator();
232
+ const result = await validator('const x: number = 1');
233
+ expect(result.valid).toBe(true);
234
+ expect(result.errors).toHaveLength(0);
235
+ });
236
+ it('should detect TypeScript syntax errors', async () => {
237
+ const validator = createTypeScriptValidator();
238
+ const result = await validator('const x: number = "string"');
239
+ // TypeScript transpiles this, so it may or may not error
240
+ // depending on strict mode and compiler options
241
+ expect(result).toBeDefined();
242
+ });
243
+ it('should handle complex TypeScript code', async () => {
244
+ const validator = createTypeScriptValidator();
245
+ const code = `
246
+ interface User {
247
+ name: string
248
+ age: number
249
+ }
250
+
251
+ function greet(user: User): string {
252
+ return \`Hello, \${user.name}!\`
253
+ }
254
+
255
+ const result = greet({ name: "Test", age: 25 })
256
+ console.log(result)
257
+ `;
258
+ const result = await validator(code);
259
+ expect(result.valid).toBe(true);
260
+ });
261
+ it('should handle async TypeScript code', async () => {
262
+ const validator = createTypeScriptValidator();
263
+ const code = `
264
+ async function fetchData(): Promise<string> {
265
+ return "data"
266
+ }
267
+
268
+ async function main() {
269
+ try {
270
+ const data = await fetchData()
271
+ console.log(data)
272
+ } catch (e) {
273
+ console.error(e)
274
+ }
275
+ }
276
+
277
+ main()
278
+ `;
279
+ const result = await validator(code);
280
+ expect(result.valid).toBe(true);
281
+ });
282
+ });
283
+ describe('Python Validator', () => {
284
+ it('should validate correct Python code', async () => {
285
+ const validator = createPythonValidator();
286
+ const result = await validator('x = 1\nprint(x)');
287
+ expect(result.valid).toBe(true);
288
+ expect(result.errors).toHaveLength(0);
289
+ });
290
+ it('should detect Python syntax errors', async () => {
291
+ const validator = createPythonValidator();
292
+ const result = await validator('def broken(\n return x');
293
+ expect(result.valid).toBe(false);
294
+ expect(result.errors.length).toBeGreaterThan(0);
295
+ });
296
+ it('should handle Python functions', async () => {
297
+ const validator = createPythonValidator();
298
+ const code = `
299
+ def greet(name: str) -> str:
300
+ return f"Hello, {name}!"
301
+
302
+ result = greet("Test")
303
+ print(result)
304
+ `;
305
+ const result = await validator(code);
306
+ expect(result.valid).toBe(true);
307
+ });
308
+ it('should handle Python classes', async () => {
309
+ const validator = createPythonValidator();
310
+ const code = `
311
+ class Calculator:
312
+ def __init__(self, value: int = 0):
313
+ self.value = value
314
+
315
+ def add(self, x: int) -> int:
316
+ self.value += x
317
+ return self.value
318
+
319
+ calc = Calculator(10)
320
+ print(calc.add(5))
321
+ `;
322
+ const result = await validator(code);
323
+ expect(result.valid).toBe(true);
324
+ });
325
+ it('should handle async Python code', async () => {
326
+ const validator = createPythonValidator();
327
+ const code = `
328
+ import asyncio
329
+
330
+ async def fetch_data():
331
+ return "data"
332
+
333
+ async def main():
334
+ result = await fetch_data()
335
+ print(result)
336
+
337
+ asyncio.run(main())
338
+ `;
339
+ const result = await validator(code);
340
+ expect(result.valid).toBe(true);
341
+ });
342
+ it('should detect indentation errors', async () => {
343
+ const validator = createPythonValidator();
344
+ const code = `
345
+ def broken():
346
+ return "wrong indent"
347
+ `;
348
+ const result = await validator(code);
349
+ expect(result.valid).toBe(false);
350
+ });
351
+ });
352
+ describe('autoFixBatch', () => {
353
+ it('should fix multiple examples', async () => {
354
+ const client = createMockLLMClient([
355
+ '```typescript\nconst a = 1\n```',
356
+ '```typescript\nconst b = 2\n```'
357
+ ]);
358
+ const examples = [
359
+ { code: 'broken1', language: 'typescript', context: 'First' },
360
+ { code: 'broken2', language: 'typescript', context: 'Second' }
361
+ ];
362
+ // Suppress console.log during test
363
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
364
+ const results = await autoFixBatch(examples, client, {
365
+ validateFn: createCountingValidator(1)
366
+ });
367
+ consoleSpy.mockRestore();
368
+ expect(results.size).toBe(2);
369
+ expect(results.get(0)?.success).toBe(true);
370
+ expect(results.get(1)?.success).toBe(true);
371
+ });
372
+ it('should handle partial failures', async () => {
373
+ let callCount = 0;
374
+ const client = {
375
+ provider: 'openai',
376
+ complete: vi.fn().mockImplementation(async () => {
377
+ callCount++;
378
+ return {
379
+ content: callCount === 1 ? '```typescript\nfixed\n```' : 'no code here',
380
+ model: 'test',
381
+ usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
382
+ finishReason: 'stop'
383
+ };
384
+ }),
385
+ isConfigured: () => true
386
+ };
387
+ const examples = [
388
+ { code: 'first', language: 'typescript', context: 'First' },
389
+ { code: 'second', language: 'typescript', context: 'Second' }
390
+ ];
391
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
392
+ const results = await autoFixBatch(examples, client, {
393
+ validateFn: createCountingValidator(1),
394
+ maxIterations: 1
395
+ });
396
+ consoleSpy.mockRestore();
397
+ expect(results.size).toBe(2);
398
+ expect(results.get(0)?.success).toBe(true);
399
+ // Second one should fail because it can't extract code
400
+ });
401
+ it('should return Map with correct indices', async () => {
402
+ const client = createMockLLMClient();
403
+ const examples = [
404
+ { code: 'a', language: 'typescript', context: 'A' },
405
+ { code: 'b', language: 'typescript', context: 'B' },
406
+ { code: 'c', language: 'typescript', context: 'C' }
407
+ ];
408
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
409
+ const results = await autoFixBatch(examples, client, {
410
+ validateFn: passingValidator
411
+ });
412
+ consoleSpy.mockRestore();
413
+ expect(results.has(0)).toBe(true);
414
+ expect(results.has(1)).toBe(true);
415
+ expect(results.has(2)).toBe(true);
416
+ });
417
+ it('should handle empty examples array', async () => {
418
+ const client = createMockLLMClient();
419
+ const results = await autoFixBatch([], client);
420
+ expect(results.size).toBe(0);
421
+ expect(client.complete).not.toHaveBeenCalled();
422
+ });
423
+ });
424
+ describe('Fix Iteration Logic', () => {
425
+ it('should update currentCode between iterations', async () => {
426
+ const responses = [
427
+ '```typescript\nconst step1 = "first"\n```',
428
+ '```typescript\nconst step2 = "second"\n```',
429
+ '```typescript\nconst step3 = "final"\n```'
430
+ ];
431
+ const client = createMockLLMClient(responses);
432
+ const example = {
433
+ code: 'initial broken',
434
+ language: 'typescript',
435
+ context: 'Test progression'
436
+ };
437
+ const result = await autoFixExample(example, client, {
438
+ validateFn: createCountingValidator(3),
439
+ maxIterations: 5
440
+ });
441
+ expect(result.success).toBe(true);
442
+ expect(result.iterations).toBe(3);
443
+ expect(result.fixedCode).toContain('step3');
444
+ });
445
+ it('should include error context in fix prompt', async () => {
446
+ const client = createMockLLMClient(['```typescript\nfixed\n```']);
447
+ const example = {
448
+ code: 'broken code',
449
+ language: 'typescript',
450
+ context: 'Error handling test'
451
+ };
452
+ await autoFixExample(example, client, {
453
+ validateFn: createCountingValidator(1),
454
+ maxIterations: 2
455
+ });
456
+ // Check that complete was called with error context
457
+ expect(client.complete).toHaveBeenCalled();
458
+ const callArgs = vi.mocked(client.complete).mock.calls[0]?.[0];
459
+ expect(callArgs?.messages[1]?.content).toContain('Error');
460
+ });
461
+ it('should stop immediately when fix succeeds', async () => {
462
+ const client = createMockLLMClient([
463
+ '```typescript\nconst success = true\n```'
464
+ ]);
465
+ let validationCalls = 0;
466
+ const trackingValidator = async () => {
467
+ validationCalls++;
468
+ // First call (initial) fails, second call (after first fix) passes
469
+ return validationCalls > 1
470
+ ? { valid: true, errors: [] }
471
+ : { valid: false, errors: ['Initial failure'] };
472
+ };
473
+ const example = {
474
+ code: 'broken',
475
+ language: 'typescript',
476
+ context: 'Test'
477
+ };
478
+ const result = await autoFixExample(example, client, {
479
+ validateFn: trackingValidator,
480
+ maxIterations: 5
481
+ });
482
+ expect(result.success).toBe(true);
483
+ expect(result.iterations).toBe(1);
484
+ expect(client.complete).toHaveBeenCalledTimes(1);
485
+ });
486
+ });
487
+ });
package/dist/cli.js CHANGED
@@ -5,7 +5,16 @@ 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.4';
8
+ import { lintCommand } from './commands/lint.js';
9
+ import { checkLinksCommand } from './commands/check-links.js';
10
+ import { mcpCommand } from './commands/mcp.js';
11
+ import { versionCommand } from './commands/version.js';
12
+ import { i18nCommand } from './commands/i18n.js';
13
+ import { monitorCommand } from './commands/monitor.js';
14
+ import { sdkCommand } from './commands/sdk.js';
15
+ import { ghActionCommand } from './commands/gh-action.js';
16
+ import { llmsTxtCommand } from './commands/llms-txt.js';
17
+ const VERSION = '0.1.8';
9
18
  async function checkForUpdates() {
10
19
  try {
11
20
  const res = await fetch('https://registry.npmjs.org/skrypt-ai/latest', {
@@ -37,4 +46,13 @@ program.addCommand(generateCommand);
37
46
  program.addCommand(watchCommand);
38
47
  program.addCommand(autofixCommand);
39
48
  program.addCommand(reviewPRCommand);
49
+ program.addCommand(lintCommand);
50
+ program.addCommand(checkLinksCommand);
51
+ program.addCommand(mcpCommand);
52
+ program.addCommand(versionCommand);
53
+ program.addCommand(i18nCommand);
54
+ program.addCommand(monitorCommand);
55
+ program.addCommand(sdkCommand);
56
+ program.addCommand(ghActionCommand);
57
+ program.addCommand(llmsTxtCommand);
40
58
  program.parse();
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const checkLinksCommand: Command;