skrypt-ai 0.7.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 (189) hide show
  1. package/dist/auth/index.js +9 -3
  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/claims/extractor.d.ts +13 -0
  14. package/dist/claims/extractor.js +138 -0
  15. package/dist/claims/index.d.ts +4 -0
  16. package/dist/claims/index.js +4 -0
  17. package/dist/claims/reporter.d.ts +9 -0
  18. package/dist/claims/reporter.js +65 -0
  19. package/dist/claims/store.d.ts +13 -0
  20. package/dist/claims/store.js +51 -0
  21. package/dist/claims/types.d.ts +34 -0
  22. package/dist/cli.js +516 -56
  23. package/dist/commands/bind.d.ts +2 -0
  24. package/dist/commands/bind.js +139 -0
  25. package/dist/commands/claims.d.ts +2 -0
  26. package/dist/commands/claims.js +84 -0
  27. package/dist/commands/coverage.d.ts +2 -0
  28. package/dist/commands/coverage.js +61 -0
  29. package/dist/commands/cron.js +0 -4
  30. package/dist/commands/generate/index.d.ts +3 -0
  31. package/dist/commands/generate/index.js +398 -0
  32. package/dist/commands/generate/scan.d.ts +41 -0
  33. package/dist/commands/generate/scan.js +275 -0
  34. package/dist/commands/generate/verify.d.ts +14 -0
  35. package/dist/commands/generate/verify.js +122 -0
  36. package/dist/commands/generate/write.d.ts +32 -0
  37. package/dist/commands/generate/write.js +184 -0
  38. package/dist/commands/import.js +16 -4
  39. package/dist/commands/init.js +68 -5
  40. package/dist/commands/llms-txt.js +6 -4
  41. package/dist/commands/monitor.d.ts +15 -0
  42. package/dist/commands/monitor.js +2 -2
  43. package/dist/commands/mutate.d.ts +2 -0
  44. package/dist/commands/mutate.js +177 -0
  45. package/dist/config/loader.d.ts +0 -1
  46. package/dist/config/loader.js +1 -1
  47. package/dist/config/types.js +2 -2
  48. package/dist/coverage/calculator.d.ts +7 -0
  49. package/dist/coverage/calculator.js +86 -0
  50. package/dist/coverage/index.d.ts +3 -0
  51. package/dist/coverage/index.js +3 -0
  52. package/dist/coverage/reporter.d.ts +9 -0
  53. package/dist/coverage/reporter.js +65 -0
  54. package/dist/coverage/types.d.ts +36 -0
  55. package/dist/generator/agents-md.d.ts +25 -0
  56. package/dist/generator/agents-md.js +122 -0
  57. package/dist/generator/generator.d.ts +3 -1
  58. package/dist/generator/generator.js +137 -23
  59. package/dist/generator/index.d.ts +2 -0
  60. package/dist/generator/index.js +2 -0
  61. package/dist/generator/mdx-serializer.d.ts +11 -0
  62. package/dist/generator/mdx-serializer.js +136 -0
  63. package/dist/generator/organizer.d.ts +6 -17
  64. package/dist/generator/organizer.js +29 -52
  65. package/dist/generator/types.d.ts +6 -0
  66. package/dist/generator/writer.js +12 -6
  67. package/dist/github/org-discovery.js +5 -0
  68. package/dist/importers/mintlify.js +4 -3
  69. package/dist/llm/anthropic-client.js +1 -0
  70. package/dist/llm/index.d.ts +15 -0
  71. package/dist/llm/index.js +148 -29
  72. package/dist/llm/openai-client.js +2 -0
  73. package/dist/llm/proxy-client.d.ts +32 -0
  74. package/dist/llm/proxy-client.js +103 -0
  75. package/dist/mutation/index.d.ts +4 -0
  76. package/dist/mutation/index.js +4 -0
  77. package/dist/mutation/mutator.d.ts +5 -0
  78. package/dist/mutation/mutator.js +101 -0
  79. package/dist/mutation/reporter.d.ts +14 -0
  80. package/dist/mutation/reporter.js +66 -0
  81. package/dist/mutation/runner.d.ts +9 -0
  82. package/dist/mutation/runner.js +70 -0
  83. package/dist/mutation/types.d.ts +51 -0
  84. package/dist/qa/checks.d.ts +1 -0
  85. package/dist/qa/checks.js +47 -0
  86. package/dist/qa/index.js +2 -1
  87. package/dist/scanner/csharp.d.ts +0 -4
  88. package/dist/scanner/csharp.js +9 -49
  89. package/dist/scanner/go.d.ts +0 -3
  90. package/dist/scanner/go.js +8 -35
  91. package/dist/scanner/index.js +78 -11
  92. package/dist/scanner/java.d.ts +0 -4
  93. package/dist/scanner/java.js +9 -49
  94. package/dist/scanner/kotlin.d.ts +0 -3
  95. package/dist/scanner/kotlin.js +6 -33
  96. package/dist/scanner/php.d.ts +0 -10
  97. package/dist/scanner/php.js +11 -55
  98. package/dist/scanner/ruby.d.ts +0 -3
  99. package/dist/scanner/ruby.js +8 -38
  100. package/dist/scanner/rust.d.ts +0 -3
  101. package/dist/scanner/rust.js +10 -37
  102. package/dist/scanner/swift.d.ts +0 -3
  103. package/dist/scanner/swift.js +8 -35
  104. package/dist/scanner/typescript.js +42 -31
  105. package/dist/scanner/utils.d.ts +41 -0
  106. package/dist/scanner/utils.js +97 -0
  107. package/dist/sentry.d.ts +3 -0
  108. package/dist/sentry.js +28 -0
  109. package/dist/template/docs.json +10 -4
  110. package/dist/template/next.config.mjs +46 -1
  111. package/dist/template/package.json +7 -4
  112. package/dist/template/public/docs-schema.json +257 -0
  113. package/dist/template/sentry.client.config.ts +12 -0
  114. package/dist/template/sentry.edge.config.ts +7 -0
  115. package/dist/template/sentry.server.config.ts +7 -0
  116. package/dist/template/src/app/docs/[...slug]/page.tsx +11 -5
  117. package/dist/template/src/app/docs/layout.tsx +2 -4
  118. package/dist/template/src/app/global-error.tsx +60 -0
  119. package/dist/template/src/app/layout.tsx +14 -23
  120. package/dist/template/src/app/llms-full.md/route.ts +29 -0
  121. package/dist/template/src/app/llms.txt/route.ts +29 -0
  122. package/dist/template/src/app/md/[...slug]/route.ts +174 -0
  123. package/dist/template/src/app/page.tsx +2 -5
  124. package/dist/template/src/app/reference/route.ts +22 -18
  125. package/dist/template/src/app/sitemap.ts +1 -1
  126. package/dist/template/src/components/ai-chat-impl.tsx +206 -0
  127. package/dist/template/src/components/ai-chat.tsx +20 -193
  128. package/dist/template/src/components/docs-layout.tsx +1 -15
  129. package/dist/template/src/components/footer.tsx +95 -19
  130. package/dist/template/src/components/header.tsx +1 -1
  131. package/dist/template/src/components/mdx/index.tsx +27 -4
  132. package/dist/template/src/components/search-dialog.tsx +5 -0
  133. package/dist/template/src/instrumentation.ts +11 -0
  134. package/dist/template/src/lib/docs-config.ts +235 -0
  135. package/dist/template/src/lib/fonts.ts +135 -0
  136. package/dist/template/src/middleware.ts +101 -0
  137. package/dist/template/src/styles/globals.css +28 -20
  138. package/dist/testing/runner.js +8 -1
  139. package/dist/utils/files.d.ts +0 -8
  140. package/dist/utils/files.js +0 -33
  141. package/package.json +2 -1
  142. package/dist/autofix/autofix.test.js +0 -487
  143. package/dist/commands/generate.d.ts +0 -9
  144. package/dist/commands/generate.js +0 -739
  145. package/dist/generator/generator.test.js +0 -259
  146. package/dist/generator/writer.test.js +0 -411
  147. package/dist/llm/llm.manual-test.js +0 -112
  148. package/dist/llm/llm.mock-test.d.ts +0 -4
  149. package/dist/llm/llm.mock-test.js +0 -79
  150. package/dist/plugins/index.d.ts +0 -47
  151. package/dist/plugins/index.js +0 -181
  152. package/dist/scanner/content-type.test.d.ts +0 -1
  153. package/dist/scanner/content-type.test.js +0 -231
  154. package/dist/scanner/integration.test.d.ts +0 -4
  155. package/dist/scanner/integration.test.js +0 -180
  156. package/dist/scanner/scanner.test.d.ts +0 -1
  157. package/dist/scanner/scanner.test.js +0 -210
  158. package/dist/scanner/typescript.manual-test.d.ts +0 -1
  159. package/dist/scanner/typescript.manual-test.js +0 -112
  160. package/dist/template/src/app/docs/auth/page.mdx +0 -589
  161. package/dist/template/src/app/docs/autofix/page.mdx +0 -624
  162. package/dist/template/src/app/docs/cli/page.mdx +0 -217
  163. package/dist/template/src/app/docs/config/page.mdx +0 -428
  164. package/dist/template/src/app/docs/configuration/page.mdx +0 -86
  165. package/dist/template/src/app/docs/deployment/page.mdx +0 -112
  166. package/dist/template/src/app/docs/generator/generator.md +0 -504
  167. package/dist/template/src/app/docs/generator/organizer.md +0 -779
  168. package/dist/template/src/app/docs/generator/page.mdx +0 -613
  169. package/dist/template/src/app/docs/github/page.mdx +0 -502
  170. package/dist/template/src/app/docs/llm/anthropic-client.md +0 -549
  171. package/dist/template/src/app/docs/llm/index.md +0 -471
  172. package/dist/template/src/app/docs/llm/page.mdx +0 -428
  173. package/dist/template/src/app/docs/plugins/page.mdx +0 -1793
  174. package/dist/template/src/app/docs/pro/page.mdx +0 -121
  175. package/dist/template/src/app/docs/quickstart/page.mdx +0 -93
  176. package/dist/template/src/app/docs/scanner/content-type.md +0 -599
  177. package/dist/template/src/app/docs/scanner/index.md +0 -212
  178. package/dist/template/src/app/docs/scanner/page.mdx +0 -307
  179. package/dist/template/src/app/docs/scanner/python.md +0 -469
  180. package/dist/template/src/app/docs/scanner/python_parser.md +0 -1056
  181. package/dist/template/src/app/docs/scanner/rust.md +0 -325
  182. package/dist/template/src/app/docs/scanner/typescript.md +0 -201
  183. package/dist/template/src/app/icon.tsx +0 -29
  184. package/dist/utils/validation.d.ts +0 -1
  185. package/dist/utils/validation.js +0 -12
  186. /package/dist/{autofix/autofix.test.d.ts → binding/types.js} +0 -0
  187. /package/dist/{generator/generator.test.d.ts → claims/types.js} +0 -0
  188. /package/dist/{generator/writer.test.d.ts → coverage/types.js} +0 -0
  189. /package/dist/{llm/llm.manual-test.d.ts → mutation/types.js} +0 -0
