BREAKING CHANGE(aidoc): migrate agent orchestration to new runAgent API and filesystem tools; refactor model handling and update README and tests

This commit is contained in:
2026-03-11 18:58:48 +00:00
parent 883985dbc0
commit 6b2957b272
11 changed files with 834 additions and 3270 deletions

View File

@@ -103,7 +103,7 @@ export class Commit {
const totalChars = diffStringArray.join('\n\n').length;
const estimatedTokens = Math.ceil(totalChars / 4);
console.log(`📊 Raw git diff statistics:`);
console.log(`Raw git diff statistics:`);
console.log(` Files changed: ${diffStringArray.length}`);
console.log(` Total characters: ${totalChars.toLocaleString()}`);
console.log(` Estimated tokens: ${estimatedTokens.toLocaleString()}`);
@@ -111,7 +111,7 @@ export class Commit {
// Calculate available tokens for diff based on total budget
const maxDiffTokens = calculateMaxDiffTokens();
console.log(`📊 Token budget: ${maxDiffTokens.toLocaleString()} tokens for diff (limit: ${TOKEN_BUDGET.OPENAI_CONTEXT_LIMIT.toLocaleString()}, overhead: ${(TOKEN_BUDGET.SMARTAGENT_OVERHEAD + TOKEN_BUDGET.TASK_PROMPT_OVERHEAD).toLocaleString()})`);
console.log(`Token budget: ${maxDiffTokens.toLocaleString()} tokens for diff (limit: ${TOKEN_BUDGET.OPENAI_CONTEXT_LIMIT.toLocaleString()}, overhead: ${(TOKEN_BUDGET.SMARTAGENT_OVERHEAD + TOKEN_BUDGET.TASK_PROMPT_OVERHEAD).toLocaleString()})`);
// Use DiffProcessor to intelligently handle large diffs
const diffProcessor = new DiffProcessor({
@@ -125,14 +125,14 @@ export class Commit {
const processedDiff = diffProcessor.processDiffs(diffStringArray);
processedDiffString = diffProcessor.formatForContext(processedDiff);
console.log(`📝 Processed diff statistics:`);
console.log(`Processed diff statistics:`);
console.log(` Full diffs: ${processedDiff.fullDiffs.length} files`);
console.log(` Summarized: ${processedDiff.summarizedDiffs.length} files`);
console.log(` Metadata only: ${processedDiff.metadataOnly.length} files`);
console.log(` Final tokens: ${processedDiff.totalTokens.toLocaleString()}`);
if (estimatedTokens > 50000) {
console.log(`DiffProcessor reduced token usage: ${estimatedTokens.toLocaleString()} ${processedDiff.totalTokens.toLocaleString()}`);
console.log(`DiffProcessor reduced token usage: ${estimatedTokens.toLocaleString()} -> ${processedDiff.totalTokens.toLocaleString()}`);
}
// Validate total tokens won't exceed limit
@@ -141,71 +141,44 @@ export class Commit {
+ TOKEN_BUDGET.TASK_PROMPT_OVERHEAD;
if (totalEstimatedTokens > TOKEN_BUDGET.OPENAI_CONTEXT_LIMIT - TOKEN_BUDGET.SAFETY_MARGIN) {
console.log(`⚠️ Warning: Estimated tokens (${totalEstimatedTokens.toLocaleString()}) approaching limit`);
console.log(`Warning: Estimated tokens (${totalEstimatedTokens.toLocaleString()}) approaching limit`);
console.log(` Consider splitting into smaller commits`);
}
} else {
processedDiffString = 'No changes.';
}
// Use DualAgentOrchestrator for commit message generation
const commitOrchestrator = new plugins.smartagent.DualAgentOrchestrator({
smartAiInstance: this.aiDocsRef.smartAiInstance,
defaultProvider: 'openai',
logPrefix: '[Commit]',
onProgress: (event) => logger.log(event.logLevel, event.logMessage),
guardianPolicyPrompt: `
You validate commit messages for semantic versioning compliance.
// Use runAgent for commit message generation with filesystem tool
const fsTools = plugins.smartagentTools.filesystemTool({ rootDir: this.projectDir });
APPROVE tool calls for:
- Reading package.json or source files to understand project context
- Using tree to see project structure
- Listing directory contents
const commitSystemPrompt = `
You create commit messages for git commits following semantic versioning conventions.
REJECT tool calls for:
- Reading files outside the project directory
- Writing, deleting, or modifying any files
- Any destructive operations
You have access to filesystem tools to explore the project if needed.
APPROVE final output if:
- Version level (fix/feat/BREAKING CHANGE) matches the scope of changes in the diff
- Commit message is clear, professional, and follows conventional commit conventions
- No personal information, licensing details, or AI mentions (Claude/Codex) included
- JSON structure is valid with all required fields
- Scope accurately reflects the changed modules/files
REJECT final output if:
- Version level doesn't match the scope of changes (e.g., "feat" for a typo fix should be "fix")
- Message is vague, unprofessional, or contains sensitive information
- JSON is malformed or missing required fields
`,
});
// Register scoped filesystem tool for agent exploration
commitOrchestrator.registerScopedFilesystemTool(this.projectDir, [
'.nogit/**',
'node_modules/**',
'.git/**',
'dist/**',
'dist_*/**',
]);
await commitOrchestrator.start();
IMPORTANT RULES:
- Only READ files (package.json, source files) for context
- Do NOT write, delete, or modify any files
- Version level (fix/feat/BREAKING CHANGE) must match the scope of changes
- Commit message must be clear, professional, and follow conventional commit conventions
- Do NOT include personal information, licensing details, or AI mentions (Claude/Codex)
- JSON structure must be valid with all required fields
- Scope must accurately reflect the changed modules/files
`;
const commitTaskPrompt = `
You create a commit message for a git commit.
Project directory: ${this.projectDir}
You have access to a filesystem tool to explore the project if needed:
- Use tree to see project structure
- Use read to read package.json or source files for context
You have access to filesystem tools to explore the project if needed:
- Use list_directory to see project structure
- Use read_file to read package.json or source files for context
Analyze the git diff below to understand what changed and generate a commit message.
You should not include any licensing information or personal information.
Never mention CLAUDE code, or codex.
Your final output (inside the task_complete tags) must be ONLY valid JSON - the raw JSON object, nothing else.
Your final response must be ONLY valid JSON - the raw JSON object, nothing else.
No explanations, no summaries, no markdown - just the JSON object that can be parsed with JSON.parse().
Here is the structure of the JSON you must return:
@@ -227,15 +200,19 @@ ${processedDiffString}
Analyze these changes and output the JSON commit message object.
`;
const commitResult = await commitOrchestrator.run(commitTaskPrompt);
await commitOrchestrator.stop();
logger.log('info', 'Starting commit message generation with agent...');
if (!commitResult.success) {
throw new Error(`Commit message generation failed: ${commitResult.status}`);
}
const commitResult = await plugins.smartagent.runAgent({
model: this.aiDocsRef.model,
prompt: commitTaskPrompt,
system: commitSystemPrompt,
tools: fsTools,
maxSteps: 10,
onToolCall: (toolName) => logger.log('info', `[Commit] Tool call: ${toolName}`),
});
// Extract JSON from result - handle cases where AI adds text around it
let jsonString = commitResult.result
let jsonString = commitResult.text
.replace(/```json\n?/gi, '')
.replace(/```\n?/gi, '');
@@ -259,30 +236,16 @@ Analyze these changes and output the JSON commit message object.
const commitMessages = await gitRepo.getAllCommitMessages();
console.log(JSON.stringify(commitMessages, null, 2));
// Use DualAgentOrchestrator for changelog generation with Guardian validation
const changelogOrchestrator = new plugins.smartagent.DualAgentOrchestrator({
smartAiInstance: this.aiDocsRef.smartAiInstance,
defaultProvider: 'openai',
logPrefix: '[Changelog]',
onProgress: (event) => logger.log(event.logLevel, event.logMessage),
guardianPolicyPrompt: `
You validate changelog generation.
const changelogSystemPrompt = `
You generate changelog.md files for software projects.
APPROVE if:
- Changelog follows proper markdown format with ## headers for each version
- Entries are chronologically ordered (newest first)
- Version ranges for trivial commits are properly summarized
RULES:
- Changelog must follow proper markdown format with ## headers for each version
- Entries must be chronologically ordered (newest first)
- Version ranges for trivial commits should be properly summarized
- No duplicate or empty entries
- Format matches: ## yyyy-mm-dd - x.x.x - scope
REJECT with feedback if:
- Markdown formatting is incorrect
- Entries are not meaningful or helpful
- Dates or versions are malformed
`,
});
await changelogOrchestrator.start();
- Format: ## yyyy-mm-dd - x.x.x - scope
`;
const changelogTaskPrompt = `
You are building a changelog.md file for the project.
@@ -291,7 +254,7 @@ Omit commits and versions that lack relevant changes, but make sure to mention t
A changelog entry should look like this:
## yyyy-mm-dd - x.x.x - scope here
main descriptiom here
main description here
- detailed bullet points follow
@@ -305,16 +268,17 @@ Here are the commit messages:
${JSON.stringify(commitMessages, null, 2)}
`;
const changelogResult = await changelogOrchestrator.run(changelogTaskPrompt);
await changelogOrchestrator.stop();
if (!changelogResult.success) {
throw new Error(`Changelog generation failed: ${changelogResult.status}`);
}
const changelogResult = await plugins.smartagent.runAgent({
model: this.aiDocsRef.model,
prompt: changelogTaskPrompt,
system: changelogSystemPrompt,
maxSteps: 1,
onToolCall: (toolName) => logger.log('info', `[Changelog] Tool call: ${toolName}`),
});
previousChangelog = plugins.smartfileFactory.fromString(
previousChangelogPath,
changelogResult.result.replaceAll('```markdown', '').replaceAll('```', ''),
changelogResult.text.replaceAll('```markdown', '').replaceAll('```', ''),
'utf8'
);
}

View File

@@ -19,53 +19,29 @@ export class Description {
}
public async build() {
// Use DualAgentOrchestrator with filesystem tool for agent-driven exploration
const descriptionOrchestrator = new plugins.smartagent.DualAgentOrchestrator({
smartAiInstance: this.aiDocsRef.smartAiInstance,
defaultProvider: 'openai',
maxIterations: 15,
maxResultChars: 10000, // Limit tool output to prevent token explosion
maxHistoryMessages: 15, // Limit history window
logPrefix: '[Description]',
onProgress: (event) => logger.log(event.logLevel, event.logMessage),
guardianPolicyPrompt: `
You validate description generation tool calls and outputs.
// Use runAgent with filesystem tool for agent-driven exploration
const fsTools = plugins.smartagentTools.filesystemTool({ rootDir: this.projectDir });
APPROVE tool calls for:
- Reading package.json, npmextra.json, or source files in the ts/ directory
- Listing directory contents to understand project structure
- Using tree to see project structure
const descriptionSystemPrompt = `
You create project descriptions and keywords for npm packages.
REJECT tool calls for:
- Reading files outside the project directory
- Writing, deleting, or modifying any files
- Any destructive operations
You have access to filesystem tools to explore the project.
For final output, APPROVE if:
- JSON is valid and parseable
- Description is a clear, concise one-sentence summary
- Keywords are relevant to the project's use cases
- Both description and keywords fields are present
REJECT final output if:
- JSON is malformed or wrapped in markdown code blocks
- Description is too long or vague
- Keywords are irrelevant or generic
`,
});
// Register scoped filesystem tool for agent exploration
descriptionOrchestrator.registerScopedFilesystemTool(this.projectDir);
await descriptionOrchestrator.start();
IMPORTANT RULES:
- Only READ files (package.json, npmextra.json, source files in ts/)
- Do NOT write, delete, or modify any files
- Your final response must be valid JSON only
- Description must be a clear, concise one-sentence summary
- Keywords must be relevant to the project's use cases
- Both description and keywords fields must be present
- Do NOT wrap JSON in markdown code blocks
`;
const descriptionTaskPrompt = `
You create a project description and keywords for an npm package.
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
Use the filesystem tools to explore the project and understand what it does:
1. First, use list_directory to see the project structure
2. Read package.json to understand the package name and current description
3. Read npmextra.json if it exists for additional metadata
4. Read key source files in ts/ directory to understand the implementation
@@ -83,16 +59,20 @@ Your answer should be parseable with JSON.parse() without modifying anything.
Don't wrap the JSON in \`\`\`json\`\`\` - just return the raw JSON object.
`;
const descriptionResult = await descriptionOrchestrator.run(descriptionTaskPrompt);
await descriptionOrchestrator.stop();
logger.log('info', 'Starting description generation with agent...');
if (!descriptionResult.success) {
throw new Error(`Description generation failed: ${descriptionResult.status}`);
}
const descriptionResult = await plugins.smartagent.runAgent({
model: this.aiDocsRef.model,
prompt: descriptionTaskPrompt,
system: descriptionSystemPrompt,
tools: fsTools,
maxSteps: 15,
onToolCall: (toolName) => logger.log('info', `[Description] Tool call: ${toolName}`),
});
console.log(descriptionResult.result);
console.log(descriptionResult.text);
const resultObject: IDescriptionInterface = JSON.parse(
descriptionResult.result.replace('```json', '').replace('```', ''),
descriptionResult.text.replace('```json', '').replace('```', ''),
);
// Use ProjectContext to get file handles for writing
@@ -120,6 +100,6 @@ Don't wrap the JSON in \`\`\`json\`\`\` - just return the raw JSON object.
console.log(`\n======================\n`);
console.log(JSON.stringify(resultObject, null, 2));
console.log(`\n======================\n`);
return descriptionResult.result;
return descriptionResult.text;
}
}

View File

@@ -28,55 +28,31 @@ export class Readme {
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.
// Use runAgent with filesystem tool for agent-driven exploration
const fsTools = plugins.smartagentTools.filesystemTool({ rootDir: this.projectDir });
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 = `
const readmeSystemPrompt = `
You create markdown READMEs for npm projects. You only output the markdown readme.
You have access to filesystem tools to explore the project. Use them to understand the codebase.
IMPORTANT RULES:
- Only READ files within the project directory
- Do NOT write, delete, or modify any files
- README must follow proper markdown format
- Must contain Install and Usage sections
- Code examples must use correct TypeScript/ESM syntax
- Documentation must be comprehensive and helpful
- Do NOT include licensing information (added separately)
- Do NOT use CommonJS syntax - only ESM
- Do NOT include "in conclusion" or similar filler
`;
const readmeTaskPrompt = `
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)
Use the filesystem tools to explore the project and understand what it does:
1. First, use list_directory to see the project structure
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)
@@ -106,15 +82,19 @@ Then generate a comprehensive README following this template:
]
`;
const readmeResult = await readmeOrchestrator.run(readmeTaskPrompt);
await readmeOrchestrator.stop();
logger.log('info', 'Starting README generation with agent...');
if (!readmeResult.success) {
throw new Error(`README generation failed: ${readmeResult.status}`);
}
const readmeResult = await plugins.smartagent.runAgent({
model: this.aiDocsRef.model,
prompt: readmeTaskPrompt,
system: readmeSystemPrompt,
tools: fsTools,
maxSteps: 25,
onToolCall: (toolName) => logger.log('info', `[README] Tool call: ${toolName}`),
});
// Clean up markdown formatting if wrapped in code blocks
let resultMessage = readmeResult.result
let resultMessage = readmeResult.text
.replace(/^```markdown\n?/i, '')
.replace(/\n?```$/i, '');
@@ -142,40 +122,19 @@ Then generate a comprehensive README following this template:
.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.
const subModuleFsTools = plugins.smartagentTools.filesystemTool({ rootDir: subModulePath });
APPROVE tool calls for:
- Reading any files within the submodule directory
- Using tree to see structure
- Using glob to find source files
const subModuleSystemPrompt = `
You create markdown READMEs for npm projects. You only output the markdown readme.
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();
IMPORTANT RULES:
- Only READ files within the submodule directory
- Do NOT write, delete, or modify any files
- README must be comprehensive, well-formatted markdown with ESM TypeScript examples
- Do NOT include licensing information (added separately)
`;
const subModulePrompt = `
You create markdown READMEs for npm projects. You only output the markdown readme.
SUB MODULE: ${subModule}
SUB MODULE DIRECTORY: ${subModulePath}
@@ -183,8 +142,8 @@ 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
Use the filesystem tools to explore the submodule:
1. Use list_directory to see the submodule structure
2. Read package.json to understand the submodule
3. Read source files in ts/ directory to understand the implementation
@@ -208,21 +167,23 @@ Generate a README following the template:
Don't use \`\`\` at the beginning or end. Only for code blocks.
`;
const subModuleResult = await subModuleOrchestrator.run(subModulePrompt);
await subModuleOrchestrator.stop();
const subModuleResult = await plugins.smartagent.runAgent({
model: this.aiDocsRef.model,
prompt: subModulePrompt,
system: subModuleSystemPrompt,
tools: subModuleFsTools,
maxSteps: 20,
onToolCall: (toolName) => logger.log('info', `[README:${subModule}] Tool call: ${toolName}`),
});
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}`);
}
const subModuleReadmeString = subModuleResult.text
.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}`);
}
return resultMessage;