import type { AiDoc } from '../classes.aidoc.js'; import * as plugins from '../plugins.js'; import * as paths from '../paths.js'; import { ProjectContext } from './projectcontext.js'; import { logger } from '../logging.js'; export class Readme { // INSTANCE private aiDocsRef: AiDoc; private projectDir: string; constructor(aiDocsRef: AiDoc, projectDirArg: string) { this.aiDocsRef = aiDocsRef; this.projectDir = projectDirArg; } public async build() { let finalReadmeString = ``; // lets first check legal before introducung any cost const projectContext = new ProjectContext(this.projectDir); const npmExtraJson = JSON.parse( (await projectContext.gatherFiles()).smartfilesNpmextraJSON.contents.toString() ); const legalInfo = npmExtraJson?.['@git.zone/tsdoc']?.legal; if (!legalInfo) { const error = new Error(`No legal information found in npmextra.json`); console.log(error); } // Gather project context upfront to avoid token explosion from filesystem tool const contextString = await projectContext.convertFilesToContext([ (await projectContext.gatherFiles()).smartfilePackageJSON, (await projectContext.gatherFiles()).smartfilesReadme, (await projectContext.gatherFiles()).smartfilesReadmeHints, (await projectContext.gatherFiles()).smartfilesNpmextraJSON, ...(await projectContext.gatherFiles()).smartfilesMod, ]); // Use DualAgentOrchestrator for readme generation const readmeOrchestrator = new plugins.smartagent.DualAgentOrchestrator({ smartAiInstance: this.aiDocsRef.smartAiInstance, defaultProvider: 'openai', guardianPolicyPrompt: ` You validate README generation. APPROVE if: - README follows proper markdown format - Contains Install and Usage sections - Code examples are correct TypeScript/ESM syntax - Documentation is comprehensive and helpful REJECT if: - README is incomplete or poorly formatted - Contains licensing information (added separately) - Uses CommonJS syntax instead of ESM - Contains "in conclusion" or similar filler `, }); await readmeOrchestrator.start(); const readmeTaskPrompt = ` You create markdown READMEs for npm projects. You only output the markdown readme. Analyze the project files provided below to understand the codebase, then generate a comprehensive README. The README should follow this template: # Project Name [The name from package.json and description] ## Install [Short text on how to install the project] ## Usage [ Give code examples here. Construct sensible scenarios for the user. Make sure to show a complete set of features of the module. Don't omit use cases. ALWAYS USE ESM SYNTAX AND TYPESCRIPT. Write at least 4000 words. More if necessary. If there is already a readme, take the Usage section as base. Remove outdated content, expand and improve. Check for completeness. Don't include any licensing information. This will be added later. Avoid "in conclusion" statements. ] Here are the project files: ${contextString} Generate the README based on these files. `; const readmeResult = await readmeOrchestrator.run(readmeTaskPrompt); await readmeOrchestrator.stop(); if (!readmeResult.success) { throw new Error(`README generation failed: ${readmeResult.status}`); } // Clean up markdown formatting if wrapped in code blocks let resultMessage = readmeResult.result .replace(/^```markdown\n?/i, '') .replace(/\n?```$/i, ''); finalReadmeString += resultMessage + '\n' + legalInfo; console.log(`\n======================\n`); console.log(resultMessage); console.log(`\n======================\n`); const readme = (await projectContext.gatherFiles()).smartfilesReadme; readme.contents = Buffer.from(finalReadmeString); await readme.write(); // lets care about monorepo aspects const tsPublishInstance = new plugins.tspublish.TsPublish(); const subModules = await tsPublishInstance.getModuleSubDirs(paths.cwd); logger.log('info', `Found ${Object.keys(subModules).length} sub modules`); for (const subModule of Object.keys(subModules)) { logger.log('info', `Building readme for ${subModule}`); const tspublishData = await plugins.fsInstance .file(plugins.path.join(paths.cwd, subModule, 'tspublish.json')) .encoding('utf8') .read(); // Gather submodule context const subModuleContext = new ProjectContext(plugins.path.join(paths.cwd, subModule)); let subModuleContextString = ''; try { const subModuleFiles = await subModuleContext.gatherFiles(); subModuleContextString = await subModuleContext.convertFilesToContext([ subModuleFiles.smartfilePackageJSON, subModuleFiles.smartfilesNpmextraJSON, ...subModuleFiles.smartfilesMod, ]); } catch (e) { // Submodule may not have all files, continue with what we have logger.log('warn', `Could not gather full context for ${subModule}`); } // Create a new orchestrator for each submodule const subModuleOrchestrator = new plugins.smartagent.DualAgentOrchestrator({ smartAiInstance: this.aiDocsRef.smartAiInstance, defaultProvider: 'openai', guardianPolicyPrompt: ` You validate README generation for submodules. APPROVE comprehensive, well-formatted markdown with ESM TypeScript examples. REJECT incomplete READMEs or those with licensing info. `, }); await subModuleOrchestrator.start(); const subModulePrompt = ` You create markdown READMEs for npm projects. You only output the markdown readme. SUB MODULE: ${subModule} IMPORTANT: YOU ARE CREATING THE README FOR THIS SUB MODULE: ${subModule} The Sub Module will be published with: ${JSON.stringify(tspublishData, null, 2)} Generate a README following the template: # Project Name [name and description from package.json] ## Install [installation instructions] ## Usage [ Code examples with complete features. ESM TypeScript syntax only. Write at least 4000 words. No licensing information. No "in conclusion". ] Don't use \`\`\` at the beginning or end. Only for code blocks. Here are the submodule files: ${subModuleContextString} Generate the README based on these files. `; const subModuleResult = await subModuleOrchestrator.run(subModulePrompt); await subModuleOrchestrator.stop(); if (subModuleResult.success) { const subModuleReadmeString = subModuleResult.result .replace(/^```markdown\n?/i, '') .replace(/\n?```$/i, '') + '\n' + legalInfo; await plugins.fsInstance .file(plugins.path.join(paths.cwd, subModule, 'readme.md')) .encoding('utf8') .write(subModuleReadmeString); logger.log('success', `Built readme for ${subModule}`); } else { logger.log('error', `Failed to build readme for ${subModule}: ${subModuleResult.status}`); } } return resultMessage; } }