skrypt-ai 0.8.0 → 0.8.1

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 (101) hide show
  1. package/dist/auth/index.js +6 -0
  2. package/dist/binding/binder.d.ts +5 -0
  3. package/dist/binding/binder.js +63 -0
  4. package/dist/binding/detector.d.ts +5 -0
  5. package/dist/binding/detector.js +51 -0
  6. package/dist/binding/doc-parser.d.ts +9 -0
  7. package/dist/binding/doc-parser.js +138 -0
  8. package/dist/binding/extractor.d.ts +14 -0
  9. package/dist/binding/extractor.js +39 -0
  10. package/dist/binding/index.d.ts +5 -0
  11. package/dist/binding/index.js +5 -0
  12. package/dist/binding/types.d.ts +74 -0
  13. package/dist/binding/types.js +1 -0
  14. package/dist/claims/extractor.d.ts +13 -0
  15. package/dist/claims/extractor.js +138 -0
  16. package/dist/claims/index.d.ts +4 -0
  17. package/dist/claims/index.js +4 -0
  18. package/dist/claims/reporter.d.ts +9 -0
  19. package/dist/claims/reporter.js +65 -0
  20. package/dist/claims/store.d.ts +13 -0
  21. package/dist/claims/store.js +51 -0
  22. package/dist/claims/types.d.ts +34 -0
  23. package/dist/claims/types.js +1 -0
  24. package/dist/cli.js +516 -56
  25. package/dist/commands/bind.d.ts +2 -0
  26. package/dist/commands/bind.js +139 -0
  27. package/dist/commands/claims.d.ts +2 -0
  28. package/dist/commands/claims.js +84 -0
  29. package/dist/commands/coverage.d.ts +2 -0
  30. package/dist/commands/coverage.js +61 -0
  31. package/dist/commands/generate/index.js +5 -0
  32. package/dist/commands/generate/scan.js +33 -14
  33. package/dist/commands/generate/write.d.ts +7 -0
  34. package/dist/commands/generate/write.js +65 -1
  35. package/dist/commands/import.js +12 -3
  36. package/dist/commands/init.js +68 -5
  37. package/dist/commands/monitor.d.ts +15 -0
  38. package/dist/commands/monitor.js +2 -2
  39. package/dist/commands/mutate.d.ts +2 -0
  40. package/dist/commands/mutate.js +177 -0
  41. package/dist/config/types.js +2 -2
  42. package/dist/coverage/calculator.d.ts +7 -0
  43. package/dist/coverage/calculator.js +86 -0
  44. package/dist/coverage/index.d.ts +3 -0
  45. package/dist/coverage/index.js +3 -0
  46. package/dist/coverage/reporter.d.ts +9 -0
  47. package/dist/coverage/reporter.js +65 -0
  48. package/dist/coverage/types.d.ts +36 -0
  49. package/dist/coverage/types.js +1 -0
  50. package/dist/generator/generator.d.ts +3 -1
  51. package/dist/generator/generator.js +137 -23
  52. package/dist/generator/mdx-serializer.js +3 -2
  53. package/dist/generator/organizer.d.ts +5 -1
  54. package/dist/generator/organizer.js +29 -14
  55. package/dist/generator/types.d.ts +6 -0
  56. package/dist/generator/writer.js +7 -2
  57. package/dist/github/org-discovery.js +5 -0
  58. package/dist/importers/mintlify.js +4 -3
  59. package/dist/llm/anthropic-client.js +1 -0
  60. package/dist/llm/index.d.ts +15 -0
  61. package/dist/llm/index.js +148 -29
  62. package/dist/llm/openai-client.js +2 -0
  63. package/dist/mutation/index.d.ts +4 -0
  64. package/dist/mutation/index.js +4 -0
  65. package/dist/mutation/mutator.d.ts +5 -0
  66. package/dist/mutation/mutator.js +101 -0
  67. package/dist/mutation/reporter.d.ts +14 -0
  68. package/dist/mutation/reporter.js +66 -0
  69. package/dist/mutation/runner.d.ts +9 -0
  70. package/dist/mutation/runner.js +70 -0
  71. package/dist/mutation/types.d.ts +51 -0
  72. package/dist/mutation/types.js +1 -0
  73. package/dist/qa/checks.d.ts +1 -0
  74. package/dist/qa/checks.js +47 -0
  75. package/dist/qa/index.js +2 -1
  76. package/dist/scanner/index.js +78 -11
  77. package/dist/scanner/typescript.js +42 -31
  78. package/dist/sentry.d.ts +3 -0
  79. package/dist/sentry.js +28 -0
  80. package/dist/template/docs.json +6 -3
  81. package/dist/template/next.config.mjs +15 -1
  82. package/dist/template/package.json +4 -3
  83. package/dist/template/public/docs-schema.json +257 -0
  84. package/dist/template/sentry.client.config.ts +12 -0
  85. package/dist/template/sentry.edge.config.ts +7 -0
  86. package/dist/template/sentry.server.config.ts +7 -0
  87. package/dist/template/src/app/docs/[...slug]/page.tsx +11 -5
  88. package/dist/template/src/app/docs/layout.tsx +2 -4
  89. package/dist/template/src/app/global-error.tsx +60 -0
  90. package/dist/template/src/app/layout.tsx +7 -16
  91. package/dist/template/src/app/page.tsx +2 -5
  92. package/dist/template/src/components/ai-chat-impl.tsx +1 -1
  93. package/dist/template/src/components/docs-layout.tsx +1 -15
  94. package/dist/template/src/components/footer.tsx +95 -19
  95. package/dist/template/src/components/header.tsx +1 -1
  96. package/dist/template/src/components/search-dialog.tsx +5 -0
  97. package/dist/template/src/instrumentation.ts +11 -0
  98. package/dist/template/src/lib/docs-config.ts +235 -0
  99. package/dist/template/src/lib/fonts.ts +3 -3
  100. package/dist/testing/runner.js +8 -1
  101. package/package.json +2 -1
