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,231 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { classifyElement, classifyElements, getRecommendedStructure, getPromptForContentType } from './content-type.js';
3
+ function createMockElement(overrides = {}) {
4
+ return {
5
+ kind: 'function',
6
+ name: 'testFunction',
7
+ signature: 'function testFunction()',
8
+ parameters: [],
9
+ filePath: '/src/test.ts',
10
+ lineNumber: 1,
11
+ isExported: true,
12
+ isPublic: true,
13
+ ...overrides,
14
+ };
15
+ }
16
+ describe('Content Type Detection', () => {
17
+ describe('classifyElement', () => {
18
+ it('should classify API elements with multiple parameters', () => {
19
+ const element = createMockElement({
20
+ name: 'fetchData',
21
+ parameters: [
22
+ { name: 'url', type: 'string' },
23
+ { name: 'options', type: 'RequestOptions' },
24
+ { name: 'timeout', type: 'number' },
25
+ ],
26
+ });
27
+ const result = classifyElement(element);
28
+ expect(result.type).toBe('api');
29
+ expect(result.confidence).toBeGreaterThan(0);
30
+ });
31
+ it('should classify handler/endpoint patterns as API', () => {
32
+ const element = createMockElement({
33
+ name: 'userHandler',
34
+ kind: 'method',
35
+ parentClass: 'UserController',
36
+ filePath: '/src/handlers/user.ts',
37
+ parameters: [
38
+ { name: 'req', type: 'Request' },
39
+ { name: 'res', type: 'Response' },
40
+ ],
41
+ });
42
+ const result = classifyElement(element);
43
+ expect(result.type).toBe('api');
44
+ });
45
+ it('should classify HTTP methods as API', () => {
46
+ const element = createMockElement({
47
+ signature: 'async function handleGET(request: Request): Promise<Response>',
48
+ docstring: 'Handles GET requests for user data',
49
+ });
50
+ const result = classifyElement(element);
51
+ expect(result.type).toBe('api');
52
+ });
53
+ it('should classify config/setup functions as guide', () => {
54
+ const element = createMockElement({
55
+ name: 'configureDatabase',
56
+ });
57
+ const result = classifyElement(element);
58
+ expect(result.type).toBe('guide');
59
+ });
60
+ it('should classify init functions as guide', () => {
61
+ const element = createMockElement({
62
+ name: 'initializeApp',
63
+ });
64
+ const result = classifyElement(element);
65
+ expect(result.type).toBe('guide');
66
+ });
67
+ it('should classify example files as tutorial', () => {
68
+ const element = createMockElement({
69
+ name: 'main',
70
+ filePath: '/examples/quickstart.ts',
71
+ });
72
+ const result = classifyElement(element);
73
+ expect(result.type).toBe('tutorial');
74
+ });
75
+ it('should classify demo functions as tutorial', () => {
76
+ const element = createMockElement({
77
+ name: 'demoFeature',
78
+ });
79
+ const result = classifyElement(element);
80
+ expect(result.type).toBe('tutorial');
81
+ });
82
+ it('should classify simple functions with few params as tutorial', () => {
83
+ const element = createMockElement({
84
+ name: 'simpleFunction',
85
+ kind: 'function',
86
+ parameters: [{ name: 'input', type: 'string' }],
87
+ });
88
+ const result = classifyElement(element);
89
+ // Simple functions default to tutorial
90
+ expect(result.type).toBe('tutorial');
91
+ });
92
+ it('should classify async API patterns correctly', () => {
93
+ const element = createMockElement({
94
+ signature: 'async function createUser(data: UserData): Promise<User>',
95
+ parameters: [
96
+ { name: 'data', type: 'UserData' },
97
+ { name: 'options', type: 'CreateOptions' },
98
+ { name: 'validate', type: 'boolean' },
99
+ ],
100
+ });
101
+ const result = classifyElement(element);
102
+ expect(result.type).toBe('api');
103
+ });
104
+ it('should return tutorial for simple functions with no indicators', () => {
105
+ // Simple functions with few params default to tutorial
106
+ const element = createMockElement({
107
+ name: 'x',
108
+ kind: 'function',
109
+ parameters: [],
110
+ docstring: undefined,
111
+ filePath: '/src/utils.ts',
112
+ });
113
+ const result = classifyElement(element);
114
+ // Gets tutorial because simple function with <=1 params
115
+ expect(result.type).toBe('tutorial');
116
+ });
117
+ it('should return overview for class elements with no indicators', () => {
118
+ // Classes without other indicators get overview
119
+ const element = createMockElement({
120
+ name: 'X',
121
+ kind: 'class',
122
+ parameters: [],
123
+ docstring: undefined,
124
+ filePath: '/src/utils.ts',
125
+ });
126
+ const result = classifyElement(element);
127
+ expect(result.type).toBe('overview');
128
+ });
129
+ it('should include reasons for classification', () => {
130
+ const element = createMockElement({
131
+ name: 'handler',
132
+ filePath: '/src/handlers/api.ts',
133
+ });
134
+ const result = classifyElement(element);
135
+ expect(result.reasons.length).toBeGreaterThan(0);
136
+ });
137
+ });
138
+ describe('classifyElements', () => {
139
+ it('should group elements by content type', () => {
140
+ const elements = [
141
+ createMockElement({
142
+ name: 'fetchUsers',
143
+ kind: 'method',
144
+ parentClass: 'UserService',
145
+ parameters: [
146
+ { name: 'page', type: 'number' },
147
+ { name: 'limit', type: 'number' },
148
+ { name: 'filter', type: 'Filter' },
149
+ ],
150
+ }),
151
+ createMockElement({
152
+ name: 'configureAuth',
153
+ kind: 'function',
154
+ parameters: [
155
+ { name: 'options', type: 'AuthOptions' },
156
+ { name: 'provider', type: 'Provider' },
157
+ ],
158
+ }),
159
+ createMockElement({
160
+ name: 'exampleUsage',
161
+ filePath: '/examples/demo.ts',
162
+ }),
163
+ ];
164
+ const result = classifyElements(elements);
165
+ expect(result.get('api')?.length).toBeGreaterThanOrEqual(1);
166
+ // configureAuth should be guide (config pattern)
167
+ expect(result.get('guide')?.length).toBeGreaterThanOrEqual(1);
168
+ expect(result.get('tutorial')?.length).toBeGreaterThanOrEqual(1);
169
+ });
170
+ it('should handle empty elements array', () => {
171
+ const result = classifyElements([]);
172
+ expect(result.get('api')).toHaveLength(0);
173
+ expect(result.get('guide')).toHaveLength(0);
174
+ expect(result.get('tutorial')).toHaveLength(0);
175
+ expect(result.get('overview')).toHaveLength(0);
176
+ });
177
+ });
178
+ describe('getRecommendedStructure', () => {
179
+ it('should return sections grouped by type', () => {
180
+ const elements = [
181
+ createMockElement({
182
+ name: 'createUser',
183
+ parameters: [
184
+ { name: 'data', type: 'UserData' },
185
+ { name: 'options', type: 'Options' },
186
+ { name: 'callback', type: 'Function' },
187
+ ],
188
+ }),
189
+ createMockElement({
190
+ name: 'setupDatabase',
191
+ }),
192
+ ];
193
+ const result = getRecommendedStructure(elements);
194
+ expect(result.sections.length).toBeGreaterThan(0);
195
+ expect(result.stats.api + result.stats.guide + result.stats.tutorial + result.stats.overview).toBe(elements.length);
196
+ });
197
+ it('should provide stats for each type', () => {
198
+ const elements = [
199
+ createMockElement({ name: 'api1', parameters: [{ name: 'a' }, { name: 'b' }, { name: 'c' }] }),
200
+ createMockElement({ name: 'api2', parameters: [{ name: 'x' }, { name: 'y' }, { name: 'z' }] }),
201
+ createMockElement({ name: 'config1' }),
202
+ ];
203
+ const result = getRecommendedStructure(elements);
204
+ expect(typeof result.stats.api).toBe('number');
205
+ expect(typeof result.stats.guide).toBe('number');
206
+ expect(typeof result.stats.tutorial).toBe('number');
207
+ expect(typeof result.stats.overview).toBe('number');
208
+ });
209
+ });
210
+ describe('getPromptForContentType', () => {
211
+ it('should return appropriate prompt for api type', () => {
212
+ const prompt = getPromptForContentType('api');
213
+ expect(prompt).toContain('API reference');
214
+ expect(prompt).toContain('parameter');
215
+ });
216
+ it('should return appropriate prompt for guide type', () => {
217
+ const prompt = getPromptForContentType('guide');
218
+ expect(prompt).toContain('guide');
219
+ expect(prompt).toContain('setup');
220
+ });
221
+ it('should return appropriate prompt for tutorial type', () => {
222
+ const prompt = getPromptForContentType('tutorial');
223
+ expect(prompt).toContain('tutorial');
224
+ expect(prompt).toContain('beginner');
225
+ });
226
+ it('should return appropriate prompt for overview type', () => {
227
+ const prompt = getPromptForContentType('overview');
228
+ expect(prompt).toContain('overview');
229
+ });
230
+ });
231
+ });
@@ -0,0 +1,20 @@
1
+ import { Scanner, ScanResult } from './types.js';
2
+ /**
3
+ * Scanner for Go source files
4
+ * Extracts: functions, methods, types (structs), interfaces
5
+ */
6
+ export declare class GoScanner implements Scanner {
7
+ languages: string[];
8
+ canHandle(filePath: string): boolean;
9
+ scanFile(filePath: string): Promise<ScanResult>;
10
+ private extractImports;
11
+ private extractFunctions;
12
+ private extractMethods;
13
+ private extractTypes;
14
+ private parseGoParams;
15
+ private splitParams;
16
+ private parseReturnType;
17
+ private getLineNumber;
18
+ private getDocComment;
19
+ private getSourceContext;
20
+ }
@@ -0,0 +1,269 @@
1
+ import { readFileSync } from 'fs';
2
+ /**
3
+ * Scanner for Go source files
4
+ * Extracts: functions, methods, types (structs), interfaces
5
+ */
6
+ export class GoScanner {
7
+ languages = ['go'];
8
+ canHandle(filePath) {
9
+ return /\.go$/.test(filePath) && !filePath.includes('_test.go');
10
+ }
11
+ async scanFile(filePath) {
12
+ try {
13
+ const source = readFileSync(filePath, 'utf-8');
14
+ const elements = [];
15
+ const errors = [];
16
+ const lines = source.split('\n');
17
+ // Extract package name
18
+ const packageMatch = source.match(/^package\s+(\w+)/m);
19
+ const packageName = packageMatch?.[1] ?? 'unknown';
20
+ // Extract imports for context
21
+ const imports = this.extractImports(source);
22
+ // Find all exported functions, methods, types
23
+ this.extractFunctions(source, lines, filePath, packageName, imports, elements);
24
+ this.extractMethods(source, lines, filePath, packageName, imports, elements);
25
+ this.extractTypes(source, lines, filePath, packageName, imports, elements);
26
+ return {
27
+ filePath,
28
+ language: 'go',
29
+ elements,
30
+ errors
31
+ };
32
+ }
33
+ catch (err) {
34
+ return {
35
+ filePath,
36
+ language: 'go',
37
+ elements: [],
38
+ errors: [`Failed to parse: ${err}`]
39
+ };
40
+ }
41
+ }
42
+ extractImports(source) {
43
+ const imports = [];
44
+ // Single import
45
+ const singleMatch = source.matchAll(/^import\s+"([^"]+)"/gm);
46
+ for (const m of singleMatch) {
47
+ if (m[1])
48
+ imports.push(m[1]);
49
+ }
50
+ // Import block
51
+ const blockMatch = source.match(/import\s*\(([\s\S]*?)\)/m);
52
+ if (blockMatch?.[1]) {
53
+ const importLines = blockMatch[1].split('\n');
54
+ for (const line of importLines) {
55
+ const match = line.match(/"([^"]+)"/);
56
+ if (match?.[1])
57
+ imports.push(match[1]);
58
+ }
59
+ }
60
+ return imports;
61
+ }
62
+ extractFunctions(source, lines, filePath, packageName, imports, elements) {
63
+ // Match: func FunctionName(params) returnType
64
+ // Only exported (uppercase first letter)
65
+ const funcRegex = /^func\s+([A-Z][a-zA-Z0-9_]*)\s*(\[[^\]]+\])?\s*\(([^)]*)\)\s*([^{]*)/gm;
66
+ let match;
67
+ while ((match = funcRegex.exec(source)) !== null) {
68
+ const name = match[1];
69
+ const paramsStr = match[3];
70
+ if (!name || paramsStr === undefined)
71
+ continue;
72
+ const typeParams = match[2] ?? '';
73
+ const returnPart = (match[4] ?? '').trim();
74
+ const lineNumber = this.getLineNumber(source, match.index);
75
+ const docstring = this.getDocComment(lines, lineNumber - 1);
76
+ const parameters = this.parseGoParams(paramsStr);
77
+ const returnType = this.parseReturnType(returnPart);
78
+ const signature = `func ${name}${typeParams}(${paramsStr})${returnPart ? ' ' + returnPart : ''}`;
79
+ elements.push({
80
+ kind: 'function',
81
+ name,
82
+ signature,
83
+ parameters,
84
+ returnType,
85
+ docstring,
86
+ filePath,
87
+ lineNumber,
88
+ isExported: true,
89
+ isPublic: true,
90
+ imports,
91
+ packageName,
92
+ sourceContext: this.getSourceContext(lines, lineNumber)
93
+ });
94
+ }
95
+ }
96
+ extractMethods(source, lines, filePath, packageName, imports, elements) {
97
+ // Match: func (receiver Type) MethodName(params) returnType
98
+ const methodRegex = /^func\s+\((\w+)\s+\*?(\w+)\)\s+([A-Z][a-zA-Z0-9_]*)\s*(\[[^\]]+\])?\s*\(([^)]*)\)\s*([^{]*)/gm;
99
+ let match;
100
+ while ((match = methodRegex.exec(source)) !== null) {
101
+ const receiverVar = match[1];
102
+ const receiverType = match[2];
103
+ const name = match[3];
104
+ const paramsStr = match[5];
105
+ if (!receiverVar || !receiverType || !name || paramsStr === undefined)
106
+ continue;
107
+ const typeParams = match[4] ?? '';
108
+ const returnPart = (match[6] ?? '').trim();
109
+ const lineNumber = this.getLineNumber(source, match.index);
110
+ const docstring = this.getDocComment(lines, lineNumber - 1);
111
+ const parameters = this.parseGoParams(paramsStr);
112
+ const returnType = this.parseReturnType(returnPart);
113
+ const receiverFull = match[0].match(/\([^)]+\)/)?.[0] ?? `(${receiverVar} ${receiverType})`;
114
+ const signature = `func ${receiverFull} ${name}${typeParams}(${paramsStr})${returnPart ? ' ' + returnPart : ''}`;
115
+ elements.push({
116
+ kind: 'method',
117
+ name,
118
+ signature,
119
+ parameters,
120
+ returnType,
121
+ docstring,
122
+ filePath,
123
+ lineNumber,
124
+ parentClass: receiverType,
125
+ isExported: true,
126
+ isPublic: true,
127
+ imports,
128
+ packageName,
129
+ sourceContext: this.getSourceContext(lines, lineNumber)
130
+ });
131
+ }
132
+ }
133
+ extractTypes(source, lines, filePath, packageName, imports, elements) {
134
+ // Match: type TypeName struct/interface
135
+ const typeRegex = /^type\s+([A-Z][a-zA-Z0-9_]*)\s+(?:struct|interface)\s*\{/gm;
136
+ let match;
137
+ while ((match = typeRegex.exec(source)) !== null) {
138
+ const name = match[1];
139
+ if (!name)
140
+ continue;
141
+ const isInterface = match[0].includes('interface');
142
+ const lineNumber = this.getLineNumber(source, match.index);
143
+ const docstring = this.getDocComment(lines, lineNumber - 1);
144
+ const signature = `type ${name} ${isInterface ? 'interface' : 'struct'}`;
145
+ elements.push({
146
+ kind: 'class', // Using 'class' for Go types
147
+ name,
148
+ signature,
149
+ parameters: [],
150
+ docstring,
151
+ filePath,
152
+ lineNumber,
153
+ isExported: true,
154
+ isPublic: true,
155
+ imports,
156
+ packageName,
157
+ sourceContext: this.getSourceContext(lines, lineNumber, 15)
158
+ });
159
+ }
160
+ }
161
+ parseGoParams(paramsStr) {
162
+ if (!paramsStr.trim())
163
+ return [];
164
+ const params = [];
165
+ // Split by comma but respect parentheses
166
+ const parts = this.splitParams(paramsStr);
167
+ for (const part of parts) {
168
+ const trimmed = part.trim();
169
+ if (!trimmed)
170
+ continue;
171
+ // Format: "name type" or "name, name2 type" or just "type"
172
+ const lastSpace = trimmed.lastIndexOf(' ');
173
+ if (lastSpace > 0) {
174
+ const type = trimmed.slice(lastSpace + 1);
175
+ const names = trimmed.slice(0, lastSpace).split(',').map(n => n.trim());
176
+ for (const name of names) {
177
+ if (name)
178
+ params.push({ name, type });
179
+ }
180
+ }
181
+ else {
182
+ // Just a type (rare in Go)
183
+ params.push({ name: trimmed, type: undefined });
184
+ }
185
+ }
186
+ return params;
187
+ }
188
+ splitParams(str) {
189
+ const parts = [];
190
+ let depth = 0;
191
+ let current = '';
192
+ for (const char of str) {
193
+ if (char === '(' || char === '[' || char === '{')
194
+ depth++;
195
+ else if (char === ')' || char === ']' || char === '}')
196
+ depth--;
197
+ else if (char === ',' && depth === 0) {
198
+ parts.push(current);
199
+ current = '';
200
+ continue;
201
+ }
202
+ current += char;
203
+ }
204
+ if (current.trim())
205
+ parts.push(current);
206
+ return parts;
207
+ }
208
+ parseReturnType(returnPart) {
209
+ const trimmed = returnPart.trim();
210
+ if (!trimmed)
211
+ return undefined;
212
+ // Remove leading/trailing braces or parens
213
+ if (trimmed.startsWith('(') && trimmed.endsWith(')')) {
214
+ return trimmed;
215
+ }
216
+ return trimmed || undefined;
217
+ }
218
+ getLineNumber(source, index) {
219
+ return source.slice(0, index).split('\n').length;
220
+ }
221
+ getDocComment(lines, lineIndex) {
222
+ const comments = [];
223
+ let i = lineIndex - 1;
224
+ while (i >= 0) {
225
+ const lineContent = lines[i];
226
+ if (!lineContent)
227
+ break;
228
+ const line = lineContent.trim();
229
+ if (line.startsWith('//')) {
230
+ comments.unshift(line.replace(/^\/\/\s?/, ''));
231
+ i--;
232
+ }
233
+ else if (line === '') {
234
+ i--;
235
+ }
236
+ else {
237
+ break;
238
+ }
239
+ }
240
+ // Also check for /* */ style
241
+ if (comments.length === 0 && lineIndex > 0) {
242
+ const prevLine = lines[lineIndex - 1];
243
+ if (prevLine?.includes('*/')) {
244
+ // Find start of block comment
245
+ let start = lineIndex - 1;
246
+ while (start >= 0 && !lines[start]?.includes('/*')) {
247
+ start--;
248
+ }
249
+ if (start >= 0 && lines[start]) {
250
+ const block = lines.slice(start, lineIndex).join('\n');
251
+ const cleaned = block
252
+ .replace(/\/\*\*?/, '')
253
+ .replace(/\*\//, '')
254
+ .split('\n')
255
+ .map(l => l.trim().replace(/^\*\s?/, ''))
256
+ .filter(Boolean)
257
+ .join('\n');
258
+ return cleaned || undefined;
259
+ }
260
+ }
261
+ }
262
+ return comments.length > 0 ? comments.join('\n') : undefined;
263
+ }
264
+ getSourceContext(lines, lineNumber, context = 5) {
265
+ const start = Math.max(0, lineNumber - context - 1);
266
+ const end = Math.min(lines.length, lineNumber + context);
267
+ return lines.slice(start, end).join('\n');
268
+ }
269
+ }
@@ -0,0 +1,21 @@
1
+ import { ScanResult } from './types.js';
2
+ export * from './types.js';
3
+ export * from './content-type.js';
4
+ export interface ScanOptions {
5
+ include?: string[];
6
+ exclude?: string[];
7
+ onProgress?: (current: number, total: number, file: string) => void;
8
+ }
9
+ export interface ScanAllResult {
10
+ files: ScanResult[];
11
+ totalElements: number;
12
+ errors: string[];
13
+ }
14
+ /**
15
+ * Scan a directory for all API elements
16
+ */
17
+ export declare function scanDirectory(dir: string, options?: ScanOptions): Promise<ScanAllResult>;
18
+ /**
19
+ * Scan a single file
20
+ */
21
+ export declare function scanFile(filePath: string): Promise<ScanResult>;
@@ -0,0 +1,137 @@
1
+ import { readdir } from 'fs/promises';
2
+ import { join, extname } from 'path';
3
+ import { PythonScanner } from './python.js';
4
+ import { TypeScriptScanner } from './typescript.js';
5
+ import { GoScanner } from './go.js';
6
+ import { RustScanner } from './rust.js';
7
+ export * from './types.js';
8
+ export * from './content-type.js';
9
+ // All available scanners
10
+ const SCANNERS = [
11
+ new PythonScanner(),
12
+ new TypeScriptScanner(),
13
+ new GoScanner(),
14
+ new RustScanner(),
15
+ ];
16
+ /**
17
+ * Get the appropriate scanner for a file
18
+ */
19
+ function getScannerForFile(filePath) {
20
+ for (const scanner of SCANNERS) {
21
+ if (scanner.canHandle(filePath)) {
22
+ return scanner;
23
+ }
24
+ }
25
+ return null;
26
+ }
27
+ /**
28
+ * Check if a path should be excluded based on patterns
29
+ */
30
+ function shouldExclude(filePath, excludePatterns) {
31
+ for (const pattern of excludePatterns) {
32
+ // Simple glob matching for common patterns
33
+ if (pattern.includes('**')) {
34
+ const parts = pattern.split('**');
35
+ if (parts.length === 2) {
36
+ const [prefix, suffix] = parts;
37
+ const prefixMatch = !prefix || filePath.includes(prefix.replace(/^\//, '').replace(/\/$/, ''));
38
+ const suffixMatch = !suffix || filePath.includes(suffix.replace(/^\//, '').replace(/\/$/, ''));
39
+ if (prefixMatch && suffixMatch)
40
+ return true;
41
+ }
42
+ }
43
+ else if (filePath.includes(pattern.replace(/\*/g, ''))) {
44
+ return true;
45
+ }
46
+ }
47
+ return false;
48
+ }
49
+ /**
50
+ * Check if a file matches include patterns
51
+ */
52
+ function shouldInclude(filePath, includePatterns) {
53
+ if (includePatterns.length === 0)
54
+ return true;
55
+ const ext = extname(filePath);
56
+ for (const pattern of includePatterns) {
57
+ // Match extension patterns like **/*.py
58
+ if (pattern.includes('*')) {
59
+ const extPattern = pattern.split('*').pop() || '';
60
+ if (ext === extPattern)
61
+ return true;
62
+ }
63
+ }
64
+ return false;
65
+ }
66
+ /**
67
+ * Recursively find all files in a directory
68
+ */
69
+ async function findFiles(dir, include, exclude) {
70
+ const files = [];
71
+ async function walk(currentDir) {
72
+ const entries = await readdir(currentDir, { withFileTypes: true });
73
+ for (const entry of entries) {
74
+ const fullPath = join(currentDir, entry.name);
75
+ if (shouldExclude(fullPath, exclude)) {
76
+ continue;
77
+ }
78
+ if (entry.isDirectory()) {
79
+ await walk(fullPath);
80
+ }
81
+ else if (entry.isFile()) {
82
+ if (shouldInclude(fullPath, include) && getScannerForFile(fullPath)) {
83
+ files.push(fullPath);
84
+ }
85
+ }
86
+ }
87
+ }
88
+ await walk(dir);
89
+ return files;
90
+ }
91
+ /**
92
+ * Scan a directory for all API elements
93
+ */
94
+ export async function scanDirectory(dir, options = {}) {
95
+ const include = options.include || ['**/*.py', '**/*.ts', '**/*.js', '**/*.go', '**/*.rs'];
96
+ const exclude = options.exclude || ['**/node_modules/**', '**/__pycache__/**', '**/dist/**'];
97
+ const files = await findFiles(dir, include, exclude);
98
+ const results = [];
99
+ const allErrors = [];
100
+ let totalElements = 0;
101
+ for (let i = 0; i < files.length; i++) {
102
+ const file = files[i];
103
+ options.onProgress?.(i + 1, files.length, file);
104
+ const scanner = getScannerForFile(file);
105
+ if (!scanner)
106
+ continue;
107
+ try {
108
+ const result = await scanner.scanFile(file);
109
+ results.push(result);
110
+ totalElements += result.elements.length;
111
+ allErrors.push(...result.errors);
112
+ }
113
+ catch (err) {
114
+ allErrors.push(`Error scanning ${file}: ${err}`);
115
+ }
116
+ }
117
+ return {
118
+ files: results,
119
+ totalElements,
120
+ errors: allErrors
121
+ };
122
+ }
123
+ /**
124
+ * Scan a single file
125
+ */
126
+ export async function scanFile(filePath) {
127
+ const scanner = getScannerForFile(filePath);
128
+ if (!scanner) {
129
+ return {
130
+ filePath,
131
+ language: 'python', // default
132
+ elements: [],
133
+ errors: [`No scanner available for ${filePath}`]
134
+ };
135
+ }
136
+ return scanner.scanFile(filePath);
137
+ }
@@ -0,0 +1,6 @@
1
+ import { Scanner, ScanResult } from './types.js';
2
+ export declare class PythonScanner implements Scanner {
3
+ languages: string[];
4
+ canHandle(filePath: string): boolean;
5
+ scanFile(filePath: string): Promise<ScanResult>;
6
+ }