skrypt-ai 0.4.2 → 0.6.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 (159) hide show
  1. package/dist/auth/index.d.ts +13 -3
  2. package/dist/auth/index.js +101 -9
  3. package/dist/auth/keychain.d.ts +5 -0
  4. package/dist/auth/keychain.js +82 -0
  5. package/dist/auth/notices.d.ts +3 -0
  6. package/dist/auth/notices.js +42 -0
  7. package/dist/autofix/index.d.ts +0 -4
  8. package/dist/autofix/index.js +10 -24
  9. package/dist/capture/browser.d.ts +11 -0
  10. package/dist/capture/browser.js +173 -0
  11. package/dist/capture/diff.d.ts +23 -0
  12. package/dist/capture/diff.js +52 -0
  13. package/dist/capture/index.d.ts +23 -0
  14. package/dist/capture/index.js +210 -0
  15. package/dist/capture/naming.d.ts +17 -0
  16. package/dist/capture/naming.js +45 -0
  17. package/dist/capture/parser.d.ts +15 -0
  18. package/dist/capture/parser.js +80 -0
  19. package/dist/capture/types.d.ts +57 -0
  20. package/dist/capture/types.js +1 -0
  21. package/dist/cli.js +20 -3
  22. package/dist/commands/autofix.js +136 -120
  23. package/dist/commands/cron.js +58 -47
  24. package/dist/commands/deploy.js +123 -102
  25. package/dist/commands/generate.js +125 -7
  26. package/dist/commands/heal.d.ts +10 -0
  27. package/dist/commands/heal.js +201 -0
  28. package/dist/commands/i18n.js +146 -111
  29. package/dist/commands/import.d.ts +2 -0
  30. package/dist/commands/import.js +157 -0
  31. package/dist/commands/init.js +19 -7
  32. package/dist/commands/lint.js +50 -44
  33. package/dist/commands/llms-txt.js +59 -49
  34. package/dist/commands/login.js +63 -34
  35. package/dist/commands/mcp.js +6 -0
  36. package/dist/commands/monitor.js +13 -8
  37. package/dist/commands/qa.d.ts +2 -0
  38. package/dist/commands/qa.js +43 -0
  39. package/dist/commands/review-pr.js +108 -92
  40. package/dist/commands/sdk.js +128 -122
  41. package/dist/commands/security.d.ts +2 -0
  42. package/dist/commands/security.js +109 -0
  43. package/dist/commands/test.js +91 -92
  44. package/dist/commands/version.js +104 -75
  45. package/dist/commands/watch.js +130 -114
  46. package/dist/config/types.js +2 -2
  47. package/dist/context-hub/index.d.ts +23 -0
  48. package/dist/context-hub/index.js +179 -0
  49. package/dist/context-hub/mappings.d.ts +8 -0
  50. package/dist/context-hub/mappings.js +55 -0
  51. package/dist/context-hub/types.d.ts +33 -0
  52. package/dist/context-hub/types.js +1 -0
  53. package/dist/generator/generator.js +39 -6
  54. package/dist/generator/types.d.ts +7 -0
  55. package/dist/generator/writer.d.ts +3 -1
  56. package/dist/generator/writer.js +36 -7
  57. package/dist/importers/confluence.d.ts +5 -0
  58. package/dist/importers/confluence.js +137 -0
  59. package/dist/importers/detect.d.ts +20 -0
  60. package/dist/importers/detect.js +121 -0
  61. package/dist/importers/docusaurus.d.ts +5 -0
  62. package/dist/importers/docusaurus.js +279 -0
  63. package/dist/importers/gitbook.d.ts +5 -0
  64. package/dist/importers/gitbook.js +189 -0
  65. package/dist/importers/github.d.ts +8 -0
  66. package/dist/importers/github.js +99 -0
  67. package/dist/importers/index.d.ts +15 -0
  68. package/dist/importers/index.js +30 -0
  69. package/dist/importers/markdown.d.ts +6 -0
  70. package/dist/importers/markdown.js +105 -0
  71. package/dist/importers/mintlify.d.ts +5 -0
  72. package/dist/importers/mintlify.js +172 -0
  73. package/dist/importers/notion.d.ts +5 -0
  74. package/dist/importers/notion.js +174 -0
  75. package/dist/importers/readme.d.ts +5 -0
  76. package/dist/importers/readme.js +184 -0
  77. package/dist/importers/transform.d.ts +90 -0
  78. package/dist/importers/transform.js +457 -0
  79. package/dist/importers/types.d.ts +37 -0
  80. package/dist/importers/types.js +1 -0
  81. package/dist/llm/anthropic-client.d.ts +1 -0
  82. package/dist/llm/anthropic-client.js +3 -1
  83. package/dist/llm/index.d.ts +6 -4
  84. package/dist/llm/index.js +76 -261
  85. package/dist/llm/openai-client.d.ts +1 -0
  86. package/dist/llm/openai-client.js +7 -2
  87. package/dist/plugins/index.js +7 -0
  88. package/dist/qa/checks.d.ts +10 -0
  89. package/dist/qa/checks.js +492 -0
  90. package/dist/qa/fixes.d.ts +30 -0
  91. package/dist/qa/fixes.js +277 -0
  92. package/dist/qa/index.d.ts +29 -0
  93. package/dist/qa/index.js +187 -0
  94. package/dist/qa/types.d.ts +24 -0
  95. package/dist/qa/types.js +1 -0
  96. package/dist/scanner/csharp.d.ts +23 -0
  97. package/dist/scanner/csharp.js +421 -0
  98. package/dist/scanner/index.js +53 -26
  99. package/dist/scanner/java.d.ts +39 -0
  100. package/dist/scanner/java.js +318 -0
  101. package/dist/scanner/kotlin.d.ts +23 -0
  102. package/dist/scanner/kotlin.js +389 -0
  103. package/dist/scanner/php.d.ts +57 -0
  104. package/dist/scanner/php.js +351 -0
  105. package/dist/scanner/python.js +17 -0
  106. package/dist/scanner/ruby.d.ts +36 -0
  107. package/dist/scanner/ruby.js +431 -0
  108. package/dist/scanner/swift.d.ts +25 -0
  109. package/dist/scanner/swift.js +392 -0
  110. package/dist/scanner/types.d.ts +1 -1
  111. package/dist/template/content/docs/_navigation.json +46 -0
  112. package/dist/template/content/docs/_sidebars.json +684 -0
  113. package/dist/template/content/docs/core.md +4544 -0
  114. package/dist/template/content/docs/index.mdx +89 -0
  115. package/dist/template/content/docs/integrations.md +1158 -0
  116. package/dist/template/content/docs/llms-full.md +403 -0
  117. package/dist/template/content/docs/llms.txt +4588 -0
  118. package/dist/template/content/docs/other.md +10379 -0
  119. package/dist/template/content/docs/tools.md +746 -0
  120. package/dist/template/content/docs/types.md +531 -0
  121. package/dist/template/docs.json +13 -11
  122. package/dist/template/mdx-components.tsx +27 -2
  123. package/dist/template/package.json +6 -0
  124. package/dist/template/public/search-index.json +1 -1
  125. package/dist/template/scripts/build-search-index.mjs +149 -13
  126. package/dist/template/src/app/api/chat/route.ts +83 -128
  127. package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
  128. package/dist/template/src/app/docs/llms-full.md +151 -4
  129. package/dist/template/src/app/docs/llms.txt +2464 -847
  130. package/dist/template/src/app/docs/page.mdx +48 -38
  131. package/dist/template/src/app/layout.tsx +3 -1
  132. package/dist/template/src/app/page.tsx +22 -8
  133. package/dist/template/src/components/ai-chat.tsx +73 -64
  134. package/dist/template/src/components/breadcrumbs.tsx +21 -23
  135. package/dist/template/src/components/copy-button.tsx +13 -9
  136. package/dist/template/src/components/copy-page-button.tsx +54 -0
  137. package/dist/template/src/components/docs-layout.tsx +37 -25
  138. package/dist/template/src/components/header.tsx +51 -10
  139. package/dist/template/src/components/mdx/card.tsx +17 -3
  140. package/dist/template/src/components/mdx/code-block.tsx +13 -9
  141. package/dist/template/src/components/mdx/code-group.tsx +13 -8
  142. package/dist/template/src/components/mdx/heading.tsx +15 -2
  143. package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
  144. package/dist/template/src/components/mdx/index.tsx +2 -0
  145. package/dist/template/src/components/mdx/mermaid.tsx +110 -0
  146. package/dist/template/src/components/mdx/screenshot.tsx +150 -0
  147. package/dist/template/src/components/scroll-to-hash.tsx +48 -0
  148. package/dist/template/src/components/sidebar.tsx +12 -18
  149. package/dist/template/src/components/table-of-contents.tsx +9 -0
  150. package/dist/template/src/lib/highlight.ts +3 -88
  151. package/dist/template/src/lib/navigation.ts +159 -0
  152. package/dist/template/src/lib/search-types.ts +4 -1
  153. package/dist/template/src/lib/search.ts +30 -7
  154. package/dist/template/src/styles/globals.css +17 -6
  155. package/dist/utils/files.d.ts +9 -1
  156. package/dist/utils/files.js +59 -10
  157. package/dist/utils/validation.d.ts +0 -3
  158. package/dist/utils/validation.js +0 -26
  159. package/package.json +5 -1
