skrypt-ai 0.6.1 → 0.8.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 (180) hide show
  1. package/dist/audit/doc-parser.d.ts +5 -0
  2. package/dist/audit/doc-parser.js +106 -0
  3. package/dist/audit/index.d.ts +4 -0
  4. package/dist/audit/index.js +4 -0
  5. package/dist/audit/matcher.d.ts +6 -0
  6. package/dist/audit/matcher.js +94 -0
  7. package/dist/audit/reporter.d.ts +9 -0
  8. package/dist/audit/reporter.js +106 -0
  9. package/dist/audit/types.d.ts +37 -0
  10. package/dist/auth/index.js +6 -4
  11. package/dist/cli.js +12 -2
  12. package/dist/commands/audit.d.ts +2 -0
  13. package/dist/commands/audit.js +59 -0
  14. package/dist/commands/config.d.ts +2 -0
  15. package/dist/commands/config.js +73 -0
  16. package/dist/commands/{generate.d.ts → generate/index.d.ts} +1 -0
  17. package/dist/commands/generate/index.js +393 -0
  18. package/dist/commands/generate/scan.d.ts +41 -0
  19. package/dist/commands/generate/scan.js +256 -0
  20. package/dist/commands/generate/verify.d.ts +14 -0
  21. package/dist/commands/generate/verify.js +122 -0
  22. package/dist/commands/generate/write.d.ts +25 -0
  23. package/dist/commands/generate/write.js +120 -0
  24. package/dist/commands/import.js +4 -1
  25. package/dist/commands/llms-txt.js +6 -4
  26. package/dist/commands/refresh.d.ts +2 -0
  27. package/dist/commands/refresh.js +158 -0
  28. package/dist/commands/review.d.ts +2 -0
  29. package/dist/commands/review.js +110 -0
  30. package/dist/commands/test.js +177 -236
  31. package/dist/commands/watch.js +29 -20
  32. package/dist/config/loader.d.ts +6 -2
  33. package/dist/config/loader.js +39 -3
  34. package/dist/config/types.d.ts +7 -0
  35. package/dist/generator/agents-md.d.ts +25 -0
  36. package/dist/generator/agents-md.js +122 -0
  37. package/dist/generator/generator.js +2 -1
  38. package/dist/generator/index.d.ts +2 -0
  39. package/dist/generator/index.js +2 -0
  40. package/dist/generator/mdx-serializer.d.ts +11 -0
  41. package/dist/generator/mdx-serializer.js +135 -0
  42. package/dist/generator/organizer.d.ts +1 -16
  43. package/dist/generator/organizer.js +0 -38
  44. package/dist/generator/types.d.ts +3 -0
  45. package/dist/generator/writer.js +65 -32
  46. package/dist/github/org-discovery.d.ts +17 -0
  47. package/dist/github/org-discovery.js +93 -0
  48. package/dist/llm/index.d.ts +2 -0
  49. package/dist/llm/index.js +8 -2
  50. package/dist/llm/proxy-client.d.ts +32 -0
  51. package/dist/llm/proxy-client.js +103 -0
  52. package/dist/next-actions/actions.d.ts +2 -0
  53. package/dist/next-actions/actions.js +190 -0
  54. package/dist/next-actions/index.d.ts +6 -0
  55. package/dist/next-actions/index.js +39 -0
  56. package/dist/next-actions/setup.d.ts +2 -0
  57. package/dist/next-actions/setup.js +72 -0
  58. package/dist/next-actions/state.d.ts +7 -0
  59. package/dist/next-actions/state.js +68 -0
  60. package/dist/next-actions/suggest.d.ts +3 -0
  61. package/dist/next-actions/suggest.js +47 -0
  62. package/dist/next-actions/types.d.ts +26 -0
  63. package/dist/refresh/differ.d.ts +9 -0
  64. package/dist/refresh/differ.js +67 -0
  65. package/dist/refresh/index.d.ts +4 -0
  66. package/dist/refresh/index.js +4 -0
  67. package/dist/refresh/manifest.d.ts +18 -0
  68. package/dist/refresh/manifest.js +71 -0
  69. package/dist/refresh/splicer.d.ts +9 -0
  70. package/dist/refresh/splicer.js +50 -0
  71. package/dist/refresh/types.d.ts +37 -0
  72. package/dist/review/index.d.ts +8 -0
  73. package/dist/review/index.js +94 -0
  74. package/dist/review/parser.d.ts +16 -0
  75. package/dist/review/parser.js +95 -0
  76. package/dist/review/types.d.ts +18 -0
  77. package/dist/scanner/csharp.d.ts +0 -4
  78. package/dist/scanner/csharp.js +9 -49
  79. package/dist/scanner/go.d.ts +0 -3
  80. package/dist/scanner/go.js +8 -35
  81. package/dist/scanner/java.d.ts +0 -4
  82. package/dist/scanner/java.js +9 -49
  83. package/dist/scanner/kotlin.d.ts +0 -3
  84. package/dist/scanner/kotlin.js +6 -33
  85. package/dist/scanner/php.d.ts +0 -10
  86. package/dist/scanner/php.js +11 -55
  87. package/dist/scanner/ruby.d.ts +0 -3
  88. package/dist/scanner/ruby.js +8 -38
  89. package/dist/scanner/rust.d.ts +0 -3
  90. package/dist/scanner/rust.js +10 -37
  91. package/dist/scanner/swift.d.ts +0 -3
  92. package/dist/scanner/swift.js +8 -35
  93. package/dist/scanner/types.d.ts +2 -0
  94. package/dist/scanner/utils.d.ts +41 -0
  95. package/dist/scanner/utils.js +97 -0
  96. package/dist/structure/index.d.ts +19 -0
  97. package/dist/structure/index.js +92 -0
  98. package/dist/structure/planner.d.ts +8 -0
  99. package/dist/structure/planner.js +180 -0
  100. package/dist/structure/topology.d.ts +16 -0
  101. package/dist/structure/topology.js +49 -0
  102. package/dist/structure/types.d.ts +26 -0
  103. package/dist/template/docs.json +5 -2
  104. package/dist/template/next.config.mjs +31 -0
  105. package/dist/template/package.json +5 -3
  106. package/dist/template/src/app/layout.tsx +13 -13
  107. package/dist/template/src/app/llms-full.md/route.ts +29 -0
  108. package/dist/template/src/app/llms.txt/route.ts +29 -0
  109. package/dist/template/src/app/md/[...slug]/route.ts +174 -0
  110. package/dist/template/src/app/reference/route.ts +22 -18
  111. package/dist/template/src/app/sitemap.ts +1 -1
  112. package/dist/template/src/components/ai-chat-impl.tsx +206 -0
  113. package/dist/template/src/components/ai-chat.tsx +20 -193
  114. package/dist/template/src/components/mdx/index.tsx +27 -4
  115. package/dist/template/src/lib/fonts.ts +135 -0
  116. package/dist/template/src/middleware.ts +101 -0
  117. package/dist/template/src/styles/globals.css +28 -20
  118. package/dist/testing/comparator.d.ts +7 -0
  119. package/dist/testing/comparator.js +77 -0
  120. package/dist/testing/docker.d.ts +21 -0
  121. package/dist/testing/docker.js +234 -0
  122. package/dist/testing/env.d.ts +16 -0
  123. package/dist/testing/env.js +58 -0
  124. package/dist/testing/extractor.d.ts +9 -0
  125. package/dist/testing/extractor.js +195 -0
  126. package/dist/testing/index.d.ts +6 -0
  127. package/dist/testing/index.js +6 -0
  128. package/dist/testing/runner.d.ts +5 -0
  129. package/dist/testing/runner.js +225 -0
  130. package/dist/testing/types.d.ts +58 -0
  131. package/dist/utils/files.d.ts +0 -8
  132. package/dist/utils/files.js +0 -33
  133. package/package.json +1 -1
  134. package/dist/autofix/autofix.test.js +0 -487
  135. package/dist/commands/generate.js +0 -445
  136. package/dist/generator/generator.test.js +0 -259
  137. package/dist/generator/writer.test.js +0 -411
  138. package/dist/llm/llm.manual-test.js +0 -112
  139. package/dist/llm/llm.mock-test.d.ts +0 -4
  140. package/dist/llm/llm.mock-test.js +0 -79
  141. package/dist/plugins/index.d.ts +0 -47
  142. package/dist/plugins/index.js +0 -181
  143. package/dist/scanner/content-type.test.js +0 -231
  144. package/dist/scanner/integration.test.d.ts +0 -4
  145. package/dist/scanner/integration.test.js +0 -180
  146. package/dist/scanner/scanner.test.js +0 -210
  147. package/dist/scanner/typescript.manual-test.d.ts +0 -1
  148. package/dist/scanner/typescript.manual-test.js +0 -112
  149. package/dist/template/src/app/docs/auth/page.mdx +0 -589
  150. package/dist/template/src/app/docs/autofix/page.mdx +0 -624
  151. package/dist/template/src/app/docs/cli/page.mdx +0 -217
  152. package/dist/template/src/app/docs/config/page.mdx +0 -428
  153. package/dist/template/src/app/docs/configuration/page.mdx +0 -86
  154. package/dist/template/src/app/docs/deployment/page.mdx +0 -112
  155. package/dist/template/src/app/docs/generator/generator.md +0 -504
  156. package/dist/template/src/app/docs/generator/organizer.md +0 -779
  157. package/dist/template/src/app/docs/generator/page.mdx +0 -613
  158. package/dist/template/src/app/docs/github/page.mdx +0 -502
  159. package/dist/template/src/app/docs/llm/anthropic-client.md +0 -549
  160. package/dist/template/src/app/docs/llm/index.md +0 -471
  161. package/dist/template/src/app/docs/llm/page.mdx +0 -428
  162. package/dist/template/src/app/docs/plugins/page.mdx +0 -1793
  163. package/dist/template/src/app/docs/pro/page.mdx +0 -121
  164. package/dist/template/src/app/docs/quickstart/page.mdx +0 -93
  165. package/dist/template/src/app/docs/scanner/content-type.md +0 -599
  166. package/dist/template/src/app/docs/scanner/index.md +0 -212
  167. package/dist/template/src/app/docs/scanner/page.mdx +0 -307
  168. package/dist/template/src/app/docs/scanner/python.md +0 -469
  169. package/dist/template/src/app/docs/scanner/python_parser.md +0 -1056
  170. package/dist/template/src/app/docs/scanner/rust.md +0 -325
  171. package/dist/template/src/app/docs/scanner/typescript.md +0 -201
  172. package/dist/template/src/app/icon.tsx +0 -29
  173. package/dist/utils/validation.d.ts +0 -1
  174. package/dist/utils/validation.js +0 -12
  175. /package/dist/{autofix/autofix.test.d.ts → audit/types.js} +0 -0
  176. /package/dist/{generator/generator.test.d.ts → next-actions/types.js} +0 -0
  177. /package/dist/{generator/writer.test.d.ts → refresh/types.js} +0 -0
  178. /package/dist/{llm/llm.manual-test.d.ts → review/types.js} +0 -0
  179. /package/dist/{scanner/content-type.test.d.ts → structure/types.js} +0 -0
  180. /package/dist/{scanner/scanner.test.d.ts → testing/types.js} +0 -0