@@ -0,0 +1,51 @@
1
+ /**
2
+ * A single mutation applied to source code
3
+ */
4
+ export interface Mutant {
5
+ id: string;
6
+ filePath: string;
7
+ originalCode: string;
8
+ mutatedCode: string;
9
+ startLine: number;
10
+ endLine: number;
11
+ type: 'return_value' | 'string_literal' | 'conditional' | 'logical_operator' | 'body_removal';
12
+ description: string;
13
+ }
14
+ /**
15
+ * Result of testing a single mutant
16
+ */
17
+ export interface MutantResult {
18
+ mutant: Mutant;
19
+ status: 'killed' | 'survived' | 'error' | 'timeout';
20
+ failedExample?: string;
21
+ error?: string;
22
+ }
23
+ /**
24
+ * Full mutation testing report
25
+ */
26
+ export interface MutationReport {
27
+ totalMutants: number;
28
+ killed: number;
29
+ survived: number;
30
+ errors: number;
31
+ timeouts: number;
32
+ mutationScore: number;
33
+ files: Array<{
34
+ filePath: string;
35
+ mutants: number;
36
+ killed: number;
37
+ survived: number;
38
+ score: number;
39
+ }>;
40
+ survivors: MutantResult[];
41
+ }
42
+ /**
43
+ * Options for mutation testing
44
+ */
45
+ export interface MutationOptions {
46
+ files?: string[];
47
+ maxMutants?: number;
48
+ json?: boolean;
49
+ minScore?: number;
50
+ timeout?: number;
51
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -8,3 +8,4 @@ export declare function checkSecurity(content: string, relPath: string): QAIssue
8
8
  export declare function checkContentQuality(content: string, relPath: string): QAIssue[];
9
9
  export declare function checkScreenshots(content: string, relPath: string, outputDir: string): QAIssue[];
10
10
  export declare function checkMdxSyntax(content: string, relPath: string): QAIssue[];
11
+ export declare function checkCodeBlockBraces(content: string, relPath: string): QAIssue[];
package/dist/qa/checks.js CHANGED
@@ -490,3 +490,50 @@ export function checkMdxSyntax(content, relPath) {
490
490
  }
491
491
  return issues;
492
492
  }
