Add agent knowledge files for all modules

This commit is contained in:
Wuesteon 2025-12-17 15:56:59 +01:00
parent 11324b5e68
commit dd06bb2e06
243 changed files with 50805 additions and 175 deletions

25
tools/agent-knowledge/.gitignore vendored Normal file
View file

@ -0,0 +1,25 @@
# Dependencies
node_modules/
# Logs
*.log
npm-debug.log*
# Coverage
coverage/
# Build outputs
dist/
# OS files
.DS_Store
Thumbs.db
# Editor files
.vscode/
.idea/
*.swp
*.swo
# Test outputs
test-project/

View file

@ -0,0 +1,112 @@
# Agent Knowledge System - Project Analysis Request
## Your Task
Analyze this codebase and help set up the Agent Knowledge System with appropriate agents for each module/package.
## What to Analyze
1. **Project Structure**: Scan the directory structure to understand the monorepo layout
2. **Package Types**: Identify apps, packages, services, libraries, and their purposes
3. **Dependencies**: Look at package.json files to understand relationships
4. **Tech Stack**: Identify frameworks (NestJS, SvelteKit, Expo, Astro, etc.)
5. **Shared Code**: Find shared packages that are used across multiple apps
## Analysis Steps
### Step 1: Discover Structure
Run these commands to understand the project:
```bash
# List top-level directories
ls -la
# Find all package.json files
find . -name "package.json" -not -path "*/node_modules/*" | head -50
# Find common config files to identify tech stack
find . -name "nest-cli.json" -o -name "svelte.config.js" -o -name "app.json" -o -name "astro.config.mjs" 2>/dev/null | head -20
```
### Step 2: Identify High-Value Modules
Look for modules that would benefit most from dedicated agents:
- **Auth modules** - Critical, used everywhere
- **Core/shared packages** - Foundation code
- **API clients** - Integration points
- **UI components** - Design system
- **Database packages** - Data layer
### Step 3: Recommend Agent Setup
For each important module, suggest:
1. Whether it needs a **single agent** or a **team**
2. What **watch patterns** should track changes to this module
3. What **related modules** the agent should also know about
## Output Format
Please provide your analysis in this format:
### Project Overview
- Type: [monorepo/single-app/etc]
- Primary tech stack: [list technologies]
- Total packages/apps: [count]
### Recommended Agents
#### High Priority (Create First)
| Path | Agent Type | Reason |
| -------------------- | ------------ | ---------------------------------- |
| packages/shared-auth | Single Agent | Auth is critical, used by all apps |
| ... | ... | ... |
#### Medium Priority
| Path | Agent Type | Reason |
| ---- | ---------- | ------ |
| ... | ... | ... |
### Recommended Teams
For complex apps that need full team coverage:
| Path | Template | Reason |
|------|----------|--------|
| apps/main-app | standard | Complex app with many features |
| ... | ... | ... |
### Watch Pattern Suggestions
```yaml
# Example for auth module
packages/shared-auth:
watches:
- packages/shared-auth/**
- packages/shared-auth-*/** # Related auth packages
- apps/*/src/**/auth/** # Auth code in apps
- services/auth-service/** # Auth service
```
## After Analysis
Once you provide the analysis, I can run these commands to set up the agents:
```bash
# Initialize the system (if not done)
npx agent-knowledge init
# Add recommended agents
npx agent-knowledge add-agent <path>
# Add teams to complex apps
npx agent-knowledge team <path> --template <template>
```
---
**Please start by exploring the project structure and providing your analysis.**

View file

@ -0,0 +1,17 @@
# Development files
node_modules/
.git/
.github/
test/
coverage/
*.log
# Editor files
.vscode/
.idea/
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db

View file

@ -0,0 +1,419 @@
# Agent Knowledge System
AI agents that live in your codebase and automatically learn from your code changes. Perfect for monorepos where you want specialized agents for each module that stay up-to-date with the code.
## Features
- **Co-located Agents** - Agents live in `.agent/` folders alongside the code they know
- **Auto-updating Knowledge** - Git hooks track changes, nightly jobs update agent memory
- **Multi-provider Support** - Works with OpenRouter (many models) or Anthropic directly
- **Claude Code Integration** - Agents work seamlessly with Claude Code
- **Monorepo Ready** - Perfect for large codebases with multiple packages/modules
## Quick Start
```bash
# Initialize in your project
npx agent-knowledge init
# Add an agent to a module
npx agent-knowledge add-agent packages/shared-auth
# Check status
npx agent-knowledge status
# Manually update agent knowledge
npx agent-knowledge update
```
## How It Works
```
┌─────────────────────────────────────────────────────────────┐
│ Your Monorepo │
│ │
│ packages/shared-auth/ │
│ ├── src/ │
│ ├── .agent/ ◄── Agent lives with the code │
│ │ ├── agent.md Persona & instructions │
│ │ ├── memory.md Auto-updated knowledge │
│ │ └── architecture.md Manual architecture docs │
│ └── package.json │
│ │
│ .knowledge/ ◄── System configuration │
│ ├── config.yaml Provider settings │
│ ├── agent-registry.yaml Tracks all agents │
│ ├── changes.jsonl Pending changes log │
│ └── .env API keys (gitignored) │
│ │
└─────────────────────────────────────────────────────────────┘
│ Git Commit │ Nightly Cron
▼ ▼
┌─────────────────┐ ┌─────────────────────┐
│ Track changes │ │ LLM summarizes │
│ in changes.jsonl│ │ updates memory.md │
└─────────────────┘ └─────────────────────┘
```
## Installation
### Global Install (optional)
```bash
npm install -g agent-knowledge
```
### Per-project (recommended)
Just use `npx`:
```bash
npx agent-knowledge init
```
## Commands
### `scan`
Auto-discover your project structure and suggest agents.
```bash
npx agent-knowledge scan
# Options
--depth <n> # Max directory depth (default: 4)
--all # Show all modules (not just top 5 per type)
--json # Output as JSON for scripting
--dry-run # Preview without creating
```
**Example output for ManaCore-like monorepo:**
```
📁 Discovered Structure:
📦 package (42)
⭐ packages/shared-auth
⭐ packages/shared-api-client
○ packages/shared-ui
...
📱 app (20)
○ apps/nutriphi
○ apps/manacore
...
⚙️ service (1)
⭐ services/mana-core-auth
🎯 Recommended Agents (high priority):
packages/shared-auth
Type: package | Reason: Contains "auth"
```
### `team`
Add a full development team to a project.
```bash
# List available team templates
npx agent-knowledge team --list
# Add standard team (6 agents)
npx agent-knowledge team apps/nutriphi
# Add startup team (3 agents) - lean
npx agent-knowledge team packages/shared-auth --template startup
# Add enterprise team (10 agents) - full coverage
npx agent-knowledge team apps/manacore --template enterprise
# Preview what would be created
npx agent-knowledge team apps/myapp --dry-run
```
**Team Templates:**
| Template | Agents | Roles |
|----------|--------|-------|
| `startup` | 3 | Tech Lead, Developer, QA |
| `standard` | 6 | Product Owner, Architect, Senior Dev, Dev, Security, QA Lead |
| `enterprise` | 10 | + Scrum Master, DevOps, Frontend/Backend Specialists, Tech Writer |
### `init`
Initialize the agent knowledge system in your project.
```bash
npx agent-knowledge init
# Options
--provider <provider> # LLM provider: openrouter (default) | anthropic
--model <model> # Default model for updates
--no-hooks # Skip git hooks installation
```
### `add-agent <path>`
Add an agent to a module/package.
```bash
npx agent-knowledge add-agent packages/shared-auth
npx agent-knowledge add-agent backends/api-gateway
# Options
--name <name> # Custom agent name
--watches <patterns> # Additional watch patterns
```
### `update`
Update agent knowledge from pending changes.
```bash
npx agent-knowledge update
# Options
--agent <path> # Update specific agent only
--model <model> # Override model for this run
--dry-run # Preview without making changes
```
### `status`
Show system status and all registered agents.
```bash
npx agent-knowledge status
```
### `setup-hooks`
Install or reinstall git hooks.
```bash
npx agent-knowledge setup-hooks
--force # Overwrite existing hooks
```
## Configuration
### LLM Providers
The system supports two providers:
**OpenRouter (recommended)** - Access to many models through one API:
```yaml
# .knowledge/config.yaml
provider: openrouter
model: google/gemini-flash-1.5 # Cheapest option
```
**Anthropic** - Direct Claude API:
```yaml
provider: anthropic
model: claude-sonnet-4-20250514
```
### Available Models (OpenRouter)
| Model | Cost | Speed | Quality | Best For |
|-------|------|-------|---------|----------|
| `google/gemini-flash-1.5` | $0.075/1M | Fast | Good | Daily batch jobs |
| `deepseek/deepseek-chat` | $0.14/1M | Fast | Good | Budget option |
| `anthropic/claude-3-haiku` | $0.25/1M | Fast | Good | Reliable default |
| `anthropic/claude-sonnet-4-20250514` | $3/1M | Medium | Excellent | Important updates |
### Environment Variables
Set in `.knowledge/.env` (gitignored) or as environment variables:
```bash
# OpenRouter
OPENROUTER_API_KEY=sk-or-v1-xxxxx
LLM_PROVIDER=openrouter
OPENROUTER_MODEL=google/gemini-flash-1.5
# Anthropic (alternative)
ANTHROPIC_API_KEY=sk-ant-xxxxx
LLM_PROVIDER=anthropic
ANTHROPIC_MODEL=claude-sonnet-4-20250514
```
## Automated Updates
### Cron Job (local)
```bash
# Add to crontab: crontab -e
# Run at 3 AM every night
0 3 * * * cd /path/to/project && npx agent-knowledge update >> .knowledge/cron.log 2>&1
```
### GitHub Actions
```yaml
# .github/workflows/update-agent-knowledge.yml
name: Update Agent Knowledge
on:
schedule:
- cron: '0 3 * * *' # 3 AM UTC daily
workflow_dispatch: # Manual trigger
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Update agent knowledge
run: npx agent-knowledge update
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "🤖 Update agent knowledge"
file_pattern: "**/.agent/memory.md .knowledge/archive/*"
```
## Using with Claude Code
Once set up, use agents naturally with Claude Code:
```
# When working on auth module
> Read packages/shared-auth/.agent/ and help me implement refresh tokens
# Claude will:
# 1. Read agent.md (persona, principles)
# 2. Read memory.md (recent changes, context)
# 3. Read architecture.md (structure)
# 4. Help with full context of the module
```
### CLAUDE.md Integration
The init command automatically adds agent system documentation to your `CLAUDE.md`:
```markdown
## Agent Knowledge System
When working on a specific module, check for `.agent/agent.md` in that directory.
If found, read all files in `.agent/` to load domain knowledge and adopt that
agent's persona and expertise.
```
## Agent File Structure
Each `.agent/` folder contains:
```
.agent/
├── agent.md # Persona, role, expertise, principles
├── memory.md # Auto-updated from code changes
├── architecture.md # Manual architecture documentation
└── [custom].md # Any additional context files
```
### agent.md (example)
```markdown
# Auth Module Expert
## Identity
You are the **Auth Module Expert** for this codebase. Deep knowledge of
authentication flows, OAuth, JWT handling, and session management.
## Expertise
- OAuth 2.0 / OIDC flows
- JWT token handling
- Session management
- Security best practices
## Principles
1. Security first - always consider attack vectors
2. Maintain backwards compatibility
3. All changes must have tests
```
### memory.md (auto-updated)
```markdown
# Auth Module Expert - Memory
## 2024-12-16
### Token Refresh Improvements
- Implemented silent refresh with retry logic
- Added 30-second buffer before expiry
- New pattern: use `tokenManager.scheduleRefresh()`
## 2024-12-14
### Bug Fix: Race Condition
- Fixed concurrent refresh calls issue
- Added mutex lock in session manager
```
## Watch Patterns
Agents watch for changes using glob patterns:
```yaml
# .knowledge/agent-registry.yaml
agents:
- path: packages/shared-auth
agent_dir: packages/shared-auth/.agent
name: Auth Expert
watches:
- packages/shared-auth/** # All files in module
- packages/core-utils/src/auth/** # Related code elsewhere
- apps/*/middleware/auth.* # Auth middleware in apps
```
## Troubleshooting
### Changes not being tracked
1. Check git hook is installed:
```bash
cat .git/hooks/post-commit | grep "Agent Knowledge"
```
2. Reinstall hooks:
```bash
npx agent-knowledge setup-hooks --force
```
### API errors during update
1. Check API key is set:
```bash
cat .knowledge/.env
```
2. Test with different model:
```bash
npx agent-knowledge update --model anthropic/claude-3-haiku
```
### Agent not receiving changes
Check watch patterns in registry:
```bash
npx agent-knowledge status
```
## License
MIT

