Files
tsdoc/ts/aidocs_classes/readme.ts
2025-12-15 14:34:02 +00:00

215 lines
6.9 KiB
TypeScript

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;
}
}