@@ -8,7 +8,7 @@ if (!homeDir) {
8
8
  }
9
9
  const CONFIG_DIR = join(homeDir, '.skrypt');
10
10
  const AUTH_FILE = join(CONFIG_DIR, 'auth.json');
11
- const API_BASE = process.env.SKRYPT_API_URL || 'https://api.skrypt.sh';
11
+ const API_BASE = process.env.SKRYPT_API_URL || 'https://app.skrypt.sh';
12
12
  /**
13
13
  * Sync auth config reader — checks env var and auth file only (no keychain).
14
14
  * Use getAuthConfigAsync() when keychain access is needed.
@@ -107,7 +107,7 @@ export async function getKeyStorageMethod() {
107
107
  }
108
108
  export async function checkPlan(apiKey) {
109
109
  try {
110
- const response = await fetch(`${API_BASE}/v1/plan`, {
110
+ const response = await fetch(`${API_BASE}/api/auth/plan`, {
111
111
  headers: {
112
112
  'Authorization': `Bearer ${apiKey}`,
113
113
  'Content-Type': 'application/json'
@@ -135,6 +135,10 @@ export async function checkPlan(apiKey) {
135
135
  }
136
136
  }
137
137
  export async function requirePro(commandName) {
138
+ // Dev bypass: requires both SKRYPT_DEV=1 and a valid signed dev token
139
+ if (process.env.SKRYPT_DEV === '1' && process.env.NODE_ENV === 'test') {
140
+ return true;
141
+ }
138
142
  const config = await getAuthConfigAsync();
139
143
  if (!config.apiKey) {
140
144
  console.error(`\n ⚡ ${commandName} requires Skrypt Pro\n`);
@@ -173,14 +177,16 @@ export async function requirePro(commandName) {
173
177
  }
174
178
  // Pro commands list
175
179
  export const PRO_COMMANDS = [
180
+ 'claims',
176
181
  'monitor',
182
+ 'mutate',
177
183
  'autofix',
178
184
  'heal',
179
185
  'test',
180
186
  'sdk',
181
187
  'gh-action',
182
- 'ci',
183
188
  'mcp',
184
189
  'refresh',
185
190
  'review',
191
+ 'review-pr',
186
192
  ];
@@ -0,0 +1,5 @@
1
+ import { CodeSymbol, DocSection, Binding } from './types.js';
2
+ /**
3
+ * Match doc sections to code symbols and produce bindings
4
+ */
5
+ export declare function createBindings(symbols: CodeSymbol[], sections: DocSection[]): Binding[];
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Match doc sections to code symbols and produce bindings
3
+ */
4
+ export function createBindings(symbols, sections) {
5
+ const bindings = [];
6
+ const seen = new Set();
7
+ const symbolsByName = new Map();
8
+ for (const sym of symbols) {
9
+ const key = sym.name.toLowerCase();
10
+ if (!symbolsByName.has(key))
11
+ symbolsByName.set(key, []);
12
+ symbolsByName.get(key).push(sym);
13
+ }
14
+ for (const section of sections) {
15
+ for (const refName of section.referencedSymbols) {
16
+ const matches = symbolsByName.get(refName.toLowerCase());
17
+ if (!matches)
18
+ continue;
19
+ for (const sym of matches) {
20
+ // O(1) dedup check using Set
21
+ const dedupKey = `${section.filePath}::${section.heading}::${sym.name}::${sym.filePath}`;
22
+ if (seen.has(dedupKey))
23
+ continue;
24
+ seen.add(dedupKey);
25
+ // Determine binding type and confidence
26
+ const { bindingType, confidence } = classifyBinding(section, sym, refName);
27
+ bindings.push({
28
+ docSection: {
29
+ filePath: section.filePath,
30
+ heading: section.heading,
31
+ startLine: section.startLine,
32
+ },
33
+ codeSymbol: {
34
+ name: sym.name,
35
+ kind: sym.kind,
36
+ filePath: sym.filePath,
37
+ hash: sym.hash,
38
+ },
39
+ confidence,
40
+ bindingType,
41
+ });
42
+ }
43
+ }
44
+ }
45
+ return bindings;
46
+ }
47
+ /**
48
+ * Classify how a doc section references a code symbol
49
+ */
50
+ function classifyBinding(section, symbol, _refName) {
51
+ // Check if any code block in the section explicitly uses this symbol
52
+ for (const block of section.codeBlocks) {
53
+ if (block.referencedSymbols.some(s => s.toLowerCase() === symbol.name.toLowerCase())) {
54
+ return { bindingType: 'explicit', confidence: 0.95 };
55
+ }
56
+ }
57
+ // Check if the section heading contains the symbol name
58
+ if (section.heading.toLowerCase().includes(symbol.name.toLowerCase())) {
59
+ return { bindingType: 'reference', confidence: 0.85 };
60
+ }
61
+ // Prose reference
62
+ return { bindingType: 'reference', confidence: 0.6 };
63
+ }
@@ -0,0 +1,5 @@
1
+ import { CodeSymbol, Binding, StaleDoc } from './types.js';
2
+ /**
3
+ * Detect which doc sections are stale by comparing old and new symbol hashes
4
+ */
5
+ export declare function detectStale(oldSymbols: CodeSymbol[], newSymbols: CodeSymbol[], bindings: Binding[]): StaleDoc[];
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Detect which doc sections are stale by comparing old and new symbol hashes
3
+ */
4
+ export function detectStale(oldSymbols, newSymbols, bindings) {
5
+ const staleDocs = [];
6
+ // Index by filePath::name for O(1) lookup
7
+ const oldByLookup = new Map();
8
+ const newByLookup = new Map();
9
+ for (const s of oldSymbols)
10
+ oldByLookup.set(lookupKey(s), s);
11
+ for (const s of newSymbols)
12
+ newByLookup.set(lookupKey(s), s);
13
+ for (const binding of bindings) {
14
+ const bKey = `${binding.codeSymbol.filePath}::${binding.codeSymbol.name}`;
15
+ const oldSym = oldByLookup.get(bKey);
16
+ const newSym = newByLookup.get(bKey);
17
+ if (!newSym && oldSym) {
18
+ // Symbol was deleted
19
+ staleDocs.push({
20
+ docPath: binding.docSection.filePath,
21
+ section: binding.docSection.heading,
22
+ reason: `Symbol '${binding.codeSymbol.name}' was deleted from ${binding.codeSymbol.filePath}`,
23
+ severity: 'high',
24
+ changedSymbol: binding.codeSymbol.name,
25
+ });
26
+ }
27
+ else if (oldSym && newSym && oldSym.hash !== newSym.hash) {
28
+ // Symbol was modified
29
+ const severity = binding.confidence >= 0.8 ? 'high' : 'medium';
30
+ staleDocs.push({
31
+ docPath: binding.docSection.filePath,
32
+ section: binding.docSection.heading,
33
+ reason: `Symbol '${binding.codeSymbol.name}' was modified in ${binding.codeSymbol.filePath}`,
34
+ severity,
35
+ changedSymbol: binding.codeSymbol.name,
36
+ });
37
+ }
38
+ }
39
+ // Deduplicate by docPath + section
40
+ const seen = new Set();
41
+ return staleDocs.filter(s => {
42
+ const key = `${s.docPath}::${s.section}`;
43
+ if (seen.has(key))
44
+ return false;
45
+ seen.add(key);
46
+ return true;
47
+ });
48
+ }
49
+ function lookupKey(s) {
50
+ return `${s.filePath}::${s.name}`;
51
+ }
@@ -0,0 +1,9 @@
1
+ import { DocSection } from './types.js';
2
+ /**
3
+ * Parse all doc files in a directory and extract sections with symbol references
4
+ */
5
+ export declare function parseDocSections(docsDir: string): DocSection[];
6
+ /**
7
+ * Parse a single markdown/MDX file into sections
8
+ */
9
+ export declare function parseFile(filePath: string): DocSection[];
@@ -0,0 +1,138 @@
1
+ import { readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join, extname } from 'path';
3
+ /**
4
+ * Parse all doc files in a directory and extract sections with symbol references
5
+ */
6
+ export function parseDocSections(docsDir) {
7
+ const files = findMarkdownFiles(docsDir);
8
+ const sections = [];
9
+ for (const filePath of files) {
10
+ sections.push(...parseFile(filePath));
11
+ }
12
+ return sections;
13
+ }
14
+ /**
15
+ * Parse a single markdown/MDX file into sections
16
+ */
17
+ export function parseFile(filePath) {
18
+ const content = readFileSync(filePath, 'utf-8');
19
+ const lines = content.split('\n');
20
+ const sections = [];
21
+ let currentSection = null;
22
+ for (let i = 0; i < lines.length; i++) {
23
+ const line = lines[i];
24
+ const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
25
+ if (headingMatch) {
26
+ // Finalize previous section
27
+ if (currentSection) {
28
+ currentSection.endLine = i - 1;
29
+ currentSection.referencedSymbols = dedup(currentSection.referencedSymbols);
30
+ sections.push(currentSection);
31
+ }
32
+ currentSection = {
33
+ filePath,
34
+ heading: headingMatch[2].replace(/`/g, ''),
35
+ headingLevel: headingMatch[1].length,
36
+ startLine: i,
37
+ endLine: lines.length - 1,
38
+ referencedSymbols: [],
39
+ codeBlocks: [],
40
+ };
41
+ }
42
+ if (!currentSection)
43
+ continue;
44
+ // Extract inline code references (backtick-wrapped names)
45
+ const inlineCodeMatches = line.matchAll(/`([a-zA-Z_]\w*(?:\.\w+)*(?:\([^)]*\))?)`/g);
46
+ for (const match of inlineCodeMatches) {
47
+ const name = match[1].replace(/\(.*\)$/, ''); // strip function call parens
48
+ currentSection.referencedSymbols.push(name);
49
+ }
50
+ // Extract code blocks (opening fence: ```lang or bare ```)
51
+ const trimmedLine = line.trim();
52
+ // Match: bare ``` or ```language — but not single-line fences like ```code here```
53
+ if (trimmedLine.startsWith('```')) {
54
+ const afterBackticks = trimmedLine.slice(3);
55
+ const isSingleLineFence = afterBackticks.includes('```');
56
+ if (!isSingleLineFence) {
57
+ const lang = afterBackticks.trim();
58
+ const blockStart = i;
59
+ const blockLines = [];
60
+ i++;
61
+ while (i < lines.length && !lines[i].trim().startsWith('```')) {
62
+ blockLines.push(lines[i]);
63
+ i++;
64
+ }
65
+ const blockContent = blockLines.join('\n');
66
+ const blockSymbols = extractSymbolsFromCode(blockContent);
67
+ const codeBlock = {
68
+ language: lang || 'unknown',
69
+ content: blockContent,
70
+ startLine: blockStart,
71
+ endLine: i,
72
+ referencedSymbols: blockSymbols,
73
+ };
74
+ currentSection.codeBlocks.push(codeBlock);
75
+ currentSection.referencedSymbols.push(...blockSymbols);
76
+ }
77
+ }
78
+ }
79
+ // Finalize last section
80
+ if (currentSection) {
81
+ currentSection.referencedSymbols = dedup(currentSection.referencedSymbols);
82
+ sections.push(currentSection);
83
+ }
84
+ return sections;
85
+ }
86
+ /**
87
+ * Extract symbol names referenced in a code block
88
+ */
89
+ function extractSymbolsFromCode(code) {
90
+ const names = [];
91
+ // Function/method calls: name(
92
+ const callMatches = code.matchAll(/\b([a-zA-Z_]\w*)\s*\(/g);
93
+ for (const m of callMatches) {
94
+ const name = m[1];
95
+ if (!isCommonKeyword(name))
96
+ names.push(name);
97
+ }
98
+ // Class instantiation: new Name
99
+ const newMatches = code.matchAll(/\bnew\s+([A-Z]\w*)/g);
100
+ for (const m of newMatches)
101
+ names.push(m[1]);
102
+ // Type references: : TypeName or <TypeName>
103
+ const typeMatches = code.matchAll(/:\s*([A-Z]\w*)|<([A-Z]\w*)>/g);
104
+ for (const m of typeMatches)
105
+ names.push(m[1] || m[2]);
106
+ return dedup(names);
107
+ }
108
+ function isCommonKeyword(name) {
109
+ const keywords = new Set([
110
+ 'if', 'else', 'for', 'while', 'return', 'const', 'let', 'var', 'function',
111
+ 'class', 'import', 'export', 'from', 'async', 'await', 'try', 'catch',
112
+ 'throw', 'new', 'typeof', 'instanceof', 'console', 'require', 'log',
113
+ 'print', 'def', 'self', 'None', 'True', 'False', 'len', 'range', 'str',
114
+ 'int', 'float', 'list', 'dict', 'set', 'map', 'filter', 'reduce',
115
+ ]);
116
+ return keywords.has(name);
117
+ }
118
+ function findMarkdownFiles(dir) {
119
+ const files = [];
120
+ function walk(d) {
121
+ const entries = readdirSync(d);
122
+ for (const entry of entries) {
123
+ const fullPath = join(d, entry);
124
+ const s = statSync(fullPath);
125
+ if (s.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules') {
126
+ walk(fullPath);
127
+ }
128
+ else if (s.isFile() && (extname(entry) === '.md' || extname(entry) === '.mdx')) {
129
+ files.push(fullPath);
130
+ }
131
+ }
132
+ }
133
+ walk(dir);
134
+ return files;
135
+ }
136
+ function dedup(arr) {
137
+ return [...new Set(arr)];
138
+ }
@@ -0,0 +1,14 @@
1
+ import { APIElement } from '../scanner/types.js';
2
+ import { CodeSymbol } from './types.js';
3
+ /**
4
+ * Extract code symbols from a source directory using the existing scanner
5
+ */
6
+ export declare function extractSymbols(sourcePath: string): Promise<CodeSymbol[]>;
7
+ /**
8
+ * Convert an APIElement from the scanner to a CodeSymbol for binding
9
+ */
10
+ export declare function elementToSymbol(el: APIElement): CodeSymbol;
11
+ /**
12
+ * Hash content for change detection
13
+ */
14
+ export declare function hashContent(content: string): string;
@@ -0,0 +1,39 @@
1
+ import { createHash } from 'crypto';
2
+ import { scanDirectory } from '../scanner/index.js';
3
+ /**
4
+ * Extract code symbols from a source directory using the existing scanner
5
+ */
6
+ export async function extractSymbols(sourcePath) {
7
+ const scanResult = await scanDirectory(sourcePath);
8
+ const symbols = [];
9
+ for (const file of scanResult.files) {
10
+ for (const el of file.elements) {
11
+ symbols.push(elementToSymbol(el));
12
+ }
13
+ }
14
+ return symbols;
15
+ }
16
+ /**
17
+ * Convert an APIElement from the scanner to a CodeSymbol for binding
18
+ */
19
+ export function elementToSymbol(el) {
20
+ return {
21
+ name: el.name,
22
+ kind: el.kind === 'function' || el.kind === 'method' || el.kind === 'class'
23
+ ? el.kind
24
+ : 'function',
25
+ filePath: el.filePath,
26
+ startLine: el.lineNumber,
27
+ endLine: el.lineNumber + (el.sourceContext?.split('\n').length ?? 1),
28
+ signature: el.signature,
29
+ hash: hashContent(el.signature + (el.sourceContext || '')),
30
+ isExported: el.isExported ?? el.isPublic ?? false,
31
+ parentClass: el.parentClass,
32
+ };
33
+ }
34
+ /**
35
+ * Hash content for change detection
36
+ */
37
+ export function hashContent(content) {
38
+ return createHash('sha256').update(content).digest('hex').slice(0, 16);
39
+ }
@@ -0,0 +1,5 @@
1
+ export * from './types.js';
2
+ export * from './extractor.js';
3
+ export * from './doc-parser.js';
4
+ export * from './binder.js';
5
+ export * from './detector.js';
@@ -0,0 +1,5 @@
1
+ export * from './types.js';
2
+ export * from './extractor.js';
3
+ export * from './doc-parser.js';
4
+ export * from './binder.js';
5
+ export * from './detector.js';
@@ -0,0 +1,74 @@
1
+ /**
2
+ * A code symbol extracted from source via AST or regex
3
+ */
4
+ export interface CodeSymbol {
5
+ name: string;
6
+ kind: 'function' | 'class' | 'method' | 'type' | 'interface' | 'constant' | 'variable';
7
+ filePath: string;
8
+ startLine: number;
9
+ endLine: number;
10
+ signature: string;
11
+ hash: string;
12
+ isExported: boolean;
13
+ parentClass?: string;
14
+ }
15
+ /**
16
+ * A section of documentation parsed from MDX/markdown
17
+ */
18
+ export interface DocSection {
19
+ filePath: string;
20
+ heading: string;
21
+ headingLevel: number;
22
+ startLine: number;
23
+ endLine: number;
24
+ referencedSymbols: string[];
25
+ codeBlocks: CodeBlockRef[];
26
+ }
27
+ /**
28
+ * A code block within a doc section
29
+ */
30
+ export interface CodeBlockRef {
31
+ language: string;
32
+ content: string;
33
+ startLine: number;
34
+ endLine: number;
35
+ referencedSymbols: string[];
36
+ }
37
+ /**
38
+ * A binding between a doc section and a code symbol
39
+ */
40
+ export interface Binding {
41
+ docSection: {
42
+ filePath: string;
43
+ heading: string;
44
+ startLine: number;
45
+ };
46
+ codeSymbol: {
47
+ name: string;
48
+ kind: string;
49
+ filePath: string;
50
+ hash: string;
51
+ };
52
+ confidence: number;
53
+ bindingType: 'explicit' | 'reference' | 'inferred';
54
+ }
55
+ /**
56
+ * A doc page detected as stale
57
+ */
58
+ export interface StaleDoc {
59
+ docPath: string;
60
+ section: string;
61
+ reason: string;
62
+ severity: 'high' | 'medium' | 'low';
63
+ changedSymbol: string;
64
+ }
65
+ /**
66
+ * The full bindings file stored in .skrypt/bindings.json
67
+ */
68
+ export interface BindingsFile {
69
+ version: 1;
70
+ generatedAt: string;
71
+ bindings: Binding[];
72
+ symbols: CodeSymbol[];
73
+ sections: DocSection[];
74
+ }
@@ -0,0 +1,13 @@
1
+ import { LLMClient } from '../llm/types.js';
2
+ import { Claim } from './types.js';
3
+ /**
4
+ * Extract testable claims from a documentation file using LLM
5
+ */
6
+ export declare function extractClaimsFromFile(filePath: string, client: LLMClient): Promise<Claim[]>;
7
+ /**
8
+ * Extract claims from all docs in a directory
9
+ */
10
+ export declare function extractClaimsFromDirectory(docsDir: string, client: LLMClient, options?: {
11
+ file?: string;
12
+ type?: Claim['type'];
13
+ }): Promise<Claim[]>;
@@ -0,0 +1,138 @@
1
+ import { readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join, extname } from 'path';
3
+ import { createHash } from 'crypto';
4
+ const EXTRACTION_PROMPT = `You are a documentation analysis expert. Extract all TESTABLE CLAIMS from this documentation section.
5
+
6
+ A testable claim is a statement that can be verified by running code. For each claim:
7
+ 1. Quote the exact text making the claim
8
+ 2. Classify its type:
9
+ - behavioral: "function X returns Y when Z" — directly testable
10
+ - constraint: "never returns null", "list is always sorted" — property-testable
11
+ - integration: "works with library X version Y" — env-testable
12
+ - performance: "handles 1000 requests/second" — benchmark-testable
13
+ - error_handling: "throws TypeError on invalid input" — exception-testable
14
+ 3. Assess confidence (0.0-1.0) that this is actually testable
15
+ 4. Mark testable: true/false
16
+ 5. Suggest a test code snippet if testable
17
+
18
+ Respond with ONLY a JSON array:
19
+ [
20
+ {
21
+ "text": "exact claim text",
22
+ "type": "behavioral|constraint|integration|performance|error_handling",
23
+ "confidence": 0.85,
24
+ "testable": true,
25
+ "suggestedTest": "// test code here"
26
+ }
27
+ ]
28
+
29
+ If no testable claims found, return [].`;
30
+ /**
31
+ * Extract testable claims from a documentation file using LLM
32
+ */
33
+ export async function extractClaimsFromFile(filePath, client) {
34
+ const content = readFileSync(filePath, 'utf-8');
35
+ const sections = splitIntoSections(content);
36
+ const claims = [];
37
+ for (const section of sections) {
38
+ if (section.content.trim().length < 50)
39
+ continue; // skip tiny sections
40
+ try {
41
+ const sectionClaims = await extractClaimsFromSection(section.content, section.heading, filePath, section.startLine, client);
42
+ claims.push(...sectionClaims);
43
+ }
44
+ catch {
45
+ // Skip failed sections
46
+ }
47
+ }
48
+ return claims;
49
+ }
50
+ /**
51
+ * Extract claims from a single documentation section
52
+ */
53
+ async function extractClaimsFromSection(content, heading, filePath, startLine, client) {
54
+ const response = await client.complete({
55
+ messages: [
56
+ { role: 'system', content: EXTRACTION_PROMPT },
57
+ { role: 'user', content: `## Section: ${heading}\n\n${content.slice(0, 3000)}` },
58
+ ],
59
+ temperature: 0,
60
+ maxTokens: 2048,
61
+ });
62
+ // Parse JSON from response
63
+ const jsonMatch = response.content.match(/\[[\s\S]*\]/);
64
+ if (!jsonMatch)
65
+ return [];
66
+ try {
67
+ const raw = JSON.parse(jsonMatch[0]);
68
+ return raw.map(item => ({
69
+ id: createHash('sha256').update(`${filePath}:${heading}:${item.text}`).digest('hex').slice(0, 12),
70
+ type: validateClaimType(item.type),
71
+ text: item.text,
72
+ source: { file: filePath, section: heading, line: startLine },
73
+ confidence: Math.max(0, Math.min(1, item.confidence)),
74
+ testable: item.testable,
75
+ suggestedTest: item.suggestedTest,
76
+ status: 'extracted',
77
+ }));
78
+ }
79
+ catch {
80
+ return [];
81
+ }
82
+ }
83
+ /**
84
+ * Extract claims from all docs in a directory
85
+ */
86
+ export async function extractClaimsFromDirectory(docsDir, client, options) {
87
+ const files = options?.file
88
+ ? [options.file]
89
+ : findMarkdownFiles(docsDir);
90
+ const allClaims = [];
91
+ for (const file of files) {
92
+ const claims = await extractClaimsFromFile(file, client);
93
+ allClaims.push(...claims);
94
+ }
95
+ if (options?.type) {
96
+ return allClaims.filter(c => c.type === options.type);
97
+ }
98
+ return allClaims;
99
+ }
100
+ function validateClaimType(type) {
101
+ const valid = ['behavioral', 'constraint', 'integration', 'performance', 'error_handling'];
102
+ return valid.includes(type) ? type : 'behavioral';
103
+ }
104
+ function splitIntoSections(content) {
105
+ const lines = content.split('\n');
106
+ const sections = [];
107
+ let current = null;
108
+ for (let i = 0; i < lines.length; i++) {
109
+ const match = lines[i].match(/^(#{1,6})\s+(.+)$/);
110
+ if (match) {
111
+ if (current)
112
+ sections.push(current);
113
+ current = { heading: match[2], content: '', startLine: i };
114
+ }
115
+ else if (current) {
116
+ current.content += lines[i] + '\n';
117
+ }
118
+ }
119
+ if (current)
120
+ sections.push(current);
121
+ return sections;
122
+ }
123
+ function findMarkdownFiles(dir) {
124
+ const files = [];
125
+ function walk(d) {
126
+ const entries = readdirSync(d);
127
+ for (const entry of entries) {
128
+ const fullPath = join(d, entry);
129
+ const s = statSync(fullPath);
130
+ if (s.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules')
131
+ walk(fullPath);
132
+ else if (s.isFile() && (extname(entry) === '.md' || extname(entry) === '.mdx'))
133
+ files.push(fullPath);
134
+ }
135
+ }
136
+ walk(dir);
137
+ return files;
138
+ }
@@ -0,0 +1,4 @@
1
+ export * from './types.js';
2
+ export * from './extractor.js';
3
+ export * from './store.js';
4
+ export * from './reporter.js';
@@ -0,0 +1,4 @@
1
+ export * from './types.js';
2
+ export * from './extractor.js';
3
+ export * from './store.js';
4
+ export * from './reporter.js';
@@ -0,0 +1,9 @@
1
+ import { Claim } from './types.js';
2
+ /**
3
+ * Print claims report to terminal
4
+ */
5
+ export declare function printClaimsReport(claims: Claim[]): void;
6
+ /**
7
+ * Format claims as JSON
8
+ */
9
+ export declare function formatClaimsJson(claims: Claim[]): string;