@every-env/cli
Version:
Multi-agent orchestrator for AI-powered development workflows
187 lines ⢠7.59 kB
JavaScript
import { FilePatternMatcher } from './file-pattern.js';
import { ContentPatternMatcher } from './content-pattern.js';
import { AgentManager } from '../core/agent-manager.js';
import { VariableResolver } from '../templates/variable-resolver.js';
import { logger } from '../utils/logger.js';
import { access } from 'fs/promises';
import chalk from 'chalk';
export class CommandPatternExecutor {
fileMatcher = new FilePatternMatcher();
contentMatcher = new ContentPatternMatcher();
variableResolver = new VariableResolver();
async executePatternsForCommand(patterns, context, options = {}) {
const tasks = [];
for (const pattern of patterns) {
const patternTasks = await this.preparePattern(pattern, context, options);
tasks.push(...patternTasks);
}
if (options.dryRun) {
this.printDryRun(tasks, context.command);
return [];
}
const manager = new AgentManager(options.maxAgents || 5);
return manager.runBatch(tasks);
}
async prepareTasksForCommand(patterns, context, options = {}) {
const tasks = [];
for (const pattern of patterns) {
const patternTasks = await this.preparePattern(pattern, context, options);
tasks.push(...patternTasks);
}
return tasks;
}
async preparePattern(pattern, context, options) {
const tasks = [];
// Get matches if pattern has matching rules
const matches = pattern.match
? await this.getMatches(pattern, options)
: [null]; // Single execution if no match rules
// Create tasks for each agent
for (const agent of pattern.agents) {
if (agent.forEach === 'match' && matches.length > 1) {
// Create task for each match
for (let i = 0; i < matches.length; i++) {
const match = matches[i];
const task = await this.createAgentTask(pattern, agent, context, {
match,
matchIndex: i,
matchTotal: matches.length,
}, options);
if (task)
tasks.push(task);
}
}
else {
// Single task for this agent
const task = await this.createAgentTask(pattern, agent, context, {}, options);
if (task)
tasks.push(task);
}
}
return tasks;
}
async getMatches(pattern, options) {
if (!pattern.match)
return [];
const matchConfigs = Array.isArray(pattern.match)
? pattern.match
: [pattern.match];
const allMatches = [];
for (const matchConfig of matchConfigs) {
// Get file matches
const fileMatches = await this.fileMatcher.match(matchConfig);
if ('content' in matchConfig && matchConfig.content) {
// Extract content from matched files
const contentMatches = await this.contentMatcher.match(matchConfig.content, fileMatches.map(f => f.path));
// Filter by --only option
const filtered = options.only
? contentMatches.filter(m => options.only.includes(m.match))
: contentMatches;
allMatches.push(...filtered.map(m => m.match));
}
else {
// Use file matches directly
allMatches.push(...fileMatches);
}
}
return allMatches;
}
async createAgentTask(pattern, agent, context, matchContext, options) {
// Check runIf conditions
if (agent.runIf && !await this.checkRunConditions(agent.runIf)) {
logger.debug(`Skipping agent ${agent.id} due to runIf conditions`);
return null;
}
// Determine output path
const outputPath = this.resolveOutputPath(agent, matchContext.match);
// Merge variables from all sources
const mergedVariables = {
...context.globalVariables,
...context.commandVariables,
...pattern.variables,
command: context.command,
};
// Create a temporary config-like object for variable resolution
const tempConfig = {
...context.config,
variables: mergedVariables,
defaultCommand: context.config?.defaultCommand || 'claude',
defaultAllowedTools: context.config?.defaultAllowedTools || [],
parallelism: context.config?.parallelism || { maxAgents: 5, batchSize: 5 },
};
// Resolve variables
const variables = await this.variableResolver.resolve(tempConfig, pattern, agent, {
outputPath,
matchItem: matchContext.match,
matchIndex: matchContext.matchIndex,
matchTotal: matchContext.matchTotal,
});
// Determine essential tools
const essentialTools = options.essentialTools || [
'Read', 'Write', 'Edit', 'MultiEdit', 'Glob', 'Grep', 'LS'
];
const mergedAllowedTools = [
...essentialTools,
...(context.config?.defaultAllowedTools || []),
...(agent.allowedTools || [])
];
// Remove duplicates
const uniqueAllowedTools = [...new Set(mergedAllowedTools)];
const agentWithDefaults = {
...agent,
command: agent.command || context.config?.defaultCommand || 'claude',
allowedTools: uniqueAllowedTools
};
logger.debug(`Agent ${agent.id} allowedTools:`, {
essentialTools,
defaultAllowedTools: context.config?.defaultAllowedTools,
agentAllowedTools: agent.allowedTools,
mergedAllowedTools: agentWithDefaults.allowedTools
});
return {
pattern,
agent: agentWithDefaults,
variables,
outputPath,
};
}
resolveOutputPath(agent, match) {
if (agent.output) {
return agent.output;
}
if (agent.outputPattern && match) {
// Replace {name} with match value
const name = typeof match === 'string' ? match : match.name || match.match;
return agent.outputPattern.replace(/{name}/g, name);
}
throw new Error(`No output path specified for agent ${agent.id}`);
}
async checkRunConditions(conditions) {
if (conditions.filesExist) {
// Check if all required files exist
for (const file of conditions.filesExist) {
try {
await access(file);
}
catch {
return false;
}
}
}
return true;
}
printDryRun(tasks, command) {
console.log(chalk.yellow(`\nš Dry run for '${command}' command - would execute:\n`));
for (const task of tasks) {
console.log(` Agent: ${chalk.bold(task.agent.id)}`);
console.log(` Output: ${task.outputPath}`);
console.log(` Prompt: ${task.agent.promptFile || '(inline prompt)'}`);
if (task.variables.name) {
console.log(` Target: ${task.variables.name}`);
}
console.log();
}
console.log(chalk.gray(`Total: ${tasks.length} agents\n`));
}
}
//# sourceMappingURL=command-pattern-executor.js.map