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 = ``; // First check legal info before introducing 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); } // Use DualAgentOrchestrator with filesystem tool for agent-driven exploration const readmeOrchestrator = new plugins.smartagent.DualAgentOrchestrator({ smartAiInstance: this.aiDocsRef.smartAiInstance, defaultProvider: 'openai', maxIterations: 25, maxResultChars: 15000, // Limit tool output to prevent token explosion maxHistoryMessages: 20, // Limit history window logPrefix: '[README]', onProgress: (event) => logger.log(event.logLevel, event.logMessage), guardianPolicyPrompt: ` You validate README generation tool calls and outputs. APPROVE tool calls for: - Reading any files within the project directory (package.json, ts/*.ts, readme.md, etc.) - Using tree to see project structure - Using glob to find source files - Listing directory contents REJECT tool calls for: - Reading files outside the project directory - Writing, deleting, or modifying any files - Any destructive operations For final README output, 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 final output if: - README is incomplete or poorly formatted - Contains licensing information (added separately) - Uses CommonJS syntax instead of ESM - Contains "in conclusion" or similar filler `, }); // Register scoped filesystem tool for agent exploration readmeOrchestrator.registerScopedFilesystemTool(this.projectDir); await readmeOrchestrator.start(); const readmeTaskPrompt = ` You create markdown READMEs for npm projects. You only output the markdown readme. PROJECT DIRECTORY: ${this.projectDir} Use the filesystem tool to explore the project and understand what it does: 1. First, use tree to see the project structure (maxDepth: 3) 2. Read package.json to understand the package name, description, and dependencies 3. Read the existing readme.md if it exists (use it as a base, improve and expand) 4. Read readme.hints.md if it exists (contains hints for documentation) 5. Read key source files in ts/ directory to understand the API and implementation 6. Focus on exported classes, interfaces, and functions Then generate a comprehensive README following 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. ] `; 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 subModulePath = plugins.path.join(paths.cwd, subModule); const tspublishData = await plugins.fsInstance .file(plugins.path.join(subModulePath, 'tspublish.json')) .encoding('utf8') .read(); // Create a new orchestrator with filesystem tool for each submodule const subModuleOrchestrator = new plugins.smartagent.DualAgentOrchestrator({ smartAiInstance: this.aiDocsRef.smartAiInstance, defaultProvider: 'openai', maxIterations: 20, maxResultChars: 12000, maxHistoryMessages: 15, logPrefix: `[README:${subModule}]`, onProgress: (event) => logger.log(event.logLevel, event.logMessage), guardianPolicyPrompt: ` You validate README generation for submodules. APPROVE tool calls for: - Reading any files within the submodule directory - Using tree to see structure - Using glob to find source files REJECT tool calls for: - Reading files outside the submodule directory - Writing, deleting, or modifying any files - Any destructive operations APPROVE final README if comprehensive, well-formatted markdown with ESM TypeScript examples. REJECT incomplete READMEs or those with licensing info. `, }); // Register scoped filesystem tool for the submodule directory subModuleOrchestrator.registerScopedFilesystemTool(subModulePath); await subModuleOrchestrator.start(); const subModulePrompt = ` You create markdown READMEs for npm projects. You only output the markdown readme. SUB MODULE: ${subModule} SUB MODULE DIRECTORY: ${subModulePath} IMPORTANT: YOU ARE CREATING THE README FOR THIS SUB MODULE: ${subModule} The Sub Module will be published with: ${JSON.stringify(tspublishData, null, 2)} Use the filesystem tool to explore the submodule: 1. Use tree to see the submodule structure 2. Read package.json to understand the submodule 3. Read source files in ts/ directory to understand the implementation 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. `; 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(subModulePath, '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; } }