@@ -0,0 +1,421 @@
1
+ import { readFileSync } from 'fs';
2
+ /**
3
+ * Scanner for C# source files
4
+ * Extracts: public classes, interfaces, enums, structs, records, methods
5
+ */
6
+ export class CSharpScanner {
7
+ languages = ['csharp'];
8
+ canHandle(filePath) {
9
+ return (/\.cs$/.test(filePath) &&
10
+ !filePath.endsWith('Test.cs') &&
11
+ !filePath.endsWith('Tests.cs') &&
12
+ !filePath.includes('/Tests/') &&
13
+ !filePath.includes('/test/') &&
14
+ !filePath.includes('\\Tests\\') &&
15
+ !filePath.includes('\\test\\'));
16
+ }
17
+ async scanFile(filePath) {
18
+ try {
19
+ const source = readFileSync(filePath, 'utf-8');
20
+ const elements = [];
21
+ const errors = [];
22
+ const lines = source.split('\n');
23
+ // Extract namespace
24
+ const namespaceMatch = source.match(/^namespace\s+([\w.]+)/m);
25
+ const packageName = namespaceMatch?.[1] ?? 'unknown';
26
+ // Extract using statements for context
27
+ const imports = this.extractImports(source);
28
+ // Find all public types and methods
29
+ this.extractTypes(source, lines, filePath, packageName, imports, elements);
30
+ this.extractMethods(source, lines, filePath, packageName, imports, elements);
31
+ return {
32
+ filePath,
33
+ language: 'csharp',
34
+ elements,
35
+ errors
36
+ };
37
+ }
38
+ catch (err) {
39
+ return {
40
+ filePath,
41
+ language: 'csharp',
42
+ elements: [],
43
+ errors: [`Failed to parse: ${err}`]
44
+ };
45
+ }
46
+ }
47
+ extractImports(source) {
48
+ const imports = [];
49
+ const regex = /^using\s+([^;]+);/gm;
50
+ let match;
51
+ while ((match = regex.exec(source)) !== null) {
52
+ if (match[1])
53
+ imports.push(match[1]);
54
+ }
55
+ return imports;
56
+ }
57
+ extractTypes(source, lines, filePath, packageName, imports, elements) {
58
+ // Match: public class/interface/enum/struct/record TypeName
59
+ // Optionally with generic params, base class, interfaces
60
+ const typeRegex = /^[ \t]*public\s+(?:(?:abstract|sealed|static|partial)\s+)*(?:(class|interface|enum|struct|record))\s+(\w+)\s*/gm;
61
+ let match;
62
+ while ((match = typeRegex.exec(source)) !== null) {
63
+ const kind = match[1];
64
+ const name = match[2];
65
+ if (!kind || !name)
66
+ continue;
67
+ // Extract generics with depth counting to handle nested generics
68
+ const afterName = match.index + match[0].length;
69
+ const generics = this.extractGenerics(source, afterName);
70
+ const lineNumber = this.getLineNumber(source, match.index);
71
+ const docstring = this.getDocComment(lines, lineNumber - 1);
72
+ // For records with primary constructor params, extract them
73
+ let parameters = [];
74
+ if (kind === 'record') {
75
+ const recordParamMatch = source.slice(match.index).match(/public\s+(?:(?:abstract|sealed|static|partial)\s+)*record\s+\w+\s*(?:<[^>]+>)?\s*\(([^)]*)\)/);
76
+ if (recordParamMatch?.[1]) {
77
+ parameters = this.parseCSharpParams(recordParamMatch[1]);
78
+ }
79
+ }
80
+ const signature = `public ${kind} ${name}${generics}`;
81
+ elements.push({
82
+ kind: 'class', // Using 'class' for all C# types
83
+ name,
84
+ signature,
85
+ parameters,
86
+ docstring,
87
+ filePath,
88
+ lineNumber,
89
+ isExported: true,
90
+ isPublic: true,
91
+ imports,
92
+ packageName,
93
+ sourceContext: this.getSourceContext(lines, lineNumber, 15)
94
+ });
95
+ }
96
+ }
97
+ extractMethods(source, lines, filePath, packageName, imports, elements) {
98
+ // We need to find which class each method belongs to.
99
+ // First, find all class/struct/record blocks and their ranges.
100
+ const classRanges = this.findClassRanges(source);
101
+ // Match public methods (instance or static, sync or async)
102
+ // Patterns:
103
+ // public ReturnType MethodName(params)
104
+ // public static ReturnType MethodName(params)
105
+ // public async Task<T> MethodName(params)
106
+ // public override ReturnType MethodName(params)
107
+ // public virtual ReturnType MethodName(params)
108
+ // Exclude constructors (name matches class name) — we include them as methods
109
+ // Exclude property accessors, event handlers
110
+ const methodRegex = /^[ \t]*public\s+((?:(?:static|async|virtual|override|abstract|sealed|new)\s+)*)(\w+)/gm;
111
+ let match;
112
+ while ((match = methodRegex.exec(source)) !== null) {
113
+ const modifiers = match[1]?.trim() || '';
114
+ const baseReturnType = match[2];
115
+ if (!baseReturnType)
116
+ continue;
117
+ // After the base return type, extract any generics with depth counting
118
+ let pos = match.index + match[0].length;
119
+ const returnGenerics = this.extractGenerics(source, pos);
120
+ pos += returnGenerics.length;
121
+ const returnType = baseReturnType + returnGenerics;
122
+ // Skip whitespace
123
+ while (pos < source.length && (source[pos] === ' ' || source[pos] === '\t'))
124
+ pos++;
125
+ // Next token should be the method name (identifier)
126
+ const nameMatch = source.slice(pos).match(/^(\w+)\s*\(/);
127
+ if (!nameMatch || !nameMatch[1])
128
+ continue;
129
+ const name = nameMatch[1];
130
+ pos += nameMatch[0].length - 1; // position at '('
131
+ // Extract params up to the matching closing paren
132
+ const paramsStart = pos + 1;
133
+ let parenDepth = 1;
134
+ let parenPos = paramsStart;
135
+ while (parenPos < source.length && parenDepth > 0) {
136
+ if (source[parenPos] === '(')
137
+ parenDepth++;
138
+ else if (source[parenPos] === ')')
139
+ parenDepth--;
140
+ parenPos++;
141
+ }
142
+ const paramsStr = source.slice(paramsStart, parenPos - 1);
143
+ if (paramsStr === undefined)
144
+ continue;
145
+ // Skip type declarations (class, interface, enum, struct, record)
146
+ if (returnType === 'class' ||
147
+ returnType === 'interface' ||
148
+ returnType === 'enum' ||
149
+ returnType === 'struct' ||
150
+ returnType === 'record') {
151
+ continue;
152
+ }
153
+ const lineNumber = this.getLineNumber(source, match.index);
154
+ const docstring = this.getDocComment(lines, lineNumber - 1);
155
+ const parameters = this.parseCSharpParams(paramsStr);
156
+ const isAsync = modifiers.includes('async');
157
+ // Find parent class
158
+ const parentClass = this.findParentClass(match.index, classRanges);
159
+ const fullModifiers = modifiers ? `${modifiers} ` : '';
160
+ const signature = `public ${fullModifiers}${returnType} ${name}(${paramsStr})`;
161
+ elements.push({
162
+ kind: 'method',
163
+ name,
164
+ signature,
165
+ parameters,
166
+ returnType: returnType || undefined,
167
+ docstring,
168
+ filePath,
169
+ lineNumber,
170
+ parentClass,
171
+ isAsync,
172
+ isExported: true,
173
+ isPublic: true,
174
+ imports,
175
+ packageName,
176
+ sourceContext: this.getSourceContext(lines, lineNumber)
177
+ });
178
+ }
179
+ // Also extract interface method signatures (no body, just declarations)
180
+ this.extractInterfaceMethods(source, lines, filePath, packageName, imports, elements, classRanges);
181
+ }
182
+ extractInterfaceMethods(source, lines, filePath, packageName, imports, elements, classRanges) {
183
+ // Interface methods don't have access modifiers in C#
184
+ // They look like: ReturnType MethodName(params);
185
+ // We only extract these inside interface blocks
186
+ const interfaceRanges = classRanges.filter(r => r.kind === 'interface');
187
+ for (const range of interfaceRanges) {
188
+ const body = source.slice(range.bodyStart, range.bodyEnd);
189
+ const methodRegex = /^[ \t]*(\w+)/gm;
190
+ let match;
191
+ while ((match = methodRegex.exec(body)) !== null) {
192
+ const baseReturnType = match[1];
193
+ if (!baseReturnType)
194
+ continue;
195
+ // Skip lines that look like properties or non-method declarations
196
+ if (baseReturnType === 'get' || baseReturnType === 'set' || baseReturnType === '//' || baseReturnType === '///')
197
+ continue;
198
+ // Extract generics for return type
199
+ let pos = match.index + match[0].length;
200
+ const returnGenerics = this.extractGenerics(body, pos);
201
+ pos += returnGenerics.length;
202
+ const returnType = baseReturnType + returnGenerics;
203
+ // Skip whitespace
204
+ while (pos < body.length && (body[pos] === ' ' || body[pos] === '\t'))
205
+ pos++;
206
+ // Next should be method name followed by '('
207
+ const nameMatch = body.slice(pos).match(/^(\w+)\s*\(/);
208
+ if (!nameMatch || !nameMatch[1])
209
+ continue;
210
+ const name = nameMatch[1];
211
+ pos += nameMatch[0].length - 1; // position at '('
212
+ // Extract params
213
+ const paramsStart = pos + 1;
214
+ let parenDepth = 1;
215
+ let parenPos = paramsStart;
216
+ while (parenPos < body.length && parenDepth > 0) {
217
+ if (body[parenPos] === '(')
218
+ parenDepth++;
219
+ else if (body[parenPos] === ')')
220
+ parenDepth--;
221
+ parenPos++;
222
+ }
223
+ const paramsStr = body.slice(paramsStart, parenPos - 1);
224
+ // Must end with semicolon (interface method declaration)
225
+ const afterParen = body.slice(parenPos - 1).match(/^\)\s*;/);
226
+ if (!afterParen)
227
+ continue;
228
+ if (paramsStr === undefined)
229
+ continue;
230
+ const absoluteIndex = range.bodyStart + match.index;
231
+ const lineNumber = this.getLineNumber(source, absoluteIndex);
232
+ const docstring = this.getDocComment(lines, lineNumber - 1);
233
+ const parameters = this.parseCSharpParams(paramsStr);
234
+ const signature = `${returnType} ${name}(${paramsStr})`;
235
+ elements.push({
236
+ kind: 'method',
237
+ name,
238
+ signature,
239
+ parameters,
240
+ returnType: returnType || undefined,
241
+ docstring,
242
+ filePath,
243
+ lineNumber,
244
+ parentClass: range.name,
245
+ isAsync: false,
246
+ isExported: true,
247
+ isPublic: true,
248
+ imports,
249
+ packageName,
250
+ sourceContext: this.getSourceContext(lines, lineNumber)
251
+ });
252
+ }
253
+ }
254
+ }
255
+ findClassRanges(source) {
256
+ const ranges = [];
257
+ const typeRegex = /^[ \t]*public\s+(?:(?:abstract|sealed|static|partial)\s+)*(?:(class|interface|enum|struct|record))\s+(\w+)/gm;
258
+ let match;
259
+ while ((match = typeRegex.exec(source)) !== null) {
260
+ const kind = match[1];
261
+ const name = match[2];
262
+ if (!kind || !name)
263
+ continue;
264
+ // Find the opening brace after this match
265
+ // But first check if a semicolon comes before the brace (e.g., single-line records)
266
+ const afterMatchStart = match.index + match[0].length;
267
+ const afterMatch = source.indexOf('{', afterMatchStart);
268
+ if (afterMatch === -1)
269
+ continue;
270
+ const semicolonBefore = source.indexOf(';', afterMatchStart);
271
+ if (semicolonBefore !== -1 && semicolonBefore < afterMatch)
272
+ continue;
273
+ // Find matching closing brace
274
+ const bodyEnd = this.findMatchingBrace(source, afterMatch);
275
+ if (bodyEnd === -1)
276
+ continue;
277
+ ranges.push({
278
+ name,
279
+ kind,
280
+ start: match.index,
281
+ bodyStart: afterMatch + 1,
282
+ bodyEnd
283
+ });
284
+ }
285
+ return ranges;
286
+ }
287
+ findMatchingBrace(source, openIndex) {
288
+ let depth = 0;
289
+ for (let i = openIndex; i < source.length; i++) {
290
+ if (source[i] === '{')
291
+ depth++;
292
+ else if (source[i] === '}') {
293
+ depth--;
294
+ if (depth === 0)
295
+ return i;
296
+ }
297
+ }
298
+ return -1;
299
+ }
300
+ findParentClass(index, classRanges) {
301
+ for (const range of classRanges) {
302
+ if (index > range.bodyStart && index < range.bodyEnd) {
303
+ return range.name;
304
+ }
305
+ }
306
+ return undefined;
307
+ }
308
+ parseCSharpParams(paramsStr) {
309
+ if (!paramsStr.trim())
310
+ return [];
311
+ const params = [];
312
+ const parts = this.splitParams(paramsStr);
313
+ for (const part of parts) {
314
+ const trimmed = part.trim();
315
+ if (!trimmed)
316
+ continue;
317
+ // Remove parameter modifiers: ref, out, in, params, this
318
+ let cleaned = trimmed.replace(/^(?:ref|out|in|params|this)\s+/, '');
319
+ // Format: "Type name" or "Type name = default"
320
+ const defaultMatch = cleaned.match(/^(.+?)\s*=\s*(.+)$/);
321
+ let defaultValue;
322
+ if (defaultMatch) {
323
+ cleaned = defaultMatch[1] ?? cleaned;
324
+ defaultValue = defaultMatch[2];
325
+ }
326
+ // Split into type and name: last word is name, rest is type
327
+ const lastSpace = cleaned.lastIndexOf(' ');
328
+ if (lastSpace > 0) {
329
+ const type = cleaned.slice(0, lastSpace).trim();
330
+ const name = cleaned.slice(lastSpace + 1).trim();
331
+ params.push({ name, type, default: defaultValue });
332
+ }
333
+ else {
334
+ // Just a name or type
335
+ params.push({ name: cleaned, type: undefined });
336
+ }
337
+ }
338
+ return params;
339
+ }
340
+ splitParams(str) {
341
+ const parts = [];
342
+ let depth = 0;
343
+ let current = '';
344
+ for (const char of str) {
345
+ if (char === '<' || char === '(' || char === '[' || char === '{')
346
+ depth++;
347
+ else if (char === '>' || char === ')' || char === ']' || char === '}')
348
+ depth--;
349
+ else if (char === ',' && depth === 0) {
350
+ parts.push(current);
351
+ current = '';
352
+ continue;
353
+ }
354
+ current += char;
355
+ }
356
+ if (current.trim())
357
+ parts.push(current);
358
+ return parts;
359
+ }
360
+ getLineNumber(source, index) {
361
+ return source.slice(0, index).split('\n').length;
362
+ }
363
+ getDocComment(lines, lineIndex) {
364
+ const comments = [];
365
+ let i = lineIndex - 1;
366
+ while (i >= 0) {
367
+ const lineContent = lines[i];
368
+ if (!lineContent)
369
+ break;
370
+ const line = lineContent.trim();
371
+ if (line.startsWith('///')) {
372
+ // XML doc comment line
373
+ let content = line.replace(/^\/\/\/\s?/, '');
374
+ // Strip XML tags like <summary>, </summary>, <param>, <returns>
375
+ content = content
376
+ .replace(/<\/?summary>/g, '')
377
+ .replace(/<\/?remarks>/g, '')
378
+ .replace(/<\/?returns>/g, '')
379
+ .replace(/<param\s+name="[^"]*">/g, '')
380
+ .replace(/<\/param>/g, '')
381
+ .trim();
382
+ if (content) {
383
+ comments.unshift(content);
384
+ }
385
+ i--;
386
+ }
387
+ else if (line === '') {
388
+ i--;
389
+ }
390
+ else if (line.startsWith('[') && line.endsWith(']')) {
391
+ i--;
392
+ }
393
+ else {
394
+ break;
395
+ }
396
+ }
397
+ return comments.length > 0 ? comments.join('\n') : undefined;
398
+ }
399
+ extractGenerics(source, startIndex) {
400
+ if (startIndex >= source.length || source[startIndex] !== '<')
401
+ return '';
402
+ let depth = 0;
403
+ let i = startIndex;
404
+ while (i < source.length) {
405
+ if (source[i] === '<')
406
+ depth++;
407
+ else if (source[i] === '>') {
408
+ depth--;
409
+ if (depth === 0)
410
+ return source.slice(startIndex, i + 1);
411
+ }
412
+ i++;
413
+ }
414
+ return '';
415
+ }
416
+ getSourceContext(lines, lineNumber, context = 5) {
417
+ const start = Math.max(0, lineNumber - context - 1);
418
+ const end = Math.min(lines.length, lineNumber + context);
419
+ return lines.slice(start, end).join('\n');
420
+ }
421
+ }
@@ -1,9 +1,15 @@
1
1
  import { readdir, stat } from 'fs/promises';
