ted-mosby 1.0.0 → 1.1.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.
- package/dist/cli.js +433 -16
- package/dist/cli.js.map +1 -1
- package/dist/prompts/wiki-system.d.ts +1 -1
- package/dist/prompts/wiki-system.d.ts.map +1 -1
- package/dist/prompts/wiki-system.js +52 -0
- package/dist/prompts/wiki-system.js.map +1 -1
- package/dist/rag/index.d.ts +68 -9
- package/dist/rag/index.d.ts.map +1 -1
- package/dist/rag/index.js +384 -79
- package/dist/rag/index.js.map +1 -1
- package/dist/site/scripts.d.ts +22 -0
- package/dist/site/scripts.d.ts.map +1 -0
- package/dist/site/scripts.js +855 -0
- package/dist/site/scripts.js.map +1 -0
- package/dist/site/styles.d.ts +11 -0
- package/dist/site/styles.d.ts.map +1 -0
- package/dist/site/styles.js +1572 -0
- package/dist/site/styles.js.map +1 -0
- package/dist/site/templates.d.ts +40 -0
- package/dist/site/templates.d.ts.map +1 -0
- package/dist/site/templates.js +336 -0
- package/dist/site/templates.js.map +1 -0
- package/dist/site-generator.d.ts +197 -0
- package/dist/site-generator.d.ts.map +1 -0
- package/dist/site-generator.js +694 -0
- package/dist/site-generator.js.map +1 -0
- package/dist/wiki-agent.d.ts +73 -0
- package/dist/wiki-agent.d.ts.map +1 -1
- package/dist/wiki-agent.js +1133 -7
- package/dist/wiki-agent.js.map +1 -1
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ import inquirer from 'inquirer';
|
|
|
10
10
|
import inquirerAutocomplete from 'inquirer-autocomplete-prompt';
|
|
11
11
|
import { DevelopmentAgentAgent } from './agent.js';
|
|
12
12
|
import { ArchitecturalWikiAgent } from './wiki-agent.js';
|
|
13
|
+
import { SiteGenerator } from './site-generator.js';
|
|
13
14
|
import { ConfigManager } from './config.js';
|
|
14
15
|
import { PermissionManager } from './permissions.js';
|
|
15
16
|
import { PlanManager, formatAge } from './planner.js';
|
|
@@ -62,6 +63,16 @@ program
|
|
|
62
63
|
.option('-f, --force', 'Force regeneration (ignore cache)')
|
|
63
64
|
.option('-v, --verbose', 'Verbose output')
|
|
64
65
|
.option('-e, --estimate', 'Estimate time and cost without running (dry run)')
|
|
66
|
+
.option('-s, --site', 'Generate interactive static site from wiki')
|
|
67
|
+
.option('--site-only', 'Only generate static site (skip wiki generation, use existing markdown)')
|
|
68
|
+
.option('--site-title <title>', 'Site title for static site')
|
|
69
|
+
.option('--theme <theme>', 'Site theme: light, dark, or auto', 'auto')
|
|
70
|
+
.option('--max-chunks <number>', 'Maximum chunks to index (for large codebases, e.g., 5000)', parseInt)
|
|
71
|
+
.option('--max-results <number>', 'Maximum search results per query (default 10, reduce for large codebases)', parseInt)
|
|
72
|
+
.option('--batch-size <number>', 'Enable batched mode: process codebase in batches of N chunks (for very large repos)', parseInt)
|
|
73
|
+
.option('--skip-index', 'Skip indexing and use existing cached index (for debugging agent behavior)')
|
|
74
|
+
.option('--max-turns <number>', 'Maximum agent turns (default 200, lower to reduce cost estimate)', parseInt)
|
|
75
|
+
.option('--direct-api', 'Use Anthropic API directly (bypasses Claude Code billing, uses your API credits)')
|
|
65
76
|
.action(async (options) => {
|
|
66
77
|
try {
|
|
67
78
|
const configManager = new ConfigManager();
|
|
@@ -135,23 +146,52 @@ program
|
|
|
135
146
|
}
|
|
136
147
|
return;
|
|
137
148
|
}
|
|
149
|
+
// Handle --site-only mode (generate site from existing markdown)
|
|
150
|
+
if (options.siteOnly) {
|
|
151
|
+
await generateStaticSite(options);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
138
154
|
const spinner = ora('Starting wiki generation...').start();
|
|
139
155
|
let currentPhase = '';
|
|
156
|
+
// Choose generation method based on options
|
|
157
|
+
const generationOptions = {
|
|
158
|
+
repoUrl: options.repo,
|
|
159
|
+
outputDir: options.output,
|
|
160
|
+
configPath: options.config,
|
|
161
|
+
accessToken: options.token || process.env.GITHUB_TOKEN,
|
|
162
|
+
model: options.model,
|
|
163
|
+
targetPath: options.path,
|
|
164
|
+
forceRegenerate: options.force,
|
|
165
|
+
verbose: options.verbose,
|
|
166
|
+
maxChunks: options.maxChunks,
|
|
167
|
+
maxSearchResults: options.maxResults,
|
|
168
|
+
batchSize: options.batchSize,
|
|
169
|
+
skipIndex: options.skipIndex,
|
|
170
|
+
maxTurns: options.maxTurns,
|
|
171
|
+
directApi: options.directApi
|
|
172
|
+
};
|
|
173
|
+
// Choose generator based on options
|
|
174
|
+
let generator;
|
|
175
|
+
if (options.directApi) {
|
|
176
|
+
console.log(chalk.yellow('\n⚡ Direct API mode: Using Anthropic API directly (bypasses Claude Code)\n'));
|
|
177
|
+
generator = agent.generateWikiDirectApi(generationOptions);
|
|
178
|
+
}
|
|
179
|
+
else if (options.skipIndex) {
|
|
180
|
+
console.log(chalk.yellow('\n⚡ Skip-index mode: Using existing cached index (debug mode)\n'));
|
|
181
|
+
generator = agent.generateWikiAgentOnly(generationOptions);
|
|
182
|
+
}
|
|
183
|
+
else if (options.batchSize) {
|
|
184
|
+
generator = agent.generateWikiBatched(generationOptions);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
generator = agent.generateWiki(generationOptions);
|
|
188
|
+
}
|
|
140
189
|
try {
|
|
141
|
-
for await (const event of
|
|
142
|
-
repoUrl: options.repo,
|
|
143
|
-
outputDir: options.output,
|
|
144
|
-
configPath: options.config,
|
|
145
|
-
accessToken: options.token || process.env.GITHUB_TOKEN,
|
|
146
|
-
model: options.model,
|
|
147
|
-
targetPath: options.path,
|
|
148
|
-
forceRegenerate: options.force,
|
|
149
|
-
verbose: options.verbose
|
|
150
|
-
})) {
|
|
190
|
+
for await (const event of generator) {
|
|
151
191
|
// Handle progress events
|
|
152
192
|
if (event.type === 'phase') {
|
|
153
|
-
// Stop spinner during indexing
|
|
154
|
-
if (event.message.includes('Indexing')) {
|
|
193
|
+
// Stop spinner during indexing/batch phases so RAG output is visible
|
|
194
|
+
if (event.message.includes('Indexing') || event.message.includes('batch') || event.message.includes('Analyzing') || event.message.includes('Finalizing')) {
|
|
155
195
|
spinner.stop();
|
|
156
196
|
console.log(chalk.cyan(`\n📊 ${event.message}`));
|
|
157
197
|
}
|
|
@@ -165,10 +205,13 @@ program
|
|
|
165
205
|
}
|
|
166
206
|
}
|
|
167
207
|
else if (event.type === 'step') {
|
|
168
|
-
//
|
|
169
|
-
if (event.message.includes('Indexed')) {
|
|
208
|
+
// Show step messages for batch progress
|
|
209
|
+
if (event.message.includes('Indexed') || event.message.includes('Batch') || event.message.includes('Found') || event.message.includes('Final index') || event.message.includes('chunks loaded')) {
|
|
170
210
|
console.log(chalk.green(` ✓ ${event.message}`));
|
|
171
|
-
spinner
|
|
211
|
+
// Resume spinner after last step before agent runs
|
|
212
|
+
if (event.message.includes('chunks loaded') || event.message.includes('Final index')) {
|
|
213
|
+
spinner.start('Generating architectural documentation...');
|
|
214
|
+
}
|
|
172
215
|
}
|
|
173
216
|
else if (options.verbose) {
|
|
174
217
|
spinner.info(event.message);
|
|
@@ -212,7 +255,14 @@ program
|
|
|
212
255
|
}
|
|
213
256
|
console.log();
|
|
214
257
|
console.log(chalk.cyan('📁 Wiki generated at:'), chalk.white(path.resolve(options.output)));
|
|
215
|
-
|
|
258
|
+
// Generate static site if requested
|
|
259
|
+
if (options.site || options.siteOnly) {
|
|
260
|
+
await generateStaticSite(options);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
console.log(chalk.gray('Open wiki/README.md to start exploring the documentation.'));
|
|
264
|
+
console.log(chalk.gray('Tip: Add --site flag to generate an interactive website.'));
|
|
265
|
+
}
|
|
216
266
|
console.log();
|
|
217
267
|
}
|
|
218
268
|
catch (error) {
|
|
@@ -228,6 +278,373 @@ program
|
|
|
228
278
|
process.exit(1);
|
|
229
279
|
}
|
|
230
280
|
});
|
|
281
|
+
// Update-docs command - incremental documentation updates
|
|
282
|
+
program
|
|
283
|
+
.command('update-docs')
|
|
284
|
+
.description('Update documentation based on changes since last index')
|
|
285
|
+
.requiredOption('-r, --repo <path>', 'Repository path (local)')
|
|
286
|
+
.option('-o, --output <dir>', 'Output directory for wiki', './wiki')
|
|
287
|
+
.option('-m, --model <model>', 'Claude model to use', 'claude-sonnet-4-20250514')
|
|
288
|
+
.option('-v, --verbose', 'Verbose output')
|
|
289
|
+
.action(async (options) => {
|
|
290
|
+
try {
|
|
291
|
+
const configManager = new ConfigManager();
|
|
292
|
+
const config = await configManager.load();
|
|
293
|
+
if (!configManager.hasApiKey()) {
|
|
294
|
+
console.log(chalk.red('❌ No API key found.'));
|
|
295
|
+
console.log(chalk.yellow('\nSet your Anthropic API key:'));
|
|
296
|
+
console.log(chalk.gray(' export ANTHROPIC_API_KEY=your-key-here'));
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
const wikiDir = path.resolve(options.output);
|
|
300
|
+
const cacheDir = path.join(wikiDir, '.ted-mosby-cache');
|
|
301
|
+
const indexStatePath = path.join(cacheDir, 'index-state.json');
|
|
302
|
+
// Check if we have an existing index
|
|
303
|
+
if (!fs.existsSync(indexStatePath)) {
|
|
304
|
+
console.log(chalk.yellow('⚠️ No existing index found.'));
|
|
305
|
+
console.log(chalk.gray('Run `ted-mosby generate` first to create the initial documentation.'));
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
const indexState = JSON.parse(fs.readFileSync(indexStatePath, 'utf-8'));
|
|
309
|
+
console.log(chalk.cyan.bold('\n📝 Documentation Update\n'));
|
|
310
|
+
console.log(chalk.white('Last indexed:'), chalk.green(indexState.commitHash.slice(0, 7)));
|
|
311
|
+
console.log(chalk.white('Indexed at:'), chalk.green(new Date(indexState.indexedAt).toLocaleString()));
|
|
312
|
+
console.log();
|
|
313
|
+
// Get changed files since last index
|
|
314
|
+
const git = (await import('simple-git')).simpleGit(options.repo);
|
|
315
|
+
const currentLog = await git.log({ maxCount: 1 });
|
|
316
|
+
const currentCommit = currentLog.latest?.hash || 'unknown';
|
|
317
|
+
if (currentCommit === indexState.commitHash) {
|
|
318
|
+
console.log(chalk.green('✓ Documentation is up to date. No changes since last index.'));
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const diffResult = await git.diff(['--name-only', indexState.commitHash, 'HEAD']);
|
|
322
|
+
const changedFiles = diffResult.split('\n').filter(f => f.trim().length > 0);
|
|
323
|
+
if (changedFiles.length === 0) {
|
|
324
|
+
console.log(chalk.green('✓ No relevant file changes detected.'));
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
console.log(chalk.white('Current commit:'), chalk.green(currentCommit.slice(0, 7)));
|
|
328
|
+
console.log(chalk.white('Changed files:'), chalk.yellow(changedFiles.length.toString()));
|
|
329
|
+
console.log();
|
|
330
|
+
// Show changed files
|
|
331
|
+
console.log(chalk.white.bold('Files changed since last index:'));
|
|
332
|
+
const relevantExts = ['.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.rs', '.java'];
|
|
333
|
+
const relevantFiles = changedFiles.filter(f => relevantExts.some(ext => f.endsWith(ext)));
|
|
334
|
+
for (const file of relevantFiles.slice(0, 15)) {
|
|
335
|
+
console.log(chalk.gray(` • ${file}`));
|
|
336
|
+
}
|
|
337
|
+
if (relevantFiles.length > 15) {
|
|
338
|
+
console.log(chalk.gray(` ... and ${relevantFiles.length - 15} more`));
|
|
339
|
+
}
|
|
340
|
+
console.log();
|
|
341
|
+
// Prompt for confirmation
|
|
342
|
+
const { confirm } = await inquirer.prompt([{
|
|
343
|
+
type: 'confirm',
|
|
344
|
+
name: 'confirm',
|
|
345
|
+
message: `Update documentation for ${relevantFiles.length} changed files?`,
|
|
346
|
+
default: true
|
|
347
|
+
}]);
|
|
348
|
+
if (!confirm) {
|
|
349
|
+
console.log(chalk.yellow('\nUpdate cancelled.'));
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
// Run incremental update
|
|
353
|
+
const spinner = ora('Updating documentation...').start();
|
|
354
|
+
const permissionManager = new PermissionManager({ policy: 'permissive' });
|
|
355
|
+
const agent = new ArchitecturalWikiAgent({
|
|
356
|
+
verbose: options.verbose,
|
|
357
|
+
apiKey: config.apiKey,
|
|
358
|
+
permissionManager
|
|
359
|
+
});
|
|
360
|
+
// TODO: Implement incremental update mode in agent
|
|
361
|
+
// For now, re-run full generation but the RAG index is cached
|
|
362
|
+
// Future: Pass changedFiles to agent for targeted updates
|
|
363
|
+
try {
|
|
364
|
+
for await (const event of agent.generateWiki({
|
|
365
|
+
repoUrl: options.repo,
|
|
366
|
+
outputDir: options.output,
|
|
367
|
+
model: options.model,
|
|
368
|
+
verbose: options.verbose,
|
|
369
|
+
forceRegenerate: true // Force re-index to update commit hash
|
|
370
|
+
})) {
|
|
371
|
+
if (event.type === 'phase') {
|
|
372
|
+
spinner.text = event.message;
|
|
373
|
+
}
|
|
374
|
+
else if (event.type === 'complete') {
|
|
375
|
+
spinner.succeed(chalk.green('Documentation updated!'));
|
|
376
|
+
}
|
|
377
|
+
else if (event.type === 'error') {
|
|
378
|
+
spinner.fail(chalk.red(event.message));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
console.log();
|
|
382
|
+
console.log(chalk.cyan('📁 Wiki updated at:'), chalk.white(wikiDir));
|
|
383
|
+
console.log(chalk.gray('Tip: Run with --site flag to regenerate the static site.'));
|
|
384
|
+
console.log();
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
spinner.fail('Update failed');
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
console.error(chalk.red('\nError:'), error instanceof Error ? error.message : String(error));
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
// Continue-generation command - complete missing wiki pages
|
|
397
|
+
program
|
|
398
|
+
.command('continue')
|
|
399
|
+
.description('Continue generating missing wiki pages (verifies completeness and creates missing pages)')
|
|
400
|
+
.requiredOption('-r, --repo <path>', 'Repository path (local)')
|
|
401
|
+
.option('-o, --output <dir>', 'Output directory for wiki', './wiki')
|
|
402
|
+
.option('-m, --model <model>', 'Claude model to use', 'claude-sonnet-4-20250514')
|
|
403
|
+
.option('-v, --verbose', 'Verbose output')
|
|
404
|
+
.option('--verify-only', 'Only verify completeness, do not generate missing pages')
|
|
405
|
+
.option('--skip-index', 'Skip indexing and use existing cached index')
|
|
406
|
+
.option('--direct-api', 'Use Anthropic API directly (bypasses Claude Code billing)')
|
|
407
|
+
.option('--max-turns <number>', 'Maximum agent turns (default 200)', parseInt)
|
|
408
|
+
.action(async (options) => {
|
|
409
|
+
try {
|
|
410
|
+
const configManager = new ConfigManager();
|
|
411
|
+
const config = await configManager.load();
|
|
412
|
+
if (!configManager.hasApiKey()) {
|
|
413
|
+
console.log(chalk.red('❌ No API key found.'));
|
|
414
|
+
console.log(chalk.yellow('\nSet your Anthropic API key:'));
|
|
415
|
+
console.log(chalk.gray(' export ANTHROPIC_API_KEY=your-key-here'));
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
const wikiDir = path.resolve(options.output);
|
|
419
|
+
// Check if wiki directory exists
|
|
420
|
+
if (!fs.existsSync(wikiDir)) {
|
|
421
|
+
console.log(chalk.red('❌ Wiki directory not found: ' + wikiDir));
|
|
422
|
+
console.log(chalk.gray('Run `ted-mosby generate` first to create the wiki.'));
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
console.log(chalk.cyan.bold('\n📋 Wiki Completeness Check\n'));
|
|
426
|
+
// Use the agent's verification method
|
|
427
|
+
const permissionManager = new PermissionManager({ policy: 'permissive' });
|
|
428
|
+
const agent = new ArchitecturalWikiAgent({
|
|
429
|
+
verbose: options.verbose,
|
|
430
|
+
apiKey: config.apiKey,
|
|
431
|
+
permissionManager
|
|
432
|
+
});
|
|
433
|
+
const verification = await agent.verifyWikiCompleteness(wikiDir);
|
|
434
|
+
console.log(chalk.white('Total pages:'), chalk.green(verification.totalPages.toString()));
|
|
435
|
+
console.log(chalk.white('Broken links:'), verification.brokenLinks.length > 0
|
|
436
|
+
? chalk.red(verification.brokenLinks.length.toString())
|
|
437
|
+
: chalk.green('0'));
|
|
438
|
+
console.log(chalk.white('Missing pages:'), verification.missingPages.length > 0
|
|
439
|
+
? chalk.red(verification.missingPages.length.toString())
|
|
440
|
+
: chalk.green('0'));
|
|
441
|
+
console.log();
|
|
442
|
+
if (verification.isComplete) {
|
|
443
|
+
console.log(chalk.green('✅ Wiki is complete! All internal links are valid.'));
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
// Show missing pages
|
|
447
|
+
console.log(chalk.yellow.bold('Missing pages:'));
|
|
448
|
+
const uniqueMissing = [...new Set(verification.brokenLinks.map(l => l.target))];
|
|
449
|
+
for (const page of uniqueMissing.slice(0, 20)) {
|
|
450
|
+
console.log(chalk.gray(` • ${page}`));
|
|
451
|
+
}
|
|
452
|
+
if (uniqueMissing.length > 20) {
|
|
453
|
+
console.log(chalk.gray(` ... and ${uniqueMissing.length - 20} more`));
|
|
454
|
+
}
|
|
455
|
+
console.log();
|
|
456
|
+
if (options.verifyOnly) {
|
|
457
|
+
console.log(chalk.yellow('Verification complete. Run without --verify-only to generate missing pages.'));
|
|
458
|
+
process.exit(verification.isComplete ? 0 : 1);
|
|
459
|
+
}
|
|
460
|
+
// Prompt for confirmation
|
|
461
|
+
const { confirm } = await inquirer.prompt([{
|
|
462
|
+
type: 'confirm',
|
|
463
|
+
name: 'confirm',
|
|
464
|
+
message: `Generate ${uniqueMissing.length} missing pages?`,
|
|
465
|
+
default: true
|
|
466
|
+
}]);
|
|
467
|
+
if (!confirm) {
|
|
468
|
+
console.log(chalk.yellow('\nGeneration cancelled.'));
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
// Run continuation
|
|
472
|
+
const spinner = ora('Generating missing pages...').start();
|
|
473
|
+
// Choose generator based on options
|
|
474
|
+
const generationOptions = {
|
|
475
|
+
repoUrl: options.repo,
|
|
476
|
+
outputDir: options.output,
|
|
477
|
+
model: options.model,
|
|
478
|
+
verbose: options.verbose,
|
|
479
|
+
missingPages: uniqueMissing, // Pass missing pages to agent for targeted generation
|
|
480
|
+
skipIndex: options.skipIndex,
|
|
481
|
+
directApi: options.directApi,
|
|
482
|
+
maxTurns: options.maxTurns
|
|
483
|
+
};
|
|
484
|
+
let generator;
|
|
485
|
+
if (options.directApi) {
|
|
486
|
+
console.log(chalk.yellow('\n⚡ Direct API mode: Using Anthropic API directly\n'));
|
|
487
|
+
generator = agent.generateWikiDirectApi(generationOptions);
|
|
488
|
+
}
|
|
489
|
+
else if (options.skipIndex) {
|
|
490
|
+
console.log(chalk.yellow('\n⚡ Skip-index mode: Using existing cached index\n'));
|
|
491
|
+
generator = agent.generateWikiAgentOnly(generationOptions);
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
generator = agent.generateWiki(generationOptions);
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
for await (const event of generator) {
|
|
498
|
+
if (event.type === 'phase') {
|
|
499
|
+
spinner.text = event.message;
|
|
500
|
+
}
|
|
501
|
+
else if (event.type === 'complete') {
|
|
502
|
+
spinner.succeed(chalk.green('Missing pages generated!'));
|
|
503
|
+
}
|
|
504
|
+
else if (event.type === 'error') {
|
|
505
|
+
spinner.fail(chalk.red(event.message));
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
// Verify again
|
|
509
|
+
console.log();
|
|
510
|
+
console.log(chalk.cyan('Verifying completeness...'));
|
|
511
|
+
const postVerification = await agent.verifyWikiCompleteness(wikiDir);
|
|
512
|
+
if (postVerification.isComplete) {
|
|
513
|
+
console.log(chalk.green('✅ Wiki is now complete! All internal links are valid.'));
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
console.log(chalk.yellow(`⚠️ ${postVerification.brokenLinks.length} broken links remain.`));
|
|
517
|
+
console.log(chalk.gray('Run `ted-mosby continue` again to generate remaining pages.'));
|
|
518
|
+
}
|
|
519
|
+
console.log();
|
|
520
|
+
console.log(chalk.cyan('📁 Wiki at:'), chalk.white(wikiDir));
|
|
521
|
+
console.log();
|
|
522
|
+
}
|
|
523
|
+
catch (error) {
|
|
524
|
+
spinner.fail('Generation failed');
|
|
525
|
+
throw error;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
catch (error) {
|
|
529
|
+
console.error(chalk.red('\nError:'), error instanceof Error ? error.message : String(error));
|
|
530
|
+
process.exit(1);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
// Verify command - check wiki completeness without generating
|
|
534
|
+
program
|
|
535
|
+
.command('verify')
|
|
536
|
+
.description('Verify wiki completeness and report broken links')
|
|
537
|
+
.option('-o, --output <dir>', 'Wiki directory to verify', './wiki')
|
|
538
|
+
.option('--json', 'Output as JSON')
|
|
539
|
+
.action(async (options) => {
|
|
540
|
+
try {
|
|
541
|
+
const wikiDir = path.resolve(options.output);
|
|
542
|
+
if (!fs.existsSync(wikiDir)) {
|
|
543
|
+
console.log(chalk.red('❌ Wiki directory not found: ' + wikiDir));
|
|
544
|
+
process.exit(1);
|
|
545
|
+
}
|
|
546
|
+
const configManager = new ConfigManager();
|
|
547
|
+
const config = await configManager.load();
|
|
548
|
+
const permissionManager = new PermissionManager({ policy: 'permissive' });
|
|
549
|
+
const agent = new ArchitecturalWikiAgent({
|
|
550
|
+
apiKey: config.apiKey,
|
|
551
|
+
permissionManager
|
|
552
|
+
});
|
|
553
|
+
const verification = await agent.verifyWikiCompleteness(wikiDir);
|
|
554
|
+
if (options.json) {
|
|
555
|
+
console.log(JSON.stringify(verification, null, 2));
|
|
556
|
+
process.exit(verification.isComplete ? 0 : 1);
|
|
557
|
+
}
|
|
558
|
+
console.log(chalk.cyan.bold('\n📋 Wiki Completeness Report\n'));
|
|
559
|
+
console.log(chalk.white('Total pages:'), chalk.green(verification.totalPages.toString()));
|
|
560
|
+
console.log(chalk.white('Broken links:'), verification.brokenLinks.length > 0
|
|
561
|
+
? chalk.red(verification.brokenLinks.length.toString())
|
|
562
|
+
: chalk.green('0'));
|
|
563
|
+
console.log();
|
|
564
|
+
if (verification.isComplete) {
|
|
565
|
+
console.log(chalk.green('✅ Wiki is complete! All internal links are valid.'));
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
console.log(chalk.yellow.bold('Broken links found:'));
|
|
569
|
+
// Group by target
|
|
570
|
+
const byTarget = new Map();
|
|
571
|
+
for (const link of verification.brokenLinks) {
|
|
572
|
+
if (!byTarget.has(link.target)) {
|
|
573
|
+
byTarget.set(link.target, []);
|
|
574
|
+
}
|
|
575
|
+
byTarget.get(link.target).push(link.source);
|
|
576
|
+
}
|
|
577
|
+
for (const [target, sources] of byTarget) {
|
|
578
|
+
console.log(chalk.red(`\n Missing: ${target}`));
|
|
579
|
+
console.log(chalk.gray(` Referenced by:`));
|
|
580
|
+
for (const source of sources.slice(0, 3)) {
|
|
581
|
+
console.log(chalk.gray(` - ${source}`));
|
|
582
|
+
}
|
|
583
|
+
if (sources.length > 3) {
|
|
584
|
+
console.log(chalk.gray(` ... and ${sources.length - 3} more`));
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
console.log();
|
|
588
|
+
console.log(chalk.yellow('Run `ted-mosby continue` to generate missing pages.'));
|
|
589
|
+
}
|
|
590
|
+
process.exit(verification.isComplete ? 0 : 1);
|
|
591
|
+
}
|
|
592
|
+
catch (error) {
|
|
593
|
+
console.error(chalk.red('\nError:'), error instanceof Error ? error.message : String(error));
|
|
594
|
+
process.exit(1);
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
// Static site generation helper
|
|
598
|
+
async function generateStaticSite(options) {
|
|
599
|
+
console.log(chalk.cyan.bold('\n🌐 Generating Interactive Static Site\n'));
|
|
600
|
+
const wikiDir = path.resolve(options.output);
|
|
601
|
+
const siteDir = path.join(path.dirname(wikiDir), 'site');
|
|
602
|
+
// Check if wiki directory exists
|
|
603
|
+
if (!fs.existsSync(wikiDir)) {
|
|
604
|
+
console.log(chalk.red('❌ Wiki directory not found: ' + wikiDir));
|
|
605
|
+
console.log(chalk.gray('Run without --site-only to generate wiki first.'));
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
const spinner = ora('Building static site...').start();
|
|
609
|
+
try {
|
|
610
|
+
const siteGenerator = new SiteGenerator({
|
|
611
|
+
wikiDir,
|
|
612
|
+
outputDir: siteDir,
|
|
613
|
+
title: options.siteTitle || 'Architecture Wiki',
|
|
614
|
+
description: 'Interactive architectural documentation',
|
|
615
|
+
theme: options.theme || 'auto',
|
|
616
|
+
features: {
|
|
617
|
+
guidedTour: false, // Disabled by default - can be enabled via flag
|
|
618
|
+
codeExplorer: true,
|
|
619
|
+
search: true,
|
|
620
|
+
progressTracking: true,
|
|
621
|
+
keyboardNav: true
|
|
622
|
+
},
|
|
623
|
+
repoUrl: options.repo || ''
|
|
624
|
+
});
|
|
625
|
+
await siteGenerator.generate();
|
|
626
|
+
spinner.succeed(chalk.green('Static site generated!'));
|
|
627
|
+
console.log();
|
|
628
|
+
console.log(chalk.cyan('🌐 Site generated at:'), chalk.white(siteDir));
|
|
629
|
+
console.log();
|
|
630
|
+
console.log(chalk.white.bold('Features included:'));
|
|
631
|
+
console.log(chalk.gray(' ✓ Interactive search (press "/" to open)'));
|
|
632
|
+
console.log(chalk.gray(' ✓ Guided tours for onboarding'));
|
|
633
|
+
console.log(chalk.gray(' ✓ Code explorer with syntax highlighting'));
|
|
634
|
+
console.log(chalk.gray(' ✓ Live Mermaid diagrams (click to zoom)'));
|
|
635
|
+
console.log(chalk.gray(' ✓ Dark/light theme toggle'));
|
|
636
|
+
console.log(chalk.gray(' ✓ Keyboard navigation (press "?" for help)'));
|
|
637
|
+
console.log(chalk.gray(' ✓ Progress tracking'));
|
|
638
|
+
console.log();
|
|
639
|
+
console.log(chalk.white('To preview locally:'));
|
|
640
|
+
console.log(chalk.gray(' npx serve ' + siteDir));
|
|
641
|
+
console.log();
|
|
642
|
+
}
|
|
643
|
+
catch (error) {
|
|
644
|
+
spinner.fail('Static site generation failed');
|
|
645
|
+
throw error;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
231
648
|
program
|
|
232
649
|
.argument('[query]', 'Direct query to the agent')
|
|
233
650
|
.option('-i, --interactive', 'Start interactive session')
|