skrypt-ai 0.5.0 → 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 (120) hide show
  1. package/dist/auth/index.js +8 -1
  2. package/dist/autofix/index.d.ts +0 -4
  3. package/dist/autofix/index.js +0 -21
  4. package/dist/capture/browser.d.ts +11 -0
  5. package/dist/capture/browser.js +173 -0
  6. package/dist/capture/diff.d.ts +23 -0
  7. package/dist/capture/diff.js +52 -0
  8. package/dist/capture/index.d.ts +23 -0
  9. package/dist/capture/index.js +210 -0
  10. package/dist/capture/naming.d.ts +17 -0
  11. package/dist/capture/naming.js +45 -0
  12. package/dist/capture/parser.d.ts +15 -0
  13. package/dist/capture/parser.js +80 -0
  14. package/dist/capture/types.d.ts +57 -0
  15. package/dist/capture/types.js +1 -0
  16. package/dist/cli.js +4 -0
  17. package/dist/commands/autofix.js +136 -120
  18. package/dist/commands/cron.js +58 -47
  19. package/dist/commands/deploy.js +123 -102
  20. package/dist/commands/generate.js +88 -6
  21. package/dist/commands/heal.d.ts +10 -0
  22. package/dist/commands/heal.js +201 -0
  23. package/dist/commands/i18n.js +146 -111
  24. package/dist/commands/lint.js +50 -44
  25. package/dist/commands/llms-txt.js +59 -49
  26. package/dist/commands/login.js +61 -43
  27. package/dist/commands/mcp.js +6 -0
  28. package/dist/commands/monitor.js +13 -8
  29. package/dist/commands/qa.d.ts +2 -0
  30. package/dist/commands/qa.js +43 -0
  31. package/dist/commands/review-pr.js +108 -102
  32. package/dist/commands/sdk.js +128 -122
  33. package/dist/commands/security.js +86 -80
  34. package/dist/commands/test.js +91 -92
  35. package/dist/commands/version.js +104 -75
  36. package/dist/commands/watch.js +130 -114
  37. package/dist/config/types.js +2 -2
  38. package/dist/context-hub/index.d.ts +23 -0
  39. package/dist/context-hub/index.js +179 -0
  40. package/dist/context-hub/mappings.d.ts +8 -0
  41. package/dist/context-hub/mappings.js +55 -0
  42. package/dist/context-hub/types.d.ts +33 -0
  43. package/dist/context-hub/types.js +1 -0
  44. package/dist/generator/generator.js +39 -6
  45. package/dist/generator/types.d.ts +7 -0
  46. package/dist/generator/writer.d.ts +3 -1
  47. package/dist/generator/writer.js +24 -4
  48. package/dist/llm/anthropic-client.d.ts +1 -0
  49. package/dist/llm/anthropic-client.js +3 -1
  50. package/dist/llm/index.d.ts +6 -4
  51. package/dist/llm/index.js +76 -261
  52. package/dist/llm/openai-client.d.ts +1 -0
  53. package/dist/llm/openai-client.js +7 -2
  54. package/dist/qa/checks.d.ts +10 -0
  55. package/dist/qa/checks.js +492 -0
  56. package/dist/qa/fixes.d.ts +30 -0
  57. package/dist/qa/fixes.js +277 -0
  58. package/dist/qa/index.d.ts +29 -0
  59. package/dist/qa/index.js +187 -0
  60. package/dist/qa/types.d.ts +24 -0
  61. package/dist/qa/types.js +1 -0
  62. package/dist/scanner/csharp.d.ts +23 -0
  63. package/dist/scanner/csharp.js +421 -0
  64. package/dist/scanner/index.js +16 -2
  65. package/dist/scanner/java.d.ts +39 -0
  66. package/dist/scanner/java.js +318 -0
  67. package/dist/scanner/kotlin.d.ts +23 -0
  68. package/dist/scanner/kotlin.js +389 -0
  69. package/dist/scanner/php.d.ts +57 -0
  70. package/dist/scanner/php.js +351 -0
  71. package/dist/scanner/ruby.d.ts +36 -0
  72. package/dist/scanner/ruby.js +431 -0
  73. package/dist/scanner/swift.d.ts +25 -0
  74. package/dist/scanner/swift.js +392 -0
  75. package/dist/scanner/types.d.ts +1 -1
  76. package/dist/template/content/docs/_navigation.json +46 -0
  77. package/dist/template/content/docs/_sidebars.json +684 -0
  78. package/dist/template/content/docs/core.md +4544 -0
  79. package/dist/template/content/docs/index.mdx +89 -0
  80. package/dist/template/content/docs/integrations.md +1158 -0
  81. package/dist/template/content/docs/llms-full.md +403 -0
  82. package/dist/template/content/docs/llms.txt +4588 -0
  83. package/dist/template/content/docs/other.md +10379 -0
  84. package/dist/template/content/docs/tools.md +746 -0
  85. package/dist/template/content/docs/types.md +531 -0
  86. package/dist/template/docs.json +13 -11
  87. package/dist/template/mdx-components.tsx +27 -2
  88. package/dist/template/package.json +6 -0
  89. package/dist/template/public/search-index.json +1 -1
  90. package/dist/template/scripts/build-search-index.mjs +84 -6
  91. package/dist/template/src/app/api/chat/route.ts +83 -128
  92. package/dist/template/src/app/docs/[...slug]/page.tsx +75 -20
  93. package/dist/template/src/app/docs/llms-full.md +151 -4
  94. package/dist/template/src/app/docs/llms.txt +2464 -847
  95. package/dist/template/src/app/docs/page.mdx +48 -38
  96. package/dist/template/src/app/layout.tsx +3 -1
  97. package/dist/template/src/app/page.tsx +22 -8
  98. package/dist/template/src/components/ai-chat.tsx +73 -64
  99. package/dist/template/src/components/breadcrumbs.tsx +21 -23
  100. package/dist/template/src/components/copy-button.tsx +13 -9
  101. package/dist/template/src/components/copy-page-button.tsx +54 -0
  102. package/dist/template/src/components/docs-layout.tsx +37 -25
  103. package/dist/template/src/components/header.tsx +51 -10
  104. package/dist/template/src/components/mdx/card.tsx +17 -3
  105. package/dist/template/src/components/mdx/code-block.tsx +13 -9
  106. package/dist/template/src/components/mdx/code-group.tsx +13 -8
  107. package/dist/template/src/components/mdx/heading.tsx +15 -2
  108. package/dist/template/src/components/mdx/highlighted-code.tsx +13 -8
  109. package/dist/template/src/components/mdx/index.tsx +2 -0
  110. package/dist/template/src/components/mdx/mermaid.tsx +110 -0
  111. package/dist/template/src/components/mdx/screenshot.tsx +150 -0
  112. package/dist/template/src/components/scroll-to-hash.tsx +48 -0
  113. package/dist/template/src/components/sidebar.tsx +12 -18
  114. package/dist/template/src/components/table-of-contents.tsx +9 -0
  115. package/dist/template/src/lib/highlight.ts +3 -88
  116. package/dist/template/src/lib/navigation.ts +159 -0
  117. package/dist/template/src/styles/globals.css +17 -6
  118. package/dist/utils/validation.d.ts +0 -3
  119. package/dist/utils/validation.js +0 -26
  120. package/package.json +3 -2