View file

@ -0,0 +1,99 @@
#!/usr/bin/env node
import { Command } from 'commander';
import chalk from 'chalk';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { initCommand } from '../src/commands/init.js';
import { addAgentCommand } from '../src/commands/add-agent.js';
import { updateCommand } from '../src/commands/update.js';
import { statusCommand } from '../src/commands/status.js';
import { setupHooksCommand } from '../src/commands/setup-hooks.js';
import { scanCommand } from '../src/commands/scan.js';
import { teamCommand } from '../src/commands/team.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
const program = new Command();
console.log(
chalk.cyan(`
${chalk.bold('Agent Knowledge System')} v${pkg.version}
AI agents that learn from your code
`)
);
program
.name('agent-knowledge')
.description('Auto-updating AI agent knowledge system for codebases')
.version(pkg.version);
program
.command('init')
.description('Initialize agent knowledge system in current project')
.option('-p, --provider <provider>', 'LLM provider (anthropic|openrouter)', 'openrouter')
.option('-m, --model <model>', 'Default model to use', 'google/gemini-flash-1.5')
.option('--no-hooks', 'Skip git hooks installation')
.action(initCommand);
program
.command('add-agent <path>')
.description('Add an agent to a module/package (e.g., packages/shared-auth)')
.option('-n, --name <name>', 'Agent display name')
.option('-w, --watches <patterns...>', 'Additional watch patterns')
.action(addAgentCommand);
program
.command('update')
.description('Manually trigger knowledge update for all agents')
.option('-a, --agent <path>', 'Update specific agent only')
.option('-m, --model <model>', 'Override model for this run')
.option('--claude', 'Use Claude Code instead of external APIs')
.option('--dry-run', 'Show what would be updated without making changes')
.action(updateCommand);
program
.command('status')
.description('Show status of all agents and pending changes')
.action(statusCommand);
program
.command('setup-hooks')
.description('Install/reinstall git hooks')
.option('-f, --force', 'Overwrite existing hooks')
.action(setupHooksCommand);
program
.command('scan')
.description('Scan project and discover modules for agents')
.option('-d, --depth <n>', 'Max directory depth to scan', '4')
.option('-a, --all', 'Show all discovered modules (not just top 5 per type)')
.option('--json', 'Output as JSON')
.option('--dry-run', 'Show what would be created without creating')
.option('--claude', 'Generate Claude Code prompt for intelligent analysis')
.option('--setup-all', 'Automatically create agents for all discovered modules')
.option('--teams', 'Use team templates for apps (with --setup-all)')
.option(
'-t, --template <template>',
'Team template for apps: startup, standard, enterprise',
'startup'
)
.option('--init-claude', 'Generate prompts for Claude Code to create rich agent descriptions')
.action(scanCommand);
program
.command('team [path]')
.description('Add a full development team to a project/module')
.option('-t, --template <template>', 'Team template: startup, standard, enterprise', 'standard')
.option('-l, --list', 'List available team templates')
.option('--dry-run', 'Show what would be created')
.action(teamCommand);
program.parse();

View file

@ -0,0 +1,29 @@
{
"name": "@manacore/agent-knowledge",
"version": "1.0.0",
"description": "Auto-updating AI agent knowledge system - agents that learn from your code changes",
"private": true,
"type": "module",
"main": "src/index.js",
"bin": {
"agent-knowledge": "bin/cli.js",
"ak": "bin/cli.js"
},
"scripts": {
"cli": "node bin/cli.js",
"update": "node bin/cli.js update --claude",
"status": "node bin/cli.js status",
"scan": "node bin/cli.js scan"
},
"dependencies": {
"chalk": "^5.3.0",
"commander": "^12.1.0",
"fs-extra": "^11.2.0",
"inquirer": "^9.2.23",
"ora": "^8.0.1",
"yaml": "^2.4.5"
},
"engines": {
"node": ">=18.0.0"
}
}

View file

