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
@@ -1,445 +0,0 @@
1
- import { Command } from 'commander';
2
- import { existsSync, copyFileSync, mkdirSync, readFileSync } from 'fs';
3
- import { resolve, basename, dirname, join } from 'path';
4
- import { loadConfig, validateConfig, checkApiKey } from '../config/index.js';
5
- import { DEFAULT_MODELS } from '../config/types.js';
6
- import { scanDirectory } from '../scanner/index.js';
7
- import { createLLMClient } from '../llm/index.js';
8
- import { generateForElements, groupDocsByFile, writeDocsToDirectory, writeDocsByTopic, writeLlmsTxt } from '../generator/index.js';
9
- import { showSecurityNotice } from '../auth/notices.js';
10
- import { runQA, printQAReport, fixQAIssues, printFixReport } from '../qa/index.js';
11
- import { extractDependencyIds, isChubInstalled, fetchContextHubDocs, exportToContextHub } from '../context-hub/index.js';
12
- import * as readline from 'readline';
13
- /**
14
- * Read .skryptignore patterns from source directory
15
- */
16
- function readIgnorePatterns(sourcePath) {
17
- const ignorePath = join(sourcePath, '.skryptignore');
18
- if (!existsSync(ignorePath))
19
- return [];
20
- const content = readFileSync(ignorePath, 'utf-8');
21
- return content
22
- .split('\n')
23
- .map(line => line.trim())
24
- .filter(line => line && !line.startsWith('#'));
25
- }
26
- /**
27
- * Auto-detect OpenAPI spec file in source directory
28
- */
29
- function findOpenAPISpec(sourcePath) {
30
- const candidates = [
31
- 'openapi.json',
32
- 'openapi.yaml',
33
- 'openapi.yml',
34
- 'swagger.json',
35
- 'swagger.yaml',
36
- 'swagger.yml',
37
- 'api.json',
38
- 'api.yaml',
39
- 'api.yml',
40
- ];
41
- for (const name of candidates) {
42
- const specPath = join(sourcePath, name);
43
- if (existsSync(specPath)) {
44
- return specPath;
45
- }
46
- }
47
- // Check common subdirectories
48
- const subdirs = ['docs', 'api', 'spec', '.'];
49
- for (const subdir of subdirs) {
50
- for (const name of candidates) {
51
- const specPath = join(sourcePath, subdir, name);
52
- if (existsSync(specPath)) {
53
- return specPath;
54
- }
55
- }
56
- }
57
- return null;
58
- }
59
- /**
60
- * Check if element should be excluded based on patterns
61
- */
62
- function shouldExcludeElement(element, patterns) {
63
- for (const pattern of patterns) {
64
- // Match by name
65
- if (pattern.startsWith('name:')) {
66
- const namePattern = pattern.slice(5);
67
- if (element.name === namePattern) {
68
- return true;
69
- }
70
- // Only use regex if the pattern contains regex metacharacters
71
- // Reject patterns with nested quantifiers to prevent catastrophic backtracking (ReDoS)
72
- if (/[*+?{}()|[\]\\^$.]/.test(namePattern)) {
73
- if (/(\+|\*|\?)\{|\(\?[^:)]|\(\.[*+].*\)\+|\([^)]*[+*][^)]*\)[+*]/.test(namePattern)) {
74
- continue; // Skip patterns prone to catastrophic backtracking
75
- }
76
- try {
77
- if (new RegExp(namePattern).test(element.name))
78
- return true;
79
- }
80
- catch {
81
- // Invalid regex — treat as literal match (already checked above)
82
- }
83
- }
84
- }
85
- // Match by file path
86
- else if (pattern.includes('/') || pattern.includes('*')) {
87
- const filePath = element.filePath;
88
- if (pattern.includes('**')) {
89
- const parts = pattern.split('**');
90
- const prefixMatch = !parts[0] || filePath.includes(parts[0].replace(/^\//, ''));
91
- const suffixMatch = !parts[1] || filePath.includes(parts[1].replace(/^\//, ''));
92
- if (prefixMatch && suffixMatch)
93
- return true;
94
- }
95
- else if (filePath.includes(pattern.replace(/\*/g, ''))) {
96
- return true;
97
- }
98
- }
99
- // Match by exact name
100
- else if (element.name === pattern) {
101
- return true;
102
- }
103
- }
104
- return false;
105
- }
106
- export const generateCommand = new Command('generate')
107
- .description('Generate documentation with code examples')
108
- .argument('<source>', 'Source directory to scan')
109
- .option('-o, --output <dir>', 'Output directory')
110
- .option('-c, --config <file>', 'Config file path')
111
- .option('--provider <name>', 'LLM provider (deepseek, openai, anthropic, google, ollama, openrouter)')
112
- .option('--model <name>', 'LLM model name')
113
- .option('--base-url <url>', 'Custom API base URL (for Ollama or proxies)')
114
- .option('--dry-run', 'Scan only, do not generate docs')
115
- .option('--multi-lang', 'Generate TypeScript + Python examples')
116
- .option('--by-topic', 'Organize output by topic instead of file')
117
- .option('--openapi <file>', 'Include OpenAPI spec file for API Playground')
118
- .option('--public-only', 'Only document exported/public APIs')
119
- .option('--exclude <patterns...>', 'Exclude patterns (files, names, or name:pattern)')
120
- .option('--llms-txt', 'Generate llms.txt for Answer Engine Optimization (AEO)')
121
- .option('--project-name <name>', 'Project name for llms.txt header')
122
- .action(async (source, options) => {
123
- try {
124
- const startTime = Date.now();
125
- // Load config (file or defaults)
126
- const config = loadConfig(options.config);
127
- // CLI flags override config
128
- if (source)
129
- config.source.path = source;
130
- if (options.output)
131
- config.output.path = options.output;
132
- if (options.provider) {
133
- const validProviders = ['deepseek', 'openai', 'anthropic', 'google', 'ollama', 'openrouter'];
134
- if (!validProviders.includes(options.provider)) {
135
- console.error(`Error: Unknown provider "${options.provider}". Valid: ${validProviders.join(', ')}`);
136
- process.exit(1);
137
- }
138
- config.llm.provider = options.provider;
139
- // Use provider's default model unless explicitly specified
140
- if (!options.model) {
141
- config.llm.model = DEFAULT_MODELS[config.llm.provider];
142
- }
143
- }
144
- if (options.model)
145
- config.llm.model = options.model;
146
- if (options.baseUrl)
147
- config.llm.baseUrl = options.baseUrl;
148
- // Validate
149
- const errors = validateConfig(config);
150
- if (errors.length > 0) {
151
- console.error('Config errors:');
152
- errors.forEach(e => console.error(` - ${e}`));
153
- process.exit(1);
154
- }
155
- // Check for API key (not needed for Ollama or dry-run)
156
- if (!options.dryRun) {
157
- const { ok, envKey } = checkApiKey(config.llm.provider);
158
- if (!ok && envKey) {
159
- console.error(`Error: ${envKey} environment variable required for ${config.llm.provider}`);
160
- process.exit(1);
161
- }
162
- }
163
- // First-run security notice
164
- showSecurityNotice();
165
- console.log('skrypt generate');
166
- console.log(` source: ${config.source.path}`);
167
- console.log(` output: ${config.output.path}`);
168
- console.log(` provider: ${config.llm.provider}`);
169
- console.log(` model: ${config.llm.model}`);
170
- if (config.llm.baseUrl) {
171
- console.log(` base url: ${config.llm.baseUrl}`);
172
- }
173
- // Routing transparency
174
- const providerEnvKeys = {
175
- openai: 'OPENAI_API_KEY',
176
- anthropic: 'ANTHROPIC_API_KEY',
177
- google: 'GOOGLE_API_KEY',
178
- deepseek: 'DEEPSEEK_API_KEY',
179
- };
180
- const providerKey = providerEnvKeys[config.llm.provider];
181
- if (providerKey && process.env[providerKey]) {
182
- console.log(` routing: direct to ${config.llm.provider} (BYOK — your key never touches Skrypt)`);
183
- }
184
- else if (config.llm.provider !== 'ollama') {
185
- console.log(' routing: via Skrypt API proxy');
186
- }
187
- console.log('');
188
- // Check source exists
189
- const sourcePath = resolve(config.source.path);
190
- if (!existsSync(sourcePath)) {
191
- console.error(`Error: Source directory not found: ${sourcePath}`);
192
- process.exit(1);
193
- }
194
- // Step 1: Scan source code
195
- console.log('Step 1: Scanning source code...');
196
- const scanResult = await scanDirectory(sourcePath, {
197
- include: config.source.include,
198
- exclude: config.source.exclude,
199
- onProgress: (current, total, file) => {
200
- process.stdout.write(`\r [${current}/${total}] ${file.slice(-50).padStart(50)}`);
201
- }
202
- });
203
- console.log('');
204
- if (scanResult.errors.length > 0) {
205
- console.log('\n Scan warnings:');
206
- scanResult.errors.slice(0, 5).forEach(e => console.log(` - ${e}`));
207
- if (scanResult.errors.length > 5) {
208
- console.log(` ... and ${scanResult.errors.length - 5} more`);
209
- }
210
- }
211
- console.log(`\n Found ${scanResult.totalElements} API elements in ${scanResult.files.length} files`);
212
- if (scanResult.totalElements === 0) {
213
- console.log(' No API elements found. Nothing to generate.');
214
- process.exit(0);
215
- }
216
- // Collect all elements
217
- let allElements = [];
218
- for (const file of scanResult.files) {
219
- for (const el of file.elements) {
220
- allElements.push(el);
221
- }
222
- }
223
- // Apply privacy filters
224
- const initialCount = allElements.length;
225
- // 1. --public-only: filter to exported/public APIs only
226
- if (options.publicOnly) {
227
- allElements = allElements.filter(el => el.isExported === true || el.isPublic === true);
228
- console.log(` --public-only: filtered to ${allElements.length} exported APIs`);
229
- }
230
- // 2. Load .skryptignore patterns
231
- const ignorePatterns = readIgnorePatterns(sourcePath);
232
- if (ignorePatterns.length > 0) {
233
- console.log(` .skryptignore: loaded ${ignorePatterns.length} patterns`);
234
- }
235
- // 3. Combine with --exclude patterns
236
- const excludePatterns = [...ignorePatterns, ...(options.exclude || [])];
237
- if (excludePatterns.length > 0) {
238
- allElements = allElements.filter(el => !shouldExcludeElement(el, excludePatterns));
239
- if (options.exclude?.length) {
240
- console.log(` --exclude: applied ${options.exclude.length} additional patterns`);
241
- }
242
- }
243
- if (initialCount !== allElements.length) {
244
- console.log(` Filtered: ${initialCount} -> ${allElements.length} elements`);
245
- }
246
- // Show summary by kind
247
- const byKind = {};
248
- for (const el of allElements) {
249
- byKind[el.kind] = (byKind[el.kind] || 0) + 1;
250
- }
251
- const pluralize = (word, count) => {
252
- if (count === 1)
253
- return word;
254
- if (word === 'class')
255
- return 'classes';
256
- return word + 's';
257
- };
258
- console.log(' ' + Object.entries(byKind).map(([k, v]) => `${v} ${pluralize(k, v)}`).join(', '));
259
- // Dry run - stop here
260
- if (options.dryRun) {
261
- console.log('\n[dry run - stopping before generation]');
262
- process.exit(0);
263
- }
264
- // Auto-read project context from README for richer doc generation
265
- let projectContext;
266
- const readmeCandidates = ['README.md', 'README.mdx', 'readme.md', 'README.rst', 'README.txt'];
267
- for (const candidate of readmeCandidates) {
268
- const readmePath = join(sourcePath, candidate);
269
- if (existsSync(readmePath)) {
270
- try {
271
- const raw = readFileSync(readmePath, 'utf-8');
272
- // Take the first ~1500 chars (intro/description section, not the whole file)
273
- projectContext = raw.slice(0, 1500);
274
- console.log(` Project context: loaded from ${candidate}`);
275
- }
276
- catch {
277
- // Skip if unreadable
278
- }
279
- break;
280
- }
281
- }
282
- // Also check parent directory (common for monorepos where source is a subdirectory)
283
- if (!projectContext) {
284
- const parentReadme = join(sourcePath, '..', 'README.md');
285
- if (existsSync(parentReadme)) {
286
- try {
287
- projectContext = readFileSync(parentReadme, 'utf-8').slice(0, 1500);
288
- console.log(' Project context: loaded from parent README.md');
289
- }
290
- catch {
291
- // Skip
292
- }
293
- }
294
- }
295
- // Context Hub enrichment: fetch third-party API docs if chub is installed
296
- let externalContext;
297
- const allImports = [];
298
- for (const el of allElements) {
299
- if (el.imports?.length) {
300
- allImports.push(...el.imports);
301
- }
302
- }
303
- const chubIds = extractDependencyIds(allImports);
304
- if (chubIds.length > 0 && isChubInstalled()) {
305
- console.log(`\n Context Hub: fetching docs for ${chubIds.length} dependencies...`);
306
- externalContext = fetchContextHubDocs(chubIds);
307
- if (externalContext.size > 0) {
308
- console.log(` Context Hub: enriching with ${externalContext.size} API references`);
309
- }
310
- }
311
- // Step 2: Generate docs
312
- console.log('\nStep 2: Generating documentation...');
313
- const client = createLLMClient({
314
- provider: config.llm.provider,
315
- model: config.llm.model,
316
- baseUrl: config.llm.baseUrl
317
- });
318
- let lastElement = '';
319
- const multiLanguage = options.multiLang ?? false;
320
- if (multiLanguage) {
321
- console.log(' mode: multi-language (TypeScript + Python)');
322
- }
323
- const docs = await generateForElements(allElements, client, {
324
- multiLanguage,
325
- externalContext,
326
- projectContext,
327
- onProgress: (progress) => {
328
- if (progress.element !== lastElement) {
329
- if (lastElement)
330
- console.log('');
331
- lastElement = progress.element;
332
- }
333
- process.stdout.write(`\r [${progress.current}/${progress.total}] ${progress.element}: ${progress.status}`.padEnd(80));
334
- }
335
- });
336
- console.log('\n');
337
- // Step 3: Write output
338
- console.log('Step 3: Writing documentation...');
339
- const outputPath = resolve(config.output.path);
340
- let filesWritten;
341
- let totalDocs;
342
- if (options.byTopic) {
343
- console.log(' mode: by-topic (grouped by concept)');
344
- const result = await writeDocsByTopic(docs, outputPath);
345
- filesWritten = result.filesWritten;
346
- totalDocs = result.totalDocs;
347
- console.log(` topics: ${result.topics.map(t => t.name).join(', ')}`);
348
- }
349
- else {
350
- // Default: file-based output
351
- const fileResults = groupDocsByFile(docs);
352
- const result = await writeDocsToDirectory(fileResults, outputPath, sourcePath);
353
- filesWritten = result.filesWritten;
354
- totalDocs = result.totalDocs;
355
- }
356
- const errorCount = docs.filter(d => d.error).length;
357
- const duration = Math.round((Date.now() - startTime) / 1000);
358
- console.log(`\n Wrote ${filesWritten} documentation files to ${outputPath}`);
359
- // Copy OpenAPI spec (provided or auto-detected)
360
- let specPath = options.openapi ? resolve(options.openapi) : null;
361
- // Auto-detect if not provided
362
- if (!specPath) {
363
- const detected = findOpenAPISpec(sourcePath);
364
- if (detected) {
365
- specPath = detected;
366
- console.log(`\n Auto-detected OpenAPI spec: ${basename(detected)}`);
367
- }
368
- }
369
- if (specPath) {
370
- if (existsSync(specPath)) {
371
- const specFilename = basename(specPath);
372
- const contentDir = dirname(outputPath);
373
- const destPath = resolve(contentDir, specFilename);
374
- mkdirSync(dirname(destPath), { recursive: true });
375
- copyFileSync(specPath, destPath);
376
- console.log(` Copied OpenAPI spec: ${specFilename} -> ${destPath}`);
377
- console.log(' API Playground will be available at /reference');
378
- }
379
- else if (options.openapi) {
380
- console.log(`\n Warning: OpenAPI spec not found: ${specPath}`);
381
- }
382
- }
383
- // Always generate llms.txt for AEO (Answer Engine Optimization)
384
- await writeLlmsTxt(docs, outputPath, {
385
- projectName: options.projectName,
386
- description: `API documentation for ${options.projectName || basename(sourcePath)}`
387
- });
388
- console.log(`\n Generated llms.txt and llms-full.md for AEO`);
389
- // Step 4: Embedded QA (auto-fix then check)
390
- console.log('\nStep 4: Running QA checks...');
391
- const fixReport = fixQAIssues(outputPath);
392
- printFixReport(fixReport);
393
- const qaReport = runQA(outputPath);
394
- printQAReport(qaReport);
395
- // Context Hub export prompt (TTY only — skip in CI/piped mode)
396
- if (process.stdin.isTTY) {
397
- console.log('');
398
- console.log(' Context Hub: Make your docs discoverable by AI coding agents.');
399
- console.log(' Context Hub is a curated registry by Andrew Ng (7K+ stars)');
400
- console.log(' https://github.com/andrewyng/context-hub');
401
- console.log('');
402
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
403
- const answer = await new Promise((resolve) => {
404
- rl.question(' Export for Context Hub? (y/N) ', (ans) => {
405
- rl.close();
406
- resolve(ans.trim().toLowerCase());
407
- });
408
- });
409
- if (answer === 'y' || answer === 'yes') {
410
- const languages = multiLanguage ? ['typescript', 'python'] : ['typescript'];
411
- const projName = options.projectName || basename(sourcePath);
412
- const result = exportToContextHub(docs, outputPath, {
413
- projectName: projName,
414
- languages,
415
- description: `API documentation for ${projName}`,
416
- });
417
- console.log(`\n Exported ${result.filesWritten} files to ${result.outputDir}`);
418
- console.log(' See context-hub/README.md for submission instructions');
419
- }
420
- }
421
- console.log('\n=== Summary ===');
422
- console.log(` Total elements: ${totalDocs}`);
423
- console.log(` Generated: ${totalDocs - errorCount}`);
424
- if (errorCount > 0) {
425
- console.log(` Errors: ${errorCount}`);
426
- }
427
- console.log(` Duration: ${duration}s`);
428
- console.log(` Output: ${outputPath}`);
429
- if (errorCount > 0) {
430
- console.log('\n Elements with errors:');
431
- docs.filter(d => d.error).slice(0, 10).forEach(d => {
432
- console.log(` - ${d.element.name}: ${d.error?.slice(0, 50)}`);
433
- });
434
- if (errorCount > 10) {
435
- console.log(` ... and ${errorCount - 10} more`);
436
- }
437
- }
438
- console.log('\nDone!');
439
- }
440
- catch (err) {
441
- const message = err instanceof Error ? err.message : String(err);
442
- console.error(`Error: ${message}`);
443
- process.exit(1);
444
- }
445
- });