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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -0
  3. package/dist/autofix/index.d.ts +46 -0
  4. package/dist/autofix/index.js +240 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +40 -0
  7. package/dist/commands/autofix.d.ts +2 -0
  8. package/dist/commands/autofix.js +143 -0
  9. package/dist/commands/generate.d.ts +2 -0
  10. package/dist/commands/generate.js +320 -0
  11. package/dist/commands/init.d.ts +2 -0
  12. package/dist/commands/init.js +56 -0
  13. package/dist/commands/review-pr.d.ts +2 -0
  14. package/dist/commands/review-pr.js +117 -0
  15. package/dist/commands/watch.d.ts +2 -0
  16. package/dist/commands/watch.js +142 -0
  17. package/dist/config/index.d.ts +2 -0
  18. package/dist/config/index.js +2 -0
  19. package/dist/config/loader.d.ts +9 -0
  20. package/dist/config/loader.js +82 -0
  21. package/dist/config/types.d.ts +24 -0
  22. package/dist/config/types.js +34 -0
  23. package/dist/generator/generator.d.ts +15 -0
  24. package/dist/generator/generator.js +144 -0
  25. package/dist/generator/index.d.ts +4 -0
  26. package/dist/generator/index.js +4 -0
  27. package/dist/generator/organizer.d.ts +29 -0
  28. package/dist/generator/organizer.js +222 -0
  29. package/dist/generator/types.d.ts +83 -0
  30. package/dist/generator/types.js +1 -0
  31. package/dist/generator/writer.d.ts +28 -0
  32. package/dist/generator/writer.js +320 -0
  33. package/dist/github/pr-comments.d.ts +40 -0
  34. package/dist/github/pr-comments.js +308 -0
  35. package/dist/llm/anthropic-client.d.ts +16 -0
  36. package/dist/llm/anthropic-client.js +92 -0
  37. package/dist/llm/index.d.ts +53 -0
  38. package/dist/llm/index.js +400 -0
  39. package/dist/llm/llm.manual-test.d.ts +1 -0
  40. package/dist/llm/llm.manual-test.js +112 -0
  41. package/dist/llm/llm.mock-test.d.ts +4 -0
  42. package/dist/llm/llm.mock-test.js +79 -0
  43. package/dist/llm/openai-client.d.ts +17 -0
  44. package/dist/llm/openai-client.js +90 -0
  45. package/dist/llm/types.d.ts +60 -0
  46. package/dist/llm/types.js +20 -0
  47. package/dist/scanner/content-type.d.ts +39 -0
  48. package/dist/scanner/content-type.js +194 -0
  49. package/dist/scanner/content-type.test.d.ts +1 -0
  50. package/dist/scanner/content-type.test.js +231 -0
  51. package/dist/scanner/go.d.ts +20 -0
  52. package/dist/scanner/go.js +269 -0
  53. package/dist/scanner/index.d.ts +21 -0
  54. package/dist/scanner/index.js +137 -0
  55. package/dist/scanner/python.d.ts +6 -0
  56. package/dist/scanner/python.js +57 -0
  57. package/dist/scanner/python_parser.py +230 -0
  58. package/dist/scanner/rust.d.ts +23 -0
  59. package/dist/scanner/rust.js +304 -0
  60. package/dist/scanner/scanner.test.d.ts +1 -0
  61. package/dist/scanner/scanner.test.js +210 -0
  62. package/dist/scanner/types.d.ts +50 -0
  63. package/dist/scanner/types.js +1 -0
  64. package/dist/scanner/typescript.d.ts +34 -0
  65. package/dist/scanner/typescript.js +327 -0
  66. package/dist/scanner/typescript.manual-test.d.ts +1 -0
  67. package/dist/scanner/typescript.manual-test.js +112 -0
  68. package/dist/template/docs.json +32 -0
  69. package/dist/template/mdx-components.tsx +62 -0
  70. package/dist/template/next-env.d.ts +6 -0
  71. package/dist/template/next.config.mjs +17 -0
  72. package/dist/template/package.json +39 -0
  73. package/dist/template/postcss.config.mjs +5 -0
  74. package/dist/template/public/search-index.json +1 -0
  75. package/dist/template/scripts/build-search-index.mjs +120 -0
  76. package/dist/template/src/app/api/mock/[...path]/route.ts +224 -0
  77. package/dist/template/src/app/api/openapi/route.ts +48 -0
  78. package/dist/template/src/app/api/rate-limit/route.ts +84 -0
  79. package/dist/template/src/app/docs/[...slug]/page.tsx +81 -0
  80. package/dist/template/src/app/docs/layout.tsx +9 -0
  81. package/dist/template/src/app/docs/page.mdx +67 -0
  82. package/dist/template/src/app/error.tsx +63 -0
  83. package/dist/template/src/app/layout.tsx +71 -0
  84. package/dist/template/src/app/page.tsx +18 -0
  85. package/dist/template/src/app/reference/route.ts +36 -0
  86. package/dist/template/src/app/robots.ts +14 -0
  87. package/dist/template/src/app/sitemap.ts +64 -0
  88. package/dist/template/src/components/breadcrumbs.tsx +41 -0
  89. package/dist/template/src/components/copy-button.tsx +29 -0
  90. package/dist/template/src/components/docs-layout.tsx +35 -0
  91. package/dist/template/src/components/edit-link.tsx +39 -0
  92. package/dist/template/src/components/feedback.tsx +52 -0
  93. package/dist/template/src/components/header.tsx +66 -0
  94. package/dist/template/src/components/mdx/accordion.tsx +48 -0
  95. package/dist/template/src/components/mdx/api-badge.tsx +57 -0
  96. package/dist/template/src/components/mdx/callout.tsx +111 -0
  97. package/dist/template/src/components/mdx/card.tsx +62 -0
  98. package/dist/template/src/components/mdx/changelog.tsx +57 -0
  99. package/dist/template/src/components/mdx/code-block.tsx +42 -0
  100. package/dist/template/src/components/mdx/code-group.tsx +125 -0
  101. package/dist/template/src/components/mdx/code-playground.tsx +322 -0
  102. package/dist/template/src/components/mdx/go-playground.tsx +235 -0
  103. package/dist/template/src/components/mdx/heading.tsx +37 -0
  104. package/dist/template/src/components/mdx/highlighted-code.tsx +89 -0
  105. package/dist/template/src/components/mdx/index.tsx +15 -0
  106. package/dist/template/src/components/mdx/param-table.tsx +71 -0
  107. package/dist/template/src/components/mdx/python-playground.tsx +293 -0
  108. package/dist/template/src/components/mdx/steps.tsx +43 -0
  109. package/dist/template/src/components/mdx/tabs.tsx +81 -0
  110. package/dist/template/src/components/rate-limit-display.tsx +183 -0
  111. package/dist/template/src/components/search-dialog.tsx +178 -0
  112. package/dist/template/src/components/sidebar.tsx +129 -0
  113. package/dist/template/src/components/syntax-theme-selector.tsx +50 -0
  114. package/dist/template/src/components/table-of-contents.tsx +84 -0
  115. package/dist/template/src/components/theme-toggle.tsx +46 -0
  116. package/dist/template/src/components/version-selector.tsx +61 -0
  117. package/dist/template/src/contexts/syntax-theme.tsx +52 -0
  118. package/dist/template/src/lib/highlight.ts +83 -0
  119. package/dist/template/src/lib/search-types.ts +37 -0
  120. package/dist/template/src/lib/search.ts +125 -0
  121. package/dist/template/src/lib/utils.ts +6 -0
  122. package/dist/template/src/styles/globals.css +152 -0
  123. package/dist/template/tsconfig.json +25 -0
  124. package/dist/template/tsconfig.tsbuildinfo +1 -0
  125. package/package.json +72 -0