@@ -4,7 +4,7 @@ import yaml from 'js-yaml';
4
4
  import { DEFAULT_CONFIG, PROVIDER_ENV_KEYS } from './types.js';
5
5
  const VALID_PROVIDERS = ['deepseek', 'openai', 'anthropic', 'google', 'ollama', 'openrouter'];
6
6
  const CONFIG_FILENAMES = ['.skrypt.yaml', '.skrypt.yml', 'skrypt.yaml', 'skrypt.yml'];
7
- export function findConfigFile(dir) {
7
+ function findConfigFile(dir) {
8
8
  for (const filename of CONFIG_FILENAMES) {
9
9
  const filepath = join(dir, filename);
10
10
  if (existsSync(filepath)) {
@@ -61,6 +61,7 @@ function mergeConfig(defaults, overrides) {
61
61
  ...defaults.source,
62
62
  ...overrides.source
63
63
  },
64
+ sources: overrides.sources,
64
65
  output: {
65
66
  ...defaults.output,
66
67
  ...overrides.output
@@ -74,10 +75,25 @@ function mergeConfig(defaults, overrides) {
74
75
  }
75
76
  export function validateConfig(config) {
76
77
  const errors = [];
77
- if (config.version !== 1) {
78
+ if (config.version !== 1 && config.version !== 2) {
78
79
  errors.push(`Unsupported config version: ${config.version}`);
79
80
  }
80
- if (!config.source.path) {
81
+ // Validate sources array if present (v2)
82
+ if (config.sources && config.sources.length > 0) {
83
+ const labels = new Set();
84
+ for (const entry of config.sources) {
85
+ if (!entry.path) {
86
+ errors.push('Each source entry must have a path');
87
+ }
88
+ if (entry.label) {
89
+ if (labels.has(entry.label)) {
90
+ errors.push(`Duplicate source label: "${entry.label}"`);
91
+ }
92
+ labels.add(entry.label);
93
+ }
94
+ }
95
+ }
96
+ else if (!config.source.path) {
81
97
  errors.push('source.path is required');
82
98
  }
83
99
  if (!config.output.path) {
@@ -88,6 +104,26 @@ export function validateConfig(config) {
88
104
  }
89
105
  return errors;
90
106
  }
107
+ /**
108
+ * Resolve v1 `source` or v2 `sources` into a normalized array of SourceEntry.
109
+ * Falls back to the single source config if no sources array is defined.
110
+ */
111
+ export function resolveSourceEntries(config) {
112
+ if (config.sources && config.sources.length > 0) {
113
+ return config.sources.map(s => ({
114
+ path: s.path,
115
+ label: s.label,
116
+ include: s.include ?? config.source.include,
117
+ exclude: s.exclude ?? config.source.exclude,
118
+ }));
119
+ }
120
+ // Backwards compat: single source
121
+ return [{
122
+ path: config.source.path,
123
+ include: config.source.include,
124
+ exclude: config.source.exclude,
125
+ }];
126
+ }
91
127
  export function checkApiKey(provider) {
92
128
  const envKey = PROVIDER_ENV_KEYS[provider];
93
129
  // Ollama doesn't need an API key
@@ -3,6 +3,12 @@ export interface SourceConfig {
3
3
  include?: string[];
4
4
  exclude?: string[];
5
5
  }
6
+ export interface SourceEntry {
7
+ path: string;
8
+ label?: string;
9
+ include?: string[];
10
+ exclude?: string[];
11
+ }
6
12
  export type LLMProvider = 'deepseek' | 'openai' | 'anthropic' | 'google' | 'ollama' | 'openrouter';
7
13
  export interface LLMConfig {
8
14
  provider: LLMProvider;
@@ -14,6 +20,7 @@ export declare const DEFAULT_MODELS: Record<LLMProvider, string>;
14
20
  export interface Config {
15
21
  version: number;
16
22
  source: SourceConfig;
23
+ sources?: SourceEntry[];
17
24
  output: {
18
25
  path: string;
19
26
  format: 'markdown' | 'mdx';
@@ -0,0 +1,25 @@
1
+ /**
2
+ * AGENTS.md / CLAUDE.md generator
3
+ *
4
+ * Generates context files that coding agents (Claude Code, Cursor, GitHub Copilot,
5
+ * OpenCode) load on every turn. Vercel research: 8KB AGENTS.md = 100% pass rate
6
+ * vs 79% for skills-based approaches.
7
+ */
8
+ export interface AgentsMdPage {
9
+ title: string;
10
+ path: string;
11
+ description?: string;
12
+ category?: string;
13
+ }
14
+ export interface AgentsMdOptions {
15
+ projectName: string;
16
+ docsPath: string;
17
+ baseUrl?: string;
18
+ pages: AgentsMdPage[];
19
+ hasLlmsTxt: boolean;
20
+ }
21
+ /**
22
+ * Generate AGENTS.md content for a project.
23
+ * Designed to be loaded by coding agents on every turn for instant docs access.
24
+ */
25
+ export declare function generateAgentsMd(options: AgentsMdOptions): string;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * AGENTS.md / CLAUDE.md generator
3
+ *
4
+ * Generates context files that coding agents (Claude Code, Cursor, GitHub Copilot,
5
+ * OpenCode) load on every turn. Vercel research: 8KB AGENTS.md = 100% pass rate
6
+ * vs 79% for skills-based approaches.
7
+ */
8
+ const MAX_SIZE_BYTES = 8 * 1024; // 8KB — Vercel's optimal size
9
+ /**
10
+ * Generate AGENTS.md content for a project.
11
+ * Designed to be loaded by coding agents on every turn for instant docs access.
12
+ */
13
+ export function generateAgentsMd(options) {
14
+ const { projectName, docsPath, baseUrl, pages, hasLlmsTxt } = options;
15
+ let out = `# ${projectName} — Documentation Guide\n\n`;
16
+ out += `When answering questions about **${projectName}**, consult these docs first.\n\n`;
17
+ // Access instructions
18
+ out += `## How to Access Docs\n\n`;
19
+ if (baseUrl) {
20
+ out += `- **Browse:** ${baseUrl}\n`;
21
+ if (hasLlmsTxt) {
22
+ out += `- **Agent index:** ${baseUrl}/llms.txt (structured summary)\n`;
23
+ out += `- **Full content:** ${baseUrl}/llms-full.md (all docs in one file)\n`;
24
+ }
25
+ out += `- **Markdown variant:** Append \`.md\` to any docs URL, or use \`Accept: text/markdown\` header\n`;
26
+ }
27
+ out += `- **Local files:** \`${docsPath}/\` (MDX/Markdown source)\n`;
28
+ out += `\n`;
29
+ // Identify entry points
30
+ const quickstarts = pages.filter(p => isEntryPoint(p, 'quickstart'));
31
+ const apiRefs = pages.filter(p => isEntryPoint(p, 'api'));
32
+ const guides = pages.filter(p => isEntryPoint(p, 'guide'));
33
+ if (quickstarts.length > 0 || apiRefs.length > 0) {
34
+ out += `## Key Entry Points\n\n`;
35
+ if (quickstarts.length > 0) {
36
+ out += `**Getting Started:**\n`;
37
+ for (const p of quickstarts.slice(0, 3)) {
38
+ out += formatPageLine(p, baseUrl);
39
+ }
40
+ }
41
+ if (apiRefs.length > 0) {
42
+ out += `**API Reference:**\n`;
43
+ for (const p of apiRefs.slice(0, 5)) {
44
+ out += formatPageLine(p, baseUrl);
45
+ }
46
+ }
47
+ if (guides.length > 0) {
48
+ out += `**Guides:**\n`;
49
+ for (const p of guides.slice(0, 5)) {
50
+ out += formatPageLine(p, baseUrl);
51
+ }
52
+ }
53
+ out += `\n`;
54
+ }
55
+ // Page index — compact, one line per page
56
+ out += `## Documentation Index\n\n`;
57
+ // Group by category if available
58
+ const byCategory = groupByCategory(pages);
59
+ for (const [category, categoryPages] of byCategory) {
60
+ if (category !== '_default') {
61
+ out += `### ${category}\n\n`;
62
+ }
63
+ for (const page of categoryPages) {
64
+ out += formatPageLine(page, baseUrl);
65
+ // Check size — stop adding pages if we're near the limit
66
+ if (Buffer.byteLength(out, 'utf-8') > MAX_SIZE_BYTES - 200) {
67
+ const remaining = pages.length - categoryPages.indexOf(page) - 1;
68
+ if (remaining > 0) {
69
+ out += `\n*...and ${remaining} more pages. See ${hasLlmsTxt ? 'llms.txt' : docsPath} for the complete index.*\n`;
70
+ }
71
+ return finalize(out);
72
+ }
73
+ }
74
+ out += `\n`;
75
+ }
76
+ return finalize(out);
77
+ }
78
+ function finalize(content) {
79
+ const out = content.trimEnd() + '\n';
80
+ // Final size check — truncate page index if over limit
81
+ if (Buffer.byteLength(out, 'utf-8') > MAX_SIZE_BYTES) {
82
+ // Find the last complete line that fits
83
+ const lines = out.split('\n');
84
+ let truncated = '';
85
+ for (const line of lines) {
86
+ const next = truncated + line + '\n';
87
+ if (Buffer.byteLength(next, 'utf-8') > MAX_SIZE_BYTES - 100) {
88
+ truncated += '\n*Index truncated to fit 8KB limit. See llms.txt for complete listing.*\n';
89
+ break;
90
+ }
91
+ truncated = next;
92
+ }
93
+ return truncated;
94
+ }
95
+ return out;
96
+ }
97
+ function formatPageLine(page, baseUrl) {
98
+ const ref = baseUrl ? `${baseUrl}/docs/${page.path}` : page.path;
99
+ const desc = page.description ? ` — ${page.description}` : '';
100
+ return `- [${page.title}](${ref})${desc}\n`;
101
+ }
102
+ function isEntryPoint(page, type) {
103
+ const lower = `${page.title} ${page.path} ${page.category || ''}`.toLowerCase();
104
+ switch (type) {
105
+ case 'quickstart':
106
+ return /quickstart|getting.started|introduction|overview|setup|install/i.test(lower);
107
+ case 'api':
108
+ return /api.ref|reference|endpoint|api$/i.test(lower);
109
+ case 'guide':
110
+ return /guide|tutorial|how.to|walkthrough/i.test(lower);
111
+ }
112
+ }
113
+ function groupByCategory(pages) {
114
+ const groups = new Map();
115
+ for (const page of pages) {
116
+ const cat = page.category || '_default';
117
+ if (!groups.has(cat))
118
+ groups.set(cat, []);
119
+ groups.get(cat).push(page);
120
+ }
121
+ return groups;
122
+ }
@@ -68,7 +68,8 @@ export async function generateForElement(element, client, options, onProgress) {
68
68
  report('generating');
69
69
  try {
70
70
  const elementContext = buildElementContext(element, options.externalContext, options.projectContext);
71
- const result = await generateDocumentation(client, elementContext, { multiLanguage: useMultiLang });
71
+ const errorContext = options.previousErrors?.get(element.name);
72
+ const result = await generateDocumentation(client, elementContext, { multiLanguage: useMultiLang, verify: options.verify, previousError: errorContext });
72
73
  report('done');
73
74
  return {
74
75
  element,
@@ -2,3 +2,5 @@ export * from './types.js';
2
2
  export * from './generator.js';
3
3
  export * from './writer.js';
4
4
  export * from './organizer.js';
5
+ export * from './mdx-serializer.js';
6
+ export * from './agents-md.js';
@@ -2,3 +2,5 @@ export * from './types.js';
2
2
  export * from './generator.js';
3
3
  export * from './writer.js';
4
4
  export * from './organizer.js';
5
+ export * from './mdx-serializer.js';
6
+ export * from './agents-md.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * MDX-to-Markdown serializer
3
+ *
4
+ * Converts MDX content with JSX components into clean markdown
5
+ * for agent consumption (llms-full.md, content negotiation, AGENTS.md).
6
+ */
7
+ /**
8
+ * Strip MDX/JSX components and convert to clean markdown.
9
+ * Handles CodeGroup, Callout, Card, Tabs, Steps, Accordion, Screenshot, etc.
10
+ */
11
+ export declare function serializeMdxToMarkdown(mdx: string): string;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * MDX-to-Markdown serializer
3
+ *
4
+ * Converts MDX content with JSX components into clean markdown
5
+ * for agent consumption (llms-full.md, content negotiation, AGENTS.md).
6
+ */
7
+ /** Extract a named attribute value from a JSX attribute string. */
8
+ function attr(attrs, name) {
9
+ const re = new RegExp(`${name}=["']([^"']+)["']`);
10
+ const m = attrs.match(re);
11
+ return m ? m[1] : undefined;
12
+ }
13
+ /**
14
+ * Strip MDX/JSX components and convert to clean markdown.
15
+ * Handles CodeGroup, Callout, Card, Tabs, Steps, Accordion, Screenshot, etc.
16
+ */
17
+ export function serializeMdxToMarkdown(mdx) {
18
+ let out = mdx;
19
+ // Remove import statements
20
+ out = out.replace(/^import\s+.*$/gm, '');
21
+ // Remove export statements (but not export default with content)
22
+ out = out.replace(/^export\s+(?!default\s).*$/gm, '');
23
+ // --- Component conversions (order matters) ---
24
+ // CodeGroup → flatten to labeled code blocks
25
+ out = out.replace(/<CodeGroup>\s*([\s\S]*?)\s*<\/CodeGroup>/g, (_match, inner) => {
26
+ return inner.trim();
27
+ });
28
+ // Callout variants: <Callout type="warning">, <Warning>, <Info>, <Note>, <Tip>, <Success>, <Error>
29
+ const calloutTypes = ['Callout', 'Info', 'Warning', 'Success', 'Error', 'Tip', 'Note'];
30
+ for (const tag of calloutTypes) {
31
+ // With type attribute: <Callout type="warning">
32
+ const typedRegex = new RegExp(`<${tag}\\s+type=["']([^"']+)["'][^>]*>\\s*([\\s\\S]*?)\\s*<\\/${tag}>`, 'g');
33
+ out = out.replace(typedRegex, (_m, type, content) => {
34
+ const label = type.charAt(0).toUpperCase() + type.slice(1);
35
+ return formatBlockquote(label, content.trim());
36
+ });
37
+ // Without type attribute: <Warning>content</Warning>
38
+ const simpleRegex = new RegExp(`<${tag}(?:\\s[^>]*)?>\\s*([\\s\\S]*?)\\s*<\\/${tag}>`, 'g');
39
+ out = out.replace(simpleRegex, (_m, content) => {
40
+ const label = tag === 'Callout' ? 'Note' : tag;
41
+ return formatBlockquote(label, content.trim());
42
+ });
43
+ }
44
+ // CardGroup → unwrap (just keep children)
45
+ out = out.replace(/<CardGroup[^>]*>\s*/g, '');
46
+ out = out.replace(/\s*<\/CardGroup>/g, '');
47
+ // Card with attributes + children (attribute-order-independent)
48
+ out = out.replace(/<Card\s+([^>]+?)>\s*([\s\S]*?)\s*<\/Card>/g, (_m, attrs, desc) => {
49
+ const title = attr(attrs, 'title');
50
+ const href = attr(attrs, 'href');
51
+ const description = desc.trim().replace(/<[^>]+>/g, '');
52
+ if (title && href) {
53
+ return description
54
+ ? `- **[${title}](${href})** — ${description}`
55
+ : `- **[${title}](${href})**`;
56
+ }
57
+ if (title) {
58
+ return description ? `- **${title}** — ${description}` : `- **${title}**`;
59
+ }
60
+ return description || '';
61
+ });
62
+ // Tabs/TabList/TabPanel → headings + content
63
+ out = out.replace(/<TabList>\s*([\s\S]*?)\s*<\/TabList>/g, '');
64
+ out = out.replace(/<Tab\s+label=["']([^"']+)["'][^>]*>\s*([\s\S]*?)\s*<\/Tab>/g, (_m, label, content) => `#### ${label}\n\n${content.trim()}\n`);
65
+ out = out.replace(/<TabPanel[^>]*>\s*([\s\S]*?)\s*<\/TabPanel>/g, (_m, content) => content.trim());
66
+ out = out.replace(/<Tabs[^>]*>\s*/g, '');
67
+ out = out.replace(/\s*<\/Tabs>/g, '');
68
+ // Steps/Step → numbered list
69
+ out = out.replace(/<Steps>\s*([\s\S]*?)\s*<\/Steps>/g, (_match, inner) => {
70
+ let stepNum = 0;
71
+ return inner.replace(/<Step\s*(?:title=["']([^"']+)["'][^>]*)?>?\s*([\s\S]*?)\s*<\/Step>/g, (_m, title, content) => {
72
+ stepNum++;
73
+ const heading = title ? `**${title}**` : '';
74
+ return `${stepNum}. ${heading}\n ${content.trim()}\n`;
75
+ });
76
+ });
77
+ // Accordion → bold title + content
78
+ out = out.replace(/<AccordionGroup[^>]*>\s*/g, '');
79
+ out = out.replace(/\s*<\/AccordionGroup>/g, '');
80
+ out = out.replace(/<Accordion\s+title=["']([^"']+)["'][^>]*>\s*([\s\S]*?)\s*<\/Accordion>/g, (_m, title, content) => `**${title}**\n\n${content.trim()}\n`);
81
+ // Screenshot → image (attribute-order-independent)
82
+ out = out.replace(/<Screenshot\s+([^>]*?)\s*\/?>/g, (_m, attrs) => {
83
+ const src = attr(attrs, 'src');
84
+ const alt = attr(attrs, 'alt');
85
+ return src ? `![${alt || 'Screenshot'}](${src})` : '';
86
+ });
87
+ // Also handle <Screenshot>...</Screenshot> (non-self-closing)
88
+ out = out.replace(/<Screenshot\s+([^>]*?)>[\s\S]*?<\/Screenshot>/g, (_m, attrs) => {
89
+ const src = attr(attrs, 'src');
90
+ return src ? `![Screenshot](${src})` : '';
91
+ });
92
+ // Frame → unwrap
93
+ out = out.replace(/<Frame[^>]*>\s*/g, '');
94
+ out = out.replace(/\s*<\/Frame>/g, '');
95
+ // DarkImage → image (use light image src)
96
+ out = out.replace(/<DarkImage\s+(?:[^>]*?src=["']([^"']+)["'][^>]*?)\s*\/?>/g, (_m, src) => `![Image](${src})`);
97
+ // CodePlayground / PythonPlayground / GoPlayground → extract code
98
+ for (const tag of ['CodePlayground', 'PythonPlayground', 'GoPlayground']) {
99
+ const regex = new RegExp(`<${tag}[^>]*>\\s*([\\s\\S]*?)\\s*<\\/${tag}>`, 'g');
100
+ out = out.replace(regex, (_m, content) => content.trim());
101
+ }
102
+ // ParamTable / Schema → pass through content
103
+ out = out.replace(/<ParamTable[^>]*>\s*([\s\S]*?)\s*<\/ParamTable>/g, (_m, c) => c.trim());
104
+ out = out.replace(/<Schema[^>]*>\s*([\s\S]*?)\s*<\/Schema>/g, (_m, c) => c.trim());
105
+ // API badges → inline text
106
+ out = out.replace(/<MethodBadge\s+method=["']([^"']+)["'][^>]*\s*\/?>/g, '`$1`');
107
+ out = out.replace(/<StatusBadge\s+code=["']([^"']+)["'][^>]*\s*\/?>/g, '`$1`');
108
+ out = out.replace(/<Endpoint\s+method=["']([^"']+)["']\s+path=["']([^"']+)["'][^>]*\s*\/?>/g, '`$1 $2`');
109
+ // LinkPreview → markdown link
110
+ out = out.replace(/<LinkPreview\s+(?:[^>]*?url=["']([^"']+)["'][^>]*?)(?:\s+title=["']([^"']+)["'])?[^>]*\s*\/?>/g, (_m, url, title) => `[${title || url}](${url})`);
111
+ // Tooltip → just the text
112
+ out = out.replace(/<Tooltip\s+[^>]*>\s*([\s\S]*?)\s*<\/Tooltip>/g, (_m, c) => c.trim());
113
+ // Changelog components → headings
114
+ out = out.replace(/<Changelog>\s*/g, '');
115
+ out = out.replace(/\s*<\/Changelog>/g, '');
116
+ out = out.replace(/<ChangelogEntry\s+(?:[^>]*?date=["']([^"']+)["'][^>]*?)>\s*([\s\S]*?)\s*<\/ChangelogEntry>/g, (_m, date, content) => `### ${date}\n\n${content.trim()}\n`);
117
+ out = out.replace(/<Change\s+type=["']([^"']+)["'][^>]*>\s*([\s\S]*?)\s*<\/Change>/g, (_m, type, content) => `- **${type}:** ${content.trim()}`);
118
+ // Mermaid → keep as code block
119
+ out = out.replace(/<Mermaid\s+chart=["'`]([^"'`]+)["'`][^>]*\s*\/?>/g, (_m, chart) => '```mermaid\n' + chart.trim() + '\n```');
120
+ // Catch-all: remove any remaining self-closing JSX tags
121
+ out = out.replace(/<[A-Z][A-Za-z]*\s[^>]*\/>/g, '');
122
+ // Catch-all: unwrap any remaining JSX component tags (keep children)
123
+ out = out.replace(/<[A-Z][A-Za-z]*(?:\s[^>]*)?>/g, '');
124
+ out = out.replace(/<\/[A-Z][A-Za-z]*>/g, '');
125
+ // Clean up excessive blank lines
126
+ out = out.replace(/\n{4,}/g, '\n\n\n');
127
+ // Trim leading/trailing whitespace
128
+ out = out.trim() + '\n';
129
+ return out;
130
+ }
131
+ function formatBlockquote(label, content) {
132
+ const lines = content.split('\n');
133
+ const quoted = lines.map(line => `> ${line}`).join('\n');
134
+ return `> **${label}:** ${quoted.replace(/^> /, '')}\n`;
135
+ }
@@ -1,4 +1,4 @@
1
- import { GeneratedDoc, Topic, TopicConfig, CrossReference, NavigationItem } from './types.js';
1
+ import { GeneratedDoc, Topic, TopicConfig, CrossReference } from './types.js';
2
2
  /**
3
3
  * Default topic configuration with common patterns
4
4
  */
@@ -15,18 +15,3 @@ export declare function detectCrossReferences(docs: GeneratedDoc[]): CrossRefere
15
15
  * Get cross-references for a specific element
16
16
  */
17
17
  export declare function getCrossRefsForElement(elementName: string, allRefs: CrossReference[]): CrossReference[];
18
- /**
19
- * Build navigation structure from topics
20
- * @internal
21
- */
22
- export declare function buildNavigation(topics: Topic[]): NavigationItem[];
23
- /**
24
- * Generate a sidebar configuration (works with multiple doc platforms)
25
- * @internal
26
- */
27
- export declare function generateSidebarConfig(topics: Topic[]): object;
28
- /**
29
- * Merge user config with defaults
30
- * @internal
31
- */
32
- export declare function mergeTopicConfig(userConfig: Partial<TopicConfig>, defaults?: TopicConfig): TopicConfig;
@@ -1,4 +1,3 @@
1
- import { slugify } from '../utils/files.js';
2
1
  /**
3
2
  * Default topic configuration with common patterns
4
3
  */
@@ -172,32 +171,6 @@ export function detectCrossReferences(docs) {
172
171
  export function getCrossRefsForElement(elementName, allRefs) {
173
172
  return allRefs.filter(r => r.fromElement === elementName);
174
173
  }
175
- /**
176
- * Build navigation structure from topics
177
- * @internal
178
- */
179
- export function buildNavigation(topics) {
180
- return topics.map(topic => ({
181
- title: topic.name,
182
- path: `/${topic.id}`,
183
- children: topic.docs.map(doc => ({
184
- title: doc.element.name,
185
- path: `/${topic.id}#${slugify(doc.element.name)}`
186
- }))
187
- }));
188
- }
189
- /**
190
- * Generate a sidebar configuration (works with multiple doc platforms)
191
- * @internal
192
- */
193
- export function generateSidebarConfig(topics) {
194
- return {
195
- navigation: topics.map(topic => ({
196
- group: topic.name,
197
- pages: topic.docs.map(doc => `${topic.id}/${slugify(doc.element.name)}`)
198
- }))
199
- };
200
- }
201
174
  /**
202
175
  * Convert string to title case
203
176
  */
@@ -206,14 +179,3 @@ function titleCase(str) {
206
179
  .replace(/-/g, ' ')
207
180
  .replace(/\b\w/g, c => c.toUpperCase());
208
181
  }
209
- /**
210
- * Merge user config with defaults
211
- * @internal
212
- */
213
- export function mergeTopicConfig(userConfig, defaults = DEFAULT_TOPIC_CONFIG) {
214
- return {
215
- topics: { ...defaults.topics, ...userConfig.topics },
216
- rules: [...defaults.rules, ...(userConfig.rules || [])],
217
- defaultTopic: userConfig.defaultTopic ?? defaults.defaultTopic
218
- };
219
- }
@@ -33,6 +33,9 @@ export interface GenerationOptions {
33
33
  externalContext?: Map<string, string>;
34
34
  maxTokens?: number;
35
35
  projectContext?: string;
36
+ verify?: boolean;
37
+ /** Error context from previous verification failures, keyed by element name */
38
+ previousErrors?: Map<string, string>;
36
39
  }
37
40
  /**
38
41
  * Result of generating docs for a file
@@ -4,6 +4,7 @@ import { existsSync } from 'fs';
4
4
  import { formatAsMarkdown } from './generator.js';
5
5
  import { organizeByTopic, detectCrossReferences, getCrossRefsForElement } from './organizer.js';
6
6
  import { slugify } from '../utils/files.js';
7
+ import { serializeMdxToMarkdown } from './mdx-serializer.js';
7
8
  /**
8
9
  * Generate llms.txt file (Answer Engine Optimization)
9
10
  * Format follows https://llmstxt.org convention
@@ -13,36 +14,45 @@ export async function writeLlmsTxt(docs, outputDir, options = {}) {
13
14
  const description = options.description || 'API documentation generated by skrypt';
14
15
  let content = `# ${projectName}\n\n`;
15
16
  content += `> ${description}\n\n`;
16
- // Group by file for organization
17
- const byFile = new Map();
17
+ // Check if docs have source labels (multi-source mode)
18
+ const hasSourceLabels = docs.some(d => d.element.sourceLabel);
19
+ // Group by source label then by file
20
+ const bySource = new Map();
18
21
  for (const doc of docs) {
22
+ const sourceKey = doc.element.sourceLabel || '_default';
23
+ if (!bySource.has(sourceKey))
24
+ bySource.set(sourceKey, new Map());
25
+ const sourceMap = bySource.get(sourceKey);
19
26
  const file = basename(doc.element.filePath).replace(/\.[^.]+$/, '');
20
- if (!byFile.has(file)) {
21
- byFile.set(file, []);
22
- }
23
- const fileDocs = byFile.get(file);
24
- if (fileDocs)
25
- fileDocs.push(doc);
27
+ if (!sourceMap.has(file))
28
+ sourceMap.set(file, []);
29
+ sourceMap.get(file).push(doc);
26
30
  }
31
+ const totalModules = Array.from(bySource.values()).reduce((sum, m) => sum + m.size, 0);
27
32
  // Summary section
28
33
  content += `## Overview\n\n`;
29
- content += `This project contains ${docs.length} documented API elements across ${byFile.size} modules.\n\n`;
34
+ content += `This project contains ${docs.length} documented API elements across ${totalModules} modules.\n\n`;
30
35
  // Quick reference
31
36
  content += `## Quick Reference\n\n`;
32
- for (const [file, fileDocs] of byFile) {
33
- content += `### ${file}\n\n`;
34
- for (const doc of fileDocs) {
35
- content += `- \`${doc.element.name}\`: ${doc.element.kind}`;
36
- if (doc.markdown) {
37
- // Extract first sentence as summary
38
- const firstSentence = doc.markdown.split(/\.\s/)[0]?.slice(0, 100);
39
- if (firstSentence) {
40
- content += ` - ${firstSentence}`;
37
+ for (const [sourceLabel, byFile] of bySource) {
38
+ if (hasSourceLabels && sourceLabel !== '_default') {
39
+ content += `### ${sourceLabel}\n\n`;
40
+ }
41
+ for (const [file, fileDocs] of byFile) {
42
+ content += `${hasSourceLabels ? '####' : '###'} ${file}\n\n`;
43
+ for (const doc of fileDocs) {
44
+ content += `- \`${doc.element.name}\`: ${doc.element.kind}`;
45
+ if (doc.markdown) {
46
+ // Extract first sentence as summary
47
+ const firstSentence = doc.markdown.split(/\.\s/)[0]?.slice(0, 100);
48
+ if (firstSentence) {
49
+ content += ` - ${firstSentence}`;
50
+ }
41
51
  }
52
+ content += '\n';
42
53
  }
43
54
  content += '\n';
44
55
  }
45
- content += '\n';
46
56
  }
47
57
  // Detailed API section
48
58
  content += `## API Details\n\n`;
@@ -52,14 +62,14 @@ export async function writeLlmsTxt(docs, outputDir, options = {}) {
52
62
  content += `**File:** ${doc.element.filePath}\n\n`;
53
63
  content += `\`\`\`\n${doc.element.signature}\n\`\`\`\n\n`;
54
64
  if (doc.markdown) {
55
- content += doc.markdown.slice(0, 500) + '\n\n';
65
+ content += serializeMdxToMarkdown(doc.markdown.slice(0, 500)) + '\n\n';
56
66
  }
57
67
  if (doc.codeExample) {
58
68
  content += `**Example:**\n\`\`\`${doc.codeLanguage}\n${doc.codeExample.slice(0, 300)}\n\`\`\`\n\n`;
59
69
  }
60
70
  }
61
- // Write both llms.txt and llms-full.md
62
- await writeFile(join(outputDir, 'llms.txt'), content, 'utf-8');
71
+ // Write both llms.txt and llms-full.md — serialize any JSX for clean agent consumption
72
+ await writeFile(join(outputDir, 'llms.txt'), serializeMdxToMarkdown(content), 'utf-8');
63
73
  // Also write a condensed version (just signatures)
64
74
  let condensed = `# ${projectName} API\n\n`;
65
75
  condensed += `> ${description}\n\n`;
@@ -67,7 +77,7 @@ export async function writeLlmsTxt(docs, outputDir, options = {}) {
67
77
  condensed += `## ${doc.element.name}\n`;
68
78
  condensed += `${doc.element.signature}\n\n`;
69
79
  }
70
- await writeFile(join(outputDir, 'llms-full.md'), condensed, 'utf-8');
80
+ await writeFile(join(outputDir, 'llms-full.md'), serializeMdxToMarkdown(condensed), 'utf-8');
71
81
  }
72
82
  /**
73
83
  * Write generated docs to output directory
@@ -134,15 +144,38 @@ async function writeIndexFile(results, outputDir, sourceDir) {
134
144
  const totalElements = results.reduce((sum, r) => sum + r.docs.length, 0);
135
145
  content += '## Summary\n\n';
136
146
  content += `- **Total elements:** ${totalElements}\n\n`;
137
- // File index
138
- content += '## Files\n\n';
139
- for (const result of results) {
140
- if (result.docs.length === 0)
141
- continue;
142
- const relPath = relative(sourceDir, result.filePath);
143
- const docFileName = relPath.replace(/\.[^.]+$/, '.md');
144
- content += `- [${relPath}](./${docFileName})`;
145
- content += ` (${result.docs.length} elements)\n`;
147
+ // Check if any docs have source labels
148
+ const hasLabels = results.some(r => r.docs.some(d => d.element.sourceLabel));
149
+ if (hasLabels) {
150
+ // Group by source label
151
+ const byLabel = new Map();
152
+ for (const result of results) {
153
+ if (result.docs.length === 0)
154
+ continue;
155
+ const label = result.docs[0]?.element.sourceLabel || 'Other';
156
+ if (!byLabel.has(label))
157
+ byLabel.set(label, []);
158
+ byLabel.get(label).push(result);
159
+ }
160
+ for (const [label, labelResults] of byLabel) {
161
+ content += `## ${label}\n\n`;
162
+ for (const result of labelResults) {
163
+ const relPath = relative(sourceDir, result.filePath);
164
+ const docFileName = relPath.replace(/\.[^.]+$/, '.md');
165
+ content += `- [${relPath}](./${docFileName}) (${result.docs.length} elements)\n`;
166
+ }
167
+ content += '\n';
168
+ }
169
+ }
170
+ else {
171
+ content += '## Files\n\n';
172
+ for (const result of results) {
173
+ if (result.docs.length === 0)
174
+ continue;
175
+ const relPath = relative(sourceDir, result.filePath);
176
+ const docFileName = relPath.replace(/\.[^.]+$/, '.md');
177
+ content += `- [${relPath}](./${docFileName}) (${result.docs.length} elements)\n`;
178
+ }
146
179
  }
147
180
  await writeFile(join(outputDir, 'README.md'), content, 'utf-8');
148
181
  }