@@ -0,0 +1,277 @@
1
+ import { basename } from 'path';
2
+ /**
3
+ * Convert kebab-case or snake_case filename to Title Case
4
+ */
5
+ function filenameToTitle(filename) {
6
+ const name = filename.replace(/\.(mdx?|md)$/, '');
7
+ return name
8
+ .split(/[-_]/)
9
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
10
+ .join(' ');
11
+ }
12
+ /**
13
+ * Extract the first h1 heading from content
14
+ */
15
+ function extractFirstH1(content) {
16
+ const lines = content.split('\n');
17
+ let inCodeBlock = false;
18
+ for (const line of lines) {
19
+ if (line.startsWith('```')) {
20
+ inCodeBlock = !inCodeBlock;
21
+ continue;
22
+ }
23
+ if (inCodeBlock)
24
+ continue;
25
+ const match = line.match(/^#\s+(.+)/);
26
+ if (match)
27
+ return match[1].trim();
28
+ }
29
+ return null;
30
+ }
31
+ /**
32
+ * Fix frontmatter issues:
33
+ * - Add frontmatter with title if none exists
34
+ * - Add title field if frontmatter exists but has no title
35
+ */
36
+ export function fixFrontmatter(content, filePath) {
37
+ const fixes = [];
38
+ let result = content;
39
+ const fmMatch = result.match(/^---\s*\n([\s\S]*?)\n---/);
40
+ if (!fmMatch) {
41
+ // No frontmatter — add it with a title
42
+ const h1 = extractFirstH1(content);
43
+ const title = h1 || filenameToTitle(basename(filePath));
44
+ result = `---\ntitle: "${title}"\n---\n\n${result}`;
45
+ fixes.push(`added missing frontmatter with title "${title}"`);
46
+ }
47
+ else {
48
+ const fm = fmMatch[1];
49
+ if (!/^title\s*:/m.test(fm)) {
50
+ // Frontmatter exists but no title
51
+ const h1 = extractFirstH1(content);
52
+ const title = h1 || filenameToTitle(basename(filePath));
53
+ // Insert title as first field in frontmatter
54
+ result = result.replace(/^---\s*\n/, `---\ntitle: "${title}"\n`);
55
+ fixes.push(`added missing title "${title}" to frontmatter`);
56
+ }
57
+ }
58
+ return { content: result, fixes };
59
+ }
60
+ /**
61
+ * Fix MDX syntax issues:
62
+ * - Strip Docusaurus @theme imports
63
+ * - Strip empty <a id="..."></a> anchor tags
64
+ */
65
+ export function fixMdxSyntax(content) {
66
+ const fixes = [];
67
+ const lines = content.split('\n');
68
+ const outputLines = [];
69
+ let inCodeBlock = false;
70
+ let strippedImports = 0;
71
+ let strippedAnchors = 0;
72
+ for (const line of lines) {
73
+ if (line.startsWith('```')) {
74
+ inCodeBlock = !inCodeBlock;
75
+ outputLines.push(line);
76
+ continue;
77
+ }
78
+ if (inCodeBlock) {
79
+ outputLines.push(line);
80
+ continue;
81
+ }
82
+ // Strip @theme imports
83
+ if (/^import\s+.*from\s+['"]@theme\//.test(line)) {
84
+ strippedImports++;
85
+ continue;
86
+ }
87
+ // Strip empty anchor tags: <a id="..."></a> or <a name="..."></a>
88
+ const anchorStripped = line.replace(/<a\s+(?:id|name)\s*=\s*["'][^"']*["']\s*>\s*<\/a>/g, '');
89
+ if (anchorStripped !== line) {
90
+ strippedAnchors++;
91
+ // Only keep the line if it has content after stripping
92
+ if (anchorStripped.trim()) {
93
+ outputLines.push(anchorStripped);
94
+ }
95
+ continue;
96
+ }
97
+ outputLines.push(line);
98
+ }
99
+ if (strippedImports > 0) {
100
+ fixes.push(`stripped ${strippedImports} @theme import${strippedImports > 1 ? 's' : ''}`);
101
+ }
102
+ if (strippedAnchors > 0) {
103
+ fixes.push(`stripped ${strippedAnchors} empty anchor tag${strippedAnchors > 1 ? 's' : ''}`);
104
+ }
105
+ return { content: outputLines.join('\n'), fixes };
106
+ }
107
+ /**
108
+ * Infer language for a code block based on content heuristics.
109
+ * More specific languages are checked first to avoid false positives
110
+ * (e.g., Rust's `let mut` matching TypeScript's `let`).
111
+ */
112
+ function inferLanguage(code) {
113
+ // Rust indicators (check before TS — `let mut` and `fn` are more specific)
114
+ if (/\bfn\s+/.test(code) || /\blet\s+mut\b/.test(code) || /\buse\s+\w+::/.test(code)) {
115
+ return 'rust';
116
+ }
117
+ // Go indicators (check before TS — `func` and `package` are unambiguous)
118
+ if (/\bfunc\s+/.test(code) || /\bpackage\s+/.test(code)) {
119
+ return 'go';
120
+ }
121
+ // Python indicators (`def ` and `class...:` are unambiguous)
122
+ if (/\bdef\s+\w/.test(code) || /\bclass\s+\w[^{]*:\s*$/m.test(code)) {
123
+ return 'python';
124
+ }
125
+ // TypeScript / JavaScript indicators
126
+ if (/\b(const|let|import|export|interface|type)\b/.test(code) || /=>/.test(code)) {
127
+ return 'typescript';
128
+ }
129
+ // Bash / shell indicators
130
+ if (/\$\s/.test(code) || /\b(npm|brew|apt|cd|mkdir|echo|curl|wget)\b/.test(code)) {
131
+ return 'bash';
132
+ }
133
+ // Python import (checked after TS since `import` is ambiguous)
134
+ if (/^\s*import\s+\w/m.test(code) || /^\s*from\s+\w+\s+import\b/m.test(code)) {
135
+ return 'python';
136
+ }
137
+ return 'text';
138
+ }
139
+ /**
140
+ * Fix code block issues:
141
+ * - Infer language for code blocks missing a language specifier
142
+ * - Close unclosed code blocks at end of file
143
+ */
144
+ export function fixCodeBlocks(content) {
145
+ const fixes = [];
146
+ const lines = content.split('\n');
147
+ const outputLines = [];
148
+ let inCodeBlock = false;
149
+ let inferredCount = 0;
150
+ for (let i = 0; i < lines.length; i++) {
151
+ const line = lines[i];
152
+ if (line.startsWith('```') && !inCodeBlock) {
153
+ // Opening code fence
154
+ inCodeBlock = true;
155
+ const lang = line.slice(3).trim().split(/\s/)[0] || '';
156
+ if (!lang) {
157
+ // Collect lines until closing fence to infer language
158
+ const codeBlockContent = [];
159
+ let j = i + 1;
160
+ while (j < lines.length && !lines[j].startsWith('```')) {
161
+ codeBlockContent.push(lines[j]);
162
+ j++;
163
+ }
164
+ const inferred = inferLanguage(codeBlockContent.join('\n'));
165
+ outputLines.push('```' + inferred);
166
+ inferredCount++;
167
+ }
168
+ else {
169
+ outputLines.push(line);
170
+ }
171
+ }
172
+ else if (line.startsWith('```') && inCodeBlock) {
173
+ // Closing code fence
174
+ inCodeBlock = false;
175
+ outputLines.push(line);
176
+ }
177
+ else {
178
+ outputLines.push(line);
179
+ }
180
+ }
181
+ // Close unclosed code block at end of file
182
+ if (inCodeBlock) {
183
+ outputLines.push('```');
184
+ fixes.push('closed unclosed code block at end of file');
185
+ }
186
+ if (inferredCount > 0) {
187
+ fixes.push(`inferred language for ${inferredCount} code block${inferredCount > 1 ? 's' : ''}`);
188
+ }
189
+ return { content: outputLines.join('\n'), fixes };
190
+ }
191
+ /**
192
+ * Fix security issues:
193
+ * - Strip <script>...</script> tags (outside code blocks)
194
+ * - Replace javascript: URLs with #
195
+ * - Strip inline on* event handlers from HTML tags
196
+ * - Add sandbox attribute to unsandboxed <iframe> tags
197
+ */
198
+ export function fixSecurity(content) {
199
+ const fixes = [];
200
+ const lines = content.split('\n');
201
+ const outputLines = [];
202
+ let inCodeBlock = false;
203
+ let strippedScripts = 0;
204
+ let fixedUrls = 0;
205
+ let strippedHandlers = 0;
206
+ let sandboxedIframes = 0;
207
+ let inScriptTag = false;
208
+ for (const line of lines) {
209
+ if (line.startsWith('```')) {
210
+ inCodeBlock = !inCodeBlock;
211
+ outputLines.push(line);
212
+ continue;
213
+ }
214
+ if (inCodeBlock) {
215
+ outputLines.push(line);
216
+ continue;
217
+ }
218
+ // Handle multi-line script tags
219
+ if (inScriptTag) {
220
+ if (/<\/script>/i.test(line)) {
221
+ inScriptTag = false;
222
+ strippedScripts++;
223
+ }
224
+ // Skip lines inside script tags
225
+ continue;
226
+ }
227
+ // Single-line script tag: <script>...</script>
228
+ if (/<script[\s>][\s\S]*<\/script>/i.test(line)) {
229
+ const cleaned = line.replace(/<script[\s>][\s\S]*?<\/script>/gi, '');
230
+ strippedScripts++;
231
+ if (cleaned.trim()) {
232
+ outputLines.push(cleaned);
233
+ }
234
+ continue;
235
+ }
236
+ // Opening script tag without closing on same line
237
+ if (/<script[\s>]/i.test(line)) {
238
+ inScriptTag = true;
239
+ // Keep any content before the script tag
240
+ const before = line.replace(/<script[\s>][\s\S]*/i, '').trim();
241
+ if (before) {
242
+ outputLines.push(before);
243
+ }
244
+ continue;
245
+ }
246
+ let processed = line;
247
+ // Replace javascript: URLs with #
248
+ if (/href\s*=\s*["']javascript:/i.test(processed)) {
249
+ processed = processed.replace(/href\s*=\s*["']javascript:[^"']*["']/gi, 'href="#"');
250
+ fixedUrls++;
251
+ }
252
+ // Strip inline on* event handlers
253
+ if (/\s+on\w+\s*=\s*["']/i.test(processed)) {
254
+ processed = processed.replace(/\s+on\w+\s*=\s*["'][^"']*["']/gi, '');
255
+ strippedHandlers++;
256
+ }
257
+ // Add sandbox to unsandboxed iframes
258
+ if (/<iframe\s/i.test(processed) && !/sandbox/i.test(processed)) {
259
+ processed = processed.replace(/<iframe\s/i, '<iframe sandbox ');
260
+ sandboxedIframes++;
261
+ }
262
+ outputLines.push(processed);
263
+ }
264
+ if (strippedScripts > 0) {
265
+ fixes.push(`stripped ${strippedScripts} <script> tag${strippedScripts > 1 ? 's' : ''}`);
266
+ }
267
+ if (fixedUrls > 0) {
268
+ fixes.push(`replaced ${fixedUrls} javascript: URL${fixedUrls > 1 ? 's' : ''}`);
269
+ }
270
+ if (strippedHandlers > 0) {
271
+ fixes.push(`stripped ${strippedHandlers} inline event handler${strippedHandlers > 1 ? 's' : ''}`);
272
+ }
273
+ if (sandboxedIframes > 0) {
274
+ fixes.push(`sandboxed ${sandboxedIframes} <iframe>${sandboxedIframes > 1 ? 's' : ''}`);
275
+ }
276
+ return { content: outputLines.join('\n'), fixes };
277
+ }
@@ -0,0 +1,29 @@
1
+ import type { QAReport } from './types.js';
2
+ export type { QAReport, QACheckResult, QAIssue } from './types.js';
3
+ export type { Severity } from './types.js';
4
+ /**
5
+ * Run embedded QA on a docs output directory.
6
+ * Returns a report with all issues found.
7
+ */
8
+ export declare function runQA(outputDir: string): QAReport;
9
+ /**
10
+ * Print QA report to stdout
11
+ */
12
+ export declare function printQAReport(report: QAReport): void;
13
+ export interface FixReport {
14
+ filesFixed: number;
15
+ totalFixes: number;
16
+ fixes: Array<{
17
+ file: string;
18
+ fixes: string[];
19
+ }>;
20
+ }
21
+ /**
22
+ * Auto-fix safe QA issues in a docs output directory.
23
+ * Runs conservative fix functions on all .md/.mdx files and writes changes back.
24
+ */
25
+ export declare function fixQAIssues(outputDir: string): FixReport;
26
+ /**
27
+ * Print fix report to stdout
28
+ */
29
+ export declare function printFixReport(report: FixReport): void;
@@ -0,0 +1,187 @@
1
+ import { readdirSync, readFileSync, writeFileSync } from 'fs';
2
+ import { join, relative } from 'path';
3
+ import { checkFrontmatter, checkHeadings, checkCodeBlocks, checkComponents, checkLinks, checkSecurity, checkContentQuality, checkMdxSyntax, checkScreenshots, } from './checks.js';
4
+ import { fixFrontmatter, fixMdxSyntax, fixCodeBlocks, fixSecurity, } from './fixes.js';
5
+ /**
6
+ * Recursively find all .md and .mdx files in a directory
7
+ */
8
+ function findDocFiles(dir, maxDepth = 20) {
9
+ const files = [];
10
+ function walk(currentDir, depth) {
11
+ if (depth > maxDepth)
12
+ return;
13
+ try {
14
+ const entries = readdirSync(currentDir, { withFileTypes: true });
15
+ for (const entry of entries) {
16
+ if (entry.name.startsWith('.') || entry.name === 'node_modules')
17
+ continue;
18
+ const fullPath = join(currentDir, entry.name);
19
+ if (entry.isDirectory()) {
20
+ walk(fullPath, depth + 1);
21
+ }
22
+ else if (entry.name.endsWith('.md') || entry.name.endsWith('.mdx')) {
23
+ // Skip metadata files
24
+ const name = entry.name.replace(/\.(mdx?|md)$/, '');
25
+ if (name.startsWith('_') || name.startsWith('llms') || name === 'README')
26
+ continue;
27
+ files.push(fullPath);
28
+ }
29
+ }
30
+ }
31
+ catch {
32
+ // Directory not accessible
33
+ }
34
+ }
35
+ walk(dir, 0);
36
+ return files;
37
+ }
38
+ /**
39
+ * Run a single check across all files and return results
40
+ */
41
+ function runCheck(name, files, checkFn) {
42
+ const start = Date.now();
43
+ const issues = [];
44
+ const allFilePaths = new Set(files.map(f => f.relPath));
45
+ for (const file of files) {
46
+ try {
47
+ const fileIssues = checkFn(file.content, file.relPath, allFilePaths);
48
+ issues.push(...fileIssues);
49
+ }
50
+ catch {
51
+ // Check failed for this file — skip
52
+ }
53
+ }
54
+ return {
55
+ check: name,
56
+ passed: issues.filter(i => i.severity === 'error').length === 0,
57
+ issues,
58
+ duration: Date.now() - start,
59
+ };
60
+ }
61
+ /**
62
+ * Run embedded QA on a docs output directory.
63
+ * Returns a report with all issues found.
64
+ */
65
+ export function runQA(outputDir) {
66
+ const startTime = Date.now();
67
+ // Find all doc files
68
+ const filePaths = findDocFiles(outputDir);
69
+ // Read all files
70
+ const files = filePaths.map(p => ({
71
+ path: p,
72
+ content: readFileSync(p, 'utf-8'),
73
+ relPath: relative(outputDir, p),
74
+ }));
75
+ // Run all checks
76
+ const checks = [
77
+ runCheck('frontmatter', files, (content, relPath) => checkFrontmatter('', content, relPath)),
78
+ runCheck('headings', files, (content, relPath) => checkHeadings(content, relPath)),
79
+ runCheck('code-blocks', files, (content, relPath) => checkCodeBlocks(content, relPath)),
80
+ runCheck('components', files, (content, relPath) => checkComponents(content, relPath)),
81
+ runCheck('links', files, (content, relPath, allFiles) => checkLinks(content, relPath, allFiles)),
82
+ runCheck('security', files, (content, relPath) => checkSecurity(content, relPath)),
83
+ runCheck('content', files, (content, relPath) => checkContentQuality(content, relPath)),
84
+ runCheck('mdx-syntax', files, (content, relPath) => checkMdxSyntax(content, relPath)),
85
+ runCheck('screenshots', files, (content, relPath) => checkScreenshots(content, relPath, outputDir)),
86
+ ];
87
+ // Aggregate
88
+ const allIssues = checks.flatMap(c => c.issues);
89
+ const errors = allIssues.filter(i => i.severity === 'error').length;
90
+ const warnings = allIssues.filter(i => i.severity === 'warning').length;
91
+ const infos = allIssues.filter(i => i.severity === 'info').length;
92
+ return {
93
+ outputDir,
94
+ filesChecked: files.length,
95
+ checks,
96
+ errors,
97
+ warnings,
98
+ infos,
99
+ passed: errors === 0,
100
+ duration: Date.now() - startTime,
101
+ };
102
+ }
103
+ /**
104
+ * Print QA report to stdout
105
+ */
106
+ export function printQAReport(report) {
107
+ const statusIcon = report.passed ? '✓' : '✗';
108
+ const statusLabel = report.passed ? 'PASSED' : 'FAILED';
109
+ console.log(`\n QA ${statusIcon} ${statusLabel}`);
110
+ console.log(` ${report.filesChecked} files checked in ${report.duration}ms`);
111
+ // Summary line
112
+ const parts = [];
113
+ if (report.errors > 0)
114
+ parts.push(`${report.errors} error${report.errors > 1 ? 's' : ''}`);
115
+ if (report.warnings > 0)
116
+ parts.push(`${report.warnings} warning${report.warnings > 1 ? 's' : ''}`);
117
+ if (report.infos > 0)
118
+ parts.push(`${report.infos} info${report.infos > 1 ? 's' : ''}`);
119
+ if (parts.length > 0) {
120
+ console.log(` ${parts.join(', ')}`);
121
+ }
122
+ // Show errors and warnings (not infos unless verbose)
123
+ const significant = report.checks
124
+ .flatMap(c => c.issues)
125
+ .filter(i => i.severity === 'error' || i.severity === 'warning');
126
+ if (significant.length > 0) {
127
+ console.log('');
128
+ const shown = significant.slice(0, 20);
129
+ for (const issue of shown) {
130
+ const icon = issue.severity === 'error' ? '✗' : '⚠';
131
+ const loc = issue.line ? `${issue.file}:${issue.line}` : issue.file;
132
+ console.log(` ${icon} [${issue.check}] ${loc}: ${issue.message}`);
133
+ }
134
+ if (significant.length > 20) {
135
+ console.log(` ... and ${significant.length - 20} more`);
136
+ }
137
+ }
138
+ }
139
+ /**
140
+ * Auto-fix safe QA issues in a docs output directory.
141
+ * Runs conservative fix functions on all .md/.mdx files and writes changes back.
142
+ */
143
+ export function fixQAIssues(outputDir) {
144
+ const filePaths = findDocFiles(outputDir);
145
+ const result = { filesFixed: 0, totalFixes: 0, fixes: [] };
146
+ for (const filePath of filePaths) {
147
+ const original = readFileSync(filePath, 'utf-8');
148
+ const relPath = relative(outputDir, filePath);
149
+ let content = original;
150
+ const allFixes = [];
151
+ // Run each fix function in sequence
152
+ const frontmatterResult = fixFrontmatter(content, filePath);
153
+ content = frontmatterResult.content;
154
+ allFixes.push(...frontmatterResult.fixes);
155
+ const mdxResult = fixMdxSyntax(content);
156
+ content = mdxResult.content;
157
+ allFixes.push(...mdxResult.fixes);
158
+ const codeResult = fixCodeBlocks(content);
159
+ content = codeResult.content;
160
+ allFixes.push(...codeResult.fixes);
161
+ const securityResult = fixSecurity(content);
162
+ content = securityResult.content;
163
+ allFixes.push(...securityResult.fixes);
164
+ // Only write if content actually changed
165
+ if (content !== original) {
166
+ writeFileSync(filePath, content, 'utf-8');
167
+ result.filesFixed++;
168
+ result.totalFixes += allFixes.length;
169
+ result.fixes.push({ file: relPath, fixes: allFixes });
170
+ }
171
+ }
172
+ return result;
173
+ }
174
+ /**
175
+ * Print fix report to stdout
176
+ */
177
+ export function printFixReport(report) {
178
+ if (report.totalFixes === 0) {
179
+ return;
180
+ }
181
+ console.log(` Auto-fixed ${report.totalFixes} issue${report.totalFixes > 1 ? 's' : ''} in ${report.filesFixed} file${report.filesFixed > 1 ? 's' : ''}`);
182
+ for (const entry of report.fixes) {
183
+ for (const fix of entry.fixes) {
184
+ console.log(` \u2713 ${entry.file}: ${fix}`);
185
+ }
186
+ }
187
+ }
@@ -0,0 +1,24 @@
1
+ export type Severity = 'error' | 'warning' | 'info';
2
+ export interface QAIssue {
3
+ file: string;
4
+ line?: number;
5
+ severity: Severity;
6
+ check: string;
7
+ message: string;
8
+ }
9
+ export interface QACheckResult {
10
+ check: string;
11
+ passed: boolean;
12
+ issues: QAIssue[];
13
+ duration: number;
14
+ }
15
+ export interface QAReport {
16
+ outputDir: string;
17
+ filesChecked: number;
18
+ checks: QACheckResult[];
19
+ errors: number;
20
+ warnings: number;
21
+ infos: number;
22
+ passed: boolean;
23
+ duration: number;
24
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import { Scanner, ScanResult } from './types.js';
2
+ /**
3
+ * Scanner for C# source files
4
+ * Extracts: public classes, interfaces, enums, structs, records, methods
5
+ */
6
+ export declare class CSharpScanner implements Scanner {
7
+ languages: string[];
8
+ canHandle(filePath: string): boolean;
9
+ scanFile(filePath: string): Promise<ScanResult>;
10
+ private extractImports;
11
+ private extractTypes;
12
+ private extractMethods;
13
+ private extractInterfaceMethods;
14
+ private findClassRanges;
15
+ private findMatchingBrace;
16
+ private findParentClass;
17
+ private parseCSharpParams;
18
+ private splitParams;
19
+ private getLineNumber;
20
+ private getDocComment;
21
+ private extractGenerics;
22
+ private getSourceContext;
23
+ }