722 lines
23 KiB
JavaScript
722 lines
23 KiB
JavaScript
const fs = require('fs-extra');
|
|
const path = require('node:path');
|
|
const glob = require('glob');
|
|
const chalk = require('chalk');
|
|
const yaml = require('js-yaml');
|
|
|
|
/**
|
|
* Dependency Resolver for BMAD modules
|
|
* Handles cross-module dependencies and ensures all required files are included
|
|
*/
|
|
class DependencyResolver {
|
|
constructor() {
|
|
this.dependencies = new Map();
|
|
this.resolvedFiles = new Set();
|
|
this.missingDependencies = new Set();
|
|
}
|
|
|
|
/**
|
|
* Resolve all dependencies for selected modules
|
|
* @param {string} bmadDir - BMAD installation directory
|
|
* @param {Array} selectedModules - Modules explicitly selected by user
|
|
* @param {Object} options - Resolution options
|
|
* @returns {Object} Resolution results with all required files
|
|
*/
|
|
async resolve(bmadDir, selectedModules = [], options = {}) {
|
|
if (options.verbose) {
|
|
console.log(chalk.cyan('Resolving module dependencies...'));
|
|
}
|
|
|
|
// Always include core as base
|
|
const modulesToProcess = new Set(['core', ...selectedModules]);
|
|
|
|
// First pass: collect all explicitly selected files
|
|
const primaryFiles = await this.collectPrimaryFiles(bmadDir, modulesToProcess);
|
|
|
|
// Second pass: parse and resolve dependencies
|
|
const allDependencies = await this.parseDependencies(primaryFiles);
|
|
|
|
// Third pass: resolve dependency paths and collect files
|
|
const resolvedDeps = await this.resolveDependencyPaths(bmadDir, allDependencies);
|
|
|
|
// Fourth pass: check for transitive dependencies
|
|
const transitiveDeps = await this.resolveTransitiveDependencies(bmadDir, resolvedDeps);
|
|
|
|
// Combine all files
|
|
const allFiles = new Set([...primaryFiles.map((f) => f.path), ...resolvedDeps, ...transitiveDeps]);
|
|
|
|
// Organize by module
|
|
const organizedFiles = this.organizeByModule(bmadDir, allFiles);
|
|
|
|
// Report results (only in verbose mode)
|
|
if (options.verbose) {
|
|
this.reportResults(organizedFiles, selectedModules);
|
|
}
|
|
|
|
return {
|
|
primaryFiles,
|
|
dependencies: resolvedDeps,
|
|
transitiveDependencies: transitiveDeps,
|
|
allFiles: [...allFiles],
|
|
byModule: organizedFiles,
|
|
missing: [...this.missingDependencies],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Collect primary files from selected modules
|
|
*/
|
|
async collectPrimaryFiles(bmadDir, modules) {
|
|
const files = [];
|
|
|
|
for (const module of modules) {
|
|
// Handle both source (src/) and installed (bmad/) directory structures
|
|
let moduleDir;
|
|
|
|
// Check if this is a source directory (has 'src' subdirectory)
|
|
const srcDir = path.join(bmadDir, 'src');
|
|
if (await fs.pathExists(srcDir)) {
|
|
// Source directory structure: src/core or src/modules/xxx
|
|
moduleDir = module === 'core' ? path.join(srcDir, 'core') : path.join(srcDir, 'modules', module);
|
|
} else {
|
|
// Installed directory structure: bmad/core or bmad/modules/xxx
|
|
moduleDir = module === 'core' ? path.join(bmadDir, 'core') : path.join(bmadDir, 'modules', module);
|
|
}
|
|
|
|
if (!(await fs.pathExists(moduleDir))) {
|
|
console.warn(chalk.yellow(`Module directory not found: ${moduleDir}`));
|
|
continue;
|
|
}
|
|
|
|
// Collect agents
|
|
const agentsDir = path.join(moduleDir, 'agents');
|
|
if (await fs.pathExists(agentsDir)) {
|
|
const agentFiles = await glob.glob('*.md', { cwd: agentsDir });
|
|
for (const file of agentFiles) {
|
|
const agentPath = path.join(agentsDir, file);
|
|
|
|
// Check for localskip attribute
|
|
const content = await fs.readFile(agentPath, 'utf8');
|
|
const hasLocalSkip = content.match(/<agent[^>]*\slocalskip="true"[^>]*>/);
|
|
if (hasLocalSkip) {
|
|
continue; // Skip agents marked for web-only
|
|
}
|
|
|
|
files.push({
|
|
path: agentPath,
|
|
type: 'agent',
|
|
module,
|
|
name: path.basename(file, '.md'),
|
|
});
|
|
}
|
|
}
|
|
|
|
// Collect tasks
|
|
const tasksDir = path.join(moduleDir, 'tasks');
|
|
if (await fs.pathExists(tasksDir)) {
|
|
const taskFiles = await glob.glob('*.md', { cwd: tasksDir });
|
|
for (const file of taskFiles) {
|
|
files.push({
|
|
path: path.join(tasksDir, file),
|
|
type: 'task',
|
|
module,
|
|
name: path.basename(file, '.md'),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
/**
|
|
* Parse dependencies from file content
|
|
*/
|
|
async parseDependencies(files) {
|
|
const allDeps = new Set();
|
|
|
|
for (const file of files) {
|
|
const content = await fs.readFile(file.path, 'utf8');
|
|
|
|
// Parse YAML frontmatter for explicit dependencies
|
|
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
if (frontmatterMatch) {
|
|
try {
|
|
// Pre-process to handle backticks in YAML values
|
|
let yamlContent = frontmatterMatch[1];
|
|
// Quote values with backticks to make them valid YAML
|
|
yamlContent = yamlContent.replaceAll(/: `([^`]+)`/g, ': "$1"');
|
|
|
|
const frontmatter = yaml.load(yamlContent);
|
|
if (frontmatter.dependencies) {
|
|
const deps = Array.isArray(frontmatter.dependencies) ? frontmatter.dependencies : [frontmatter.dependencies];
|
|
|
|
for (const dep of deps) {
|
|
allDeps.add({
|
|
from: file.path,
|
|
dependency: dep,
|
|
type: 'explicit',
|
|
});
|
|
}
|
|
}
|
|
|
|
// Check for template dependencies
|
|
if (frontmatter.template) {
|
|
const templates = Array.isArray(frontmatter.template) ? frontmatter.template : [frontmatter.template];
|
|
for (const template of templates) {
|
|
allDeps.add({
|
|
from: file.path,
|
|
dependency: template,
|
|
type: 'template',
|
|
});
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.warn(chalk.yellow(`Failed to parse frontmatter in ${file.name}: ${error.message}`));
|
|
}
|
|
}
|
|
|
|
// Parse content for command references (cross-module dependencies)
|
|
const commandRefs = this.parseCommandReferences(content);
|
|
for (const ref of commandRefs) {
|
|
allDeps.add({
|
|
from: file.path,
|
|
dependency: ref,
|
|
type: 'command',
|
|
});
|
|
}
|
|
|
|
// Parse for file path references
|
|
const fileRefs = this.parseFileReferences(content);
|
|
for (const ref of fileRefs) {
|
|
// Determine type based on path format
|
|
// Paths starting with bmad/ are absolute references to the bmad installation
|
|
const depType = ref.startsWith('bmad/') ? 'bmad-path' : 'file';
|
|
allDeps.add({
|
|
from: file.path,
|
|
dependency: ref,
|
|
type: depType,
|
|
});
|
|
}
|
|
}
|
|
|
|
return allDeps;
|
|
}
|
|
|
|
/**
|
|
* Parse command references from content
|
|
*/
|
|
parseCommandReferences(content) {
|
|
const refs = new Set();
|
|
|
|
// Match @task-{name} or @agent-{name} or @{module}-{type}-{name}
|
|
const commandPattern = /@(task-|agent-|bmad-)([a-z0-9-]+)/g;
|
|
let match;
|
|
|
|
while ((match = commandPattern.exec(content)) !== null) {
|
|
refs.add(match[0]);
|
|
}
|
|
|
|
// Match file paths like bmad/core/agents/analyst
|
|
const pathPattern = /bmad\/(core|bmm|cis)\/(agents|tasks)\/([a-z0-9-]+)/g;
|
|
|
|
while ((match = pathPattern.exec(content)) !== null) {
|
|
refs.add(match[0]);
|
|
}
|
|
|
|
return [...refs];
|
|
}
|
|
|
|
/**
|
|
* Parse file path references from content
|
|
*/
|
|
parseFileReferences(content) {
|
|
const refs = new Set();
|
|
|
|
// Match relative paths like ../templates/file.yaml or ./data/file.md
|
|
const relativePattern = /['"](\.\.?\/[^'"]+\.(md|yaml|yml|xml|json|txt|csv))['"]/g;
|
|
let match;
|
|
|
|
while ((match = relativePattern.exec(content)) !== null) {
|
|
refs.add(match[1]);
|
|
}
|
|
|
|
// Parse exec attributes in command tags
|
|
const execPattern = /exec="([^"]+)"/g;
|
|
while ((match = execPattern.exec(content)) !== null) {
|
|
let execPath = match[1];
|
|
if (execPath && execPath !== '*') {
|
|
// Remove {project-root} prefix to get the actual path
|
|
// Usage is like {project-root}/bmad/core/tasks/foo.md
|
|
if (execPath.includes('{project-root}')) {
|
|
execPath = execPath.replace('{project-root}', '');
|
|
}
|
|
refs.add(execPath);
|
|
}
|
|
}
|
|
|
|
// Parse tmpl attributes in command tags
|
|
const tmplPattern = /tmpl="([^"]+)"/g;
|
|
while ((match = tmplPattern.exec(content)) !== null) {
|
|
let tmplPath = match[1];
|
|
if (tmplPath && tmplPath !== '*') {
|
|
// Remove {project-root} prefix to get the actual path
|
|
// Usage is like {project-root}/bmad/core/tasks/foo.md
|
|
if (tmplPath.includes('{project-root}')) {
|
|
tmplPath = tmplPath.replace('{project-root}', '');
|
|
}
|
|
refs.add(tmplPath);
|
|
}
|
|
}
|
|
|
|
return [...refs];
|
|
}
|
|
|
|
/**
|
|
* Resolve dependency paths to actual files
|
|
*/
|
|
async resolveDependencyPaths(bmadDir, dependencies) {
|
|
const resolved = new Set();
|
|
|
|
for (const dep of dependencies) {
|
|
const resolvedPaths = await this.resolveSingleDependency(bmadDir, dep);
|
|
for (const path of resolvedPaths) {
|
|
resolved.add(path);
|
|
}
|
|
}
|
|
|
|
return resolved;
|
|
}
|
|
|
|
/**
|
|
* Resolve a single dependency to file paths
|
|
*/
|
|
async resolveSingleDependency(bmadDir, dep) {
|
|
const paths = [];
|
|
|
|
switch (dep.type) {
|
|
case 'explicit':
|
|
case 'file': {
|
|
let depPath = dep.dependency;
|
|
|
|
// Handle {project-root} prefix if present
|
|
if (depPath.includes('{project-root}')) {
|
|
// Remove {project-root} and resolve as bmad path
|
|
depPath = depPath.replace('{project-root}', '');
|
|
|
|
if (depPath.startsWith('bmad/')) {
|
|
const bmadPath = depPath.replace(/^bmad\//, '');
|
|
|
|
// Handle glob patterns
|
|
if (depPath.includes('*')) {
|
|
// Extract the base path and pattern
|
|
const pathParts = bmadPath.split('/');
|
|
const module = pathParts[0];
|
|
const filePattern = pathParts.at(-1);
|
|
const middlePath = pathParts.slice(1, -1).join('/');
|
|
|
|
let basePath;
|
|
if (module === 'core') {
|
|
basePath = path.join(bmadDir, 'core', middlePath);
|
|
} else {
|
|
basePath = path.join(bmadDir, 'modules', module, middlePath);
|
|
}
|
|
|
|
if (await fs.pathExists(basePath)) {
|
|
const files = await glob.glob(filePattern, { cwd: basePath });
|
|
for (const file of files) {
|
|
paths.push(path.join(basePath, file));
|
|
}
|
|
}
|
|
} else {
|
|
// Direct path
|
|
if (bmadPath.startsWith('core/')) {
|
|
const corePath = path.join(bmadDir, bmadPath);
|
|
if (await fs.pathExists(corePath)) {
|
|
paths.push(corePath);
|
|
}
|
|
} else {
|
|
const parts = bmadPath.split('/');
|
|
const module = parts[0];
|
|
const rest = parts.slice(1).join('/');
|
|
const modulePath = path.join(bmadDir, 'modules', module, rest);
|
|
|
|
if (await fs.pathExists(modulePath)) {
|
|
paths.push(modulePath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Regular relative path handling
|
|
const sourceDir = path.dirname(dep.from);
|
|
|
|
// Handle glob patterns
|
|
if (depPath.includes('*')) {
|
|
const basePath = path.resolve(sourceDir, path.dirname(depPath));
|
|
const pattern = path.basename(depPath);
|
|
|
|
if (await fs.pathExists(basePath)) {
|
|
const files = await glob.glob(pattern, { cwd: basePath });
|
|
for (const file of files) {
|
|
paths.push(path.join(basePath, file));
|
|
}
|
|
}
|
|
} else {
|
|
// Direct file reference
|
|
const fullPath = path.resolve(sourceDir, depPath);
|
|
if (await fs.pathExists(fullPath)) {
|
|
paths.push(fullPath);
|
|
} else {
|
|
this.missingDependencies.add(`${depPath} (referenced by ${path.basename(dep.from)})`);
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case 'command': {
|
|
// Resolve command references to actual files
|
|
const commandPath = await this.resolveCommandToPath(bmadDir, dep.dependency);
|
|
if (commandPath) {
|
|
paths.push(commandPath);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case 'bmad-path': {
|
|
// Resolve bmad/ paths (from {project-root}/bmad/... references)
|
|
// These are paths relative to the src directory structure
|
|
const bmadPath = dep.dependency.replace(/^bmad\//, '');
|
|
|
|
// Try to resolve as if it's in src structure
|
|
// bmad/core/tasks/foo.md -> src/core/tasks/foo.md
|
|
// bmad/bmm/tasks/bar.md -> src/modules/bmm/tasks/bar.md
|
|
|
|
if (bmadPath.startsWith('core/')) {
|
|
const corePath = path.join(bmadDir, bmadPath);
|
|
if (await fs.pathExists(corePath)) {
|
|
paths.push(corePath);
|
|
} else {
|
|
// Not found, but don't report as missing since it might be installed later
|
|
}
|
|
} else {
|
|
// It's a module path like bmm/tasks/foo.md or cis/agents/bar.md
|
|
const parts = bmadPath.split('/');
|
|
const module = parts[0];
|
|
const rest = parts.slice(1).join('/');
|
|
const modulePath = path.join(bmadDir, 'modules', module, rest);
|
|
|
|
if (await fs.pathExists(modulePath)) {
|
|
paths.push(modulePath);
|
|
} else {
|
|
// Not found, but don't report as missing since it might be installed later
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case 'template': {
|
|
// Resolve template references
|
|
let templateDep = dep.dependency;
|
|
|
|
// Handle {project-root} prefix if present
|
|
if (templateDep.includes('{project-root}')) {
|
|
// Remove {project-root} and treat as bmad-path
|
|
templateDep = templateDep.replace('{project-root}', '');
|
|
|
|
// Now resolve as a bmad path
|
|
if (templateDep.startsWith('bmad/')) {
|
|
const bmadPath = templateDep.replace(/^bmad\//, '');
|
|
|
|
if (bmadPath.startsWith('core/')) {
|
|
const corePath = path.join(bmadDir, bmadPath);
|
|
if (await fs.pathExists(corePath)) {
|
|
paths.push(corePath);
|
|
}
|
|
} else {
|
|
// Module path like cis/templates/brainstorm.md
|
|
const parts = bmadPath.split('/');
|
|
const module = parts[0];
|
|
const rest = parts.slice(1).join('/');
|
|
const modulePath = path.join(bmadDir, 'modules', module, rest);
|
|
|
|
if (await fs.pathExists(modulePath)) {
|
|
paths.push(modulePath);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Regular relative template path
|
|
const sourceDir = path.dirname(dep.from);
|
|
const templatePath = path.resolve(sourceDir, templateDep);
|
|
|
|
if (await fs.pathExists(templatePath)) {
|
|
paths.push(templatePath);
|
|
} else {
|
|
this.missingDependencies.add(`Template: ${dep.dependency}`);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
// No default
|
|
}
|
|
|
|
return paths;
|
|
}
|
|
|
|
/**
|
|
* Resolve command reference to file path
|
|
*/
|
|
async resolveCommandToPath(bmadDir, command) {
|
|
// Parse command format: @task-name or @agent-name or bmad/module/type/name
|
|
|
|
if (command.startsWith('@task-')) {
|
|
const taskName = command.slice(6);
|
|
// Search all modules for this task
|
|
for (const module of ['core', 'bmm', 'cis']) {
|
|
const taskPath =
|
|
module === 'core'
|
|
? path.join(bmadDir, 'core', 'tasks', `${taskName}.md`)
|
|
: path.join(bmadDir, 'modules', module, 'tasks', `${taskName}.md`);
|
|
if (await fs.pathExists(taskPath)) {
|
|
return taskPath;
|
|
}
|
|
}
|
|
} else if (command.startsWith('@agent-')) {
|
|
const agentName = command.slice(7);
|
|
// Search all modules for this agent
|
|
for (const module of ['core', 'bmm', 'cis']) {
|
|
const agentPath =
|
|
module === 'core'
|
|
? path.join(bmadDir, 'core', 'agents', `${agentName}.md`)
|
|
: path.join(bmadDir, 'modules', module, 'agents', `${agentName}.md`);
|
|
if (await fs.pathExists(agentPath)) {
|
|
return agentPath;
|
|
}
|
|
}
|
|
} else if (command.startsWith('bmad/')) {
|
|
// Direct path reference
|
|
const parts = command.split('/');
|
|
if (parts.length >= 4) {
|
|
const [, module, type, ...nameParts] = parts;
|
|
const name = nameParts.join('/'); // Handle nested paths
|
|
|
|
// Check if name already has extension
|
|
const fileName = name.endsWith('.md') ? name : `${name}.md`;
|
|
|
|
const filePath =
|
|
module === 'core' ? path.join(bmadDir, 'core', type, fileName) : path.join(bmadDir, 'modules', module, type, fileName);
|
|
if (await fs.pathExists(filePath)) {
|
|
return filePath;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Don't report as missing if it's a self-reference within the module being installed
|
|
if (!command.includes('cis') || command.includes('brain')) {
|
|
// Only report missing if it's a true external dependency
|
|
// this.missingDependencies.add(`Command: ${command}`);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Resolve transitive dependencies (dependencies of dependencies)
|
|
*/
|
|
async resolveTransitiveDependencies(bmadDir, directDeps) {
|
|
const transitive = new Set();
|
|
const processed = new Set();
|
|
|
|
// Process each direct dependency
|
|
for (const depPath of directDeps) {
|
|
if (processed.has(depPath)) continue;
|
|
processed.add(depPath);
|
|
|
|
// Only process markdown and YAML files for transitive deps
|
|
if ((depPath.endsWith('.md') || depPath.endsWith('.yaml') || depPath.endsWith('.yml')) && (await fs.pathExists(depPath))) {
|
|
const content = await fs.readFile(depPath, 'utf8');
|
|
const subDeps = await this.parseDependencies([
|
|
{
|
|
path: depPath,
|
|
type: 'dependency',
|
|
module: this.getModuleFromPath(bmadDir, depPath),
|
|
name: path.basename(depPath),
|
|
},
|
|
]);
|
|
|
|
const resolvedSubDeps = await this.resolveDependencyPaths(bmadDir, subDeps);
|
|
for (const subDep of resolvedSubDeps) {
|
|
if (!directDeps.has(subDep)) {
|
|
transitive.add(subDep);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return transitive;
|
|
}
|
|
|
|
/**
|
|
* Get module name from file path
|
|
*/
|
|
getModuleFromPath(bmadDir, filePath) {
|
|
const relative = path.relative(bmadDir, filePath);
|
|
const parts = relative.split(path.sep);
|
|
|
|
// Handle source directory structure (src/core or src/modules/xxx)
|
|
if (parts[0] === 'src') {
|
|
if (parts[1] === 'core') {
|
|
return 'core';
|
|
} else if (parts[1] === 'modules' && parts.length > 2) {
|
|
return parts[2];
|
|
}
|
|
}
|
|
|
|
// Check if it's in modules directory (installed structure)
|
|
if (parts[0] === 'modules' && parts.length > 1) {
|
|
return parts[1];
|
|
}
|
|
|
|
// Otherwise return the first part (core, etc.)
|
|
// But don't return 'src' as a module name
|
|
if (parts[0] === 'src') {
|
|
return 'unknown';
|
|
}
|
|
return parts[0] || 'unknown';
|
|
}
|
|
|
|
/**
|
|
* Organize files by module
|
|
*/
|
|
organizeByModule(bmadDir, files) {
|
|
const organized = {};
|
|
|
|
for (const file of files) {
|
|
const module = this.getModuleFromPath(bmadDir, file);
|
|
if (!organized[module]) {
|
|
organized[module] = {
|
|
agents: [],
|
|
tasks: [],
|
|
templates: [],
|
|
data: [],
|
|
other: [],
|
|
};
|
|
}
|
|
|
|
// Get relative path correctly based on module structure
|
|
let moduleBase;
|
|
|
|
// Check if file is in source directory structure
|
|
if (file.includes('/src/core/') || file.includes('/src/modules/')) {
|
|
moduleBase = module === 'core' ? path.join(bmadDir, 'src', 'core') : path.join(bmadDir, 'src', 'modules', module);
|
|
} else {
|
|
// Installed structure
|
|
moduleBase = module === 'core' ? path.join(bmadDir, 'core') : path.join(bmadDir, 'modules', module);
|
|
}
|
|
|
|
const relative = path.relative(moduleBase, file);
|
|
|
|
// Check file path for categorization
|
|
// Brain-tech files are data, not tasks (even though they're in tasks/brain-tech/)
|
|
if (file.includes('/brain-tech/')) {
|
|
organized[module].data.push(file);
|
|
} else if (relative.startsWith('agents/') || file.includes('/agents/')) {
|
|
organized[module].agents.push(file);
|
|
} else if (relative.startsWith('tasks/') || file.includes('/tasks/')) {
|
|
organized[module].tasks.push(file);
|
|
} else if (relative.includes('template') || file.includes('/templates/')) {
|
|
organized[module].templates.push(file);
|
|
} else if (relative.includes('data/')) {
|
|
organized[module].data.push(file);
|
|
} else {
|
|
organized[module].other.push(file);
|
|
}
|
|
}
|
|
|
|
return organized;
|
|
}
|
|
|
|
/**
|
|
* Report resolution results
|
|
*/
|
|
reportResults(organized, selectedModules) {
|
|
console.log(chalk.green('\n✓ Dependency resolution complete'));
|
|
|
|
for (const [module, files] of Object.entries(organized)) {
|
|
const isSelected = selectedModules.includes(module) || module === 'core';
|
|
const totalFiles = files.agents.length + files.tasks.length + files.templates.length + files.data.length + files.other.length;
|
|
|
|
if (totalFiles > 0) {
|
|
console.log(chalk.cyan(`\n ${module.toUpperCase()} module:`));
|
|
console.log(chalk.dim(` Status: ${isSelected ? 'Selected' : 'Dependencies only'}`));
|
|
|
|
if (files.agents.length > 0) {
|
|
console.log(chalk.dim(` Agents: ${files.agents.length}`));
|
|
}
|
|
if (files.tasks.length > 0) {
|
|
console.log(chalk.dim(` Tasks: ${files.tasks.length}`));
|
|
}
|
|
if (files.templates.length > 0) {
|
|
console.log(chalk.dim(` Templates: ${files.templates.length}`));
|
|
}
|
|
if (files.data.length > 0) {
|
|
console.log(chalk.dim(` Data files: ${files.data.length}`));
|
|
}
|
|
if (files.other.length > 0) {
|
|
console.log(chalk.dim(` Other files: ${files.other.length}`));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.missingDependencies.size > 0) {
|
|
console.log(chalk.yellow('\n ⚠ Missing dependencies:'));
|
|
for (const missing of this.missingDependencies) {
|
|
console.log(chalk.yellow(` - ${missing}`));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a bundle for web deployment
|
|
* @param {Object} resolution - Resolution results from resolve()
|
|
* @returns {Object} Bundle data ready for web
|
|
*/
|
|
async createWebBundle(resolution) {
|
|
const bundle = {
|
|
metadata: {
|
|
created: new Date().toISOString(),
|
|
modules: Object.keys(resolution.byModule),
|
|
totalFiles: resolution.allFiles.length,
|
|
},
|
|
agents: {},
|
|
tasks: {},
|
|
templates: {},
|
|
data: {},
|
|
};
|
|
|
|
// Bundle all files by type
|
|
for (const filePath of resolution.allFiles) {
|
|
if (!(await fs.pathExists(filePath))) continue;
|
|
|
|
const content = await fs.readFile(filePath, 'utf8');
|
|
const relative = path.relative(path.dirname(resolution.primaryFiles[0]?.path || '.'), filePath);
|
|
|
|
if (filePath.includes('/agents/')) {
|
|
bundle.agents[relative] = content;
|
|
} else if (filePath.includes('/tasks/')) {
|
|
bundle.tasks[relative] = content;
|
|
} else if (filePath.includes('template')) {
|
|
bundle.templates[relative] = content;
|
|
} else {
|
|
bundle.data[relative] = content;
|
|
}
|
|
}
|
|
|
|
return bundle;
|
|
}
|
|
}
|
|
|
|
module.exports = { DependencyResolver };
|