493
+ // ── Check: Unescaped curly braces inside fenced code blocks (.md files only) ──
494
+ // When .md files are compiled as MDX (format: 'mdx'), curly braces inside code
495
+ // blocks are interpreted as JSX expressions, causing "Could not parse expression
496
+ // with acorn" errors. The template uses format: 'md' for .md files to avoid this,
497
+ // but this check serves as a defensive warning layer.
498
+ export function checkCodeBlockBraces(content, relPath) {
499
+ // Only check .md files — .mdx files intentionally use JSX expressions
500
+ if (relPath.endsWith('.mdx'))
501
+ return [];
502
+ const issues = [];
503
+ const lines = content.split('\n');
504
+ let inCodeBlock = false;
505
+ let codeBlockStart = 0;
506
+ let braceCount = 0;
507
+ for (let i = 0; i < lines.length; i++) {
508
+ const line = lines[i];
509
+ if (line.startsWith('```')) {
510
+ if (!inCodeBlock) {
511
+ inCodeBlock = true;
512
+ codeBlockStart = i + 1;
513
+ braceCount = 0;
514
+ }
515
+ else {
516
+ // Closing fence — report if braces were found
517
+ if (braceCount > 0) {
518
+ issues.push({
519
+ file: relPath,
520
+ line: codeBlockStart,
521
+ severity: 'info',
522
+ check: 'mdx-syntax',
523
+ message: `Code block contains ${braceCount} unescaped curly brace${braceCount > 1 ? 's' : ''} — may break MDX compilation if format is not set to "md"`,
524
+ });
525
+ }
526
+ inCodeBlock = false;
527
+ }
528
+ continue;
529
+ }
530
+ if (inCodeBlock) {
531
+ // Count curly braces in code block lines
532
+ for (const ch of line) {
533
+ if (ch === '{' || ch === '}')
534
+ braceCount++;
535
+ }
536
+ }
537
+ }
538
+ return issues;
539
+ }
package/dist/qa/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { readdirSync, readFileSync, writeFileSync } from 'fs';
2
2
  import { join, relative } from 'path';
3
- import { checkFrontmatter, checkHeadings, checkCodeBlocks, checkComponents, checkLinks, checkSecurity, checkContentQuality, checkMdxSyntax, checkScreenshots, } from './checks.js';
3
+ import { checkFrontmatter, checkHeadings, checkCodeBlocks, checkComponents, checkLinks, checkSecurity, checkContentQuality, checkMdxSyntax, checkCodeBlockBraces, checkScreenshots, } from './checks.js';
4
4
  import { fixFrontmatter, fixMdxSyntax, fixCodeBlocks, fixSecurity, } from './fixes.js';
5
5
  /**
6
6
  * Recursively find all .md and .mdx files in a directory
@@ -82,6 +82,7 @@ export function runQA(outputDir) {
82
82
  runCheck('security', files, (content, relPath) => checkSecurity(content, relPath)),
83
83
  runCheck('content', files, (content, relPath) => checkContentQuality(content, relPath)),
84
84
  runCheck('mdx-syntax', files, (content, relPath) => checkMdxSyntax(content, relPath)),
85
+ runCheck('mdx-code-braces', files, (content, relPath) => checkCodeBlockBraces(content, relPath)),
85
86
  runCheck('screenshots', files, (content, relPath) => checkScreenshots(content, relPath, outputDir)),
86
87
  ];
87
88
  // Aggregate
@@ -1,4 +1,5 @@
1
1
  import { readdir, stat } from 'fs/promises';
2
+ import { statSync } from 'fs';
2
3
  import { join, parse as parsePath } from 'path';
3
4
  import { PythonScanner } from './python.js';
4
5
  import { TypeScriptScanner } from './typescript.js';
@@ -52,13 +53,23 @@ function globToRegex(pattern) {
52
53
  re = re.replace(/\0GLOBSTAR\0/g, '.*');
53
54
  return new RegExp(`(^|/)${re}($|/)`);
54
55
  }
56
+ /** Cache compiled glob RegExp patterns so they're created once per pattern, not once per file. */
57
+ const globRegexCache = new Map();
58
+ function getCachedGlobRegex(pattern) {
59
+ let regex = globRegexCache.get(pattern);
60
+ if (!regex) {
61
+ regex = globToRegex(pattern);
62
+ globRegexCache.set(pattern, regex);
63
+ }
64
+ return regex;
65
+ }
55
66
  /**
56
67
  * Check if a path should be excluded based on patterns
57
68
  */
