@git.zone/tsdoc
Version:
A comprehensive TypeScript documentation tool that leverages AI to generate and enhance project documentation, including dynamic README creation, API docs via TypeDoc, and smart commit message generation.
279 lines (255 loc) • 21.4 kB
JavaScript
import * as plugins from '../plugins.js';
import { AiDoc } from '../classes.aidoc.js';
import { ProjectContext } from './projectcontext.js';
import { DiffProcessor } from '../classes.diffprocessor.js';
import { logger } from '../logging.js';
// Token budget configuration for OpenAI API limits
const TOKEN_BUDGET = {
OPENAI_CONTEXT_LIMIT: 272000, // OpenAI's configured limit
SAFETY_MARGIN: 10000, // Buffer to avoid hitting exact limit
SMARTAGENT_OVERHEAD: 180000, // System msgs, tools, history, formatting
TASK_PROMPT_OVERHEAD: 2000, // Task prompt template size
};
/**
* Calculate max tokens available for diff content based on total budget
*/
function calculateMaxDiffTokens() {
const available = TOKEN_BUDGET.OPENAI_CONTEXT_LIMIT
- TOKEN_BUDGET.SAFETY_MARGIN
- TOKEN_BUDGET.SMARTAGENT_OVERHEAD
- TOKEN_BUDGET.TASK_PROMPT_OVERHEAD;
return Math.max(available, 30000);
}
export class Commit {
constructor(aiDocsRef, projectDirArg) {
this.aiDocsRef = aiDocsRef;
this.projectDir = projectDirArg;
}
async buildNextCommitObject() {
const smartgitInstance = new plugins.smartgit.Smartgit();
await smartgitInstance.init();
const gitRepo = await plugins.smartgit.GitRepo.fromOpeningRepoDir(smartgitInstance, this.projectDir);
// Define comprehensive exclusion patterns
// smartgit@3.3.0+ supports glob patterns natively
const excludePatterns = [
// Lock files
'pnpm-lock.yaml',
'package-lock.json',
'npm-shrinkwrap.json',
'yarn.lock',
'deno.lock',
'bun.lockb',
// Build artifacts (main culprit for large diffs!)
'dist/**',
'dist_*/**', // dist_ts, dist_web, etc.
'build/**',
'.next/**',
'out/**',
'public/dist/**',
// Compiled/bundled files
'**/*.js.map',
'**/*.d.ts.map',
'**/*.min.js',
'**/*.bundle.js',
'**/*.chunk.js',
// IDE/Editor directories
'.claude/**',
'.cursor/**',
'.vscode/**',
'.idea/**',
'**/*.swp',
'**/*.swo',
// Logs and caches
'.nogit/**',
'**/*.log',
'.cache/**',
'.rpt2_cache/**',
'coverage/**',
'.nyc_output/**',
];
// Pass glob patterns directly to smartgit - it handles matching internally
const diffStringArray = await gitRepo.getUncommittedDiff(excludePatterns);
// Process diffs intelligently using DiffProcessor
let processedDiffString;
if (diffStringArray.length > 0) {
// Diagnostic logging for raw diff statistics
const totalChars = diffStringArray.join('\n\n').length;
const estimatedTokens = Math.ceil(totalChars / 4);
console.log(`📊 Raw git diff statistics:`);
console.log(` Files changed: ${diffStringArray.length}`);
console.log(` Total characters: ${totalChars.toLocaleString()}`);
console.log(` Estimated tokens: ${estimatedTokens.toLocaleString()}`);
console.log(` Exclusion patterns: ${excludePatterns.length}`);
// 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()})`);
// Use DiffProcessor to intelligently handle large diffs
const diffProcessor = new DiffProcessor({
maxDiffTokens, // Dynamic based on total budget
smallFileLines: 300, // Most source files are under 300 lines
mediumFileLines: 800, // Only very large files get head/tail treatment
sampleHeadLines: 75, // When sampling, show more context
sampleTailLines: 75, // When sampling, show more context
});
const processedDiff = diffProcessor.processDiffs(diffStringArray);
processedDiffString = diffProcessor.formatForContext(processedDiff);
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()}`);
}
// Validate total tokens won't exceed limit
const totalEstimatedTokens = processedDiff.totalTokens
+ TOKEN_BUDGET.SMARTAGENT_OVERHEAD
+ 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(` 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.
APPROVE tool calls for:
- Reading package.json or source files to understand project context
- Using tree to see project structure
- Listing directory contents
REJECT tool calls for:
- Reading files outside the project directory
- Writing, deleting, or modifying any files
- Any destructive operations
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();
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
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.
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:
{
"recommendedNextVersionLevel": "fix" | "feat" | "BREAKING CHANGE",
"recommendedNextVersionScope": "string",
"recommendedNextVersionMessage": "string (ONLY the description body WITHOUT the type(scope): prefix - e.g. 'bump dependency to ^1.2.6' NOT 'fix(deps): bump dependency to ^1.2.6')",
"recommendedNextVersionDetails": ["string"],
"recommendedNextVersion": "x.x.x"
}
For recommendedNextVersionDetails, only add entries that have obvious value to the reader.
Here is the git diff showing what changed:
${processedDiffString}
Analyze these changes and output the JSON commit message object.
`;
const commitResult = await commitOrchestrator.run(commitTaskPrompt);
await commitOrchestrator.stop();
if (!commitResult.success) {
throw new Error(`Commit message generation failed: ${commitResult.status}`);
}
// Extract JSON from result - handle cases where AI adds text around it
let jsonString = commitResult.result
.replace(/```json\n?/gi, '')
.replace(/```\n?/gi, '');
// Try to find JSON object in the result
const jsonMatch = jsonString.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
throw new Error(`Could not find JSON object in result: ${jsonString.substring(0, 100)}...`);
}
jsonString = jsonMatch[0];
const resultObject = JSON.parse(jsonString);
const previousChangelogPath = plugins.path.join(this.projectDir, 'changelog.md');
let previousChangelog;
if (await plugins.fsInstance.file(previousChangelogPath).exists()) {
previousChangelog = await plugins.smartfileFactory.fromFilePath(previousChangelogPath);
}
if (!previousChangelog) {
// lets build the changelog based on that
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.
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
- 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();
const changelogTaskPrompt = `
You are building a changelog.md file for the project.
Omit commits and versions that lack relevant changes, but make sure to mention them as a range with a summarizing message instead.
A changelog entry should look like this:
## yyyy-mm-dd - x.x.x - scope here
main descriptiom here
- detailed bullet points follow
You are given:
* the commit messages of the project
Only return the changelog file content, so it can be written directly to changelog.md.
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}`);
}
previousChangelog = plugins.smartfileFactory.fromString(previousChangelogPath, changelogResult.result.replaceAll('```markdown', '').replaceAll('```', ''), 'utf8');
}
let oldChangelog = previousChangelog.contents.toString().replace('# Changelog\n\n', '');
if (oldChangelog.startsWith('\n')) {
oldChangelog = oldChangelog.replace('\n', '');
}
let newDateString = new plugins.smarttime.ExtendedDate().exportToHyphedSortableDate();
let newChangelog = `# Changelog\n\n${`## ${newDateString} - {{nextVersion}} - {{nextVersionScope}}
{{nextVersionMessage}}
{{nextVersionDetails}}`}\n\n${oldChangelog}`;
resultObject.changelog = newChangelog;
return resultObject;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tbWl0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvYWlkb2NzX2NsYXNzZXMvY29tbWl0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUM1QyxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDckQsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQzVELE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFFdkMsbURBQW1EO0FBQ25ELE1BQU0sWUFBWSxHQUFHO0lBQ25CLG9CQUFvQixFQUFFLE1BQU0sRUFBSSw0QkFBNEI7SUFDNUQsYUFBYSxFQUFFLEtBQUssRUFBYSxzQ0FBc0M7SUFDdkUsbUJBQW1CLEVBQUUsTUFBTSxFQUFNLDBDQUEwQztJQUMzRSxvQkFBb0IsRUFBRSxJQUFJLEVBQU8sNEJBQTRCO0NBQ3JELENBQUM7QUFFWDs7R0FFRztBQUNILFNBQVMsc0JBQXNCO0lBQzdCLE1BQU0sU0FBUyxHQUFHLFlBQVksQ0FBQyxvQkFBb0I7VUFDL0MsWUFBWSxDQUFDLGFBQWE7VUFDMUIsWUFBWSxDQUFDLG1CQUFtQjtVQUNoQyxZQUFZLENBQUMsb0JBQW9CLENBQUM7SUFDdEMsT0FBTyxJQUFJLENBQUMsR0FBRyxDQUFDLFNBQVMsRUFBRSxLQUFLLENBQUMsQ0FBQztBQUNwQyxDQUFDO0FBV0QsTUFBTSxPQUFPLE1BQU07SUFJakIsWUFBWSxTQUFnQixFQUFFLGFBQXFCO1FBQ2pELElBQUksQ0FBQyxTQUFTLEdBQUcsU0FBUyxDQUFDO1FBQzNCLElBQUksQ0FBQyxVQUFVLEdBQUcsYUFBYSxDQUFDO0lBQ2xDLENBQUM7SUFFTSxLQUFLLENBQUMscUJBQXFCO1FBQ2hDLE1BQU0sZ0JBQWdCLEdBQUcsSUFBSSxPQUFPLENB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