2
- import { join, extname, parse as parsePath } from 'path';
2
+ import { join, parse as parsePath } from 'path';
3
3
  import { PythonScanner } from './python.js';
4
4
  import { TypeScriptScanner } from './typescript.js';
5
5
  import { GoScanner } from './go.js';
6
6
  import { RustScanner } from './rust.js';
7
+ import { JavaScanner } from './java.js';
8
+ import { CSharpScanner } from './csharp.js';
9
+ import { PHPScanner } from './php.js';
10
+ import { KotlinScanner } from './kotlin.js';
11
+ import { SwiftScanner } from './swift.js';
12
+ import { RubyScanner } from './ruby.js';
7
13
  export * from './types.js';
8
14
  export * from './content-type.js';
9
15
  // All available scanners
@@ -12,6 +18,12 @@ const SCANNERS = [
12
18
  new TypeScriptScanner(),
13
19
  new GoScanner(),
14
20
  new RustScanner(),
21
+ new JavaScanner(),
22
+ new CSharpScanner(),
23
+ new PHPScanner(),
24
+ new KotlinScanner(),
25
+ new SwiftScanner(),
26
+ new RubyScanner(),
15
27
  ];
16
28
  /**
17
29
  * Get the appropriate scanner for a file
@@ -24,25 +36,30 @@ function getScannerForFile(filePath) {
24
36
  }
25
37
  return null;
26
38
  }
39
+ /**
40
+ * Convert a glob pattern to a RegExp for proper matching.
41
+ * Supports: ** (any path), * (any segment), ? (any char)
42
+ */
43
+ function globToRegex(pattern) {
44
+ // Normalize separators
45
+ let re = pattern.replace(/\\/g, '/');
46
+ // Escape regex special chars except * and ?
47
+ re = re.replace(/[.+^${}()|[\]\\]/g, '\\$&');
48
+ // Convert glob tokens to regex
49
+ re = re.replace(/\*\*/g, '\0GLOBSTAR\0');
50
+ re = re.replace(/\*/g, '[^/]*');
51
+ re = re.replace(/\?/g, '[^/]');
52
+ re = re.replace(/\0GLOBSTAR\0/g, '.*');
53
+ return new RegExp(`(^|/)${re}($|/)`);
54
+ }
27
55
  /**
28
56
  * Check if a path should be excluded based on patterns
29
57
  */
