@every-env/cli
Version:
Multi-agent orchestrator for AI-powered development workflows
196 lines • 7.96 kB
JavaScript
import { FilePatternMatcher } from './file-pattern.js';
import { ContentPatternMatcher } from './content-pattern.js';
import { AgentManager } from '../core/agent-manager.js';
import { mkdir } from 'fs/promises';
import { dirname } from 'path';
import { logger } from '../utils/logger.js';
const ESSENTIAL_TOOLS = ['Read', 'Write', 'Edit', 'MultiEdit', 'Glob', 'Grep', 'LS'];
export class PatternManager {
fileMatcher = new FilePatternMatcher();
contentMatcher = new ContentPatternMatcher();
async executePatterns(patterns, config, options = {}) {
const { dryRun = false, maxAgents = config.parallelism?.maxAgents || 5 } = options;
logger.debug('Executing patterns', {
patternCount: patterns.length,
patternNames: patterns.map(p => p.name),
maxAgents
});
const tasks = await this.prepareTasks(patterns, config);
logger.debug(`Prepared ${tasks.length} tasks from ${patterns.length} patterns`);
if (dryRun) {
this.printDryRun(tasks);
return [];
}
const agentManager = new AgentManager(maxAgents);
return agentManager.runBatch(tasks);
}
async prepareTasks(patterns, config) {
const tasks = [];
logger.debug('Preparing tasks for patterns', { patternCount: patterns.length });
for (const pattern of patterns) {
logger.debug(`Preparing tasks for pattern: ${pattern.name}`);
const patternTasks = await this.preparePatternTasks(pattern, config);
logger.debug(`Pattern ${pattern.name} generated ${patternTasks.length} tasks`);
tasks.push(...patternTasks);
}
logger.debug(`Total tasks prepared: ${tasks.length}`);
return tasks;
}
async preparePatternTasks(pattern, config) {
const tasks = [];
// Resolve pattern variables
const patternVariables = {
...config.variables,
...pattern.variables,
};
logger.debug(`Pattern ${pattern.name} has ${pattern.agents.length} agents`);
for (const agent of pattern.agents) {
if (agent.forEach === 'match') {
// Execute for each file match
const matchedFiles = await this.getMatchedFiles(pattern, config);
for (let i = 0; i < matchedFiles.length; i++) {
const file = matchedFiles[i];
const variables = {
...patternVariables,
file,
name: file,
index: i,
total: matchedFiles.length,
};
const task = await this.createTask(agent, variables, config, pattern);
tasks.push(task);
}
}
else if (agent.forEach === 'content') {
// Execute for each content match
const contentMatches = await this.getContentMatches(pattern, config);
for (let i = 0; i < contentMatches.length; i++) {
const match = contentMatches[i];
const variables = {
...patternVariables,
...match,
name: match.match,
index: i,
total: contentMatches.length,
};
const task = await this.createTask(agent, variables, config, pattern);
tasks.push(task);
}
}
else {
// Single execution
const task = await this.createTask(agent, patternVariables, config, pattern);
tasks.push(task);
}
}
return tasks;
}
async createTask(agent, variables, config, pattern) {
logger.debug(`Creating task for agent ${agent.id}`, {
pattern: pattern.name,
hasOutput: !!agent.output,
variableCount: Object.keys(variables).length
});
// Merge allowed tools
const allowedTools = [
...ESSENTIAL_TOOLS,
...(config.defaultAllowedTools || []),
...(agent.allowedTools || []),
];
// Resolve output path with template variables
let outputPath;
if (agent.output) {
// Simple template replacement for output paths
outputPath = agent.output;
for (const [key, value] of Object.entries(variables)) {
if (typeof value === 'string' || typeof value === 'number') {
outputPath = outputPath.replace(new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'g'), String(value));
}
}
}
// Ensure output directory exists
if (outputPath && outputPath !== '') {
logger.debug(`Creating output directory for ${outputPath}`);
await mkdir(dirname(outputPath), { recursive: true });
}
return {
agent: {
...agent,
command: agent.command || config.defaultCommand || 'claude',
allowedTools: [...new Set(allowedTools)],
},
outputPath: outputPath || '',
variables,
pattern,
};
}
async getMatchedFiles(pattern, _config) {
if (!pattern.match)
return [];
// Handle union type for match
const match = Array.isArray(pattern.match) ? pattern.match[0] : pattern.match;
if (!match || !('files' in match))
return [];
const results = await this.fileMatcher.match(match, process.cwd());
return results.map(r => typeof r === 'string' ? r : r.path);
}
async getContentMatches(pattern, _config) {
if (!pattern.match)
return [];
// Handle union type for match
const match = Array.isArray(pattern.match) ? pattern.match[0] : pattern.match;
if (!match || !('content' in match) || !match.content)
return [];
const files = await this.getMatchedFiles(pattern, _config);
return this.contentMatcher.match(match.content, files);
}
printDryRun(tasks) {
console.log(`\nDry run - ${tasks.length} agent tasks would be executed:\n`);
for (const task of tasks) {
console.log(` Agent: ${task.agent.id}`);
if (task.agent.promptFile) {
console.log(` Prompt: ${task.agent.promptFile}`);
}
else {
console.log(` Prompt: (inline prompt)`);
}
if (task.outputPath) {
console.log(` Output: ${task.outputPath}`);
}
if (task.variables.name) {
console.log(` Target: ${String(task.variables.name)}`);
}
console.log('');
}
console.log(`Total: ${tasks.length} agents\n`);
}
validatePattern(pattern) {
const errors = [];
if (!pattern.name) {
errors.push('Pattern must have a name');
}
if (!pattern.agents || pattern.agents.length === 0) {
errors.push('Pattern must have agents');
return errors;
}
for (let i = 0; i < pattern.agents.length; i++) {
const agent = pattern.agents[i];
if (!agent.id) {
errors.push(`Agent at index ${i} must have an id`);
}
if (!agent.promptFile) {
errors.push(`Agent ${agent.id || i} must have promptFile`);
}
}
return errors;
}
findPatterns(config, names) {
if (!config.patterns)
return [];
if (names.length === 0) {
return config.patterns;
}
return config.patterns.filter(p => names.includes(p.name));
}
}
//# sourceMappingURL=pattern-manager-old.js.map