@ -0,0 +1,304 @@
import chalk from 'chalk';
import ora from 'ora';
import inquirer from 'inquirer';
import fs from 'fs-extra';
import path from 'path';
import yaml from 'yaml';
export async function addAgentCommand(modulePath, options) {
const cwd = process.cwd();
const fullPath = path.join(cwd, modulePath);
const agentDir = path.join(fullPath, '.agent');
console.log(chalk.blue(`\n🤖 Adding agent for: ${modulePath}\n`));
// Validate path exists
if (!(await fs.pathExists(fullPath))) {
console.log(chalk.red(`Error: Path does not exist: ${fullPath}`));
process.exit(1);
}
// Check if agent already exists
if (await fs.pathExists(agentDir)) {
const { overwrite } = await inquirer.prompt([
{
type: 'confirm',
name: 'overwrite',
message: '.agent directory already exists. Overwrite?',
default: false,
},
]);
if (!overwrite) {
console.log(chalk.yellow('Aborted.'));
return;
}
}
// Create .knowledge directory if needed (Claude Code only mode)
const knowledgeDir = path.join(cwd, '.knowledge');
if (!(await fs.pathExists(knowledgeDir))) {
await fs.ensureDir(knowledgeDir);
await fs.writeFile(path.join(knowledgeDir, 'agent-registry.yaml'), 'agents: []\n');
console.log(chalk.gray('Created .knowledge/ directory (Claude Code only mode)\n'));
}
// Gather agent information
const moduleName = path.basename(modulePath);
const parentDir = path.basename(path.dirname(modulePath));
const answers = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Agent display name:',
default: options.name || `${formatName(moduleName)} Expert`,
},
{
type: 'input',
name: 'role',
message: 'Agent role/title:',
default: `${formatName(moduleName)} Specialist`,
},
{
type: 'input',
name: 'expertise',
message: 'Primary expertise (comma-separated):',
default: guessExpertise(moduleName),
},
{
type: 'editor',
name: 'description',
message: 'Agent identity/description (opens editor):',
default: `Deep expert in the ${moduleName} module. Understands all patterns, APIs, and implementation details.`,
},
{
type: 'input',
name: 'watches',
message: 'Watch patterns (comma-separated, supports globs):',
default: [modulePath + '/**', ...(options.watches || [])].join(', '),
},
{
type: 'confirm',
name: 'scanForContext',
message: 'Scan module for initial context (package.json, README, etc)?',
default: true,
},
]);
const spinner = ora('Creating agent...').start();
try {
// Create .agent directory
await fs.ensureDir(agentDir);
// Scan for initial context if requested
let initialContext = '';
if (answers.scanForContext) {
initialContext = await scanModuleContext(fullPath);
}
// Create agent.md
const agentContent = `# ${answers.name}
## Identity
You are the **${answers.name}** for this codebase. ${answers.description}
## Role
${answers.role}
## Expertise
${answers.expertise
.split(',')
.map((e) => `- ${e.trim()}`)
.join('\n')}
## Principles
1. Understand existing patterns before suggesting changes
2. Maintain consistency with the module's established conventions
3. Consider impacts on consumers of this module
4. Document breaking changes clearly
5. Write tests for all changes
## Key Paths
- Source: \`${modulePath}/src/\`
- Tests: \`${modulePath}/test/\` or \`${modulePath}/__tests__/\`
- Types: \`${modulePath}/types/\` or \`${modulePath}/src/types/\`
## How to Use This Agent
When working on ${moduleName}:
1. Read this file and memory.md for context
2. Check architecture.md for structural understanding
3. Reference memory.md for recent changes and decisions
`;
await fs.writeFile(path.join(agentDir, 'agent.md'), agentContent);
spinner.succeed('Created .agent/agent.md');
// Create memory.md
spinner.start('Creating memory.md...');
const memoryContent = `# ${answers.name} - Memory
This file is automatically updated with learnings from code changes.
## Recent Updates
*No updates yet. Memory will be populated after code changes are processed.*
---
## Historical Context
${initialContext || '*Initial context will be added here.*'}
`;
await fs.writeFile(path.join(agentDir, 'memory.md'), memoryContent);
spinner.succeed('Created .agent/memory.md');
// Create architecture.md
spinner.start('Creating architecture.md...');
const architectureContent = `# ${formatName(moduleName)} Architecture
## Overview
*Document the high-level architecture of this module here.*
## Directory Structure
\`\`\`
${modulePath}/
src/ # Source code
test/ # Tests
types/ # TypeScript types (if applicable)
package.json # Dependencies
\`\`\`
## Key Components
*List and describe the main components/files here.*
## Dependencies
*Document key dependencies and why they're used.*
## Integration Points
*How other modules/apps interact with this module.*
`;
await fs.writeFile(path.join(agentDir, 'architecture.md'), architectureContent);
spinner.succeed('Created .agent/architecture.md');
// Update agent registry
spinner.start('Updating agent registry...');
await updateRegistry(cwd, {
path: modulePath,
agent_dir: path.join(modulePath, '.agent'),
name: answers.name,
watches: answers.watches.split(',').map((w) => w.trim()),
});
spinner.succeed('Updated .knowledge/agent-registry.yaml');
console.log(chalk.green(`\n✅ Agent created successfully at ${modulePath}/.agent/\n`));
console.log(chalk.cyan('Files created:'));
console.log(chalk.gray(` ${modulePath}/.agent/agent.md - Agent persona & instructions`));
console.log(chalk.gray(` ${modulePath}/.agent/memory.md - Auto-updated knowledge`));
console.log(
chalk.gray(` ${modulePath}/.agent/architecture.md - Architecture docs (edit manually)`)
);
console.log(chalk.cyan('\nNext steps:'));
console.log(chalk.white(' 1. Review and customize .agent/agent.md'));
console.log(chalk.white(' 2. Document architecture in .agent/architecture.md'));
console.log(chalk.white(' 3. Make commits - changes will be tracked automatically'));
console.log(chalk.white(' 4. Run update to populate memory:'));
console.log(chalk.gray(' npx agent-knowledge update\n'));
} catch (error) {
spinner.fail('Failed to create agent');
console.error(chalk.red(error.message));
process.exit(1);
}
}
function formatName(name) {
return name
.split(/[-_]/)
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
function guessExpertise(moduleName) {
const expertiseMap = {
auth: 'Authentication, Authorization, OAuth, JWT, Sessions, Security',
ui: 'React Components, Design System, Accessibility, Styling',
api: 'REST APIs, GraphQL, HTTP, Request Handling, Middleware',
database: 'SQL, Migrations, ORM, Query Optimization, Data Modeling',
utils: 'Utility Functions, Helpers, Common Patterns',
core: 'Core Business Logic, Domain Models, Validation',
config: 'Configuration Management, Environment Variables, Feature Flags',
};
for (const [key, expertise] of Object.entries(expertiseMap)) {
if (moduleName.toLowerCase().includes(key)) {
return expertise;
}
}
return 'Module internals, APIs, Patterns, Best Practices';
}
async function scanModuleContext(modulePath) {
let context = '';
// Check for package.json
const packagePath = path.join(modulePath, 'package.json');
if (await fs.pathExists(packagePath)) {
const pkg = await fs.readJson(packagePath);
context += `### Package Info\n`;
context += `- Name: ${pkg.name || 'unknown'}\n`;
context += `- Version: ${pkg.version || 'unknown'}\n`;
if (pkg.description) {
context += `- Description: ${pkg.description}\n`;
}
if (pkg.dependencies) {
context += `- Key dependencies: ${Object.keys(pkg.dependencies).slice(0, 5).join(', ')}\n`;
}
context += '\n';
}
// Check for README
const readmePaths = ['README.md', 'readme.md', 'README.MD'];
for (const readmeName of readmePaths) {
const readmePath = path.join(modulePath, readmeName);
if (await fs.pathExists(readmePath)) {
const readme = await fs.readFile(readmePath, 'utf-8');
// Take first 500 chars
context += `### From README\n`;
context += readme.substring(0, 500).trim();
if (readme.length > 500) context += '...';
context += '\n\n';
break;
}
}
return context;
}
async function updateRegistry(cwd, agent) {
const registryPath = path.join(cwd, '.knowledge', 'agent-registry.yaml');
let registry = { agents: [] };
if (await fs.pathExists(registryPath)) {
const content = await fs.readFile(registryPath, 'utf-8');
registry = yaml.parse(content) || { agents: [] };
}
// Remove existing entry for this path
registry.agents = registry.agents.filter((a) => a.path !== agent.path);
// Add new entry
registry.agents.push(agent);
await fs.writeFile(registryPath, yaml.stringify(registry));
}

View file

@ -0,0 +1,361 @@
import chalk from 'chalk';
import ora from 'ora';
import inquirer from 'inquirer';
import fs from 'fs-extra';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const TEMPLATES_DIR = path.join(__dirname, '..', '..', 'templates');
export async function initCommand(options) {
const cwd = process.cwd();
console.log(chalk.blue('\n📦 Initializing Agent Knowledge System...\n'));
// Check if already initialized
if (await fs.pathExists(path.join(cwd, '.knowledge'))) {
const { overwrite } = await inquirer.prompt([
{
type: 'confirm',
name: 'overwrite',
message: '.knowledge directory already exists. Reinitialize?',
default: false,
},
]);
if (!overwrite) {
console.log(chalk.yellow('Aborted.'));
return;
}
}
// Gather configuration
const answers = await inquirer.prompt([
{
type: 'list',
name: 'provider',
message: 'Select your primary LLM provider:',
choices: [
{ name: 'OpenRouter (recommended - access to many models)', value: 'openrouter' },
{ name: 'Anthropic (Claude direct)', value: 'anthropic' },
],
default: options.provider,
},
{
type: 'list',
name: 'model',
message: 'Select default model for knowledge updates:',
choices: (ans) =>
ans.provider === 'openrouter'
? [
{
name: 'google/gemini-flash-1.5 (cheapest - $0.075/1M)',
value: 'google/gemini-flash-1.5',
},
{
name: 'deepseek/deepseek-chat (very cheap - $0.14/1M)',
value: 'deepseek/deepseek-chat',
},
{
name: 'anthropic/claude-3-haiku (reliable - $0.25/1M)',
value: 'anthropic/claude-3-haiku',
},
{
name: 'anthropic/claude-sonnet-4-20250514 (best quality - $3/1M)',
value: 'anthropic/claude-sonnet-4-20250514',
},
]
: [
{ name: 'claude-3-haiku-20240307 (fast)', value: 'claude-3-haiku-20240307' },
{ name: 'claude-sonnet-4-20250514 (balanced)', value: 'claude-sonnet-4-20250514' },
],
default: options.model,
},
{
type: 'input',
name: 'apiKey',
message: (ans) =>
`Enter your ${ans.provider === 'openrouter' ? 'OpenRouter' : 'Anthropic'} API key:`,
validate: (input) => input.length > 10 || 'Please enter a valid API key',
},
{
type: 'confirm',
name: 'installHooks',
message: 'Install git hooks for automatic change tracking?',
default: options.hooks !== false,
},
{
type: 'confirm',
name: 'addToGitignore',
message: 'Add sensitive files to .gitignore?',
default: true,
},
{
type: 'confirm',
name: 'createClaudeConfig',
message: 'Create CLAUDE.md configuration for Claude Code integration?',
default: true,
},
]);
const spinner = ora('Creating directory structure...').start();
try {
// 1. Create .knowledge directory structure
await fs.ensureDir(path.join(cwd, '.knowledge', 'archive'));
// 2. Create config file
const config = {
version: '1.0.0',
provider: answers.provider,
model: answers.model,
models: {
batch: {
provider: answers.provider,
model: answers.model,
},
quality: {
provider: answers.provider,
model:
answers.provider === 'openrouter'
? 'anthropic/claude-sonnet-4-20250514'
: 'claude-sonnet-4-20250514',
},
fallbacks:
answers.provider === 'openrouter'
? [
{ provider: 'openrouter', model: 'anthropic/claude-3-haiku' },
{ provider: 'openrouter', model: 'google/gemini-flash-1.5' },
]
: [],
},
budget: {
daily_limit_usd: 1.0,
alert_threshold: 0.8,
},
};
await fs.writeFile(path.join(cwd, '.knowledge', 'config.yaml'), generateYaml(config));
spinner.succeed('Created .knowledge/config.yaml');
// 3. Create agent registry
spinner.start('Creating agent registry...');
const registry = {
agents: [],
};
await fs.writeFile(path.join(cwd, '.knowledge', 'agent-registry.yaml'), generateYaml(registry));
spinner.succeed('Created .knowledge/agent-registry.yaml');
// 4. Create empty changes file
await fs.writeFile(path.join(cwd, '.knowledge', 'changes.jsonl'), '');
// 5. Create .env file for API key
spinner.start('Creating environment file...');
const envContent =
answers.provider === 'openrouter'
? `# Agent Knowledge System Configuration
OPENROUTER_API_KEY=${answers.apiKey}
LLM_PROVIDER=openrouter
OPENROUTER_MODEL=${answers.model}
`
: `# Agent Knowledge System Configuration
ANTHROPIC_API_KEY=${answers.apiKey}
LLM_PROVIDER=anthropic
ANTHROPIC_MODEL=${answers.model}
`;
await fs.writeFile(path.join(cwd, '.knowledge', '.env'), envContent);
spinner.succeed('Created .knowledge/.env');
// 6. Install git hooks
if (answers.installHooks) {
spinner.start('Installing git hooks...');
await installGitHooks(cwd);
spinner.succeed('Installed git hooks');
}
// 7. Update .gitignore
if (answers.addToGitignore) {
spinner.start('Updating .gitignore...');
await updateGitignore(cwd);
spinner.succeed('Updated .gitignore');
}
// 8. Create CLAUDE.md integration
if (answers.createClaudeConfig) {
spinner.start('Creating Claude Code integration...');
await createClaudeConfig(cwd);
spinner.succeed('Created/updated CLAUDE.md');
}
// 9. Create update script
spinner.start('Creating update scripts...');
await fs.ensureDir(path.join(cwd, 'scripts', 'agents'));
await fs.copyFile(
path.join(TEMPLATES_DIR, 'update-agents.js'),
path.join(cwd, 'scripts', 'agents', 'update-agents.js')
);
spinner.succeed('Created scripts/agents/update-agents.js');
console.log(chalk.green('\n✅ Agent Knowledge System initialized successfully!\n'));
console.log(chalk.cyan('Next steps:'));
console.log(chalk.white(' 1. Add agents to your modules:'));
console.log(chalk.gray(' npx agent-knowledge add-agent packages/shared-auth'));
console.log(chalk.white(' 2. Make some commits and watch changes being tracked'));
console.log(chalk.white(' 3. Run manual update or set up cron job:'));
console.log(chalk.gray(' npx agent-knowledge update'));
console.log(chalk.white(' 4. Set up nightly cron (optional):'));
console.log(chalk.gray(' 0 3 * * * cd /your/project && npx agent-knowledge update\n'));
} catch (error) {
spinner.fail('Initialization failed');
console.error(chalk.red(error.message));
process.exit(1);
}
}
async function installGitHooks(cwd) {
const hooksDir = path.join(cwd, '.git', 'hooks');
if (!(await fs.pathExists(path.join(cwd, '.git')))) {
console.log(chalk.yellow(' Warning: Not a git repository, skipping hooks'));
return;
}
await fs.ensureDir(hooksDir);
const hookContent = `#!/bin/bash
# Agent Knowledge System - Change Tracker
# Appends commit changes to .knowledge/changes.jsonl
CHANGES_FILE=".knowledge/changes.jsonl"
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
COMMIT_SHA=$(git rev-parse HEAD)
COMMIT_MSG=$(git log -1 --pretty=format:"%s" | sed 's/"/\\\\"/g')
AUTHOR=$(git log -1 --pretty=format:"%an")
BRANCH=$(git branch --show-current)
# Only track if .knowledge exists
if [ ! -d ".knowledge" ]; then
exit 0
fi
# Get changed files
git diff-tree --no-commit-id --name-status -r HEAD | while read status file; do
# Determine module from path
MODULE=$(echo "$file" | cut -d'/' -f1-2)
# Get truncated diff
DIFF=$(git show HEAD -- "$file" 2>/dev/null | head -50 | sed 's/"/\\\\"/g' | tr '\\n' '\\\\n' | head -c 2000)
# Append to changes log
echo "{\\"timestamp\\":\\"$TIMESTAMP\\",\\"commit\\":\\"$COMMIT_SHA\\",\\"message\\":\\"$COMMIT_MSG\\",\\"author\\":\\"$AUTHOR\\",\\"branch\\":\\"$BRANCH\\",\\"module\\":\\"$MODULE\\",\\"file\\":\\"$file\\",\\"status\\":\\"$status\\",\\"diff\\":\\"$DIFF\\"}" >> "$CHANGES_FILE"
done
echo "📝 Changes logged for agent knowledge system"
`;
const hookPath = path.join(hooksDir, 'post-commit');
// Check if hook exists and has other content
if (await fs.pathExists(hookPath)) {
const existing = await fs.readFile(hookPath, 'utf-8');
if (!existing.includes('Agent Knowledge System')) {
// Append to existing hook
await fs.appendFile(hookPath, '\n\n' + hookContent);
}
} else {
await fs.writeFile(hookPath, hookContent);
}
await fs.chmod(hookPath, '755');
}
async function updateGitignore(cwd) {
const gitignorePath = path.join(cwd, '.gitignore');
const additions = `
# Agent Knowledge System
.knowledge/.env
.knowledge/archive/
`;
if (await fs.pathExists(gitignorePath)) {
const existing = await fs.readFile(gitignorePath, 'utf-8');
if (!existing.includes('Agent Knowledge System')) {
await fs.appendFile(gitignorePath, additions);
}
} else {
await fs.writeFile(gitignorePath, additions.trim());
}
}
async function createClaudeConfig(cwd) {
const claudePath = path.join(cwd, 'CLAUDE.md');
const agentSection = `
## Agent Knowledge System
This project uses specialized AI agents with auto-updating knowledge. When working on a specific module:
1. Check if \`.agent/agent.md\` exists in the directory you're working in
2. If found, read all files in \`.agent/\` to load domain knowledge
3. Adopt that agent's persona, expertise, and principles
4. Reference the memory.md for recent changes and context
### Finding Agents
Search for agents with: \`find . -name "agent.md" -path "*/.agent/*"\`
### Agent Structure
Each \`.agent/\` folder contains:
- \`agent.md\` - Persona, role, expertise, principles
- \`memory.md\` - Auto-updated knowledge from code changes
- \`architecture.md\` - Module architecture (manual)
- Additional context files as needed
`;
if (await fs.pathExists(claudePath)) {
const existing = await fs.readFile(claudePath, 'utf-8');
if (!existing.includes('Agent Knowledge System')) {
await fs.appendFile(claudePath, '\n' + agentSection);
}
} else {
await fs.writeFile(
claudePath,
`# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
${agentSection}`
);
}
}
function generateYaml(obj, indent = 0) {
let yaml = '';
const spaces = ' '.repeat(indent);
for (const [key, value] of Object.entries(obj)) {
if (Array.isArray(value)) {
yaml += `${spaces}${key}:\n`;
for (const item of value) {
if (typeof item === 'object') {
yaml += `${spaces} -\n`;
for (const [k, v] of Object.entries(item)) {
yaml += `${spaces} ${k}: ${v}\n`;
}
} else {
yaml += `${spaces} - ${item}\n`;
}
}
} else if (typeof value === 'object' && value !== null) {
yaml += `${spaces}${key}:\n`;
yaml += generateYaml(value, indent + 1);
} else {
yaml += `${spaces}${key}: ${value}\n`;
}
}
return yaml;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,122 @@
import chalk from 'chalk';
import ora from 'ora';
import fs from 'fs-extra';
import path from 'path';
export async function setupHooksCommand(options) {
const cwd = process.cwd();
console.log(chalk.blue('\n🔧 Setting up Git Hooks...\n'));
// Check if git repo
const gitPath = path.join(cwd, '.git');
if (!(await fs.pathExists(gitPath))) {
console.log(chalk.red('Error: Not a git repository'));
process.exit(1);
}
// Handle worktrees: .git can be a file pointing to the actual gitdir
let hooksDir;
const gitStat = await fs.stat(gitPath);
if (gitStat.isFile()) {
// It's a worktree - read the gitdir reference
const gitContent = await fs.readFile(gitPath, 'utf-8');
const match = gitContent.match(/gitdir:\s*(.+)/);
if (match) {
const worktreeGitDir = match[1].trim();
// For worktrees, hooks are in the common dir (main repo's .git/hooks)
// The worktree gitdir is like: /path/to/main/.git/worktrees/branch-name
// We need: /path/to/main/.git/hooks
const mainGitDir = path.resolve(worktreeGitDir, '..', '..');
hooksDir = path.join(mainGitDir, 'hooks');
console.log(chalk.gray(`Detected worktree, using hooks from: ${hooksDir}\n`));
} else {
console.log(chalk.red('Error: Could not parse .git file'));
process.exit(1);
}
} else {
// Normal repo
hooksDir = path.join(cwd, '.git', 'hooks');
}
const spinner = ora('Installing hooks...').start();
try {
await fs.ensureDir(hooksDir);
const hookContent = `#!/bin/bash
# Agent Knowledge System - Change Tracker
# Automatically logs code changes for AI agent knowledge updates
CHANGES_FILE=".knowledge/changes.jsonl"
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
COMMIT_SHA=$(git rev-parse HEAD)
COMMIT_MSG=$(git log -1 --pretty=format:"%s" | sed 's/"/\\\\"/g')
AUTHOR=$(git log -1 --pretty=format:"%an")
BRANCH=$(git branch --show-current)
# Only track if .knowledge directory exists
if [ ! -d ".knowledge" ]; then
exit 0
fi
# Get changed files and log them
git diff-tree --no-commit-id --name-status -r HEAD | while IFS=$'\\t' read -r status file; do
# Skip if file is empty
[ -z "$file" ] && continue
# Determine module from path (first two path segments)
MODULE=$(echo "$file" | cut -d'/' -f1-2)
# Get truncated diff for context (max 2000 chars)
DIFF=$(git show HEAD -- "$file" 2>/dev/null | head -50 | sed 's/"/\\\\"/g' | tr '\\n' ' ' | cut -c1-2000)
# Create JSON entry and append
echo "{\\"timestamp\\":\\"$TIMESTAMP\\",\\"commit\\":\\"$COMMIT_SHA\\",\\"message\\":\\"$COMMIT_MSG\\",\\"author\\":\\"$AUTHOR\\",\\"branch\\":\\"$BRANCH\\",\\"module\\":\\"$MODULE\\",\\"file\\":\\"$file\\",\\"status\\":\\"$status\\",\\"diff\\":\\"$DIFF\\"}" >> "$CHANGES_FILE"
done
# Notify user
CHANGE_COUNT=$(git diff-tree --no-commit-id --name-only -r HEAD | wc -l | tr -d ' ')
if [ "$CHANGE_COUNT" -gt 0 ]; then
echo "📝 Tracked $CHANGE_COUNT file(s) for agent knowledge"
fi
`;
const hookPath = path.join(hooksDir, 'post-commit');
// Check if hook already exists
if (await fs.pathExists(hookPath)) {
const existing = await fs.readFile(hookPath, 'utf-8');
if (existing.includes('Agent Knowledge System')) {
if (options.force) {
// Replace the AKS section
const beforeAKS = existing.split('# Agent Knowledge System')[0];
await fs.writeFile(hookPath, beforeAKS.trim() + '\n\n' + hookContent);
spinner.succeed('Replaced existing Agent Knowledge System hook');
} else {
spinner.info('Hook already installed. Use --force to reinstall.');
return;
}
} else {
// Append to existing hook
await fs.appendFile(hookPath, '\n\n' + hookContent);
spinner.succeed('Appended to existing post-commit hook');
}
} else {
await fs.writeFile(hookPath, hookContent);
spinner.succeed('Created post-commit hook');
}
// Make executable
await fs.chmod(hookPath, '755');
console.log(chalk.green('\n✅ Git hooks installed successfully!\n'));
console.log(chalk.gray('Changes will now be tracked automatically on each commit.'));
} catch (error) {
spinner.fail('Failed to install hooks');
console.error(chalk.red(error.message));
process.exit(1);
}
}

View file

@ -0,0 +1,131 @@
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
import yaml from 'yaml';
export async function statusCommand() {
const cwd = process.cwd();
const knowledgeDir = path.join(cwd, '.knowledge');
console.log(chalk.blue('\n📊 Agent Knowledge System Status\n'));
// Check initialization
if (!(await fs.pathExists(knowledgeDir))) {
console.log(chalk.red('❌ Not initialized'));
console.log(chalk.gray('Run: npx agent-knowledge init'));
return;
}
console.log(chalk.green('✓ Initialized\n'));
// Load config
const configPath = path.join(knowledgeDir, 'config.yaml');
if (await fs.pathExists(configPath)) {
const config = yaml.parse(await fs.readFile(configPath, 'utf-8'));
console.log(chalk.cyan('Configuration:'));
console.log(chalk.gray(` Provider: ${config.provider}`));
console.log(chalk.gray(` Model: ${config.model}`));
console.log('');
}
// Load registry
const registryPath = path.join(knowledgeDir, 'agent-registry.yaml');
const registry = yaml.parse(await fs.readFile(registryPath, 'utf-8'));
console.log(chalk.cyan(`Registered Agents (${registry.agents.length}):`));
if (registry.agents.length === 0) {
console.log(chalk.gray(' No agents registered'));
console.log(chalk.gray(' Add one with: npx agent-knowledge add-agent <path>'));
} else {
for (const agent of registry.agents) {
const agentDir = path.join(cwd, agent.agent_dir);
const memoryPath = path.join(agentDir, 'memory.md');
let status = '✓';
let memoryInfo = '';
if (!(await fs.pathExists(agentDir))) {
status = '❌';
memoryInfo = chalk.red('(missing)');
} else if (await fs.pathExists(memoryPath)) {
const stats = await fs.stat(memoryPath);
const lastUpdate = stats.mtime.toLocaleDateString();
memoryInfo = chalk.gray(`(updated: ${lastUpdate})`);
} else {
memoryInfo = chalk.yellow('(no memory yet)');
}
console.log(` ${status} ${chalk.white(agent.name)}`);
console.log(` ${chalk.gray('Path:')} ${agent.path}`);
console.log(` ${chalk.gray('Memory:')} ${memoryInfo}`);
console.log(` ${chalk.gray('Watches:')} ${agent.watches.length} patterns`);
}
}
// Check pending changes
console.log('');
const changesPath = path.join(knowledgeDir, 'changes.jsonl');
let pendingChanges = 0;
if (await fs.pathExists(changesPath)) {
const content = await fs.readFile(changesPath, 'utf-8');
pendingChanges = content.split('\n').filter((line) => line.trim()).length;
}
console.log(chalk.cyan('Pending Changes:'));
if (pendingChanges === 0) {
console.log(chalk.gray(' No pending changes'));
} else {
console.log(chalk.yellow(` ${pendingChanges} changes waiting to be processed`));
console.log(chalk.gray(' Run: npx agent-knowledge update'));
}
// Check git hooks (handle worktrees)
console.log('');
console.log(chalk.cyan('Git Hooks:'));
let hookPath;
const gitPath = path.join(cwd, '.git');
const gitStat = await fs.stat(gitPath).catch(() => null);
if (gitStat?.isFile()) {
// It's a worktree - parse gitdir reference
const gitContent = await fs.readFile(gitPath, 'utf-8');
const match = gitContent.match(/gitdir:\s*(.+)/);
if (match) {
const worktreeGitDir = match[1].trim();
// Hooks are in the main repo's .git/hooks
const mainGitDir = path.resolve(worktreeGitDir, '..', '..');
hookPath = path.join(mainGitDir, 'hooks', 'post-commit');
}
} else {
hookPath = path.join(cwd, '.git', 'hooks', 'post-commit');
}
if (hookPath && (await fs.pathExists(hookPath))) {
const hookContent = await fs.readFile(hookPath, 'utf-8');
if (hookContent.includes('Agent Knowledge System')) {
console.log(chalk.green(' ✓ post-commit hook installed'));
} else {
console.log(chalk.yellow(' ⚠ post-commit hook exists but missing AKS integration'));
console.log(chalk.gray(' Run: npx agent-knowledge setup-hooks'));
}
} else {
console.log(chalk.yellow(' ⚠ post-commit hook not installed'));
console.log(chalk.gray(' Run: npx agent-knowledge setup-hooks'));
}
// Archive stats
console.log('');
const archiveDir = path.join(knowledgeDir, 'archive');
if (await fs.pathExists(archiveDir)) {
const archives = await fs.readdir(archiveDir);
if (archives.length > 0) {
console.log(chalk.cyan('Archive:'));
console.log(chalk.gray(` ${archives.length} archived change files`));
}
}
console.log('');
}

View file

@ -0,0 +1,996 @@
import chalk from 'chalk';
import ora from 'ora';
import inquirer from 'inquirer';
import fs from 'fs-extra';
import path from 'path';
import yaml from 'yaml';
// Team templates based on software development best practices
const TEAM_TEMPLATES = {
startup: {
name: 'Startup Team',
description: 'Lean team for small projects and MVPs (3 agents)',
roles: [
{
id: 'tech-lead',
name: 'Tech Lead',
icon: '👨‍💻',
role: 'Technical Leader & Senior Developer',
identity:
'Experienced technical leader who makes architecture decisions, reviews code, and handles complex implementation. Balances pragmatism with best practices.',
expertise: [
'Architecture decisions',
'Code review',
'Technical debt management',
'Performance optimization',
'Mentoring and knowledge sharing',
],
principles: [
'Ship fast but maintain quality',
'Make reversible decisions quickly',
'Document critical decisions',
'Keep the codebase simple and maintainable',
],
},
{
id: 'developer',
name: 'Developer',
icon: '💻',
role: 'Full-Stack Developer',
identity:
'Versatile developer who can work across the stack. Focused on shipping features while maintaining code quality.',
expertise: [
'Feature implementation',
'Bug fixes',
'API development',
'Frontend development',
'Database operations',
],
principles: [
'Write clean, readable code',
'Test critical paths',
'Ask questions early',
'Refactor as you go',
],
},
{
id: 'qa',
name: 'QA Engineer',
icon: '🧪',
role: 'Quality Assurance Specialist',
identity:
'Quality guardian who ensures the product works correctly. Thinks about edge cases and user experience.',
expertise: [
'Test planning',
'Manual testing',
'Automated testing',
'Bug reporting',
'User experience validation',
],
principles: [
'Think like a user',
'Test edge cases',
'Automate repetitive tests',
'Report bugs with clear reproduction steps',
],
},
],
},
standard: {
name: 'Standard Team',
description: 'Balanced team for most projects (6 agents)',
roles: [
{
id: 'product-owner',
name: 'Product Owner',
icon: '📋',
role: 'Product Strategy & Requirements Specialist',
identity:
'Voice of the customer and business. Defines what to build, prioritizes features, and ensures the product delivers value.',
expertise: [
'Requirements gathering',
'User story writing',
'Feature prioritization',
'Stakeholder communication',
'Product roadmap planning',
],
principles: [
'Focus on user value',
'Prioritize ruthlessly',
'Define clear acceptance criteria',
'Balance business needs with technical constraints',
],
tools: ['Can use Perplexity for market research and competitive analysis'],
},
{
id: 'architect',
name: 'Architect',
icon: '🏗️',
role: 'System Architecture & Design Specialist',
identity:
'Designs the overall system structure. Makes decisions about patterns, technologies, and how components interact.',
expertise: [
'System design',
'API design',
'Database modeling',
'Integration patterns',
'Scalability planning',
'Technology selection',
],
principles: [
'Design for change',
'Keep it simple until complexity is needed',
'Document architectural decisions (ADRs)',
'Consider non-functional requirements',
'Plan for failure',
],
tools: ['Can use Perplexity for researching best practices and patterns'],
},
{
id: 'senior-dev',
name: 'Senior Developer',
icon: '👨‍💻',
role: 'Senior Software Engineer',
identity:
'Experienced developer who handles complex features, mentors others, and ensures code quality through reviews.',
expertise: [
'Complex feature implementation',
'Code review',
'Performance optimization',
'Technical debt reduction',
'Debugging complex issues',
],
principles: [
'Write self-documenting code',
'Review code thoroughly but constructively',
'Mentor through code examples',
'Balance perfection with pragmatism',
],
},
{
id: 'developer',
name: 'Developer',
icon: '💻',
role: 'Software Developer',
identity:
'Core implementer who turns designs into working code. Focused on feature delivery and bug fixing.',
expertise: [
'Feature implementation',
'Bug fixes',
'Unit testing',
'API integration',
'Code documentation',
],
principles: [
'Follow established patterns',
'Write tests for your code',
'Ask for help when stuck',
'Keep commits small and focused',
],
},
{
id: 'security',
name: 'Security Engineer',
icon: '🔒',
role: 'Application Security Specialist',
identity:
'Security guardian who identifies vulnerabilities, reviews code for security issues, and ensures secure practices.',
expertise: [
'Security code review',
'OWASP Top 10',
'Authentication & authorization',
'Input validation',
'Secure API design',
'Dependency vulnerability scanning',
],
principles: [
'Security is everyones responsibility',
'Defense in depth',
'Least privilege principle',
'Never trust user input',
'Keep dependencies updated',
],
tools: ['Can use Perplexity for CVE research and security best practices'],
},
{
id: 'qa-lead',
name: 'QA Lead',
icon: '🧪',
role: 'Quality Assurance Lead',
identity:
'Quality strategist who designs test plans, sets up automation frameworks, and ensures comprehensive test coverage.',
expertise: [
'Test strategy',
'Test automation architecture',
'Performance testing',
'Integration testing',
'CI/CD test integration',
'Bug triage',
],
principles: [
'Test early, test often',
'Automate regression tests',
'Focus on critical user journeys',
'Quality is built in, not tested in',
],
},
],
},
enterprise: {
name: 'Enterprise Team',
description: 'Full team for large/critical projects (10 agents)',
roles: [
{
id: 'product-owner',
name: 'Product Owner',
icon: '📋',
role: 'Product Strategy & Vision',
identity:
'Strategic product leader who defines the product vision, manages the backlog, and ensures business value delivery.',
expertise: [
'Product vision',
'Roadmap planning',
'Stakeholder management',
'Market analysis',
'Feature prioritization',
'ROI analysis',
],
principles: [
'Data-driven decisions',
'User-centric design',
'Clear success metrics',
'Transparent prioritization',
],
tools: ['Perplexity for market research', 'Competitive analysis'],
},
{
id: 'scrum-master',
name: 'Scrum Master',
icon: '🎯',
role: 'Agile Coach & Process Facilitator',
identity:
'Process guardian who ensures the team follows agile practices, removes blockers, and facilitates ceremonies.',
expertise: [
'Agile methodologies',
'Sprint planning',
'Retrospectives',
'Blocker resolution',
'Team velocity tracking',
'Process improvement',
],
principles: [
'Servant leadership',
'Continuous improvement',
'Protect the team',
'Foster collaboration',
],
},
{
id: 'architect',
name: 'Solutions Architect',
icon: '🏗️',
role: 'Enterprise Architecture Specialist',
identity:
'Senior architect who designs large-scale systems, ensures consistency across teams, and makes strategic technical decisions.',
expertise: [
'Enterprise architecture',
'Microservices design',
'Cloud architecture',
'API gateway patterns',
'Event-driven architecture',
'Data architecture',
],
principles: [
'Architect for the enterprise',
'Standardize where it matters',
'Document everything',
'Plan for 10x scale',
],
tools: ['Perplexity for architecture patterns research'],
},
{
id: 'tech-lead',
name: 'Tech Lead',
icon: '👨‍💻',
role: 'Technical Team Lead',
identity:
'Hands-on technical leader who guides the development team, makes day-to-day technical decisions, and ensures code quality.',
expertise: [
'Technical leadership',
'Code review',
'Sprint technical planning',
'Technical debt management',
'Team mentoring',
],
principles: [
'Lead by example',
'Unblock the team',
'Balance velocity and quality',
'Grow team capabilities',
],
},
{
id: 'senior-dev-1',
name: 'Senior Developer (Backend)',
icon: '⚙️',
role: 'Senior Backend Engineer',
identity:
'Backend specialist focused on APIs, business logic, data processing, and system integrations.',
expertise: [
'API development',
'Database optimization',
'Business logic implementation',
'Integration patterns',
'Performance tuning',
],
principles: [
'API-first design',
'Optimize for maintainability',
'Handle errors gracefully',
'Log meaningfully',
],
},
{
id: 'senior-dev-2',
name: 'Senior Developer (Frontend)',
icon: '🎨',
role: 'Senior Frontend Engineer',
identity:
'Frontend specialist focused on user interfaces, state management, and user experience implementation.',
expertise: [
'UI architecture',
'State management',
'Component design',
'Accessibility',
'Performance optimization',
],
principles: [
'User experience first',
'Accessible by default',
'Responsive design',
'Optimize perceived performance',
],
},
{
id: 'security',
name: 'Security Engineer',
icon: '🔒',
role: 'Application Security Engineer',
identity:
'Security specialist who performs security reviews, penetration testing mindset, and ensures compliance.',
expertise: [
'Security architecture review',
'Threat modeling',
'OWASP compliance',
'Security testing',
'Incident response',
'Compliance (SOC2, GDPR)',
],
principles: [
'Shift security left',
'Zero trust architecture',
'Defense in depth',
'Security by design',
],
tools: ['Perplexity for CVE and security research'],
},
{
id: 'devops',
name: 'DevOps Engineer',
icon: '🚀',
role: 'DevOps & Platform Engineer',
identity:
'Infrastructure and automation specialist who manages CI/CD, deployments, and platform reliability.',
expertise: [
'CI/CD pipelines',
'Infrastructure as Code',
'Container orchestration',
'Monitoring & alerting',
'Deployment strategies',
'Cost optimization',
],
principles: [
'Automate everything',
'Infrastructure as code',
'Monitor proactively',
'Plan for failure',
],
},
{
id: 'qa-lead',
name: 'QA Lead',
icon: '🧪',
role: 'Quality Engineering Lead',
identity:
'Quality strategist who defines test strategy, manages QA team, and ensures comprehensive quality coverage.',
expertise: [
'Test strategy',
'Automation frameworks',
'Performance testing',
'Security testing',
'Quality metrics',
'Release quality gates',
],
principles: [
'Quality is everyones job',
'Shift testing left',
'Automate strategically',
'Measure quality',
],
},
{
id: 'tech-writer',
name: 'Technical Writer',
icon: '📝',
role: 'Documentation Specialist',
identity:
'Documentation expert who creates clear, comprehensive technical documentation for developers and users.',
expertise: [
'API documentation',
'User guides',
'Architecture documentation',
'README files',
'Runbooks',
'Knowledge base',
],
principles: [
'Write for the audience',
'Keep docs up to date',
'Include examples',
'Document decisions',
],
},
],
},
};
export async function teamCommand(modulePath, options) {
// List templates (no path required)
if (options.list) {
console.log(chalk.blue('\n📋 Available Team Templates:\n'));
for (const [key, template] of Object.entries(TEAM_TEMPLATES)) {
console.log(chalk.cyan(` ${key}`));
console.log(chalk.white(` ${template.name} - ${template.description}`));
console.log(chalk.gray(` Roles: ${template.roles.map((r) => r.name).join(', ')}`));
console.log('');
}
return;
}
// Require path if not listing
if (!modulePath) {
console.log(chalk.red('Error: Path is required'));
console.log(chalk.gray('Usage: npx agent-knowledge team <path> [options]'));
console.log(chalk.gray(' npx agent-knowledge team --list'));
return;
}
const cwd = process.cwd();
const fullPath = path.join(cwd, modulePath);
const template = TEAM_TEMPLATES[options.template];
if (!template) {
console.log(chalk.red(`Unknown template: ${options.template}`));
console.log(chalk.gray('Use --list to see available templates'));
return;
}
console.log(chalk.blue(`\n👥 Adding ${template.name} to ${modulePath}\n`));
console.log(chalk.gray(`Template: ${template.description}\n`));
// Validate path
if (!(await fs.pathExists(fullPath))) {
console.log(chalk.red(`Path does not exist: ${fullPath}`));
return;
}
// Create .knowledge directory if needed (minimal setup for Claude Code only mode)
const knowledgeDir = path.join(cwd, '.knowledge');
if (!(await fs.pathExists(knowledgeDir))) {
await fs.ensureDir(knowledgeDir);
// Create minimal registry
await fs.writeFile(path.join(knowledgeDir, 'agent-registry.yaml'), 'agents: []\n');
console.log(chalk.gray('Created .knowledge/ directory (Claude Code only mode)\n'));
}
// Show team preview
console.log(chalk.cyan('Team members to create:\n'));
for (const role of template.roles) {
console.log(` ${role.icon} ${chalk.white(role.name)} - ${chalk.gray(role.role)}`);
}
console.log('');
if (options.dryRun) {
console.log(chalk.yellow('[DRY RUN] No files created'));
return;
}
// Confirm
const { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: `Create ${template.roles.length} team agents?`,
default: true,
},
]);
if (!confirm) {
console.log(chalk.yellow('Aborted.'));
return;
}
const spinner = ora('Scanning module for context...').start();
// Gather domain knowledge from the module
const domainContext = await gatherDomainContext(fullPath, modulePath);
spinner.text = 'Creating team...';
try {
// Create .agent/team directory
const teamDir = path.join(fullPath, '.agent', 'team');
await fs.ensureDir(teamDir);
// Get module name for context
const moduleName = path.basename(modulePath);
// Create each team member with domain context
for (const role of template.roles) {
const agentFile = path.join(teamDir, `${role.id}.md`);
const content = generateTeamAgentContent(role, moduleName, modulePath, domainContext);
await fs.writeFile(agentFile, content);
spinner.text = `Created ${role.name}...`;
}
// Create team index
const indexContent = generateTeamIndex(template, moduleName, modulePath);
await fs.writeFile(path.join(fullPath, '.agent', 'team.md'), indexContent);
// Create/update main agent.md if it doesn't exist
const mainAgentPath = path.join(fullPath, '.agent', 'agent.md');
if (!(await fs.pathExists(mainAgentPath))) {
const mainContent = generateMainAgentWithTeam(template, moduleName, modulePath);
await fs.writeFile(mainAgentPath, mainContent);
}
// Create memory.md if it doesn't exist
const memoryPath = path.join(fullPath, '.agent', 'memory.md');
if (!(await fs.pathExists(memoryPath))) {
await fs.writeFile(
memoryPath,
`# ${moduleName} - Memory
This file is automatically updated with learnings from code changes.
## Recent Updates
*No updates yet.*
`
);
}
// Update registry with team watch patterns
await updateRegistryWithTeam(cwd, modulePath, template);
spinner.succeed(`Created ${template.roles.length} team agents`);
console.log(chalk.green(`\n✅ Team created at ${modulePath}/.agent/team/\n`));
console.log(chalk.cyan('Files created:'));
console.log(chalk.gray(` ${modulePath}/.agent/team.md - Team overview`));
for (const role of template.roles) {
console.log(chalk.gray(` ${modulePath}/.agent/team/${role.id}.md`));
}
console.log(chalk.cyan('\nUsage with Claude:'));
console.log(chalk.white(` "Read ${modulePath}/.agent/team.md and help as the Architect"`));
console.log(chalk.white(` "Act as the Security Engineer and review this code"`));
console.log(chalk.white(` "As the Product Owner, help me write user stories"\n`));
} catch (error) {
spinner.fail('Failed to create team');
console.error(chalk.red(error.message));
}
}
/**
* Gather domain-specific context from the module
*/
async function gatherDomainContext(fullPath, modulePath) {
const context = {
name: path.basename(modulePath),
description: '',
techStack: [],
features: [],
structure: [],
dependencies: [],
readme: '',
hasBackend: false,
hasMobile: false,
hasWeb: false,
hasLanding: false,
};
// Check for package.json
const packageJsonPath = path.join(fullPath, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
try {
const pkg = await fs.readJson(packageJsonPath);
context.description = pkg.description || '';
context.dependencies = Object.keys(pkg.dependencies || {}).slice(0, 15);
// Detect tech from dependencies
if (pkg.dependencies?.['@nestjs/core']) context.techStack.push('NestJS');
if (pkg.dependencies?.['svelte'] || pkg.dependencies?.['@sveltejs/kit'])
context.techStack.push('SvelteKit');
if (pkg.dependencies?.['expo'] || pkg.dependencies?.['react-native'])
context.techStack.push('Expo/React Native');
if (pkg.dependencies?.['astro']) context.techStack.push('Astro');
if (pkg.dependencies?.['next']) context.techStack.push('Next.js');
if (pkg.dependencies?.['drizzle-orm']) context.techStack.push('Drizzle ORM');
if (pkg.dependencies?.['prisma']) context.techStack.push('Prisma');
if (pkg.dependencies?.['@supabase/supabase-js']) context.techStack.push('Supabase');
} catch (e) {
/* ignore */
}
}
// Check for README
for (const readmeName of ['README.md', 'readme.md']) {
const readmePath = path.join(fullPath, readmeName);
if (await fs.pathExists(readmePath)) {
const content = await fs.readFile(readmePath, 'utf-8');
context.readme = content.substring(0, 2000);
// Try to extract features from README
const featuresMatch = content.match(/## Features[\s\S]*?(?=##|$)/i);
if (featuresMatch) {
const featureLines = featuresMatch[0].match(/^[-*]\s+.+$/gm);
if (featureLines) {
context.features = featureLines.slice(0, 10).map((f) => f.replace(/^[-*]\s+/, ''));
}
}
break;
}
}
// Check for nested app structure (like ManaCore)
const appsDir = path.join(fullPath, 'apps');
if (await fs.pathExists(appsDir)) {
const subApps = await fs.readdir(appsDir).catch(() => []);
context.hasBackend = subApps.includes('backend');
context.hasMobile = subApps.includes('mobile');
context.hasWeb = subApps.includes('web');
context.hasLanding = subApps.includes('landing');
// Scan each sub-app for tech stack
for (const subApp of subApps) {
const subPkgPath = path.join(appsDir, subApp, 'package.json');
if (await fs.pathExists(subPkgPath)) {
try {
const subPkg = await fs.readJson(subPkgPath);
if (subPkg.dependencies?.['@nestjs/core'] && !context.techStack.includes('NestJS')) {
context.techStack.push('NestJS');
}
if (
(subPkg.dependencies?.['svelte'] || subPkg.dependencies?.['@sveltejs/kit']) &&
!context.techStack.includes('SvelteKit')
) {
context.techStack.push('SvelteKit');
}
if (
(subPkg.dependencies?.['expo'] || subPkg.dependencies?.['react-native']) &&
!context.techStack.includes('Expo/React Native')
) {
context.techStack.push('Expo/React Native');
}
if (subPkg.dependencies?.['astro'] && !context.techStack.includes('Astro')) {
context.techStack.push('Astro');
}
} catch (e) {
/* ignore */
}
}
}
}
// Scan src directory for structure hints
const srcDir = path.join(fullPath, 'src');
if (await fs.pathExists(srcDir)) {
const srcContents = await fs.readdir(srcDir).catch(() => []);
context.structure = srcContents.filter((f) => !f.startsWith('.')).slice(0, 10);
}
return context;
}
function generateTeamAgentContent(role, moduleName, modulePath, domainContext = {}) {
const expertiseList = role.expertise.map((e) => `- ${e}`).join('\n');
const principlesList = role.principles.map((p, i) => `${i + 1}. ${p}`).join('\n');
const toolsList = role.tools ? `\n## Tools\n${role.tools.map((t) => `- ${t}`).join('\n')}\n` : '';
// Build domain-specific sections
const domainSection = generateDomainSection(role, moduleName, domainContext);
return `# ${role.icon} ${role.name}
## Module Context
**Module:** ${moduleName}
**Path:** ${modulePath}
${domainContext.description ? `**Description:** ${domainContext.description}` : ''}
${domainContext.techStack?.length > 0 ? `**Tech Stack:** ${domainContext.techStack.join(', ')}` : ''}
${domainContext.hasBackend || domainContext.hasMobile || domainContext.hasWeb ? `**Platforms:** ${[domainContext.hasBackend && 'Backend', domainContext.hasMobile && 'Mobile', domainContext.hasWeb && 'Web', domainContext.hasLanding && 'Landing'].filter(Boolean).join(', ')}` : ''}
## Identity
${role.identity}
## Role
${role.role}
${domainSection}
## Expertise
${expertiseList}
## Principles
${principlesList}
${toolsList}
## How to Invoke
Ask Claude to act as this role:
- "As the ${role.name}, review this code"
- "Help me as the ${role.name} with..."
- "What would the ${role.name} think about..."
## Collaboration
This agent works with other team members. For complex tasks:
- Consult the **Architect** for system design decisions
- Coordinate with **Security** for security-sensitive changes
- Work with **QA** on test coverage
`;
}
/**
* Generate domain-specific content based on role and context
*/
function generateDomainSection(role, moduleName, context) {
if (!context || Object.keys(context).length === 0) {
return '';
}
const sections = [];
// Product Owner - needs to know about features and product
if (role.id === 'product-owner') {
sections.push(`
## Product Knowledge: ${moduleName}
`);
if (context.description) {
sections.push(`**What it does:** ${context.description}`);
}
if (context.features?.length > 0) {
sections.push(`
**Known Features:**
${context.features.map((f) => `- ${f}`).join('\n')}`);
}
if (context.hasBackend || context.hasMobile || context.hasWeb) {
sections.push(`
**Available Platforms:**
${context.hasBackend ? '- Backend API (NestJS)\n' : ''}${context.hasMobile ? '- Mobile App (Expo/React Native)\n' : ''}${context.hasWeb ? '- Web App (SvelteKit)\n' : ''}${context.hasLanding ? '- Landing Page (Astro)\n' : ''}`);
}
sections.push(`
**Your Focus:** Understand what users need from ${moduleName}, define clear requirements, and prioritize features that deliver the most value.`);
}
// Architect - needs to know about tech stack and structure
if (role.id === 'architect' || role.id === 'tech-lead') {
sections.push(`
## Technical Context: ${moduleName}
`);
if (context.techStack?.length > 0) {
sections.push(`**Tech Stack:** ${context.techStack.join(', ')}`);
}
if (context.structure?.length > 0) {
sections.push(`
**Code Structure:**
\`\`\`
src/
${context.structure.map((s) => `├── ${s}/`).join('\n')}
\`\`\``);
}
if (context.dependencies?.length > 0) {
sections.push(`
**Key Dependencies:** ${context.dependencies.slice(0, 8).join(', ')}`);
}
if (context.hasBackend || context.hasMobile || context.hasWeb) {
sections.push(`
**Architecture Pattern:** Multi-platform app with ${[context.hasBackend && 'Backend', context.hasMobile && 'Mobile', context.hasWeb && 'Web'].filter(Boolean).join(' + ')}`);
}
}
// Backend devs - need backend context
if (role.id === 'senior-dev-1' || (role.id === 'senior-dev' && context.hasBackend)) {
if (context.hasBackend) {
sections.push(`
## Backend Context
**Framework:** NestJS
**Location:** \`${context.name}/apps/backend/\`
**Focus:** API endpoints, business logic, database operations`);
}
}
// Frontend devs - need frontend context
if (role.id === 'senior-dev-2' || role.id === 'developer') {
if (context.hasWeb || context.hasMobile) {
sections.push(`
## Frontend Context
${context.hasWeb ? `**Web:** SvelteKit at \`${context.name}/apps/web/\`\n` : ''}${context.hasMobile ? `**Mobile:** Expo at \`${context.name}/apps/mobile/\`\n` : ''}**Focus:** User interface, state management, user experience`);
}
}
// Security - needs to know about auth and sensitive areas
if (role.id === 'security') {
sections.push(`
## Security Context: ${moduleName}
**Review Focus:**
- Authentication flows (if using @manacore/shared-auth)
- API endpoint authorization
- Input validation on all endpoints
- Sensitive data handling
- Dependency vulnerabilities
${context.hasBackend ? '**Backend Security:** Review NestJS guards, validators, and middleware\n' : ''}${context.hasMobile || context.hasWeb ? '**Client Security:** Review token storage, API calls, input sanitization\n' : ''}`);
}
// QA - needs to know about testable features
if (role.id === 'qa-lead' || role.id === 'qa') {
sections.push(`
## Testing Context: ${moduleName}
`);
if (context.features?.length > 0) {
sections.push(`**Features to Test:**
${context.features
.slice(0, 5)
.map((f) => `- [ ] ${f}`)
.join('\n')}`);
}
if (context.hasBackend || context.hasMobile || context.hasWeb) {
sections.push(`
**Test Coverage Needed:**
${context.hasBackend ? '- [ ] API endpoint tests (backend)\n' : ''}${context.hasWeb ? '- [ ] E2E tests (web)\n' : ''}${context.hasMobile ? '- [ ] Mobile app tests\n' : ''}${context.hasLanding ? '- [ ] Landing page tests\n' : ''}`);
}
}
// DevOps - needs deployment context
if (role.id === 'devops') {
sections.push(`
## DevOps Context: ${moduleName}
**Deployments:**
${context.hasBackend ? '- Backend: NestJS service\n' : ''}${context.hasWeb ? '- Web: SvelteKit (SSR or static)\n' : ''}${context.hasMobile ? '- Mobile: Expo (iOS/Android builds)\n' : ''}${context.hasLanding ? '- Landing: Astro (static)\n' : ''}
**Focus:** CI/CD pipelines, environment configuration, monitoring`);
}
return sections.join('\n');
}
function generateTeamIndex(template, moduleName, modulePath) {
let content = `# ${moduleName} Development Team
## Team Template: ${template.name}
${template.description}
## Module Context
- **Module:** ${moduleName}
- **Path:** ${modulePath}
## Team Members
| Role | Specialty | File |
|------|-----------|------|
`;
for (const role of template.roles) {
content += `| ${role.icon} ${role.name} | ${role.role} | [${role.id}.md](./team/${role.id}.md) |\n`;
}
content += `
## How to Use This Team
### Single Role
Ask Claude to act as a specific team member:
\`\`\`
Read ${modulePath}/.agent/team/architect.md and help me design the API
\`\`\`
### Multiple Perspectives
Get input from multiple team members:
\`\`\`
Read the team files in ${modulePath}/.agent/team/ and give me perspectives
from the Architect, Security Engineer, and QA Lead on this approach
\`\`\`
### Team Review
Request a full team review:
\`\`\`
Act as each team member in ${modulePath}/.agent/team/ and review this PR
\`\`\`
## Team Dynamics
### For New Features
1. **Product Owner** - Define requirements and acceptance criteria
2. **Architect** - Design the solution
3. **Senior Developer** - Implement with code review
4. **Security** - Security review
5. **QA** - Test planning and execution
### For Bug Fixes
1. **Developer** - Investigate and fix
2. **QA** - Verify fix and check for regression
3. **Security** - If security-related, review the fix
### For Architecture Changes
1. **Architect** - Propose and document design
2. **Tech Lead** - Review feasibility
3. **Security** - Assess security implications
4. **DevOps** - Assess operational impact
`;
return content;
}
function generateMainAgentWithTeam(template, moduleName, modulePath) {
return `# ${moduleName} Expert
## Identity
You are the **${moduleName} Expert** with access to a full development team.
You can channel any team member's expertise as needed.
## Team Available
${template.roles.map((r) => `- ${r.icon} **${r.name}** - ${r.role}`).join('\n')}
See [team.md](./team.md) for full team details.
## How to Use
- For general questions, I'll respond as the module expert
- For specific perspectives, ask me to act as a team member
- For comprehensive reviews, I can provide multiple perspectives
## Module Context
- **Path:** ${modulePath}
- **Team:** ${template.name} (${template.roles.length} roles)
## Principles
1. Leverage the right expertise for each task
2. Consider multiple perspectives on complex decisions
3. Maintain knowledge continuity through memory.md
`;
}
async function updateRegistryWithTeam(cwd, modulePath, template) {
const registryPath = path.join(cwd, '.knowledge', 'agent-registry.yaml');
let registry = { agents: [] };
if (await fs.pathExists(registryPath)) {
const content = await fs.readFile(registryPath, 'utf-8');
registry = yaml.parse(content) || { agents: [] };
}
// Remove existing entry
registry.agents = registry.agents.filter((a) => a.path !== modulePath);
// Add new entry with team info
registry.agents.push({
path: modulePath,
agent_dir: `${modulePath}/.agent`,
name: `${path.basename(modulePath)} Team`,
team: template.name,
team_size: template.roles.length,
watches: [`${modulePath}/**`],
});
await fs.writeFile(registryPath, yaml.stringify(registry));
}

View file

@ -0,0 +1,368 @@
import chalk from 'chalk';
import ora from 'ora';
import fs from 'fs-extra';
import path from 'path';
import yaml from 'yaml';
import readline from 'readline';
import { spawn } from 'child_process';
import { LLMClient } from '../lib/llm-client.js';
import { loadConfig } from '../lib/config.js';
export async function updateCommand(options) {
const cwd = process.cwd();
const knowledgeDir = path.join(cwd, '.knowledge');
console.log(chalk.blue('\n🔄 Updating Agent Knowledge...\n'));
// Check initialization
if (!(await fs.pathExists(knowledgeDir))) {
console.log(chalk.red('Error: Agent Knowledge System not initialized.'));
console.log(chalk.gray('Run: npx agent-knowledge init'));
process.exit(1);
}
// Load registry
const registryPath = path.join(knowledgeDir, 'agent-registry.yaml');
const registry = yaml.parse(await fs.readFile(registryPath, 'utf-8'));
if (registry.agents.length === 0) {
console.log(chalk.yellow('No agents registered. Add agents first:'));
console.log(chalk.gray(' npx agent-knowledge add-agent <path>'));
return;
}
// Read changes
const changesPath = path.join(knowledgeDir, 'changes.jsonl');
const changes = await readChanges(changesPath);
if (changes.length === 0) {
console.log(chalk.yellow('No pending changes to process.'));
console.log(chalk.gray('Make some commits and changes will be tracked automatically.'));
return;
}
console.log(chalk.gray(`Found ${changes.length} changes to process\n`));
// Filter agents if specific one requested
let agents = registry.agents;
if (options.agent) {
agents = agents.filter((a) => a.path === options.agent || a.agent_dir.includes(options.agent));
if (agents.length === 0) {
console.log(chalk.red(`No agent found matching: ${options.agent}`));
return;
}
}
// Group changes by agent
const changesByAgent = groupChangesByAgent(changes, agents);
// If --claude mode, generate prompt file and spawn Claude Code
if (options.claude) {
await updateWithClaude(cwd, knowledgeDir, agents, changesByAgent, options);
return;
}
// Standard LLM mode
const config = await loadConfig(cwd);
const llm = new LLMClient(config);
if (options.model) {
console.log(chalk.gray(`Using model override: ${options.model}\n`));
}
// Process each agent
let totalUpdated = 0;
for (const agent of agents) {
const agentChanges = changesByAgent[agent.agent_dir] || [];
if (agentChanges.length === 0) {
console.log(chalk.gray(`⏭️ ${agent.name}: No relevant changes`));
continue;
}
console.log(chalk.cyan(`\n🤖 ${agent.name} (${agentChanges.length} changes)`));
if (options.dryRun) {
console.log(chalk.gray(' [DRY RUN] Would update memory.md with:'));
for (const change of agentChanges.slice(0, 5)) {
console.log(chalk.gray(` - ${change.file}: ${change.message.substring(0, 50)}`));
}
if (agentChanges.length > 5) {
console.log(chalk.gray(` ... and ${agentChanges.length - 5} more`));
}
continue;
}
const spinner = ora('Updating memory...').start();
try {
await updateAgentMemory(cwd, agent, agentChanges, llm, options.model);
spinner.succeed(`Updated ${agent.agent_dir}/memory.md`);
totalUpdated++;
} catch (error) {
spinner.fail(`Failed to update ${agent.name}: ${error.message}`);
}
}
// Archive processed changes (unless dry run)
if (!options.dryRun && totalUpdated > 0) {
await archiveChanges(knowledgeDir);
console.log(chalk.gray('\n📦 Changes archived'));
}
console.log(chalk.green(`\n✅ Updated ${totalUpdated} agent(s)\n`));
}
/**
* Update agent memories using Claude Code
*/
async function updateWithClaude(cwd, knowledgeDir, agents, changesByAgent, options) {
// Filter to only agents with changes
const agentsWithChanges = agents.filter((a) => (changesByAgent[a.agent_dir] || []).length > 0);
if (agentsWithChanges.length === 0) {
console.log(chalk.yellow('No agents have relevant changes.'));
return;
}
console.log(
chalk.cyan(`\n📝 Generating Claude Code prompt for ${agentsWithChanges.length} agent(s)...\n`)
);
// Build the prompt
let prompt = `# Agent Memory Update Task
You need to update the memory.md files for the following agents based on recent code changes.
## Instructions
For EACH agent listed below:
1. Read the agent's existing memory.md file (if it exists)
2. Analyze the changes relevant to that agent
3. Update the memory.md with new learnings:
- Add a dated section at the TOP (under "## Recent Updates")
- Extract: new patterns, bug fixes, breaking changes, new features, architectural decisions
- Be concise but capture important context
- Keep under 400 lines total
4. Write the updated content to the memory.md file
## Agents to Update
`;
for (const agent of agentsWithChanges) {
const agentChanges = changesByAgent[agent.agent_dir] || [];
// Group by date
const byDate = {};
for (const change of agentChanges) {
const date = change.timestamp?.split('T')[0] || 'unknown';
byDate[date] = byDate[date] || [];
byDate[date].push({
file: change.file,
message: change.message,
author: change.author,
status: change.status,
});
}
prompt += `### ${agent.name}
- **Memory file**: \`${agent.agent_dir}/memory.md\`
- **Changes**: ${agentChanges.length} file(s) modified
\`\`\`json
${JSON.stringify(byDate, null, 2)}
\`\`\`
`;
}
prompt += `
## After Completion
After updating all memory.md files, provide a summary of what was updated.
`;
// Write prompt to file
const promptPath = path.join(knowledgeDir, 'claude-update-memory.md');
await fs.writeFile(promptPath, prompt);
console.log(chalk.gray(`Prompt written to: ${promptPath}\n`));
if (options.dryRun) {
console.log(chalk.yellow('[DRY RUN] Would run Claude Code with the above prompt.'));
console.log(
chalk.gray(
`\nTo run manually:\n cd ${cwd} && claude --dangerously-skip-permissions "Read .knowledge/claude-update-memory.md and update all agent memory files."\n`
)
);
return;
}
// Ask user if they want to run Claude now
const inquirer = (await import('inquirer')).default;
const { autoRun } = await inquirer.prompt([
{
type: 'confirm',
name: 'autoRun',
message: 'Run Claude Code now to update memory files?',
default: true,
},
]);
if (autoRun) {
console.log(chalk.cyan('\n🚀 Spawning Claude Code...\n'));
const claudePrompt =
'Read .knowledge/claude-update-memory.md and update all agent memory files.';
await new Promise((resolve) => {
const claude = spawn('claude', ['--dangerously-skip-permissions', claudePrompt], {
cwd,
stdio: 'inherit',
});
claude.on('error', (err) => {
console.error(chalk.red(`Failed to spawn Claude Code: ${err.message}`));
console.log(
chalk.gray(
`\nRun manually:\n cd ${cwd} && claude --dangerously-skip-permissions "${claudePrompt}"\n`
)
);
resolve();
});
claude.on('close', (code) => {
if (code === 0) {
console.log(chalk.green('\n✅ Claude Code completed successfully'));
// Archive changes after successful update
archiveChanges(knowledgeDir).then(() => {
console.log(chalk.gray('📦 Changes archived'));
});
} else {
console.log(chalk.yellow(`\nClaude Code exited with code ${code}`));
}
resolve();
});
});
} else {
console.log(
chalk.gray(
`\nRun manually when ready:\n cd ${cwd} && claude --dangerously-skip-permissions "Read .knowledge/claude-update-memory.md and update all agent memory files."\n`
)
);
}
}
async function readChanges(changesPath) {
const changes = [];
if (!(await fs.pathExists(changesPath))) {
return changes;
}
const content = await fs.readFile(changesPath, 'utf-8');
for (const line of content.split('\n')) {
if (line.trim()) {
try {
changes.push(JSON.parse(line));
} catch (e) {
// Skip invalid lines
}
}
}
return changes;
}
function groupChangesByAgent(changes, agents) {
const grouped = {};
for (const agent of agents) {
grouped[agent.agent_dir] = [];
for (const change of changes) {
const matches = agent.watches.some((pattern) => {
const regex = new RegExp(
'^' + pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*').replace(/\//g, '\\/') + '$'
);
return regex.test(change.file);
});
if (matches) {
grouped[agent.agent_dir].push(change);
}
}
}
return grouped;
}
async function updateAgentMemory(cwd, agent, changes, llm, modelOverride) {
const memoryPath = path.join(cwd, agent.agent_dir, 'memory.md');
const existingMemory = (await fs.pathExists(memoryPath))
? await fs.readFile(memoryPath, 'utf-8')
: '';
// Group changes by date
const byDate = {};
for (const change of changes) {
const date = change.timestamp?.split('T')[0] || 'unknown';
byDate[date] = byDate[date] || [];
byDate[date].push({
file: change.file,
message: change.message,
author: change.author,
status: change.status,
diff: change.diff?.substring(0, 500),
});
}
const prompt = `You are updating the memory/knowledge file for a specialized code agent called "${agent.name}".
## Existing Memory
${existingMemory || '(empty - this is a new agent)'}
## Recent Changes to Process
${JSON.stringify(byDate, null, 2)}
## Instructions
1. Analyze the changes and extract important knowledge:
- New patterns or approaches introduced
- Breaking changes or deprecations
- Bug fixes and why they were needed
- New features or capabilities
- Architectural decisions evident from the changes
2. Update the memory file:
- Add a new dated section at the TOP (under "## Recent Updates")
- Be concise but capture important context
- Keep important historical context (don't delete valuable old entries)
- Remove redundant or outdated information
- Keep total length reasonable (under 400 lines)
3. Format as clean markdown with clear sections
Output ONLY the updated memory.md content, no explanations or code blocks.`;
const updatedMemory = await llm.complete(prompt, {
model: modelOverride,
maxTokens: 3000,
});
await fs.writeFile(memoryPath, updatedMemory);
}
async function archiveChanges(knowledgeDir) {
const changesPath = path.join(knowledgeDir, 'changes.jsonl');
const archiveDir = path.join(knowledgeDir, 'archive');
await fs.ensureDir(archiveDir);
const date = new Date().toISOString().split('T')[0];
const archivePath = path.join(archiveDir, `changes-${date}.jsonl`);
// Append to today's archive
const content = await fs.readFile(changesPath, 'utf-8');
await fs.appendFile(archivePath, content);
// Clear main changes file
await fs.writeFile(changesPath, '');
}

View file

@ -0,0 +1,5 @@
// Agent Knowledge System
// Main entry point for programmatic usage
export { LLMClient } from './lib/llm-client.js';
export { loadConfig, saveConfig } from './lib/config.js';

View file

@ -0,0 +1,108 @@
import fs from 'fs-extra';
import path from 'path';
import yaml from 'yaml';
/**
* Load configuration from .knowledge/config.yaml and .env
*/
export async function loadConfig(cwd) {
const config = {
provider: 'openrouter',
model: 'google/gemini-flash-1.5',
models: {
batch: { provider: 'openrouter', model: 'google/gemini-flash-1.5' },
quality: { provider: 'openrouter', model: 'anthropic/claude-sonnet-4-20250514' },
fallbacks: [],
},
};
// Load from config.yaml
const configPath = path.join(cwd, '.knowledge', 'config.yaml');
if (await fs.pathExists(configPath)) {
const yamlConfig = yaml.parse(await fs.readFile(configPath, 'utf-8'));
Object.assign(config, yamlConfig);
}
// Load from .env
const envPath = path.join(cwd, '.knowledge', '.env');
if (await fs.pathExists(envPath)) {
const envContent = await fs.readFile(envPath, 'utf-8');
const envVars = parseEnv(envContent);
if (envVars.OPENROUTER_API_KEY) {
config.openrouterApiKey = envVars.OPENROUTER_API_KEY;
}
if (envVars.ANTHROPIC_API_KEY) {
config.anthropicApiKey = envVars.ANTHROPIC_API_KEY;
}
if (envVars.LLM_PROVIDER) {
config.provider = envVars.LLM_PROVIDER;
}
if (envVars.OPENROUTER_MODEL) {
config.model = envVars.OPENROUTER_MODEL;
}
if (envVars.ANTHROPIC_MODEL) {
config.model = envVars.ANTHROPIC_MODEL;
}
}
// Also check process environment (takes precedence)
if (process.env.OPENROUTER_API_KEY) {
config.openrouterApiKey = process.env.OPENROUTER_API_KEY;
}
if (process.env.ANTHROPIC_API_KEY) {
config.anthropicApiKey = process.env.ANTHROPIC_API_KEY;
}
if (process.env.LLM_PROVIDER) {
config.provider = process.env.LLM_PROVIDER;
}
if (process.env.OPENROUTER_MODEL || process.env.ANTHROPIC_MODEL) {
config.model = process.env.OPENROUTER_MODEL || process.env.ANTHROPIC_MODEL;
}
return config;
}
/**
* Simple .env parser
*/
function parseEnv(content) {
const vars = {};
for (const line of content.split('\n')) {
const trimmed = line.trim();
// Skip comments and empty lines
if (!trimmed || trimmed.startsWith('#')) {
continue;
}
const eqIndex = trimmed.indexOf('=');
if (eqIndex === -1) {
continue;
}
const key = trimmed.slice(0, eqIndex).trim();
let value = trimmed.slice(eqIndex + 1).trim();
// Remove quotes if present
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
vars[key] = value;
}
return vars;
}
/**
* Save configuration
*/
export async function saveConfig(cwd, config) {
const configPath = path.join(cwd, '.knowledge', 'config.yaml');
await fs.writeFile(configPath, yaml.stringify(config));
}

View file

@ -0,0 +1,160 @@
import chalk from 'chalk';
/**
* Unified LLM client supporting multiple providers
*/
export class LLMClient {
constructor(config) {
this.config = config;
this.provider = config.provider || 'openrouter';
}
/**
* Complete a prompt using the configured provider
*/
async complete(prompt, options = {}) {
const provider = options.provider || this.provider;
try {
if (provider === 'anthropic') {
return await this.callAnthropic(prompt, options);
} else if (provider === 'openrouter') {
return await this.callOpenRouter(prompt, options);
}
throw new Error(`Unknown provider: ${provider}`);
} catch (error) {
console.error(chalk.yellow(`${provider} failed: ${error.message}`));
// Try fallbacks if available
if (options.allowFallback !== false && this.config.models?.fallbacks) {
return await this.tryFallbacks(prompt, options);
}
throw error;
}
}
/**
* Call Anthropic API directly
*/
async callAnthropic(prompt, options = {}) {
const apiKey = this.config.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
if (!apiKey) {
throw new Error('ANTHROPIC_API_KEY not configured');
}
const model = options.model || this.config.model || 'claude-sonnet-4-20250514';
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
},
body: JSON.stringify({
model: model,
max_tokens: options.maxTokens || 2000,
messages: [{ role: 'user', content: prompt }],
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Anthropic API error: ${response.status} - ${error}`);
}
const data = await response.json();
return data.content[0].text;
}
/**
* Call OpenRouter API
*/
async callOpenRouter(prompt, options = {}) {
const apiKey = this.config.openrouterApiKey || process.env.OPENROUTER_API_KEY;
if (!apiKey) {
throw new Error('OPENROUTER_API_KEY not configured');
}
const model = options.model || this.config.model || 'google/gemini-flash-1.5';
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://github.com/agent-knowledge',
'X-Title': 'Agent Knowledge System',
},
body: JSON.stringify({
model: model,
messages: [{ role: 'user', content: prompt }],
max_tokens: options.maxTokens || 2000,
temperature: options.temperature || 0.3,
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`OpenRouter API error: ${response.status} - ${error}`);
}
const data = await response.json();
if (!data.choices || !data.choices[0]) {
throw new Error('Invalid response from OpenRouter');
}
return data.choices[0].message.content;
}
/**
* Try fallback providers/models
*/
async tryFallbacks(prompt, options) {
const fallbacks = this.config.models?.fallbacks || [];
for (const fallback of fallbacks) {
try {
console.log(chalk.gray(` Trying fallback: ${fallback.provider}/${fallback.model}`));
return await this.complete(prompt, {
...options,
provider: fallback.provider,
model: fallback.model,
allowFallback: false, // Don't recurse
});
} catch (error) {
console.error(chalk.gray(` Fallback failed: ${error.message}`));
continue;
}
}
throw new Error('All providers and fallbacks failed');
}
/**
* Get available models for a provider
*/
static getAvailableModels(provider) {
if (provider === 'openrouter') {
return [
{ id: 'google/gemini-flash-1.5', name: 'Gemini Flash 1.5', cost: '$0.075/1M' },
{ id: 'deepseek/deepseek-chat', name: 'DeepSeek Chat', cost: '$0.14/1M' },
{ id: 'anthropic/claude-3-haiku', name: 'Claude 3 Haiku', cost: '$0.25/1M' },
{ id: 'mistralai/mistral-small', name: 'Mistral Small', cost: '$0.20/1M' },
{ id: 'anthropic/claude-sonnet-4-20250514', name: 'Claude Sonnet', cost: '$3/1M' },
{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini', cost: '$0.15/1M' },
];
} else if (provider === 'anthropic') {
return [
{ id: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku', cost: '$0.25/1M' },
{ id: 'claude-sonnet-4-20250514', name: 'Claude Sonnet', cost: '$3/1M' },
{ id: 'claude-opus-4-20250514', name: 'Claude Opus', cost: '$15/1M' },
];
}
return [];
}
}

View file

@ -0,0 +1,283 @@
#!/usr/bin/env node
/**
* Agent Knowledge Update Script
*
* This script is designed to be run via cron job or CI to update agent memories.
* Can also be run manually: node scripts/agents/update-agents.js
*
* Usage:
* node scripts/agents/update-agents.js # Update all agents
* node scripts/agents/update-agents.js --dry-run # Preview changes
* node scripts/agents/update-agents.js --agent packages/auth # Specific agent
*
* Cron example (3 AM daily):
* 0 3 * * * cd /path/to/project && node scripts/agents/update-agents.js >> .knowledge/cron.log 2>&1
*/
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const PROJECT_ROOT = path.resolve(__dirname, '..', '..');
// Configuration
const KNOWLEDGE_DIR = path.join(PROJECT_ROOT, '.knowledge');
const CHANGES_FILE = path.join(KNOWLEDGE_DIR, 'changes.jsonl');
const REGISTRY_FILE = path.join(KNOWLEDGE_DIR, 'agent-registry.yaml');
const CONFIG_FILE = path.join(KNOWLEDGE_DIR, 'config.yaml');
const ENV_FILE = path.join(KNOWLEDGE_DIR, '.env');
async function main() {
console.log('🌙 Agent Knowledge Update Script');
console.log(` Project: ${PROJECT_ROOT}`);
console.log(` Time: ${new Date().toISOString()}\n`);
const args = process.argv.slice(2);
const dryRun = args.includes('--dry-run');
const agentFilter = args.find((a) => a.startsWith('--agent='))?.split('=')[1];
// Load config
const config = await loadConfig();
console.log(` Provider: ${config.provider}`);
console.log(` Model: ${config.model}\n`);
// Load registry
const registry = await loadYaml(REGISTRY_FILE);
let agents = registry.agents || [];
if (agentFilter) {
agents = agents.filter((a) => a.path.includes(agentFilter));
console.log(` Filtering to agent: ${agentFilter}`);
}
if (agents.length === 0) {
console.log('No agents to update.');
return;
}
// Read changes
const changes = await readChanges();
if (changes.length === 0) {
console.log('No pending changes to process.');
return;
}
console.log(`📊 Processing ${changes.length} changes for ${agents.length} agent(s)...\n`);
// Group changes by agent
const changesByAgent = groupChangesByAgent(changes, agents);
// Update each agent
let updated = 0;
for (const agent of agents) {
const agentChanges = changesByAgent[agent.agent_dir] || [];
if (agentChanges.length === 0) {
console.log(`⏭️ ${agent.name}: No relevant changes`);
continue;
}
console.log(`🤖 ${agent.name}: ${agentChanges.length} changes`);
if (dryRun) {
console.log(' [DRY RUN] Would update memory');
continue;
}
try {
await updateAgentMemory(agent, agentChanges, config);
console.log(` ✓ Updated memory.md`);
updated++;
} catch (error) {
console.error(` ✗ Error: ${error.message}`);
}
}
// Archive changes
if (!dryRun && updated > 0) {
await archiveChanges();
console.log('\n📦 Changes archived');
}
console.log(`\n✅ Updated ${updated} agent(s)`);
}
async function loadConfig() {
const config = {
provider: process.env.LLM_PROVIDER || 'openrouter',
model: process.env.OPENROUTER_MODEL || process.env.ANTHROPIC_MODEL || 'google/gemini-flash-1.5',
openrouterApiKey: process.env.OPENROUTER_API_KEY,
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
};
// Load from .env file
try {
const envContent = await fs.readFile(ENV_FILE, 'utf-8');
for (const line of envContent.split('\n')) {
const [key, ...valueParts] = line.split('=');
if (key && valueParts.length > 0) {
const value = valueParts.join('=').trim();
if (key.trim() === 'OPENROUTER_API_KEY') config.openrouterApiKey = value;
if (key.trim() === 'ANTHROPIC_API_KEY') config.anthropicApiKey = value;
if (key.trim() === 'LLM_PROVIDER') config.provider = value;
if (key.trim() === 'OPENROUTER_MODEL') config.model = value;
}
}
} catch (e) {
// .env file not found, use defaults
}
// Load from config.yaml
try {
const yamlConfig = await loadYaml(CONFIG_FILE);
if (yamlConfig.provider) config.provider = yamlConfig.provider;
if (yamlConfig.model) config.model = yamlConfig.model;
} catch (e) {
// config.yaml not found
}
return config;
}
async function loadYaml(filePath) {
const content = await fs.readFile(filePath, 'utf-8');
// Simple YAML parser for our use case
const result = { agents: [] };
let currentAgent = null;
for (const line of content.split('\n')) {
if (line.trim().startsWith('- path:')) {
if (currentAgent) result.agents.push(currentAgent);
currentAgent = { path: line.split(':')[1].trim(), watches: [] };
} else if (currentAgent) {
if (line.includes('agent_dir:')) currentAgent.agent_dir = line.split(':')[1].trim();
if (line.includes('name:')) currentAgent.name = line.split(':').slice(1).join(':').trim();
if (line.trim().startsWith('- ') && !line.includes(':')) {
currentAgent.watches.push(line.trim().slice(2));
}
}
}
if (currentAgent) result.agents.push(currentAgent);
return result;
}
async function readChanges() {
try {
const content = await fs.readFile(CHANGES_FILE, 'utf-8');
return content
.split('\n')
.filter((line) => line.trim())
.map((line) => {
try {
return JSON.parse(line);
} catch {
return null;
}
})
.filter(Boolean);
} catch {
return [];
}
}
function groupChangesByAgent(changes, agents) {
const grouped = {};
for (const agent of agents) {
grouped[agent.agent_dir] = changes.filter((change) =>
agent.watches.some((pattern) => {
const regex = new RegExp('^' + pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*'));
return regex.test(change.file);
})
);
}
return grouped;
}
async function updateAgentMemory(agent, changes, config) {
const memoryPath = path.join(PROJECT_ROOT, agent.agent_dir, 'memory.md');
let existingMemory = '';
try {
existingMemory = await fs.readFile(memoryPath, 'utf-8');
} catch {
/* new file */
}
const byDate = {};
for (const c of changes) {
const date = c.timestamp?.split('T')[0] || 'unknown';
byDate[date] = byDate[date] || [];
byDate[date].push({ file: c.file, message: c.message, author: c.author });
}
const prompt = `You are updating the memory file for "${agent.name}".
## Existing Memory
${existingMemory || '(empty)'}
## Recent Changes
${JSON.stringify(byDate, null, 2)}
## Instructions
Update the memory file:
- Add dated section at TOP for new learnings
- Be concise, capture important context
- Keep under 400 lines
Output ONLY the updated memory.md content.`;
const updatedMemory = await callLLM(prompt, config);
await fs.writeFile(memoryPath, updatedMemory);
}
async function callLLM(prompt, config) {
if (config.provider === 'openrouter') {
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer ${config.openrouterApiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: config.model,
messages: [{ role: 'user', content: prompt }],
max_tokens: 3000,
}),
});
const data = await response.json();
return data.choices[0].message.content;
} else {
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': config.anthropicApiKey,
'anthropic-version': '2023-06-01',
},
body: JSON.stringify({
model: config.model,
max_tokens: 3000,
messages: [{ role: 'user', content: prompt }],
}),
});
const data = await response.json();
return data.content[0].text;
}
}
async function archiveChanges() {
const archiveDir = path.join(KNOWLEDGE_DIR, 'archive');
await fs.mkdir(archiveDir, { recursive: true });
const date = new Date().toISOString().split('T')[0];
const archivePath = path.join(archiveDir, `changes-${date}.jsonl`);
const content = await fs.readFile(CHANGES_FILE, 'utf-8');
await fs.appendFile(archivePath, content);
await fs.writeFile(CHANGES_FILE, '');
}
main().catch((err) => {
console.error('Fatal error:', err);
process.exit(1);
});