@@ -0,0 +1,92 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ /**
3
+ * Anthropic Claude client
4
+ */
5
+ export class AnthropicClient {
6
+ provider = 'anthropic';
7
+ client;
8
+ model;
9
+ maxRetries;
10
+ constructor(config) {
11
+ this.model = config.model;
12
+ this.maxRetries = config.maxRetries ?? 3;
13
+ this.client = new Anthropic({
14
+ apiKey: config.apiKey,
15
+ timeout: config.timeout ?? 60000,
16
+ maxRetries: this.maxRetries
17
+ });
18
+ }
19
+ isConfigured() {
20
+ return true; // Anthropic SDK handles validation
21
+ }
22
+ async complete(request) {
23
+ const model = request.model || this.model;
24
+ // Separate system message from conversation
25
+ const systemMessage = request.messages.find(m => m.role === 'system');
26
+ const conversationMessages = request.messages.filter(m => m.role !== 'system');
27
+ try {
28
+ const response = await this.client.messages.create({
29
+ model,
30
+ max_tokens: request.maxTokens ?? 4096,
31
+ system: systemMessage?.content,
32
+ messages: conversationMessages.map(m => ({
33
+ role: m.role,
34
+ content: m.content
35
+ })),
36
+ temperature: request.temperature ?? 0
37
+ });
38
+ // Extract text content from response
39
+ const content = response.content
40
+ .filter(block => block.type === 'text')
41
+ .map(block => block.text)
42
+ .join('');
43
+ return {
44
+ content,
45
+ model: response.model,
46
+ usage: {
47
+ inputTokens: response.usage.input_tokens,
48
+ outputTokens: response.usage.output_tokens,
49
+ totalTokens: response.usage.input_tokens + response.usage.output_tokens
50
+ },
51
+ finishReason: this.mapStopReason(response.stop_reason)
52
+ };
53
+ }
54
+ catch (error) {
55
+ throw this.handleError(error);
56
+ }
57
+ }
58
+ mapStopReason(reason) {
59
+ switch (reason) {
60
+ case 'end_turn':
61
+ case 'stop_sequence':
62
+ return 'stop';
63
+ case 'max_tokens':
64
+ return 'length';
65
+ default:
66
+ return 'error';
67
+ }
68
+ }
69
+ handleError(error) {
70
+ if (error instanceof Anthropic.APIError) {
71
+ const status = error.status;
72
+ const message = error.message;
73
+ if (status === 401) {
74
+ return new Error('Authentication failed for Anthropic: Invalid API key');
75
+ }
76
+ if (status === 429) {
77
+ return new Error(`Rate limited by Anthropic: ${message}`);
78
+ }
79
+ if (status === 500 || status === 502 || status === 503) {
80
+ return new Error(`Anthropic service error: ${message}`);
81
+ }
82
+ if (status === 400 && message.includes('credit')) {
83
+ return new Error('Anthropic API: Insufficient credits');
84
+ }
85
+ return new Error(`Anthropic API error (${status}): ${message}`);
86
+ }
87
+ if (error instanceof Error) {
88
+ return error;
89
+ }
90
+ return new Error(`Unknown error from Anthropic: ${String(error)}`);
91
+ }
92
+ }
@@ -0,0 +1,53 @@
1
+ import { LLMProvider } from '../config/types.js';
2
+ import { LLMClient } from './types.js';
3
+ export * from './types.js';
4
+ export type { LLMClient };
5
+ /**
6
+ * Create an LLM client for the specified provider
7
+ */
8
+ export declare function createLLMClient(config: {
9
+ provider: LLMProvider;
10
+ model: string;
11
+ baseUrl?: string;
12
+ timeout?: number;
13
+ maxRetries?: number;
14
+ }): LLMClient;
15
+ /**
16
+ * Extended element info for better doc generation
17
+ */
18
+ export interface ElementContext {
19
+ kind: string;
20
+ name: string;
21
+ signature: string;
22
+ parameters: Array<{
23
+ name: string;
24
+ type?: string;
25
+ default?: string;
26
+ }>;
27
+ returnType?: string;
28
+ docstring?: string;
29
+ parentClass?: string;
30
+ imports?: string[];
31
+ packageName?: string;
32
+ sourceContext?: string;
33
+ filePath?: string;
34
+ }
35
+ /**
36
+ * Generated documentation result
37
+ */
38
+ export interface GeneratedDocResult {
39
+ markdown: string;
40
+ codeExample: string;
41
+ pythonExample?: string;
42
+ typescriptExample?: string;
43
+ }
44
+ /**
45
+ * Helper to generate documentation for a code element
46
+ */
47
+ export declare function generateDocumentation(client: LLMClient, element: ElementContext, options?: {
48
+ multiLanguage?: boolean;
49
+ }): Promise<GeneratedDocResult>;
50
+ /**
51
+ * Helper to fix a broken code sample with smart strategies
52
+ */
53
+ export declare function fixCodeSample(client: LLMClient, code: string, error: string, context: string, iteration?: number): Promise<string>;
@@ -0,0 +1,400 @@
1
+ import { PROVIDER_ENV_KEYS } from '../config/types.js';
2
+ import { OpenAICompatibleClient } from './openai-client.js';
3
+ import { AnthropicClient } from './anthropic-client.js';
4
+ export * from './types.js';
5
+ /**
6
+ * Create an LLM client for the specified provider
7
+ */
8
+ export function createLLMClient(config) {
9
+ // Get API key from environment
10
+ const envKey = PROVIDER_ENV_KEYS[config.provider];
11
+ const apiKey = envKey ? process.env[envKey] || '' : '';
12
+ const clientConfig = {
13
+ provider: config.provider,
14
+ model: config.model,
15
+ apiKey,
16
+ baseUrl: config.baseUrl,
17
+ timeout: config.timeout,
18
+ maxRetries: config.maxRetries
19
+ };
20
+ // Use Anthropic client for Anthropic, OpenAI-compatible for everything else
21
+ if (config.provider === 'anthropic') {
22
+ return new AnthropicClient(clientConfig);
23
+ }
24
+ return new OpenAICompatibleClient(clientConfig);
25
+ }
26
+ /**
27
+ * Helper to generate documentation for a code element
28
+ */
29
+ export async function generateDocumentation(client, element, options) {
30
+ const useMultiLang = options?.multiLanguage ?? true;
31
+ const prompt = buildDocPrompt(element, useMultiLang);
32
+ const response = await client.complete({
33
+ messages: [
34
+ {
35
+ role: 'system',
36
+ content: useMultiLang ? SYSTEM_PROMPT_MULTI_LANG : SYSTEM_PROMPT
37
+ },
38
+ {
39
+ role: 'user',
40
+ content: prompt
41
+ }
42
+ ],
43
+ temperature: 0,
44
+ maxTokens: useMultiLang ? 4096 : 2048
45
+ });
46
+ return useMultiLang
47
+ ? parseMultiLangResponse(response.content)
48
+ : parseDocResponse(response.content);
49
+ }
50
+ // Phase 2: Multi-language system prompt
51
+ const SYSTEM_PROMPT_MULTI_LANG = `You are an expert technical writer creating OUTCOMES-FOCUSED documentation with DUAL-LANGUAGE examples.
52
+
53
+ Your goal: Help developers achieve their task FAST. Lead with "Use this to..." not "This function...".
54
+
55
+ ## Documentation Rules
56
+ 1. Do NOT include a heading with the function/class name - start directly with the use case
57
+ 2. Start with the PRIMARY USE CASE - what problem does this solve?
58
+ 3. Use a markdown table for parameters (Name | Type | Required | Description)
59
+ 4. Clearly document what is returned and when
60
+
61
+ ## Code Example Rules (CRITICAL)
62
+ Generate TWO self-contained examples: TypeScript AND Python.
63
+
64
+ Both examples MUST be:
65
+ 1. COMPLETELY SELF-CONTAINED - no external imports from the library
66
+ 2. EXECUTABLE standalone - inline all types and mock all functions
67
+ 3. Using realistic data (API keys, user IDs, etc.)
68
+ 4. Wrapped in try/except (Python) or try/catch (TypeScript)
69
+ 5. Showing environment variable usage
70
+ 6. Ending with print/console.log showing expected output
71
+
72
+ ## TypeScript Example Pattern
73
+ \`\`\`typescript
74
+ // Inline types
75
+ type Config = { apiKey: string }
76
+
77
+ // Mock implementation
78
+ function createTool(config: Config) {
79
+ return { execute: async (q: string) => ({ results: [q] }) }
80
+ }
81
+
82
+ // Usage
83
+ const tool = createTool({ apiKey: process.env.API_KEY || 'your-key' })
84
+
85
+ async function main() {
86
+ try {
87
+ const result = await tool.execute('query')
88
+ console.log('Result:', result)
89
+ // Output: { results: ['query'] }
90
+ } catch (error) {
91
+ console.error('Failed:', error)
92
+ }
93
+ }
94
+ main()
95
+ \`\`\`
96
+
97
+ ## Python Example Pattern
98
+ \`\`\`python
99
+ import os
100
+ from dataclasses import dataclass
101
+ from typing import Optional, List, Dict, Any
102
+
103
+ # Inline types
104
+ @dataclass
105
+ class Config:
106
+ api_key: str
107
+ timeout: int = 30
108
+
109
+ # Mock implementation
110
+ class Tool:
111
+ def __init__(self, config: Config):
112
+ self.config = config
113
+
114
+ async def execute(self, query: str) -> Dict[str, Any]:
115
+ return {"results": [query]}
116
+
117
+ # Usage
118
+ import asyncio
119
+
120
+ async def main():
121
+ try:
122
+ tool = Tool(Config(api_key=os.getenv("API_KEY", "your-key")))
123
+ result = await tool.execute("query")
124
+ print(f"Result: {result}")
125
+ # Output: {'results': ['query']}
126
+ except Exception as e:
127
+ print(f"Failed: {e}")
128
+
129
+ asyncio.run(main())
130
+ \`\`\`
131
+
132
+ ## Output Format
133
+ ---MARKDOWN---
134
+ [Your markdown documentation with parameter table]
135
+ ---TYPESCRIPT---
136
+ [Self-contained TypeScript example]
137
+ ---PYTHON---
138
+ [Self-contained Python example]
139
+ ---END---`;
140
+ const SYSTEM_PROMPT = `You are an expert technical writer creating OUTCOMES-FOCUSED documentation.
141
+
142
+ Your goal: Help developers achieve their task FAST. Lead with "Use this to..." not "This function...".
143
+
144
+ ## Documentation Rules
145
+ 1. Do NOT include a heading with the function/class name - start directly with the use case
146
+ 2. Start with the PRIMARY USE CASE - what problem does this solve?
147
+ 3. Use a markdown table for parameters (Name | Type | Required | Description)
148
+ 4. Clearly document what is returned and when
149
+
150
+ ## Code Example Rules (CRITICAL)
151
+ Your code examples MUST be COMPLETELY SELF-CONTAINED and EXECUTABLE:
152
+
153
+ 1. NEVER import from the library being documented (e.g., "@supermemory/tools")
154
+ 2. Instead, INLINE any types or simple implementations needed
155
+ 3. Use realistic but simple data (real-looking API keys, user IDs, etc.)
156
+ 4. ALWAYS wrap async code in try/catch with meaningful error handling
157
+ 5. ALWAYS show environment variable usage: process.env.API_KEY || 'your-api-key'
158
+ 6. End with a console.log showing expected output
159
+ 7. Include a brief comment showing what output to expect
160
+
161
+ ## Self-Contained Example Pattern
162
+ BAD (imports from library - will fail):
163
+ \`\`\`typescript
164
+ import { createTool } from '@mylib/tools'
165
+ const tool = createTool('key')
166
+ \`\`\`
167
+
168
+ GOOD (self-contained - will run):
169
+ \`\`\`typescript
170
+ // Define the types inline
171
+ type ToolConfig = { apiKey: string; timeout?: number }
172
+
173
+ // Simulate the function behavior
174
+ function createTool(config: ToolConfig) {
175
+ return {
176
+ execute: async (query: string) => {
177
+ console.log(\`Searching for: \${query}\`)
178
+ return { results: ['result1', 'result2'] }
179
+ }
180
+ }
181
+ }
182
+
183
+ // Usage example
184
+ const tool = createTool({
185
+ apiKey: process.env.MY_API_KEY || 'your-api-key-here'
186
+ })
187
+
188
+ async function main() {
189
+ try {
190
+ const result = await tool.execute('my query')
191
+ console.log('Results:', result)
192
+ // Output: { results: ['result1', 'result2'] }
193
+ } catch (error) {
194
+ console.error('Failed:', error)
195
+ }
196
+ }
197
+
198
+ main()
199
+ \`\`\`
200
+
201
+ Output format:
202
+ ---MARKDOWN---
203
+ [Your markdown documentation with parameter table]
204
+ ---CODE---
205
+ [Your SELF-CONTAINED, EXECUTABLE code example]
206
+ ---END---`;
207
+ function buildDocPrompt(element, multiLanguage = false) {
208
+ let prompt = `Generate documentation for this ${element.kind}:\n\n`;
209
+ prompt += `**Name:** ${element.name}\n`;
210
+ prompt += `**Signature:** ${element.signature}\n`;
211
+ if (element.parentClass) {
212
+ prompt += `**Class:** ${element.parentClass}\n`;
213
+ }
214
+ if (element.packageName && element.packageName !== 'unknown') {
215
+ prompt += `**Package:** ${element.packageName} (DO NOT import from this - make example self-contained)\n`;
216
+ }
217
+ if (element.parameters.length > 0) {
218
+ prompt += `\n**Parameters:**\n`;
219
+ for (const param of element.parameters) {
220
+ prompt += `- ${param.name}`;
221
+ if (param.type)
222
+ prompt += `: ${param.type}`;
223
+ if (param.default)
224
+ prompt += ` = ${param.default}`;
225
+ prompt += '\n';
226
+ }
227
+ }
228
+ if (element.returnType) {
229
+ prompt += `\n**Returns:** ${element.returnType}\n`;
230
+ }
231
+ if (element.docstring) {
232
+ prompt += `\n**Existing docstring:**\n${element.docstring}\n`;
233
+ }
234
+ if (element.sourceContext) {
235
+ prompt += `\n**Source context (for understanding, not for importing):**\n\`\`\`\n${element.sourceContext}\n\`\`\`\n`;
236
+ }
237
+ if (element.imports && element.imports.length > 0) {
238
+ prompt += `\n**File imports (shows dependencies - your example should NOT use these):**\n`;
239
+ prompt += element.imports.slice(0, 5).join('\n') + '\n';
240
+ }
241
+ if (multiLanguage) {
242
+ prompt += `\n**IMPORTANT:** Generate BOTH TypeScript AND Python self-contained examples.`;
243
+ }
244
+ else {
245
+ prompt += `\n**IMPORTANT:** Generate a SELF-CONTAINED example that runs without any external imports.`;
246
+ }
247
+ return prompt;
248
+ }
249
+ function parseDocResponse(content) {
250
+ // Extract markdown section
251
+ const markdownMatch = content.match(/---MARKDOWN---\s*([\s\S]*?)\s*---CODE---/);
252
+ const markdown = markdownMatch?.[1]?.trim() || content;
253
+ // Extract code section
254
+ const codeMatch = content.match(/---CODE---\s*([\s\S]*?)\s*---END---/);
255
+ let codeExample = codeMatch?.[1]?.trim() || '';
256
+ // Clean up code fences if present
257
+ codeExample = codeExample.replace(/^```\w*\n?/, '').replace(/\n?```$/, '');
258
+ return { markdown, codeExample, typescriptExample: codeExample };
259
+ }
260
+ function parseMultiLangResponse(content) {
261
+ // Extract markdown section
262
+ const markdownMatch = content.match(/---MARKDOWN---\s*([\s\S]*?)\s*---TYPESCRIPT---/);
263
+ const markdown = markdownMatch?.[1]?.trim() || '';
264
+ // Extract TypeScript section
265
+ const tsMatch = content.match(/---TYPESCRIPT---\s*([\s\S]*?)\s*---PYTHON---/);
266
+ let typescriptExample = tsMatch?.[1]?.trim() || '';
267
+ typescriptExample = typescriptExample.replace(/^```\w*\n?/, '').replace(/\n?```$/, '');
268
+ // Extract Python section
269
+ const pyMatch = content.match(/---PYTHON---\s*([\s\S]*?)\s*---END---/);
270
+ let pythonExample = pyMatch?.[1]?.trim() || '';
271
+ pythonExample = pythonExample.replace(/^```\w*\n?/, '').replace(/\n?```$/, '');
272
+ // Primary code example is TypeScript (for backwards compat)
273
+ return {
274
+ markdown,
275
+ codeExample: typescriptExample,
276
+ typescriptExample,
277
+ pythonExample
278
+ };
279
+ }
280
+ /**
281
+ * Helper to fix a broken code sample with smart strategies
282
+ */
283
+ export async function fixCodeSample(client, code, error, context, iteration = 1) {
284
+ // Analyze error type to provide better fix guidance
285
+ const fixStrategy = analyzeErrorAndGetStrategy(error, code);
286
+ const response = await client.complete({
287
+ messages: [
288
+ {
289
+ role: 'system',
290
+ content: FIX_SYSTEM_PROMPT
291
+ },
292
+ {
293
+ role: 'user',
294
+ content: `Fix this code example. It must be COMPLETELY SELF-CONTAINED and runnable.
295
+
296
+ **Current code:**
297
+ \`\`\`
298
+ ${code}
299
+ \`\`\`
300
+
301
+ **Error:**
302
+ ${error}
303
+
304
+ **Fix strategy:** ${fixStrategy}
305
+
306
+ **Context:**
307
+ ${context}
308
+
309
+ **Iteration ${iteration}:** ${iteration > 2 ? 'SIMPLIFY the example drastically - focus on demonstrating the concept, not real functionality.' : 'Make it self-contained.'}
310
+
311
+ Return ONLY the fixed code, no explanations or markdown fences.`
312
+ }
313
+ ],
314
+ temperature: iteration > 3 ? 0.3 : 0, // Add creativity on later iterations
315
+ maxTokens: 1024
316
+ });
317
+ // Clean up any markdown fences
318
+ return response.content
319
+ .replace(/^```\w*\n?/, '')
320
+ .replace(/\n?```$/, '')
321
+ .trim();
322
+ }
323
+ const FIX_SYSTEM_PROMPT = `You are fixing a documentation code example to make it SELF-CONTAINED and RUNNABLE.
324
+
325
+ ## Fix Strategies by Error Type
326
+
327
+ **Import/Module errors ("Cannot find module", "is not defined"):**
328
+ - DO NOT try to fix the import
329
+ - INLINE the type definitions
330
+ - MOCK the function behavior with a simple implementation
331
+ - Show what the function WOULD do conceptually
332
+
333
+ **Type errors ("Type X is not assignable"):**
334
+ - Define the type inline with \`type X = {...}\`
335
+ - Use simpler types if complex ones cause issues
336
+
337
+ **Runtime errors ("undefined is not a function", "Cannot read property"):**
338
+ - Initialize all variables
339
+ - Add null checks
340
+ - Use optional chaining (?.)
341
+
342
+ **Async errors ("await is only valid in async"):**
343
+ - Wrap in async function
344
+ - Add proper try/catch
345
+ - Call the async function at the end
346
+
347
+ ## Example Fix Pattern
348
+ If the error is "Cannot find module '@mylib/tools'":
349
+
350
+ WRONG (trying to fix import):
351
+ \`\`\`
352
+ import { tool } from '@mylib/tools-v2' // Still fails
353
+ \`\`\`
354
+
355
+ RIGHT (inline everything):
356
+ \`\`\`
357
+ // Define types inline
358
+ type ToolResult = { data: string[] }
359
+
360
+ // Mock the tool behavior
361
+ const tool = {
362
+ search: async (query: string): Promise<ToolResult> => {
363
+ // Simulated behavior
364
+ return { data: ['result1', 'result2'] }
365
+ }
366
+ }
367
+
368
+ // Usage
369
+ async function main() {
370
+ const result = await tool.search('test')
371
+ console.log(result) // { data: ['result1', 'result2'] }
372
+ }
373
+ main()
374
+ \`\`\`
375
+
376
+ Return ONLY the fixed code.`;
377
+ function analyzeErrorAndGetStrategy(error, _code) {
378
+ const errorLower = error.toLowerCase();
379
+ if (errorLower.includes('cannot find module') || errorLower.includes('module not found')) {
380
+ const importMatch = error.match(/['"]([^'"]+)['"]/)?.[1] || 'the library';
381
+ return `IMPORT ERROR: Cannot import from "${importMatch}". Remove ALL imports and inline types/mock functions instead.`;
382
+ }
383
+ if (errorLower.includes('is not defined') || errorLower.includes('is not a function')) {
384
+ const undefinedMatch = error.match(/(\w+) is not (defined|a function)/)?.[1];
385
+ return `UNDEFINED: "${undefinedMatch}" is not available. Define it inline or mock it.`;
386
+ }
387
+ if (errorLower.includes('type') && (errorLower.includes('assignable') || errorLower.includes('missing'))) {
388
+ return `TYPE ERROR: Type mismatch. Simplify types or define them inline with correct structure.`;
389
+ }
390
+ if (errorLower.includes('await') && errorLower.includes('async')) {
391
+ return `ASYNC ERROR: Wrap code in async function and call it: async function main() {...} main()`;
392
+ }
393
+ if (errorLower.includes('timeout') || errorLower.includes('timed out')) {
394
+ return `TIMEOUT: Code took too long. Remove any actual API calls or network requests. Use mock data.`;
395
+ }
396
+ if (errorLower.includes('syntaxerror') || errorLower.includes('unexpected token')) {
397
+ return `SYNTAX ERROR: Fix syntax issues. Check for missing brackets, quotes, or semicolons.`;
398
+ }
399
+ return `GENERAL: Make the code self-contained. Define all types inline, mock all external calls, use try/catch.`;
400
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,112 @@
1
+ import { createLLMClient, generateDocumentation, fixCodeSample } from './index.js';
2
+ import { PROVIDER_ENV_KEYS } from '../config/types.js';
3
+ async function runTests() {
4
+ console.log('=== LLM Client Tests ===\n');
5
+ // Find an available provider
6
+ const providers = ['deepseek', 'openai', 'anthropic', 'google', 'openrouter', 'ollama'];
7
+ let availableProvider = null;
8
+ let availableModel = null;
9
+ for (const provider of providers) {
10
+ const envKey = PROVIDER_ENV_KEYS[provider];
11
+ if (!envKey || process.env[envKey]) {
12
+ availableProvider = provider;
13
+ availableModel = getTestModel(provider);
14
+ break;
15
+ }
16
+ }
17
+ if (!availableProvider) {
18
+ console.log('No LLM provider available. Set one of these env vars:');
19
+ console.log(' DEEPSEEK_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY,');
20
+ console.log(' GOOGLE_API_KEY, OPENROUTER_API_KEY, or run Ollama locally');
21
+ process.exit(0);
22
+ }
23
+ console.log(`Using provider: ${availableProvider} (${availableModel})`);
24
+ console.log('');
25
+ // Test 1: Create client
26
+ console.log('Test 1: Create LLM client');
27
+ const client = createLLMClient({
28
+ provider: availableProvider,
29
+ model: availableModel
30
+ });
31
+ console.log(` provider: ${client.provider}`);
32
+ console.log(' ✓ client created\n');
33
+ // Test 2: Simple completion
34
+ console.log('Test 2: Simple completion');
35
+ try {
36
+ const response = await client.complete({
37
+ messages: [
38
+ { role: 'user', content: 'Say "hello" and nothing else.' }
39
+ ],
40
+ maxTokens: 10
41
+ });
42
+ console.log(` response: "${response.content.trim()}"`);
43
+ console.log(` model: ${response.model}`);
44
+ console.log(` tokens: ${response.usage.totalTokens}`);
45
+ console.log(' ✓ completion works\n');
46
+ }
47
+ catch (err) {
48
+ console.log(` ✗ completion failed: ${err}`);
49
+ process.exit(1);
50
+ }
51
+ // Test 3: Generate documentation
52
+ console.log('Test 3: Generate documentation');
53
+ try {
54
+ const result = await generateDocumentation(client, {
55
+ kind: 'function',
56
+ name: 'add',
57
+ signature: 'def add(a: int, b: int) -> int',
58
+ parameters: [
59
+ { name: 'a', type: 'int' },
60
+ { name: 'b', type: 'int' }
61
+ ],
62
+ returnType: 'int',
63
+ docstring: 'Add two numbers together.'
64
+ });
65
+ console.log(` markdown length: ${result.markdown.length} chars`);
66
+ console.log(` code example length: ${result.codeExample.length} chars`);
67
+ if (result.codeExample.includes('add(')) {
68
+ console.log(' ✓ code example calls the function\n');
69
+ }
70
+ else {
71
+ console.log(` code example:\n${result.codeExample}`);
72
+ console.log(' ⚠ code example might not call the function\n');
73
+ }
74
+ }
75
+ catch (err) {
76
+ console.log(` ✗ doc generation failed: ${err}`);
77
+ process.exit(1);
78
+ }
79
+ // Test 4: Fix broken code
80
+ console.log('Test 4: Fix broken code');
81
+ try {
82
+ const fixed = await fixCodeSample(client, 'print(add(1, 2)', // Missing closing paren
83
+ 'SyntaxError: unexpected EOF while parsing', 'This is a code example for the add function');
84
+ console.log(` fixed code: ${fixed}`);
85
+ if (fixed.includes(')')) {
86
+ console.log(' ✓ code was fixed\n');
87
+ }
88
+ else {
89
+ console.log(' ⚠ fix might be incomplete\n');
90
+ }
91
+ }
92
+ catch (err) {
93
+ console.log(` ✗ code fix failed: ${err}`);
94
+ process.exit(1);
95
+ }
96
+ console.log('✓ All LLM client tests passed');
97
+ }
98
+ function getTestModel(provider) {
99
+ switch (provider) {
100
+ case 'deepseek': return 'deepseek-chat';
101
+ case 'openai': return 'gpt-4o-mini';
102
+ case 'anthropic': return 'claude-sonnet-4-5-20241022';
103
+ case 'google': return 'gemini-2.0-flash';
104
+ case 'ollama': return 'llama3.2:1b'; // Small model for testing
105
+ case 'openrouter': return 'openai/gpt-4o-mini';
106
+ default: return 'gpt-4o-mini';
107
+ }
108
+ }
109
+ runTests().catch(err => {
110
+ console.error('Test failed:', err);
111
+ process.exit(1);
112
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Mock tests for LLM client - tests structure without API calls
3
+ */
4
+ export {};