autoagent-cli
Version:
Run autonomous AI agents using Claude or Gemini for task execution
320 lines (319 loc) • 13.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateChange = validateChange;
exports.scoreChange = scoreChange;
exports.categorizeChanges = categorizeChanges;
exports.detectDependencies = detectDependencies;
exports.resolveConflicts = resolveConflicts;
exports.prioritizeChanges = prioritizeChanges;
exports.analyzeImprovements = analyzeImprovements;
exports.getAnalysisQualityScore = getAnalysisQualityScore;
const fs_1 = require("fs");
const path_1 = require("path");
const index_js_1 = require("../types/index.js");
function validateChange(change, issuesDir = './issues', plansDir = './plans') {
const errors = [];
const warnings = [];
if (change.type === undefined) {
errors.push('Change type is required');
}
if (!change.target) {
errors.push('Change target is required');
}
if (!change.description) {
errors.push('Change description is required');
}
if (!change.content) {
errors.push('Change content is required');
}
if (!change.rationale) {
warnings.push('Change rationale is missing - this helps understand why the change is needed');
}
if (change.type === index_js_1.ChangeType.MODIFY_ISSUE || change.type === index_js_1.ChangeType.MODIFY_PLAN) {
const isIssue = change.type === index_js_1.ChangeType.MODIFY_ISSUE;
const dir = isIssue ? issuesDir : plansDir;
const expectedPattern = isIssue ? /^\d+-.*\.md$/ : /^\d+-.*\.md$/;
if (!expectedPattern.test(change.target)) {
errors.push(`Invalid ${isIssue ? 'issue' : 'plan'} filename format: ${change.target}`);
}
else {
const targetPath = (0, path_1.join)(dir, change.target);
if (!(0, fs_1.existsSync)(targetPath)) {
errors.push(`Target file does not exist: ${targetPath}`);
}
}
}
if (change.type === index_js_1.ChangeType.ADD_ISSUE || change.type === index_js_1.ChangeType.ADD_PLAN) {
const isIssue = change.type === index_js_1.ChangeType.ADD_ISSUE;
const dir = isIssue ? issuesDir : plansDir;
const targetPath = (0, path_1.join)(dir, change.target);
if ((0, fs_1.existsSync)(targetPath)) {
errors.push(`File already exists: ${targetPath}`);
}
}
if (change.content.trim().length < 10) {
errors.push('Change content is too short (minimum 10 characters)');
}
if (change.content.length > 50000) {
warnings.push('Change content is very large (>50KB) - consider splitting into smaller changes');
}
return {
isValid: errors.length === 0,
errors,
warnings
};
}
function scoreChange(change) {
let impact = 0.5;
if (change.type === index_js_1.ChangeType.ADD_ISSUE || change.type === index_js_1.ChangeType.ADD_PLAN) {
impact = 0.8;
}
else if (change.type === index_js_1.ChangeType.ADD_DEPENDENCY) {
impact = 0.6;
}
const criticalKeywords = ['critical', 'essential', 'required', 'must', 'breaking'];
const hasCriticalKeyword = criticalKeywords.some(keyword => change.rationale.toLowerCase().includes(keyword));
if (hasCriticalKeyword) {
impact = Math.min(1.0, impact + 0.2);
}
let complexity = 0.3;
const contentLines = change.content.split('\n').length;
if (contentLines > 100) {
complexity = 0.7;
}
else if (contentLines > 50) {
complexity = 0.5;
}
if (change.type === index_js_1.ChangeType.MODIFY_ISSUE || change.type === index_js_1.ChangeType.MODIFY_PLAN) {
complexity = Math.min(1.0, complexity + 0.2);
}
let confidence = 0.7;
if (change.rationale.length > 100) {
confidence = 0.8;
}
if (change.rationale.length > 200) {
confidence = 0.9;
}
if (contentLines > 200) {
confidence = Math.max(0.3, confidence - 0.3);
}
const composite = (impact * 0.5) + ((1 - complexity) * 0.3) + (confidence * 0.2);
return {
impact,
complexity,
confidence,
composite
};
}
function categorizeChanges(changes) {
const categories = new Map();
for (const change of changes) {
const category = `${change.type}:${change.target.split('-')[0]}`;
if (!categories.has(category)) {
categories.set(category, []);
}
const categoryArray = categories.get(category);
if (categoryArray !== undefined) {
categoryArray.push(change);
}
}
return categories;
}
function detectDependencies(changes) {
const dependencies = [];
for (let i = 0; i < changes.length; i++) {
for (let j = i + 1; j < changes.length; j++) {
const change1 = changes[i];
const change2 = changes[j];
if (change1 && change2 && change1.type === index_js_1.ChangeType.ADD_DEPENDENCY) {
const mentionsTarget = change1.content.includes(change2.target) ||
change1.description.includes(change2.target);
const issueNumMatch = change2.target.match(/^(\d+)-/);
const mentionsIssueNum = issueNumMatch !== null && (change1.content.includes(`Issue ${issueNumMatch[1]}`) ||
change1.description.includes(`Issue ${issueNumMatch[1]}`));
if (mentionsTarget || mentionsIssueNum) {
dependencies.push({
from: change1,
to: change2,
type: 'requires',
reason: 'Dependency change references target'
});
}
}
if (change1 && change2 && change2.type === index_js_1.ChangeType.ADD_DEPENDENCY) {
const mentionsTarget = change2.content.includes(change1.target) ||
change2.description.includes(change1.target);
const issueNumMatch = change1.target.match(/^(\d+)-/);
const mentionsIssueNum = issueNumMatch !== null && (change2.content.includes(`Issue ${issueNumMatch[1]}`) ||
change2.description.includes(`Issue ${issueNumMatch[1]}`));
if (mentionsTarget || mentionsIssueNum) {
dependencies.push({
from: change2,
to: change1,
type: 'requires',
reason: 'Dependency change references target'
});
}
}
if (change1 && change2) {
if (change1.type === index_js_1.ChangeType.ADD_PLAN && change2.type === index_js_1.ChangeType.ADD_ISSUE) {
const issueNum = change2.target.match(/^(\d+)-/)?.[1];
if (issueNum !== undefined && change1.target.includes(`issue-${issueNum}`)) {
dependencies.push({
from: change1,
to: change2,
type: 'requires',
reason: 'Plan requires corresponding issue'
});
}
}
else if (change2.type === index_js_1.ChangeType.ADD_PLAN && change1.type === index_js_1.ChangeType.ADD_ISSUE) {
const issueNum = change1.target.match(/^(\d+)-/)?.[1];
if (issueNum !== undefined && change2.target.includes(`issue-${issueNum}`)) {
dependencies.push({
from: change2,
to: change1,
type: 'requires',
reason: 'Plan requires corresponding issue'
});
}
}
}
if (change1 && change2 && change1.target === change2.target &&
(change1.type === index_js_1.ChangeType.MODIFY_ISSUE || change1.type === index_js_1.ChangeType.MODIFY_PLAN) &&
(change2.type === index_js_1.ChangeType.MODIFY_ISSUE || change2.type === index_js_1.ChangeType.MODIFY_PLAN)) {
dependencies.push({
from: change1,
to: change2,
type: 'conflicts',
reason: 'Multiple modifications to the same file'
});
}
}
}
return dependencies;
}
function resolveConflicts(changes, dependencies) {
const conflicts = dependencies.filter(dep => dep.type === 'conflicts');
const conflictMap = new Map();
for (const conflict of conflicts) {
const key = conflict.from.target;
if (!conflictMap.has(key)) {
conflictMap.set(key, []);
}
const conflictList = conflictMap.get(key);
if (conflictList) {
conflictList.push(conflict.from, conflict.to);
}
}
const resolved = [];
const unresolvedConflicts = [];
const processedChanges = new Set();
for (const change of changes) {
if (processedChanges.has(change)) {
continue;
}
const conflictGroup = conflictMap.get(change.target);
if (!conflictGroup || conflictGroup.length === 0) {
resolved.push(change);
processedChanges.add(change);
}
else {
const uniqueConflicts = Array.from(new Set(conflictGroup));
const scored = uniqueConflicts.map(c => ({ change: c, score: scoreChange(c) }));
scored.sort((a, b) => b.score.composite - a.score.composite);
if (scored.length > 0 && scored[0] !== undefined) {
resolved.push(scored[0].change);
processedChanges.add(scored[0].change);
for (let i = 1; i < scored.length; i++) {
const currentItem = scored[i];
if (currentItem !== undefined && !processedChanges.has(currentItem.change)) {
unresolvedConflicts.push({
change1: scored[0].change,
change2: currentItem.change,
reason: 'Conflicting modifications to the same file - selected higher scoring change'
});
processedChanges.add(currentItem.change);
}
}
}
}
}
return { resolved, conflicts: unresolvedConflicts };
}
function prioritizeChanges(changes, dependencies) {
const scoredChanges = changes.map(change => ({
...change,
score: scoreChange(change),
dependencies: [],
conflicts: [],
priority: 0
}));
for (const dep of dependencies) {
const fromChange = scoredChanges.find(c => c.type === dep.from.type &&
c.target === dep.from.target &&
c.description === dep.from.description);
if (fromChange) {
if (dep.type === 'requires') {
fromChange.dependencies.push(dep.to.target);
}
else if (dep.type === 'conflicts') {
fromChange.conflicts.push(dep.to.target);
}
}
}
const visited = new Set();
const result = [];
let currentPriority = 1;
function visit(change) {
if (visited.has(change)) {
return;
}
visited.add(change);
for (const depTarget of change.dependencies) {
const dep = scoredChanges.find(c => c.target === depTarget);
if (dep && !visited.has(dep)) {
visit(dep);
}
}
change.priority = currentPriority++;
result.push(change);
}
scoredChanges.sort((a, b) => b.score.composite - a.score.composite);
for (const change of scoredChanges) {
visit(change);
}
return result;
}
function analyzeImprovements(analysis, issuesDir = './issues', plansDir = './plans') {
const validChanges = [];
const invalidChanges = [];
for (const change of analysis.changes) {
const validation = validateChange(change, issuesDir, plansDir);
if (validation.isValid) {
validChanges.push(change);
}
else {
invalidChanges.push({ change, errors: validation.errors });
}
}
const dependencies = detectDependencies(validChanges);
const { resolved, conflicts } = resolveConflicts(validChanges, dependencies);
const executionOrder = prioritizeChanges(resolved, dependencies);
return {
validChanges: executionOrder,
invalidChanges,
dependencies: dependencies.filter(d => d.type !== 'conflicts'),
conflicts,
executionOrder
};
}
function getAnalysisQualityScore(analysis) {
if (analysis.changes.length === 0) {
return 0.0;
}
const scores = analysis.changes.map(scoreChange);
const avgComposite = scores.reduce((sum, s) => sum + s.composite, 0) / scores.length;
const combinedScore = (analysis.score + avgComposite) / 2;
return Math.max(0.0, Math.min(1.0, combinedScore));
}