231 lines
7.8 KiB
TypeScript
231 lines
7.8 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 = ``;
|
|
|
|
// 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;
|
|
}
|
|
}
|