58
69
  function shouldExclude(filePath, excludePatterns) {
59
70
  const normalized = filePath.replace(/\\/g, '/');
60
71
  for (const pattern of excludePatterns) {
61
- if (globToRegex(pattern).test(normalized))
72
+ if (getCachedGlobRegex(pattern).test(normalized))
62
73
  return true;
63
74
  }
64
75
  return false;
@@ -71,13 +82,16 @@ function shouldInclude(filePath, includePatterns) {
71
82
  return true;
72
83
  const normalized = filePath.replace(/\\/g, '/');
73
84
  for (const pattern of includePatterns) {
74
- if (globToRegex(pattern).test(normalized))
85
+ if (getCachedGlobRegex(pattern).test(normalized))
75
86
  return true;
76
87
  }
77
88
  return false;
78
89
  }
79
90
  const MAX_SCAN_DEPTH = 30;
80
91
  const MAX_SCAN_FILES = 10000;
92
+ /** Skip files larger than 1 MB to avoid memory bloat and slow parsing */
93
+ const MAX_FILE_SIZE = 1024 * 1024;
94
+ const SCAN_CONCURRENCY = 8;
81
95
  /**
82
96
  * Recursively find all files in a directory
83
97
  */
@@ -113,6 +127,38 @@ async function findFiles(dir, include, exclude) {
113
127
  await walk(dir, 0);
114
128
  return files;
115
129
  }
130
+ /**
131
+ * Simple concurrency limiter (pLimit-style). Limits the number of
132
+ * concurrently executing async functions without adding a dependency.
133
+ */
134
+ function createScanLimiter(concurrency) {
135
+ let active = 0;
136
+ const queue = [];
137
+ function next() {
138
+ if (queue.length > 0 && active < concurrency) {
139
+ active++;
140
+ const resolve = queue.shift();
141
+ resolve();
142
+ }
143
+ }
144
+ return async function limit(fn) {
145
+ if (active >= concurrency) {
146
+ await new Promise((resolve) => {
147
+ queue.push(resolve);
148
+ });
149
+ }
150
+ else {
151
+ active++;
152
+ }
153
+ try {
154
+ return await fn();
155
+ }
156
+ finally {
157
+ active--;
158
+ next();
159
+ }
160
+ };
161
+ }
116
162
  /**
117
163
  * Scan a directory (or single file) for all API elements
118
164
  */
@@ -129,27 +175,48 @@ export async function scanDirectory(dir, options = {}) {
129
175
  else {
130
176
  files = await findFiles(dir, include, exclude);
131
177
  }
132
- const results = [];
178
+ // Pre-allocate results array to preserve original file ordering
179
+ const results = new Array(files.length);
133
180
  const allErrors = [];
134
181
  let totalElements = 0;
135
- for (let i = 0; i < files.length; i++) {
136
- const file = files[i];
137
- options.onProgress?.(i + 1, files.length, file);
182
+ let progressCounter = 0;
183
+ const limit = createScanLimiter(SCAN_CONCURRENCY);
184
+ const promises = files.map((file, i) => limit(async () => {
185
+ // P3: Skip files larger than MAX_FILE_SIZE
186
+ try {
187
+ const fileStats = statSync(file);
188
+ if (fileStats.size > MAX_FILE_SIZE) {
189
+ const sizeMB = (fileStats.size / (1024 * 1024)).toFixed(1);
190
+ console.warn(` Warning: Skipping ${file} (${sizeMB} MB exceeds 1 MB limit)`);
191
+ return;
192
+ }
193
+ }
194
+ catch {
195
+ // If stat fails, let scanFile handle the error
196
+ }
197
+ progressCounter++;
198
+ options.onProgress?.(progressCounter, files.length, file);
138
199
  const scanner = getScannerForFile(file);
139
200
  if (!scanner)
140
- continue;
201
+ return;
141
202
  try {
142
203
  const result = await scanner.scanFile(file);
143
- results.push(result);
144
- totalElements += result.elements.length;
145
- allErrors.push(...result.errors);
204
+ results[i] = result;
146
205
  }
147
206
  catch (err) {
148
207
  allErrors.push(`Error scanning ${file}: ${err}`);
149
208
  }
209
+ }));
210
+ await Promise.all(promises);
211
+ // Collect results in original order
212
+ for (const result of results) {
213
+ if (!result)
214
+ continue;
215
+ totalElements += result.elements.length;
216
+ allErrors.push(...result.errors);
150
217
  }
151
218
  return {
152
- files: results,
219
+ files: results.filter((r) => r !== undefined),
153
220
  totalElements,
154
221
  errors: allErrors
155
222
  };
@@ -1,5 +1,14 @@
1
- import ts from 'typescript';
2
1
  import { readFileSync } from 'fs';
2
+ /**
3
+ * Lazy-loaded TypeScript compiler (23MB, ~221ms load). Only loaded on first TS/JS scan.
4
+ * `_ts` holds the runtime module; type annotations use the `import type ts` namespace.
5
+ */
6
+ let _ts;
7
+ async function ensureTS() {
8
+ if (!_ts) {
9
+ _ts = (await import('typescript')).default;
10
+ }
11
+ }
3
12
  export class TypeScriptScanner {
4
13
  languages = ['typescript', 'javascript'];
5
14
  canHandle(filePath) {
@@ -9,11 +18,13 @@ export class TypeScriptScanner {
9
18
  const language = filePath.endsWith('.ts') || filePath.endsWith('.tsx')
10
19
  ? 'typescript'
11
20
  : 'javascript';
21
+ // Ensure TypeScript compiler is loaded (lazy, cached after first call)
22
+ await ensureTS();
12
23
  try {
13
24
  const source = readFileSync(filePath, 'utf-8');
14
- const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, filePath.endsWith('.tsx') || filePath.endsWith('.jsx')
15
- ? ts.ScriptKind.TSX
16
- : ts.ScriptKind.TS);
25
+ const sourceFile = _ts.createSourceFile(filePath, source, _ts.ScriptTarget.Latest, true, filePath.endsWith('.tsx') || filePath.endsWith('.jsx')
26
+ ? _ts.ScriptKind.TSX
27
+ : _ts.ScriptKind.TS);
17
28
  const elements = [];
18
29
  const errors = [];
19
30
  // Extract imports for context
@@ -41,8 +52,8 @@ export class TypeScriptScanner {
41
52
  */
42
53
  extractImports(sourceFile) {
43
54
  const imports = [];
44
- ts.forEachChild(sourceFile, node => {
45
- if (ts.isImportDeclaration(node)) {
55
+ _ts.forEachChild(sourceFile, node => {
56
+ if (_ts.isImportDeclaration(node)) {
46
57
  imports.push(node.getText(sourceFile));
47
58
  }
48
59
  });
@@ -82,7 +93,7 @@ export class TypeScriptScanner {
82
93
  }
83
94
  visit(node, sourceFile, elements, filePath, source, imports, packageName, parentClass) {
84
95
  // Function declarations
85
- if (ts.isFunctionDeclaration(node) && node.name) {
96
+ if (_ts.isFunctionDeclaration(node) && node.name) {
86
97
  const name = node.name.text;
87
98
  if (!this.isPrivate(name) && this.isExported(node)) {
88
99
  const element = this.extractFunction(node, sourceFile, filePath);
@@ -93,10 +104,10 @@ export class TypeScriptScanner {
93
104
  }
94
105
  }
95
106
  // Arrow functions assigned to const (exported)
96
- if (ts.isVariableStatement(node) && this.isExported(node)) {
107
+ if (_ts.isVariableStatement(node) && this.isExported(node)) {
97
108
  for (const decl of node.declarationList.declarations) {
98
- if (ts.isIdentifier(decl.name) && decl.initializer) {
99
- if (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer)) {
109
+ if (_ts.isIdentifier(decl.name) && decl.initializer) {
110
+ if (_ts.isArrowFunction(decl.initializer) || _ts.isFunctionExpression(decl.initializer)) {
100
111
  const name = decl.name.text;
101
112
  if (!this.isPrivate(name)) {
102
113
  const element = this.extractArrowFunction(decl, decl.initializer, sourceFile, filePath);
@@ -110,7 +121,7 @@ export class TypeScriptScanner {
110
121
  }
111
122
  }
112
123
  // Class declarations
113
- if (ts.isClassDeclaration(node) && node.name) {
124
+ if (_ts.isClassDeclaration(node) && node.name) {
114
125
  const name = node.name.text;
115
126
  if (!this.isPrivate(name) && this.isExported(node)) {
116
127
  const classElement = this.extractClass(node, sourceFile, filePath);
@@ -120,7 +131,7 @@ export class TypeScriptScanner {
120
131
  elements.push(classElement);
121
132
  // Extract methods
122
133
  for (const member of node.members) {
123
- if (ts.isMethodDeclaration(member) && member.name) {
134
+ if (_ts.isMethodDeclaration(member) && member.name) {
124
135
  const methodName = this.getPropertyName(member.name);
125
136
  if (methodName && !this.isPrivateMember(member) && !methodName.startsWith('_')) {
126
137
  const methodElement = this.extractMethod(member, sourceFile, filePath, name);
@@ -130,7 +141,7 @@ export class TypeScriptScanner {
130
141
  elements.push(methodElement);
131
142
  }
132
143
  }
133
- if (ts.isConstructorDeclaration(member)) {
144
+ if (_ts.isConstructorDeclaration(member)) {
134
145
  const ctorElement = this.extractConstructor(member, sourceFile, filePath, name);
135
146
  ctorElement.imports = imports;
136
147
  ctorElement.packageName = packageName;
@@ -141,28 +152,28 @@ export class TypeScriptScanner {
141
152
  }
142
153
  }
143
154
  // Recurse into children (but not into function/class bodies for top-level scan)
144
- if (ts.isSourceFile(node) || ts.isModuleBlock(node)) {
145
- ts.forEachChild(node, child => this.visit(child, sourceFile, elements, filePath, source, imports, packageName, parentClass));
155
+ if (_ts.isSourceFile(node) || _ts.isModuleBlock(node)) {
156
+ _ts.forEachChild(node, child => this.visit(child, sourceFile, elements, filePath, source, imports, packageName, parentClass));
146
157
  }
147
158
  }
148
159
  isPrivate(name) {
149
160
  return name.startsWith('_') && !name.startsWith('__');
150
161
  }
151
162
  isPrivateMember(node) {
152
- const modifiers = ts.getModifiers(node);
163
+ const modifiers = _ts.getModifiers(node);
153
164
  if (modifiers) {
154
165
  for (const mod of modifiers) {
155
- if (mod.kind === ts.SyntaxKind.PrivateKeyword)
166
+ if (mod.kind === _ts.SyntaxKind.PrivateKeyword)
156
167
  return true;
157
168
  }
158
169
  }
159
170
  return false;
160
171
  }
161
172
  isExported(node) {
162
- const modifiers = ts.getModifiers(node);
173
+ const modifiers = _ts.getModifiers(node);
163
174
  if (modifiers) {
164
175
  for (const mod of modifiers) {
165
- if (mod.kind === ts.SyntaxKind.ExportKeyword)
176
+ if (mod.kind === _ts.SyntaxKind.ExportKeyword)
166
177
  return true;
167
178
  }
168
179
  }
@@ -170,15 +181,15 @@ export class TypeScriptScanner {
170
181
  return false;
171
182
  }
172
183
  getPropertyName(name) {
173
- if (ts.isIdentifier(name))
184
+ if (_ts.isIdentifier(name))
174
185
  return name.text;
175
- if (ts.isStringLiteral(name))
186
+ if (_ts.isStringLiteral(name))
176
187
  return name.text;
177
188
  return undefined;
178
189
  }
179
190
  extractFunction(node, sourceFile, filePath) {
180
191
  const name = node.name?.text ?? 'anonymous';
181
- const isAsync = node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;
192
+ const isAsync = node.modifiers?.some(m => m.kind === _ts.SyntaxKind.AsyncKeyword) ?? false;
182
193
  return {
183
194
  kind: 'function',
184
195
  name,
@@ -194,8 +205,8 @@ export class TypeScriptScanner {
194
205
  };
195
206
  }
196
207
  extractArrowFunction(decl, fn, sourceFile, filePath) {
197
- const name = ts.isIdentifier(decl.name) ? decl.name.text : 'anonymous';
198
- const isAsync = fn.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;
208
+ const name = _ts.isIdentifier(decl.name) ? decl.name.text : 'anonymous';
209
+ const isAsync = fn.modifiers?.some(m => m.kind === _ts.SyntaxKind.AsyncKeyword) ?? false;
199
210
  return {
200
211
  kind: 'function',
201
212
  name,
@@ -215,10 +226,10 @@ export class TypeScriptScanner {
215
226
  let signature = `class ${name}`;
216
227
  if (node.heritageClauses) {
217
228
  for (const clause of node.heritageClauses) {
218
- if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
229
+ if (clause.token === _ts.SyntaxKind.ExtendsKeyword) {
219
230
  signature += ` extends ${clause.types.map(t => t.getText(sourceFile)).join(', ')}`;
220
231
  }
221
- if (clause.token === ts.SyntaxKind.ImplementsKeyword) {
232
+ if (clause.token === _ts.SyntaxKind.ImplementsKeyword) {
222
233
  signature += ` implements ${clause.types.map(t => t.getText(sourceFile)).join(', ')}`;
223
234
  }
224
235
  }
@@ -237,7 +248,7 @@ export class TypeScriptScanner {
237
248
  }
238
249
  extractMethod(node, sourceFile, filePath, parentClass) {
239
250
  const name = this.getPropertyName(node.name) ?? 'anonymous';
240
- const isAsync = node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;
251
+ const isAsync = node.modifiers?.some(m => m.kind === _ts.SyntaxKind.AsyncKeyword) ?? false;
241
252
  return {
242
253
  kind: 'method',
243
254
  name,
@@ -267,7 +278,7 @@ export class TypeScriptScanner {
267
278
  }
268
279
  extractParameters(params, sourceFile) {
269
280
  return params.map(p => {
270
- const name = ts.isIdentifier(p.name) ? p.name.text : p.name.getText(sourceFile);
281
+ const name = _ts.isIdentifier(p.name) ? p.name.text : p.name.getText(sourceFile);
271
282
  return {
272
283
  name: p.dotDotDotToken ? `...${name}` : name,
273
284
  type: p.type ? p.type.getText(sourceFile) : undefined,
@@ -276,7 +287,7 @@ export class TypeScriptScanner {
276
287
  });
277
288
  }
278
289
  buildSignature(node, sourceFile) {
279
- const async = node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ? 'async ' : '';
290
+ const async = node.modifiers?.some(m => m.kind === _ts.SyntaxKind.AsyncKeyword) ? 'async ' : '';
280
291
  const name = node.name.text;
281
292
  const typeParams = node.typeParameters
282
293
  ? `<${node.typeParameters.map(t => t.getText(sourceFile)).join(', ')}>`
@@ -287,7 +298,7 @@ export class TypeScriptScanner {
287
298
  }
288
299
  buildArrowSignature(decl, fn, sourceFile) {
289
300
  const name = decl.name.text;
290
- const async = fn.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ? 'async ' : '';
301
+ const async = fn.modifiers?.some(m => m.kind === _ts.SyntaxKind.AsyncKeyword) ? 'async ' : '';
291
302
  const typeParams = fn.typeParameters
292
303
  ? `<${fn.typeParameters.map(t => t.getText(sourceFile)).join(', ')}>`
293
304
  : '';
@@ -296,7 +307,7 @@ export class TypeScriptScanner {
296
307
  return `const ${name} = ${async}${typeParams}(${params})${returnType} => ...`;
297
308
  }
298
309
  buildMethodSignature(node, sourceFile) {
299
- const async = node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ? 'async ' : '';
310
+ const async = node.modifiers?.some(m => m.kind === _ts.SyntaxKind.AsyncKeyword) ? 'async ' : '';
300
311
  const name = this.getPropertyName(node.name);
301
312
  const typeParams = node.typeParameters
302
313
  ? `<${node.typeParameters.map(t => t.getText(sourceFile)).join(', ')}>`
@@ -0,0 +1,3 @@
1
+ import * as Sentry from '@sentry/node';
2
+ export declare function initSentry(): void;
3
+ export { Sentry };
package/dist/sentry.js ADDED
@@ -0,0 +1,28 @@
1
+ import * as Sentry from '@sentry/node';
2
+ const DSN = process.env.SENTRY_DSN;
3
+ export function initSentry() {
4
+ if (!DSN)
5
+ return; // Gracefully disable when no DSN configured
6
+ Sentry.init({
7
+ dsn: DSN,
8
+ tracesSampleRate: 0.2,
9
+ environment: process.env.NODE_ENV || 'production',
10
+ release: process.env.npm_package_version,
11
+ beforeSend(event) {
12
+ // Strip any user file paths for privacy
13
+ if (event.exception?.values) {
14
+ for (const ex of event.exception.values) {
15
+ if (ex.stacktrace?.frames) {
16
+ for (const frame of ex.stacktrace.frames) {
17
+ if (frame.filename && !frame.filename.includes('node_modules')) {
18
+ frame.filename = frame.filename.replace(/\/Users\/[^/]+/, '~');
19
+ }
20
+ }
21
+ }
22
+ }
23
+ }
24
+ return event;
25
+ },
26
+ });
27
+ }
28
+ export { Sentry };
@@ -1,4 +1,5 @@
1
1
  {
2
+ "$schema": "./public/docs-schema.json",
2
3
  "name": "Skrypt",
3
4
  "description": "AI-powered documentation generation",
4
5
  "fonts": {
@@ -6,8 +7,7 @@
6
7
  "mono": "JetBrains Mono"
7
8
  },
8
9
  "theme": {
9
- "primaryColor": "#3b82f6",
10
- "accentColor": "#8b5cf6"
10
+ "primaryColor": "#3b82f6"
11
11
  },
12
12
  "navigation": [
13
13
  {
@@ -33,6 +33,9 @@
33
33
  "links": [
34
34
  { "title": "GitHub", "url": "https://github.com/debgotwired/skrypt" },
35
35
  { "title": "npm", "url": "https://www.npmjs.com/package/skrypt-ai" }
36
- ]
36
+ ],
37
+ "social": {
38
+ "github": "https://github.com/debgotwired/skrypt"
39
+ }
37
40
  }
38
41
  }
@@ -1,6 +1,7 @@
1
1
  import createMDX from '@next/mdx'
2
2
  import remarkGfm from 'remark-gfm'
3
3
  import remarkFrontmatter from 'remark-frontmatter'
4
+ import { withSentryConfig } from '@sentry/nextjs'
4
5
 
5
6
  const withMDX = createMDX({
6
7
  extension: /\.mdx?$/,
@@ -47,4 +48,17 @@ const nextConfig = {
47
48
  },
48
49
  }
49
50
 
50
- export default withMDX(nextConfig)
51
+ export default withSentryConfig(withMDX(nextConfig), {
52
+ // Suppress Sentry build logs
53
+ silent: true,
54
+
55
+ // Source maps: disabled for now — enable per-customer later
56
+ sourcemaps: {
57
+ disable: true,
58
+ },
59
+
60
+ // Tunnel Sentry events through Next.js to bypass ad-blockers
61
+ tunnelRoute: '/monitoring',
62
+
63
+ // Do not set org/project — each customer configures their own via env vars
64
+ })
@@ -17,6 +17,8 @@
17
17
  "@mdx-js/react": "^3.1.0",
18
18
  "@next/mdx": "^15.3.0",
19
19
  "@orama/orama": "^3.1.0",
20
+ "@sentry/nextjs": "^10.45.0",
21
+ "ai": "^4.0.0",
20
22
  "clsx": "^2.1.0",
21
23
  "gray-matter": "^4.0.3",
22
24
  "lucide-react": "^0.500.0",
@@ -26,7 +28,6 @@
26
28
  "react-dom": "^19.0.0",
27
29
  "remark-frontmatter": "^5.0.0",
28
30
  "remark-gfm": "^4.0.0",
29
- "ai": "^4.0.0",
30
31
  "shiki": "^3.0.0",
31
32
  "streamdown": "^1.0.0",
32
33
  "tailwind-merge": "^3.0.0",
@@ -34,8 +35,8 @@
34
35
  },
35
36
  "optionalDependencies": {
36
37
  "@codesandbox/sandpack-react": "^2.20.0",
37
- "mermaid": "^11.13.0",
38
- "@scalar/nextjs-api-reference": "^0.4.0"
38
+ "@scalar/nextjs-api-reference": "^0.4.0",
39
+ "mermaid": "^11.13.0"
39
40
  },
40
41
  "devDependencies": {
41
42
  "@tailwindcss/postcss": "^4.1.0",