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,57 @@
1
+ import { spawn } from 'child_process';
2
+ import { dirname, join } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ const PARSER_SCRIPT = join(__dirname, 'python_parser.py');
6
+ export class PythonScanner {
7
+ languages = ['python'];
8
+ canHandle(filePath) {
9
+ return filePath.endsWith('.py');
10
+ }
11
+ async scanFile(filePath) {
12
+ return new Promise((resolve) => {
13
+ const proc = spawn('python3', [PARSER_SCRIPT, filePath], {
14
+ stdio: ['ignore', 'pipe', 'pipe']
15
+ });
16
+ let stdout = '';
17
+ let stderr = '';
18
+ proc.stdout.on('data', (data) => {
19
+ stdout += data.toString();
20
+ });
21
+ proc.stderr.on('data', (data) => {
22
+ stderr += data.toString();
23
+ });
24
+ proc.on('close', (code) => {
25
+ if (code !== 0 || stderr) {
26
+ resolve({
27
+ filePath,
28
+ language: 'python',
29
+ elements: [],
30
+ errors: [stderr || `Python parser exited with code ${code}`]
31
+ });
32
+ return;
33
+ }
34
+ try {
35
+ const result = JSON.parse(stdout);
36
+ resolve(result);
37
+ }
38
+ catch (e) {
39
+ resolve({
40
+ filePath,
41
+ language: 'python',
42
+ elements: [],
43
+ errors: [`Failed to parse output: ${e}`]
44
+ });
45
+ }
46
+ });
47
+ proc.on('error', (err) => {
48
+ resolve({
49
+ filePath,
50
+ language: 'python',
51
+ elements: [],
52
+ errors: [`Failed to spawn Python: ${err.message}`]
53
+ });
54
+ });
55
+ });
56
+ }
57
+ }
@@ -0,0 +1,230 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Extract API elements from Python files using the ast module.
4
+ Called by the Node.js scanner via subprocess.
5
+ Outputs JSON to stdout.
6
+ """
7
+
8
+ import ast
9
+ import json
10
+ import sys
11
+ from typing import Any
12
+
13
+
14
+ def get_docstring(node: ast.AST) -> str | None:
15
+ """Extract docstring from a node if present."""
16
+ return ast.get_docstring(node)
17
+
18
+
19
+ def get_type_annotation(annotation: ast.AST | None) -> str | None:
20
+ """Convert type annotation AST to string."""
21
+ if annotation is None:
22
+ return None
23
+ return ast.unparse(annotation)
24
+
25
+
26
+ def get_default_value(default: ast.AST | None) -> str | None:
27
+ """Convert default value AST to string."""
28
+ if default is None:
29
+ return None
30
+ return ast.unparse(default)
31
+
32
+
33
+ def extract_parameters(args: ast.arguments) -> list[dict[str, Any]]:
34
+ """Extract parameters from function arguments."""
35
+ params = []
36
+
37
+ # Calculate defaults offset (defaults align to end of args)
38
+ num_args = len(args.args)
39
+ num_defaults = len(args.defaults)
40
+ defaults_offset = num_args - num_defaults
41
+
42
+ for i, arg in enumerate(args.args):
43
+ # Skip 'self' and 'cls' for methods
44
+ if arg.arg in ('self', 'cls'):
45
+ continue
46
+
47
+ param = {
48
+ 'name': arg.arg,
49
+ 'type': get_type_annotation(arg.annotation),
50
+ }
51
+
52
+ # Check if this arg has a default
53
+ default_idx = i - defaults_offset
54
+ if default_idx >= 0 and default_idx < len(args.defaults):
55
+ param['default'] = get_default_value(args.defaults[default_idx])
56
+
57
+ params.append(param)
58
+
59
+ # Handle *args
60
+ if args.vararg:
61
+ params.append({
62
+ 'name': f'*{args.vararg.arg}',
63
+ 'type': get_type_annotation(args.vararg.annotation),
64
+ })
65
+
66
+ # Handle keyword-only args
67
+ for i, arg in enumerate(args.kwonlyargs):
68
+ param = {
69
+ 'name': arg.arg,
70
+ 'type': get_type_annotation(arg.annotation),
71
+ }
72
+ if i < len(args.kw_defaults) and args.kw_defaults[i]:
73
+ param['default'] = get_default_value(args.kw_defaults[i])
74
+ params.append(param)
75
+
76
+ # Handle **kwargs
77
+ if args.kwarg:
78
+ params.append({
79
+ 'name': f'**{args.kwarg.arg}',
80
+ 'type': get_type_annotation(args.kwarg.annotation),
81
+ })
82
+
83
+ return params
84
+
85
+
86
+ def build_signature(name: str, args: ast.arguments, returns: ast.AST | None, is_async: bool) -> str:
87
+ """Build the function signature as a string."""
88
+ params = []
89
+
90
+ for arg in args.args:
91
+ p = arg.arg
92
+ if arg.annotation:
93
+ p += f': {ast.unparse(arg.annotation)}'
94
+ params.append(p)
95
+
96
+ if args.vararg:
97
+ p = f'*{args.vararg.arg}'
98
+ if args.vararg.annotation:
99
+ p += f': {ast.unparse(args.vararg.annotation)}'
100
+ params.append(p)
101
+
102
+ for i, arg in enumerate(args.kwonlyargs):
103
+ p = arg.arg
104
+ if arg.annotation:
105
+ p += f': {ast.unparse(arg.annotation)}'
106
+ if i < len(args.kw_defaults) and args.kw_defaults[i]:
107
+ p += f' = {ast.unparse(args.kw_defaults[i])}'
108
+ params.append(p)
109
+
110
+ if args.kwarg:
111
+ p = f'**{args.kwarg.arg}'
112
+ if args.kwarg.annotation:
113
+ p += f': {ast.unparse(args.kwarg.annotation)}'
114
+ params.append(p)
115
+
116
+ sig = f"{'async ' if is_async else ''}def {name}({', '.join(params)})"
117
+ if returns:
118
+ sig += f' -> {ast.unparse(returns)}'
119
+
120
+ return sig
121
+
122
+
123
+ def extract_function(node: ast.FunctionDef | ast.AsyncFunctionDef, file_path: str, parent_class: str | None = None) -> dict[str, Any]:
124
+ """Extract a function or method."""
125
+ is_async = isinstance(node, ast.AsyncFunctionDef)
126
+ is_method = parent_class is not None
127
+ is_public = not node.name.startswith('_') or node.name.startswith('__') and node.name.endswith('__')
128
+
129
+ return {
130
+ 'kind': 'method' if is_method else 'function',
131
+ 'name': node.name,
132
+ 'signature': build_signature(node.name, node.args, node.returns, is_async),
133
+ 'parameters': extract_parameters(node.args),
134
+ 'returnType': get_type_annotation(node.returns),
135
+ 'docstring': get_docstring(node),
136
+ 'filePath': file_path,
137
+ 'lineNumber': node.lineno,
138
+ 'parentClass': parent_class,
139
+ 'isAsync': is_async,
140
+ 'isPublic': is_public,
141
+ }
142
+
143
+
144
+ def extract_class(node: ast.ClassDef, file_path: str) -> list[dict[str, Any]]:
145
+ """Extract a class and its methods."""
146
+ elements = []
147
+ is_public = not node.name.startswith('_')
148
+
149
+ # Add the class itself
150
+ bases = [ast.unparse(b) for b in node.bases]
151
+ signature = f"class {node.name}"
152
+ if bases:
153
+ signature += f"({', '.join(bases)})"
154
+
155
+ elements.append({
156
+ 'kind': 'class',
157
+ 'name': node.name,
158
+ 'signature': signature,
159
+ 'parameters': [], # Classes don't have parameters in signature
160
+ 'docstring': get_docstring(node),
161
+ 'filePath': file_path,
162
+ 'lineNumber': node.lineno,
163
+ 'isPublic': is_public,
164
+ })
165
+
166
+ # Extract methods
167
+ for item in node.body:
168
+ if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)):
169
+ # Skip private methods unless they're dunder methods
170
+ if item.name.startswith('_') and not (item.name.startswith('__') and item.name.endswith('__')):
171
+ continue
172
+ elements.append(extract_function(item, file_path, parent_class=node.name))
173
+
174
+ return elements
175
+
176
+
177
+ def scan_file(file_path: str) -> dict[str, Any]:
178
+ """Scan a Python file and extract all API elements."""
179
+ try:
180
+ with open(file_path, 'r', encoding='utf-8') as f:
181
+ source = f.read()
182
+
183
+ tree = ast.parse(source, filename=file_path)
184
+ except SyntaxError as e:
185
+ return {
186
+ 'filePath': file_path,
187
+ 'language': 'python',
188
+ 'elements': [],
189
+ 'errors': [f'Syntax error: {e}']
190
+ }
191
+ except Exception as e:
192
+ return {
193
+ 'filePath': file_path,
194
+ 'language': 'python',
195
+ 'elements': [],
196
+ 'errors': [str(e)]
197
+ }
198
+
199
+ elements = []
200
+
201
+ for node in ast.iter_child_nodes(tree):
202
+ # Top-level functions
203
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
204
+ # Skip private functions
205
+ if node.name.startswith('_') and not (node.name.startswith('__') and node.name.endswith('__')):
206
+ continue
207
+ elements.append(extract_function(node, file_path))
208
+
209
+ # Classes
210
+ elif isinstance(node, ast.ClassDef):
211
+ # Skip private classes
212
+ if node.name.startswith('_'):
213
+ continue
214
+ elements.extend(extract_class(node, file_path))
215
+
216
+ return {
217
+ 'filePath': file_path,
218
+ 'language': 'python',
219
+ 'elements': elements,
220
+ 'errors': []
221
+ }
222
+
223
+
224
+ if __name__ == '__main__':
225
+ if len(sys.argv) != 2:
226
+ print(json.dumps({'error': 'Usage: python_parser.py <file_path>'}))
227
+ sys.exit(1)
228
+
229
+ result = scan_file(sys.argv[1])
230
+ print(json.dumps(result, indent=2))
@@ -0,0 +1,23 @@
1
+ import { Scanner, ScanResult } from './types.js';
2
+ /**
3
+ * Scanner for Rust source files
4
+ * Extracts: pub functions, pub structs, pub enums, impl blocks, traits
5
+ */
6
+ export declare class RustScanner implements Scanner {
7
+ languages: string[];
8
+ canHandle(filePath: string): boolean;
9
+ scanFile(filePath: string): Promise<ScanResult>;
10
+ private inferCrateName;
11
+ private extractUseStatements;
12
+ private extractFunctions;
13
+ private isTopLevelFn;
14
+ private extractStructsAndEnums;
15
+ private extractTraits;
16
+ private extractImplMethods;
17
+ private extractBlock;
18
+ private parseRustParams;
19
+ private splitParams;
20
+ private getLineNumber;
21
+ private getDocComment;
22
+ private getSourceContext;
23
+ }
@@ -0,0 +1,304 @@
1
+ import { readFileSync } from 'fs';
2
+ /**
3
+ * Scanner for Rust source files
4
+ * Extracts: pub functions, pub structs, pub enums, impl blocks, traits
5
+ */
6
+ export class RustScanner {
7
+ languages = ['rust'];
8
+ canHandle(filePath) {
9
+ return /\.rs$/.test(filePath) && !filePath.includes('/tests/');
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
+ // Infer crate/module name from path
18
+ const packageName = this.inferCrateName(filePath);
19
+ // Extract use statements for context
20
+ const imports = this.extractUseStatements(source);
21
+ // Find all pub items
22
+ this.extractFunctions(source, lines, filePath, packageName, imports, elements);
23
+ this.extractStructsAndEnums(source, lines, filePath, packageName, imports, elements);
24
+ this.extractTraits(source, lines, filePath, packageName, imports, elements);
25
+ this.extractImplMethods(source, lines, filePath, packageName, imports, elements);
26
+ return {
27
+ filePath,
28
+ language: 'rust',
29
+ elements,
30
+ errors
31
+ };
32
+ }
33
+ catch (err) {
34
+ return {
35
+ filePath,
36
+ language: 'rust',
37
+ elements: [],
38
+ errors: [`Failed to parse: ${err}`]
39
+ };
40
+ }
41
+ }
42
+ inferCrateName(filePath) {
43
+ // Look for Cargo.toml parent or use directory name
44
+ const parts = filePath.split('/');
45
+ const srcIndex = parts.indexOf('src');
46
+ if (srcIndex > 0) {
47
+ return parts[srcIndex - 1];
48
+ }
49
+ return parts[parts.length - 2] || 'unknown';
50
+ }
51
+ extractUseStatements(source) {
52
+ const uses = [];
53
+ const regex = /^use\s+([^;]+);/gm;
54
+ let match;
55
+ while ((match = regex.exec(source)) !== null) {
56
+ uses.push(match[1]);
57
+ }
58
+ return uses;
59
+ }
60
+ extractFunctions(source, lines, filePath, packageName, imports, elements) {
61
+ // Match: pub fn function_name<generics>(params) -> ReturnType
62
+ // Also handles pub async fn, pub const fn, pub unsafe fn
63
+ const funcRegex = /^(\s*)(pub(?:\s+(?:async|const|unsafe))?\s+fn)\s+(\w+)\s*(<[^>]+>)?\s*\(([^)]*)\)\s*(->\s*[^{]+)?/gm;
64
+ let match;
65
+ while ((match = funcRegex.exec(source)) !== null) {
66
+ const indent = match[1];
67
+ // Skip if indented (inside impl block - handled separately)
68
+ if (indent.length > 0 && !this.isTopLevelFn(source, match.index))
69
+ continue;
70
+ const fnKeyword = match[2];
71
+ const name = match[3];
72
+ const generics = match[4] || '';
73
+ const paramsStr = match[5];
74
+ const returnPart = match[6]?.trim() || '';
75
+ const lineNumber = this.getLineNumber(source, match.index);
76
+ const docstring = this.getDocComment(lines, lineNumber - 1);
77
+ const parameters = this.parseRustParams(paramsStr);
78
+ const returnType = returnPart ? returnPart.replace(/^->\s*/, '').trim() : undefined;
79
+ const isAsync = fnKeyword.includes('async');
80
+ const signature = `${fnKeyword} ${name}${generics}(${paramsStr})${returnPart ? ' ' + returnPart : ''}`;
81
+ elements.push({
82
+ kind: 'function',
83
+ name,
84
+ signature,
85
+ parameters,
86
+ returnType,
87
+ docstring,
88
+ filePath,
89
+ lineNumber,
90
+ isAsync,
91
+ isExported: true,
92
+ isPublic: true,
93
+ imports,
94
+ packageName,
95
+ sourceContext: this.getSourceContext(lines, lineNumber)
96
+ });
97
+ }
98
+ }
99
+ isTopLevelFn(source, index) {
100
+ // Check if we're inside an impl block by counting brace balance
101
+ const before = source.slice(0, index);
102
+ const braceBalance = (before.match(/\{/g) || []).length - (before.match(/\}/g) || []).length;
103
+ return braceBalance === 0;
104
+ }
105
+ extractStructsAndEnums(source, lines, filePath, packageName, imports, elements) {
106
+ // Match: pub struct StructName<generics> or pub enum EnumName
107
+ const typeRegex = /^pub\s+(struct|enum)\s+(\w+)\s*(<[^>]+>)?/gm;
108
+ let match;
109
+ while ((match = typeRegex.exec(source)) !== null) {
110
+ const kind = match[1];
111
+ const name = match[2];
112
+ const generics = match[3] || '';
113
+ const lineNumber = this.getLineNumber(source, match.index);
114
+ const docstring = this.getDocComment(lines, lineNumber - 1);
115
+ const signature = `pub ${kind} ${name}${generics}`;
116
+ elements.push({
117
+ kind: 'class', // Using 'class' for Rust structs/enums
118
+ name,
119
+ signature,
120
+ parameters: [],
121
+ docstring,
122
+ filePath,
123
+ lineNumber,
124
+ isExported: true,
125
+ isPublic: true,
126
+ imports,
127
+ packageName,
128
+ sourceContext: this.getSourceContext(lines, lineNumber, 20)
129
+ });
130
+ }
131
+ }
132
+ extractTraits(source, lines, filePath, packageName, imports, elements) {
133
+ // Match: pub trait TraitName<generics>
134
+ const traitRegex = /^pub\s+trait\s+(\w+)\s*(<[^>]+>)?/gm;
135
+ let match;
136
+ while ((match = traitRegex.exec(source)) !== null) {
137
+ const name = match[1];
138
+ const generics = match[2] || '';
139
+ const lineNumber = this.getLineNumber(source, match.index);
140
+ const docstring = this.getDocComment(lines, lineNumber - 1);
141
+ const signature = `pub trait ${name}${generics}`;
142
+ elements.push({
143
+ kind: 'class', // Using 'class' for traits
144
+ name,
145
+ signature,
146
+ parameters: [],
147
+ docstring,
148
+ filePath,
149
+ lineNumber,
150
+ isExported: true,
151
+ isPublic: true,
152
+ imports,
153
+ packageName,
154
+ sourceContext: this.getSourceContext(lines, lineNumber, 15)
155
+ });
156
+ }
157
+ }
158
+ extractImplMethods(source, lines, filePath, packageName, imports, elements) {
159
+ // Find impl blocks and their methods
160
+ // Match: impl<generics> TypeName { ... }
161
+ // Or: impl<generics> Trait for TypeName { ... }
162
+ const implRegex = /impl\s*(<[^>]+>)?\s*(?:(\w+)\s+for\s+)?(\w+)(?:<[^>]+>)?\s*\{/g;
163
+ let implMatch;
164
+ while ((implMatch = implRegex.exec(source)) !== null) {
165
+ // implMatch[2] contains trait name if implementing a trait (for future use)
166
+ const typeName = implMatch[3];
167
+ const implStart = implMatch.index;
168
+ // Find the matching closing brace
169
+ const implBody = this.extractBlock(source, implStart + implMatch[0].length - 1);
170
+ if (!implBody)
171
+ continue;
172
+ // Extract pub fn methods from the impl body
173
+ const methodRegex = /^\s*(pub(?:\s+(?:async|const|unsafe))?\s+fn)\s+(\w+)\s*(<[^>]+>)?\s*\(([^)]*)\)\s*(->\s*[^{]+)?/gm;
174
+ let methodMatch;
175
+ while ((methodMatch = methodRegex.exec(implBody)) !== null) {
176
+ const fnKeyword = methodMatch[1];
177
+ const name = methodMatch[2];
178
+ const generics = methodMatch[3] || '';
179
+ const paramsStr = methodMatch[4];
180
+ const returnPart = methodMatch[5]?.trim() || '';
181
+ const bodyIndex = implBody.indexOf(methodMatch[0]);
182
+ const fullIndex = implStart + implMatch[0].length + bodyIndex;
183
+ const lineNumber = this.getLineNumber(source, fullIndex);
184
+ const docstring = this.getDocComment(lines, lineNumber - 1);
185
+ const parameters = this.parseRustParams(paramsStr);
186
+ const returnType = returnPart ? returnPart.replace(/^->\s*/, '').trim() : undefined;
187
+ const isAsync = fnKeyword.includes('async');
188
+ const signature = `${fnKeyword} ${name}${generics}(${paramsStr})${returnPart ? ' ' + returnPart : ''}`;
189
+ elements.push({
190
+ kind: 'method',
191
+ name,
192
+ signature,
193
+ parameters,
194
+ returnType,
195
+ docstring,
196
+ filePath,
197
+ lineNumber,
198
+ parentClass: typeName,
199
+ isAsync,
200
+ isExported: true,
201
+ isPublic: true,
202
+ imports,
203
+ packageName,
204
+ sourceContext: this.getSourceContext(lines, lineNumber)
205
+ });
206
+ }
207
+ }
208
+ }
209
+ extractBlock(source, startIndex) {
210
+ let depth = 0;
211
+ let i = startIndex;
212
+ let start = -1;
213
+ while (i < source.length) {
214
+ if (source[i] === '{') {
215
+ if (depth === 0)
216
+ start = i;
217
+ depth++;
218
+ }
219
+ else if (source[i] === '}') {
220
+ depth--;
221
+ if (depth === 0) {
222
+ return source.slice(start + 1, i);
223
+ }
224
+ }
225
+ i++;
226
+ }
227
+ return null;
228
+ }
229
+ parseRustParams(paramsStr) {
230
+ if (!paramsStr.trim())
231
+ return [];
232
+ const params = [];
233
+ const parts = this.splitParams(paramsStr);
234
+ for (const part of parts) {
235
+ const trimmed = part.trim();
236
+ if (!trimmed)
237
+ continue;
238
+ // Skip self, &self, &mut self
239
+ if (/^&?(?:mut\s+)?self$/.test(trimmed))
240
+ continue;
241
+ // Format: "name: Type" or "mut name: Type"
242
+ const colonIdx = trimmed.indexOf(':');
243
+ if (colonIdx > 0) {
244
+ let name = trimmed.slice(0, colonIdx).trim();
245
+ const type = trimmed.slice(colonIdx + 1).trim();
246
+ // Remove mut prefix from name
247
+ name = name.replace(/^mut\s+/, '');
248
+ params.push({ name, type });
249
+ }
250
+ }
251
+ return params;
252
+ }
253
+ splitParams(str) {
254
+ const parts = [];
255
+ let depth = 0;
256
+ let current = '';
257
+ for (const char of str) {
258
+ if (char === '<' || char === '(' || char === '[' || char === '{')
259
+ depth++;
260
+ else if (char === '>' || char === ')' || char === ']' || char === '}')
261
+ depth--;
262
+ else if (char === ',' && depth === 0) {
263
+ parts.push(current);
264
+ current = '';
265
+ continue;
266
+ }
267
+ current += char;
268
+ }
269
+ if (current.trim())
270
+ parts.push(current);
271
+ return parts;
272
+ }
273
+ getLineNumber(source, index) {
274
+ return source.slice(0, index).split('\n').length;
275
+ }
276
+ getDocComment(lines, lineIndex) {
277
+ const comments = [];
278
+ let i = lineIndex - 1;
279
+ while (i >= 0) {
280
+ const line = lines[i].trim();
281
+ // Rust doc comments: /// or //!
282
+ if (line.startsWith('///') || line.startsWith('//!')) {
283
+ comments.unshift(line.replace(/^\/\/[/!]\s?/, ''));
284
+ i--;
285
+ }
286
+ else if (line.startsWith('#[')) {
287
+ // Skip attributes
288
+ i--;
289
+ }
290
+ else if (line === '') {
291
+ i--;
292
+ }
293
+ else {
294
+ break;
295
+ }
296
+ }
297
+ return comments.length > 0 ? comments.join('\n') : undefined;
298
+ }
299
+ getSourceContext(lines, lineNumber, context = 5) {
300
+ const start = Math.max(0, lineNumber - context - 1);
301
+ const end = Math.min(lines.length, lineNumber + context);
302
+ return lines.slice(start, end).join('\n');
303
+ }
304
+ }
@@ -0,0 +1 @@
1
+ export {};