182 lines
5.2 KiB
JavaScript
182 lines
5.2 KiB
JavaScript
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const yaml = require('yaml');
|
|
const { glob } = require('glob');
|
|
|
|
/**
|
|
* Team Loader
|
|
*
|
|
* Discovers and loads team bundles from module definitions.
|
|
* Teams are predefined collections of agents and workflows for common use cases.
|
|
*/
|
|
|
|
/**
|
|
* Discover all available teams across modules
|
|
* @param {string} projectRoot - Project root directory
|
|
* @returns {Promise<Object[]>} Array of team metadata { name, module, path, description }
|
|
*/
|
|
async function discoverTeams(projectRoot) {
|
|
const teams = [];
|
|
const pattern = path.join(projectRoot, 'src/modules/*/teams/team-*.yaml');
|
|
|
|
try {
|
|
const files = await glob(pattern, { absolute: true });
|
|
|
|
for (const filePath of files) {
|
|
try {
|
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
const teamData = yaml.parse(content);
|
|
|
|
// Extract team name from filename (team-fullstack.yaml -> fullstack)
|
|
const filename = path.basename(filePath);
|
|
const teamName = filename.replace(/^team-/, '').replace(/\.yaml$/, '');
|
|
|
|
// Extract module name from path
|
|
const moduleName = path.basename(path.dirname(path.dirname(filePath)));
|
|
|
|
teams.push({
|
|
name: teamName,
|
|
module: moduleName,
|
|
path: filePath,
|
|
description: teamData.bundle?.description || 'No description',
|
|
bundleName: teamData.bundle?.name || teamName,
|
|
icon: teamData.bundle?.icon || '👥',
|
|
});
|
|
} catch (error) {
|
|
// Skip files that can't be parsed
|
|
console.warn(`Warning: Could not parse team file ${filePath}: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
return teams;
|
|
} catch (error) {
|
|
// If glob fails, return empty array
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load a specific team by name
|
|
* @param {string} teamName - Team name (e.g., 'fullstack', 'gamedev')
|
|
* @param {string} projectRoot - Project root directory
|
|
* @returns {Promise<Object>} Team data with metadata
|
|
*/
|
|
async function loadTeam(teamName, projectRoot) {
|
|
if (!teamName) {
|
|
throw new Error('Team name is required');
|
|
}
|
|
|
|
// Discover all teams
|
|
const teams = await discoverTeams(projectRoot);
|
|
|
|
// Find matching team
|
|
const team = teams.find((t) => t.name.toLowerCase() === teamName.toLowerCase());
|
|
|
|
if (!team) {
|
|
// Provide helpful error with suggestions
|
|
const availableTeams = teams.map((t) => t.name).join(', ');
|
|
throw new Error(
|
|
`Team '${teamName}' not found. Available teams: ${availableTeams || 'none'}`,
|
|
);
|
|
}
|
|
|
|
// Load full team definition
|
|
const content = fs.readFileSync(team.path, 'utf8');
|
|
const teamData = yaml.parse(content);
|
|
|
|
return {
|
|
name: team.name,
|
|
module: team.module,
|
|
description: team.description,
|
|
bundleName: team.bundleName,
|
|
icon: team.icon,
|
|
agents: teamData.agents || [],
|
|
workflows: teamData.workflows || [],
|
|
party: teamData.party,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Expand team definition to full agents and workflows list
|
|
* @param {string} teamName - Team name
|
|
* @param {string} projectRoot - Project root directory
|
|
* @returns {Promise<Object>} { agents: [], workflows: [], module: string }
|
|
*/
|
|
async function expandTeam(teamName, projectRoot) {
|
|
const team = await loadTeam(teamName, projectRoot);
|
|
|
|
return {
|
|
agents: team.agents || [],
|
|
workflows: team.workflows || [],
|
|
module: team.module,
|
|
description: team.description,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Apply modifiers to team selections (additive/subtractive)
|
|
* @param {Object} team - Team expansion result
|
|
* @param {string[]} agentModifiers - Agent modifiers (+agent, -agent)
|
|
* @param {string[]} workflowModifiers - Workflow modifiers (+workflow, -workflow)
|
|
* @returns {Object} Modified team with updated agents/workflows
|
|
*/
|
|
function applyTeamModifiers(team, agentModifiers = [], workflowModifiers = []) {
|
|
const result = {
|
|
...team,
|
|
agents: [...team.agents],
|
|
workflows: [...team.workflows],
|
|
};
|
|
|
|
// Parse and apply agent modifiers
|
|
for (const modifier of agentModifiers) {
|
|
if (modifier.startsWith('+')) {
|
|
const agent = modifier.substring(1);
|
|
if (!result.agents.includes(agent)) {
|
|
result.agents.push(agent);
|
|
}
|
|
} else if (modifier.startsWith('-')) {
|
|
const agent = modifier.substring(1);
|
|
result.agents = result.agents.filter((a) => a !== agent);
|
|
}
|
|
}
|
|
|
|
// Parse and apply workflow modifiers
|
|
for (const modifier of workflowModifiers) {
|
|
if (modifier.startsWith('+')) {
|
|
const workflow = modifier.substring(1);
|
|
if (!result.workflows.includes(workflow)) {
|
|
result.workflows.push(workflow);
|
|
}
|
|
} else if (modifier.startsWith('-')) {
|
|
const workflow = modifier.substring(1);
|
|
result.workflows = result.workflows.filter((w) => w !== workflow);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get team descriptions for help text
|
|
* @param {string} projectRoot - Project root directory
|
|
* @returns {Promise<Object>} Map of team name to description
|
|
*/
|
|
async function getTeamDescriptions(projectRoot) {
|
|
const teams = await discoverTeams(projectRoot);
|
|
const descriptions = {};
|
|
|
|
for (const team of teams) {
|
|
descriptions[team.name] = team.description;
|
|
}
|
|
|
|
return descriptions;
|
|
}
|
|
|
|
module.exports = {
|
|
discoverTeams,
|
|
loadTeam,
|
|
expandTeam,
|
|
applyTeamModifiers,
|
|
getTeamDescriptions,
|
|
};
|