@webdevtoday/claude-agents
Version:
AI-powered development shop with 15 specialized agents for Claude Code. Features concurrent execution, shared memory, context-forge integration, and web dashboard for 80% faster development.
290 lines (235 loc) • 9.75 kB
JavaScript
import { readdirSync, readFileSync, existsSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import yaml from 'yaml';
import { createContextAwareConfig, detectContextForge } from './contextForgeDetector.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export function getAvailableAgents() {
const agents = [];
const checkedPaths = new Set();
// Define all possible agent directories
const agentsDirs = [
join(__dirname, '..', '..', 'agents'), // Standard npm package location
join(__dirname, '..', '..', '..', 'agents'), // Global npm installation
join(__dirname, '..', '..', '..', '..', 'claude-agents', 'agents'), // Alternative global location
join(process.cwd(), 'agents'), // Local development
];
for (const agentsDir of agentsDirs) {
// Skip if already checked or doesn't exist
if (checkedPaths.has(agentsDir) || !existsSync(agentsDir)) {
continue;
}
checkedPaths.add(agentsDir);
const agentDirs = readdirSync(agentsDir, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
for (const agentName of agentDirs) {
const metadataPath = join(agentsDir, agentName, 'metadata.json');
const agentPath = join(agentsDir, agentName, 'agent.md');
if (existsSync(metadataPath) && existsSync(agentPath)) {
try {
const metadata = JSON.parse(readFileSync(metadataPath, 'utf-8'));
const agentContent = readFileSync(agentPath, 'utf-8');
// Parse YAML frontmatter
const frontmatterMatch = agentContent.match(/^---\n([\s\S]*?)\n---/);
let frontmatter = {};
if (frontmatterMatch) {
frontmatter = yaml.parse(frontmatterMatch[1]);
}
agents.push({
name: agentName,
...metadata,
frontmatter,
content: agentContent
});
} catch (error) {
console.error(`Error loading agent ${agentName}:`, error);
}
}
}
}
return agents;
}
export function getAgentDetails(agentName) {
// Define all possible agent directories
const agentsDirs = [
join(__dirname, '..', '..', 'agents'), // Standard npm package location
join(__dirname, '..', '..', '..', 'agents'), // Global npm installation
join(__dirname, '..', '..', '..', '..', 'claude-agents', 'agents'), // Alternative global location
join(process.cwd(), 'agents'), // Local development
];
for (const agentsDir of agentsDirs) {
const agentDir = join(agentsDir, agentName);
const metadataPath = join(agentDir, 'metadata.json');
const agentPath = join(agentDir, 'agent.md');
const hooksPath = join(agentDir, 'hooks.json');
if (existsSync(metadataPath) && existsSync(agentPath)) {
try {
const metadata = JSON.parse(readFileSync(metadataPath, 'utf-8'));
const agentContent = readFileSync(agentPath, 'utf-8');
const hooks = existsSync(hooksPath)
? JSON.parse(readFileSync(hooksPath, 'utf-8'))
: null;
// Parse YAML frontmatter
const frontmatterMatch = agentContent.match(/^---\n([\s\S]*?)\n---/);
let frontmatter = {};
let content = agentContent;
if (frontmatterMatch) {
frontmatter = yaml.parse(frontmatterMatch[1]);
content = agentContent.replace(frontmatterMatch[0], '').trim();
}
return {
name: agentName,
...metadata,
frontmatter,
content,
fullContent: agentContent,
hooks
};
} catch (error) {
console.error(`Error loading agent ${agentName}:`, error);
}
}
}
return null;
}
export function formatAgentForInstall(agent) {
const { frontmatter, fullContent } = agent;
// Ensure proper frontmatter format
const formattedFrontmatter = {
name: agent.name,
description: frontmatter.description || agent.description,
tools: frontmatter.tools || agent.requirements?.tools?.join(', ') || ''
};
// Create the properly formatted agent file
const yamlFrontmatter = yaml.stringify(formattedFrontmatter).trim();
const content = fullContent.replace(/^---\n[\s\S]*?\n---/, '').trim();
return `---\n${yamlFrontmatter}\n---\n\n${content}`;
}
/**
* Creates a context-aware agent configuration
* This enhances agents with knowledge of context-forge project structure
*/
export function createContextAwareAgent(agentName, projectPath = process.cwd()) {
const agent = getAgentDetails(agentName);
if (!agent) {
return null;
}
// Get context-forge configuration
const contextConfig = createContextAwareConfig(projectPath);
// Enhance agent with context awareness
const enhancedAgent = {
...agent,
contextForge: contextConfig,
isContextAware: contextConfig.isContextForgeProject
};
// If this is a context-forge project, add specific instructions
if (contextConfig.isContextForgeProject) {
const contextInstructions = generateContextInstructions(contextConfig);
enhancedAgent.contextInstructions = contextInstructions;
// Prepend context instructions to agent content
enhancedAgent.enhancedContent = `${contextInstructions}\n\n${agent.content}`;
} else {
enhancedAgent.enhancedContent = agent.content;
}
return enhancedAgent;
}
/**
* Generates context-specific instructions for agents in context-forge projects
*/
function generateContextInstructions(contextConfig) {
let instructions = `## Context-Forge Project Detected
This is a context-forge project with established conventions and structures. You must respect and work within the existing framework.
### Project Configuration
`;
if (contextConfig.projectRules) {
instructions += `
**Project**: ${contextConfig.projectRules.projectName || 'Unknown'}
**Tech Stack**: ${contextConfig.projectRules.techStack.join(', ') || 'Not specified'}
`;
}
instructions += `
### Available Resources
`;
if (contextConfig.detection.structure.hasClaudeMd) {
instructions += `- **CLAUDE.md**: Project rules and conventions (MUST READ AND FOLLOW)\n`;
}
if (contextConfig.detection.structure.hasPRPs) {
instructions += `- **PRPs Directory**: ${contextConfig.availablePRPs?.length || 0} existing PRPs available\n`;
if (contextConfig.availablePRPs?.length > 0) {
instructions += ` Available PRPs:\n`;
contextConfig.availablePRPs.forEach(prp => {
instructions += ` - ${prp.name}: ${prp.goal || 'No goal specified'}\n`;
});
}
}
if (contextConfig.detection.structure.hasImplementationDocs) {
instructions += `- **Implementation Plan**: ${contextConfig.implementationPlan?.currentStage ? `Currently on Stage ${contextConfig.implementationPlan.currentStage}` : 'Available'}\n`;
}
if (contextConfig.detection.structure.hasSlashCommands) {
instructions += `- **Slash Commands**: ${contextConfig.availableCommands?.length || 0} commands available\n`;
}
if (contextConfig.detection.structure.hasHooks) {
instructions += `- **Claude Hooks**: Active hooks that may be triggered by your actions\n`;
}
instructions += `
### Critical Rules for Context-Forge Projects
1. **ALWAYS** read CLAUDE.md before making any changes
2. **CHECK** for existing PRPs before creating new ones
3. **FOLLOW** the implementation plan in Docs/Implementation.md
4. **USE** existing validation commands from PRPs
5. **RESPECT** existing project structure and conventions
6. **APPEND** to existing documentation rather than overwriting
7. **TRIGGER** appropriate hooks when available
### Working with PRPs
When a relevant PRP exists:
- Read the PRP thoroughly
- Follow its validation gates
- Use its specified commands
- Respect its success criteria
When no PRP exists but PRPs directory is present:
- Consider if a PRP should be created
- Follow the existing PRP format
- Include validation gates
### Implementation Plan Awareness
`;
if (contextConfig.implementationPlan) {
const plan = contextConfig.implementationPlan;
instructions += `Current Progress:\n`;
plan.stages.forEach(stage => {
instructions += `- Stage ${stage.number}: ${stage.name} (${stage.completedTasks}/${stage.totalTasks} tasks completed)\n`;
});
}
return instructions;
}
/**
* Gets agent runtime configuration based on project context
*/
export function getAgentRuntimeConfig(agentName, projectPath = process.cwd()) {
const contextAwareAgent = createContextAwareAgent(agentName, projectPath);
if (!contextAwareAgent) {
return null;
}
const config = {
agent: contextAwareAgent,
adaptations: contextAwareAgent.contextForge?.adaptations || {},
projectContext: contextAwareAgent.contextForge || null
};
// Add specific runtime behaviors based on context
if (contextAwareAgent.isContextAware) {
config.behaviors = {
// Prefer reading existing files before creating
readBeforeWrite: true,
// Check for PRPs before creating new ones
checkExistingPRPs: contextAwareAgent.contextForge.detection.structure.hasPRPs,
// Follow validation from existing PRPs
useExistingValidation: contextAwareAgent.contextForge.detection.structure.hasPRPs,
// Respect implementation stages
followImplementationPlan: contextAwareAgent.contextForge.detection.structure.hasImplementationDocs,
// Use context-forge slash commands when available
preferContextCommands: contextAwareAgent.contextForge.detection.structure.hasSlashCommands
};
}
return config;
}