30
58
  function shouldExclude(filePath, excludePatterns) {
59
+ const normalized = filePath.replace(/\\/g, '/');
31
60
  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, ''))) {
61
+ if (globToRegex(pattern).test(normalized))
44
62
  return true;
45
- }
46
63
  }
47
64
  return false;
48
65
  }
@@ -52,31 +69,39 @@ function shouldExclude(filePath, excludePatterns) {
52
69
  function shouldInclude(filePath, includePatterns) {
53
70
  if (includePatterns.length === 0)
54
71
  return true;
55
- const ext = extname(filePath);
72
+ const normalized = filePath.replace(/\\/g, '/');
56
73
  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
- }
74
+ if (globToRegex(pattern).test(normalized))
75
+ return true;
63
76
  }
64
77
  return false;
65
78
  }
79
+ const MAX_SCAN_DEPTH = 30;
80
+ const MAX_SCAN_FILES = 10000;
66
81
  /**
67
82
  * Recursively find all files in a directory
68
83
  */
69
84
  async function findFiles(dir, include, exclude) {
70
85
  const files = [];
71
- async function walk(currentDir) {
86
+ async function walk(currentDir, depth) {
87
+ if (depth > MAX_SCAN_DEPTH)
88
+ return;
89
+ if (files.length >= MAX_SCAN_FILES)
90
+ return;
72
91
  const entries = await readdir(currentDir, { withFileTypes: true });
73
92
  for (const entry of entries) {
93
+ if (files.length >= MAX_SCAN_FILES)
94
+ return;
74
95
  const fullPath = join(currentDir, entry.name);
75
96
  if (shouldExclude(fullPath, exclude)) {
76
97
  continue;
77
98
  }
99
+ // Skip symlinks to prevent infinite loops and path escape
100
+ if (entry.isSymbolicLink()) {
101
+ continue;
102
+ }
78
103
  if (entry.isDirectory()) {
79
- await walk(fullPath);
104
+ await walk(fullPath, depth + 1);
80
105
  }
81
106
  else if (entry.isFile()) {
82
107
  if (shouldInclude(fullPath, include) && getScannerForFile(fullPath)) {
@@ -85,15 +110,15 @@ async function findFiles(dir, include, exclude) {
85
110
  }
86
111
  }
87
112
  }
88
- await walk(dir);
113
+ await walk(dir, 0);
89
114
  return files;
90
115
  }
91
116
  /**
92
117
  * Scan a directory (or single file) for all API elements
93
118
  */
94
119
  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/**'];
120
+ const include = options.include || ['**/*.py', '**/*.ts', '**/*.js', '**/*.go', '**/*.rs', '**/*.java', '**/*.cs', '**/*.php', '**/*.kt', '**/*.kts', '**/*.swift', '**/*.rb'];
121
+ const exclude = options.exclude || ['**/node_modules/**', '**/__pycache__/**', '**/dist/**', '**/target/**', '**/vendor/**', '**/build/**', '**/bin/**', '**/obj/**', '**/.build/**'];
97
122
  // Check if input is a file or directory
98
123
  const stats = await stat(dir);
99
124
  let files;
@@ -139,6 +164,8 @@ export async function scanFile(filePath) {
139
164
  const langMap = {
140
165
  ts: 'typescript', tsx: 'typescript', js: 'javascript', jsx: 'javascript',
141
166
  py: 'python', go: 'go', rs: 'rust',
167
+ java: 'java', cs: 'csharp', php: 'php',
168
+ kt: 'kotlin', kts: 'kotlin', swift: 'swift', rb: 'ruby',
142
169
  };
143
170
  const language = langMap[ext] ?? 'typescript';
144
171
  return {
@@ -0,0 +1,39 @@
1
+ import { Scanner, ScanResult } from './types.js';
2
+ /**
3
+ * Scanner for Java source files
4
+ * Extracts: public classes, public methods, public static methods
5
+ */
6
+ export declare class JavaScanner implements Scanner {
7
+ languages: string[];
8
+ canHandle(filePath: string): boolean;
9
+ scanFile(filePath: string): Promise<ScanResult>;
10
+ private extractPackage;
11
+ private extractImports;
12
+ /**
13
+ * Extracts the full generics string starting at startIndex by counting `<>` depth.
14
+ * Returns empty string if source[startIndex] is not '<'.
15
+ */
16
+ private extractGenerics;
17
+ private extractClasses;
18
+ private extractMethods;
19
+ /**
20
+ * Extracts interface methods that don't have explicit `public` modifier.
21
+ * In Java, interface methods are implicitly public.
22
+ */
23
+ private extractInterfaceMethods;
24
+ /**
25
+ * Finds all class/interface/enum/record declarations and their brace-delimited ranges.
26
+ */
27
+ private findClassRanges;
28
+ private findMatchingBrace;
29
+ /**
30
+ * Finds the innermost class that contains the given source index,
31
+ * using brace-depth-aware class ranges.
32
+ */
33
+ private findParentClass;
34
+ private parseParams;
35
+ private splitParams;
36
+ private getLineNumber;
37
+ private getJavadoc;
38
+ private getSourceContext;
39
+ }