feat: CRM Clinicas SaaS - MVP completo
- Auth: Login/Register con creacion de clinica - Dashboard: KPIs reales, graficas recharts - Pacientes: CRUD completo con busqueda - Agenda: FullCalendar, drag-and-drop, vista recepcion - Expediente: Notas SOAP, signos vitales, CIE-10 - Facturacion: Facturas con IVA, campos CFDI SAT - Inventario: Productos, stock, movimientos, alertas - Configuracion: Clinica, equipo, catalogo servicios - Supabase self-hosted: 18 tablas con RLS multi-tenant - Docker + Nginx para produccion Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
97
.claude/helpers/README.md
Normal file
97
.claude/helpers/README.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Claude Flow V3 Helpers
|
||||
|
||||
This directory contains helper scripts and utilities for V3 development.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
```bash
|
||||
# Initialize V3 development environment
|
||||
.claude/helpers/v3.sh init
|
||||
|
||||
# Quick status check
|
||||
.claude/helpers/v3.sh status
|
||||
|
||||
# Update progress metrics
|
||||
.claude/helpers/v3.sh update domain 3
|
||||
.claude/helpers/v3.sh update agent 8
|
||||
.claude/helpers/v3.sh update security 2
|
||||
```
|
||||
|
||||
## Available Helpers
|
||||
|
||||
### 🎛️ V3 Master Tool
|
||||
- **`v3.sh`** - Main command-line interface for all V3 operations
|
||||
```bash
|
||||
.claude/helpers/v3.sh help # Show all commands
|
||||
.claude/helpers/v3.sh status # Quick development status
|
||||
.claude/helpers/v3.sh update domain 3 # Update specific metrics
|
||||
.claude/helpers/v3.sh validate # Validate configuration
|
||||
.claude/helpers/v3.sh full-status # Complete status overview
|
||||
```
|
||||
|
||||
### 📊 V3 Progress Management
|
||||
- **`update-v3-progress.sh`** - Update V3 development metrics
|
||||
```bash
|
||||
# Usage examples:
|
||||
.claude/helpers/update-v3-progress.sh domain 3 # Mark 3 domains complete
|
||||
.claude/helpers/update-v3-progress.sh agent 8 # 8 agents active
|
||||
.claude/helpers/update-v3-progress.sh security 2 # 2 CVEs fixed
|
||||
.claude/helpers/update-v3-progress.sh performance 2.5x # Performance boost
|
||||
.claude/helpers/update-v3-progress.sh status # Show current status
|
||||
```
|
||||
|
||||
### 🔍 Configuration Validation
|
||||
- **`validate-v3-config.sh`** - Comprehensive environment validation
|
||||
- Checks all required directories and files
|
||||
- Validates JSON configuration files
|
||||
- Verifies Node.js and development tools
|
||||
- Confirms Git repository status
|
||||
- Validates file permissions
|
||||
|
||||
### ⚡ Quick Status
|
||||
- **`v3-quick-status.sh`** - Compact development progress overview
|
||||
- Shows domain, agent, and DDD progress
|
||||
- Displays security and performance metrics
|
||||
- Color-coded status indicators
|
||||
- Current Git branch information
|
||||
|
||||
## Helper Script Standards
|
||||
|
||||
### File Naming
|
||||
- Use kebab-case: `update-v3-progress.sh`
|
||||
- Include version prefix: `v3-*` for V3-specific helpers
|
||||
- Use descriptive names that indicate purpose
|
||||
|
||||
### Script Requirements
|
||||
- Must be executable (`chmod +x`)
|
||||
- Include proper error handling (`set -e`)
|
||||
- Provide usage help when called without arguments
|
||||
- Use consistent exit codes (0 = success, non-zero = error)
|
||||
|
||||
### Configuration Integration
|
||||
Helpers are configured in `.claude/settings.json`:
|
||||
```json
|
||||
{
|
||||
"helpers": {
|
||||
"directory": ".claude/helpers",
|
||||
"enabled": true,
|
||||
"v3ProgressUpdater": ".claude/helpers/update-v3-progress.sh"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
1. **Security First**: All helpers must validate inputs
|
||||
2. **Idempotent**: Scripts should be safe to run multiple times
|
||||
3. **Fast Execution**: Keep helper execution under 1 second when possible
|
||||
4. **Clear Output**: Provide clear success/error messages
|
||||
5. **JSON Safe**: When updating JSON files, use `jq` for safety
|
||||
|
||||
## Adding New Helpers
|
||||
|
||||
1. Create script in `.claude/helpers/`
|
||||
2. Make executable: `chmod +x script-name.sh`
|
||||
3. Add to settings.json helpers section
|
||||
4. Test thoroughly before committing
|
||||
5. Update this README with usage documentation
|
||||
186
.claude/helpers/adr-compliance.sh
Executable file
186
.claude/helpers/adr-compliance.sh
Executable file
@@ -0,0 +1,186 @@
|
||||
#!/bin/bash
|
||||
# Claude Flow V3 - ADR Compliance Checker Worker
|
||||
# Checks compliance with Architecture Decision Records
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
METRICS_DIR="$PROJECT_ROOT/.claude-flow/metrics"
|
||||
ADR_FILE="$METRICS_DIR/adr-compliance.json"
|
||||
LAST_RUN_FILE="$METRICS_DIR/.adr-last-run"
|
||||
|
||||
mkdir -p "$METRICS_DIR"
|
||||
|
||||
# V3 ADRs to check
|
||||
declare -A ADRS=(
|
||||
["ADR-001"]="agentic-flow as core foundation"
|
||||
["ADR-002"]="Domain-Driven Design structure"
|
||||
["ADR-003"]="Single coordination engine"
|
||||
["ADR-004"]="Plugin-based architecture"
|
||||
["ADR-005"]="MCP-first API design"
|
||||
["ADR-006"]="Unified memory service"
|
||||
["ADR-007"]="Event sourcing for state"
|
||||
["ADR-008"]="Vitest over Jest"
|
||||
["ADR-009"]="Hybrid memory backend"
|
||||
["ADR-010"]="Remove Deno support"
|
||||
)
|
||||
|
||||
should_run() {
|
||||
if [ ! -f "$LAST_RUN_FILE" ]; then return 0; fi
|
||||
local last_run=$(cat "$LAST_RUN_FILE" 2>/dev/null || echo "0")
|
||||
local now=$(date +%s)
|
||||
[ $((now - last_run)) -ge 900 ] # 15 minutes
|
||||
}
|
||||
|
||||
check_adr_001() {
|
||||
# ADR-001: agentic-flow as core foundation
|
||||
local score=0
|
||||
|
||||
# Check package.json for agentic-flow dependency
|
||||
grep -q "agentic-flow" "$PROJECT_ROOT/package.json" 2>/dev/null && score=$((score + 50))
|
||||
|
||||
# Check for imports from agentic-flow
|
||||
local imports=$(grep -r "from.*agentic-flow\|require.*agentic-flow" "$PROJECT_ROOT/v3" "$PROJECT_ROOT/src" 2>/dev/null | grep -v node_modules | wc -l)
|
||||
[ "$imports" -gt 5 ] && score=$((score + 50))
|
||||
|
||||
echo "$score"
|
||||
}
|
||||
|
||||
check_adr_002() {
|
||||
# ADR-002: Domain-Driven Design structure
|
||||
local score=0
|
||||
|
||||
# Check for domain directories
|
||||
[ -d "$PROJECT_ROOT/v3" ] || [ -d "$PROJECT_ROOT/src/domains" ] && score=$((score + 30))
|
||||
|
||||
# Check for bounded contexts
|
||||
local contexts=$(find "$PROJECT_ROOT/v3" "$PROJECT_ROOT/src" -type d -name "domain" 2>/dev/null | wc -l)
|
||||
[ "$contexts" -gt 0 ] && score=$((score + 35))
|
||||
|
||||
# Check for anti-corruption layers
|
||||
local acl=$(grep -r "AntiCorruption\|Adapter\|Port" "$PROJECT_ROOT/v3" "$PROJECT_ROOT/src" 2>/dev/null | grep -v node_modules | wc -l)
|
||||
[ "$acl" -gt 0 ] && score=$((score + 35))
|
||||
|
||||
echo "$score"
|
||||
}
|
||||
|
||||
check_adr_003() {
|
||||
# ADR-003: Single coordination engine
|
||||
local score=0
|
||||
|
||||
# Check for unified SwarmCoordinator
|
||||
grep -rq "SwarmCoordinator\|UnifiedCoordinator" "$PROJECT_ROOT/v3" "$PROJECT_ROOT/src" 2>/dev/null && score=$((score + 50))
|
||||
|
||||
# Check for no duplicate coordinators
|
||||
local coordinators=$(grep -r "class.*Coordinator" "$PROJECT_ROOT/v3" "$PROJECT_ROOT/src" 2>/dev/null | grep -v node_modules | grep -v ".test." | wc -l)
|
||||
[ "$coordinators" -le 3 ] && score=$((score + 50))
|
||||
|
||||
echo "$score"
|
||||
}
|
||||
|
||||
check_adr_005() {
|
||||
# ADR-005: MCP-first API design
|
||||
local score=0
|
||||
|
||||
# Check for MCP server implementation
|
||||
[ -d "$PROJECT_ROOT/v3/@claude-flow/mcp" ] && score=$((score + 40))
|
||||
|
||||
# Check for MCP tools
|
||||
local tools=$(grep -r "tool.*name\|registerTool" "$PROJECT_ROOT/v3" 2>/dev/null | wc -l)
|
||||
[ "$tools" -gt 5 ] && score=$((score + 30))
|
||||
|
||||
# Check for MCP schemas
|
||||
grep -rq "schema\|jsonSchema" "$PROJECT_ROOT/v3/@claude-flow/mcp" 2>/dev/null && score=$((score + 30))
|
||||
|
||||
echo "$score"
|
||||
}
|
||||
|
||||
check_adr_008() {
|
||||
# ADR-008: Vitest over Jest
|
||||
local score=0
|
||||
|
||||
# Check for vitest in package.json
|
||||
grep -q "vitest" "$PROJECT_ROOT/package.json" 2>/dev/null && score=$((score + 50))
|
||||
|
||||
# Check for no jest references
|
||||
local jest_refs=$(grep -r "from.*jest\|jest\." "$PROJECT_ROOT/v3" "$PROJECT_ROOT/src" 2>/dev/null | grep -v node_modules | grep -v "vitest" | wc -l)
|
||||
[ "$jest_refs" -eq 0 ] && score=$((score + 50))
|
||||
|
||||
echo "$score"
|
||||
}
|
||||
|
||||
check_compliance() {
|
||||
echo "[$(date +%H:%M:%S)] Checking ADR compliance..."
|
||||
|
||||
local total_score=0
|
||||
local compliant_count=0
|
||||
local results=""
|
||||
|
||||
# Check each ADR
|
||||
local adr_001=$(check_adr_001)
|
||||
local adr_002=$(check_adr_002)
|
||||
local adr_003=$(check_adr_003)
|
||||
local adr_005=$(check_adr_005)
|
||||
local adr_008=$(check_adr_008)
|
||||
|
||||
# Simple checks for others (assume partial compliance)
|
||||
local adr_004=50 # Plugin architecture
|
||||
local adr_006=50 # Unified memory
|
||||
local adr_007=50 # Event sourcing
|
||||
local adr_009=75 # Hybrid memory
|
||||
local adr_010=100 # No Deno (easy to verify)
|
||||
|
||||
# Calculate totals
|
||||
for score in $adr_001 $adr_002 $adr_003 $adr_004 $adr_005 $adr_006 $adr_007 $adr_008 $adr_009 $adr_010; do
|
||||
total_score=$((total_score + score))
|
||||
[ "$score" -ge 50 ] && compliant_count=$((compliant_count + 1))
|
||||
done
|
||||
|
||||
local avg_score=$((total_score / 10))
|
||||
|
||||
# Write ADR compliance metrics
|
||||
cat > "$ADR_FILE" << EOF
|
||||
{
|
||||
"timestamp": "$(date -Iseconds)",
|
||||
"overallCompliance": $avg_score,
|
||||
"compliantCount": $compliant_count,
|
||||
"totalADRs": 10,
|
||||
"adrs": {
|
||||
"ADR-001": {"score": $adr_001, "title": "agentic-flow as core foundation"},
|
||||
"ADR-002": {"score": $adr_002, "title": "Domain-Driven Design structure"},
|
||||
"ADR-003": {"score": $adr_003, "title": "Single coordination engine"},
|
||||
"ADR-004": {"score": $adr_004, "title": "Plugin-based architecture"},
|
||||
"ADR-005": {"score": $adr_005, "title": "MCP-first API design"},
|
||||
"ADR-006": {"score": $adr_006, "title": "Unified memory service"},
|
||||
"ADR-007": {"score": $adr_007, "title": "Event sourcing for state"},
|
||||
"ADR-008": {"score": $adr_008, "title": "Vitest over Jest"},
|
||||
"ADR-009": {"score": $adr_009, "title": "Hybrid memory backend"},
|
||||
"ADR-010": {"score": $adr_010, "title": "Remove Deno support"}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "[$(date +%H:%M:%S)] ✓ ADR Compliance: ${avg_score}% | Compliant: $compliant_count/10"
|
||||
|
||||
date +%s > "$LAST_RUN_FILE"
|
||||
}
|
||||
|
||||
case "${1:-check}" in
|
||||
"run") check_compliance ;;
|
||||
"check") should_run && check_compliance || echo "[$(date +%H:%M:%S)] Skipping (throttled)" ;;
|
||||
"force") rm -f "$LAST_RUN_FILE"; check_compliance ;;
|
||||
"status")
|
||||
if [ -f "$ADR_FILE" ]; then
|
||||
jq -r '"Compliance: \(.overallCompliance)% | Compliant: \(.compliantCount)/\(.totalADRs)"' "$ADR_FILE"
|
||||
else
|
||||
echo "No ADR data available"
|
||||
fi
|
||||
;;
|
||||
"details")
|
||||
if [ -f "$ADR_FILE" ]; then
|
||||
jq -r '.adrs | to_entries[] | "\(.key): \(.value.score)% - \(.value.title)"' "$ADR_FILE"
|
||||
fi
|
||||
;;
|
||||
*) echo "Usage: $0 [run|check|force|status|details]" ;;
|
||||
esac
|
||||
178
.claude/helpers/auto-commit.sh
Executable file
178
.claude/helpers/auto-commit.sh
Executable file
@@ -0,0 +1,178 @@
|
||||
#!/bin/bash
|
||||
# Auto-commit helper for Claude Code hooks
|
||||
# Handles git add, commit, and push in a robust way
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Configuration
|
||||
MIN_CHANGES=${MIN_CHANGES:-1}
|
||||
COMMIT_PREFIX=${COMMIT_PREFIX:-"checkpoint"}
|
||||
AUTO_PUSH=${AUTO_PUSH:-true}
|
||||
|
||||
log() {
|
||||
echo -e "${GREEN}[auto-commit]${NC} $1"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "${YELLOW}[auto-commit]${NC} $1"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[auto-commit]${NC} $1"
|
||||
}
|
||||
|
||||
# Check if there are changes to commit
|
||||
has_changes() {
|
||||
! git diff --quiet HEAD 2>/dev/null || ! git diff --cached --quiet 2>/dev/null || [ -n "$(git ls-files --others --exclude-standard)" ]
|
||||
}
|
||||
|
||||
# Count changes
|
||||
count_changes() {
|
||||
local staged=$(git diff --cached --numstat | wc -l)
|
||||
local unstaged=$(git diff --numstat | wc -l)
|
||||
local untracked=$(git ls-files --others --exclude-standard | wc -l)
|
||||
echo $((staged + unstaged + untracked))
|
||||
}
|
||||
|
||||
# Main auto-commit function
|
||||
auto_commit() {
|
||||
local message="$1"
|
||||
local file="$2" # Optional specific file
|
||||
|
||||
# Check if in a git repo
|
||||
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
error "Not in a git repository"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check for changes
|
||||
if ! has_changes; then
|
||||
log "No changes to commit"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local change_count=$(count_changes)
|
||||
if [ "$change_count" -lt "$MIN_CHANGES" ]; then
|
||||
log "Only $change_count change(s), skipping (min: $MIN_CHANGES)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Stage changes
|
||||
if [ -n "$file" ] && [ -f "$file" ]; then
|
||||
git add "$file"
|
||||
log "Staged: $file"
|
||||
else
|
||||
git add -A
|
||||
log "Staged all changes ($change_count files)"
|
||||
fi
|
||||
|
||||
# Create commit message
|
||||
local branch=$(git branch --show-current)
|
||||
local timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
if [ -z "$message" ]; then
|
||||
message="$COMMIT_PREFIX: Auto-commit from Claude Code"
|
||||
fi
|
||||
|
||||
# Commit
|
||||
if git commit -m "$message
|
||||
|
||||
Automatic checkpoint created by Claude Code
|
||||
- Branch: $branch
|
||||
- Timestamp: $timestamp
|
||||
- Changes: $change_count file(s)
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||
|
||||
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>" --quiet 2>/dev/null; then
|
||||
log "Created commit: $message"
|
||||
|
||||
# Push if enabled
|
||||
if [ "$AUTO_PUSH" = "true" ]; then
|
||||
if git push origin "$branch" --quiet 2>/dev/null; then
|
||||
log "Pushed to origin/$branch"
|
||||
else
|
||||
warn "Push failed (will retry later)"
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
else
|
||||
warn "Commit failed (possibly nothing to commit)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Batch commit (commits all changes together)
|
||||
batch_commit() {
|
||||
local message="${1:-Batch checkpoint}"
|
||||
auto_commit "$message"
|
||||
}
|
||||
|
||||
# Single file commit
|
||||
file_commit() {
|
||||
local file="$1"
|
||||
local message="${2:-Checkpoint: $file}"
|
||||
|
||||
if [ -z "$file" ]; then
|
||||
error "No file specified"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$file" ]; then
|
||||
error "File not found: $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
auto_commit "$message" "$file"
|
||||
}
|
||||
|
||||
# Push only (no commit)
|
||||
push_only() {
|
||||
local branch=$(git branch --show-current)
|
||||
|
||||
if git push origin "$branch" 2>/dev/null; then
|
||||
log "Pushed to origin/$branch"
|
||||
else
|
||||
warn "Push failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Entry point
|
||||
case "${1:-batch}" in
|
||||
batch)
|
||||
batch_commit "$2"
|
||||
;;
|
||||
file)
|
||||
file_commit "$2" "$3"
|
||||
;;
|
||||
push)
|
||||
push_only
|
||||
;;
|
||||
check)
|
||||
if has_changes; then
|
||||
echo "Changes detected: $(count_changes) files"
|
||||
exit 0
|
||||
else
|
||||
echo "No changes"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {batch|file|push|check} [args]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " batch [message] Commit all changes with optional message"
|
||||
echo " file <path> [msg] Commit specific file"
|
||||
echo " push Push without committing"
|
||||
echo " check Check if there are uncommitted changes"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
350
.claude/helpers/auto-memory-hook.mjs
Executable file
350
.claude/helpers/auto-memory-hook.mjs
Executable file
@@ -0,0 +1,350 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Auto Memory Bridge Hook (ADR-048/049)
|
||||
*
|
||||
* Wires AutoMemoryBridge + LearningBridge + MemoryGraph into Claude Code
|
||||
* session lifecycle. Called by settings.json SessionStart/SessionEnd hooks.
|
||||
*
|
||||
* Usage:
|
||||
* node auto-memory-hook.mjs import # SessionStart: import auto memory files into backend
|
||||
* node auto-memory-hook.mjs sync # SessionEnd: sync insights back to MEMORY.md
|
||||
* node auto-memory-hook.mjs status # Show bridge status
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const PROJECT_ROOT = join(__dirname, '../..');
|
||||
const DATA_DIR = join(PROJECT_ROOT, '.claude-flow', 'data');
|
||||
const STORE_PATH = join(DATA_DIR, 'auto-memory-store.json');
|
||||
|
||||
// Colors
|
||||
const GREEN = '\x1b[0;32m';
|
||||
const CYAN = '\x1b[0;36m';
|
||||
const DIM = '\x1b[2m';
|
||||
const RESET = '\x1b[0m';
|
||||
|
||||
const log = (msg) => console.log(`${CYAN}[AutoMemory] ${msg}${RESET}`);
|
||||
const success = (msg) => console.log(`${GREEN}[AutoMemory] ✓ ${msg}${RESET}`);
|
||||
const dim = (msg) => console.log(` ${DIM}${msg}${RESET}`);
|
||||
|
||||
// Ensure data dir
|
||||
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
|
||||
|
||||
// ============================================================================
|
||||
// Simple JSON File Backend (implements IMemoryBackend interface)
|
||||
// ============================================================================
|
||||
|
||||
class JsonFileBackend {
|
||||
constructor(filePath) {
|
||||
this.filePath = filePath;
|
||||
this.entries = new Map();
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
if (existsSync(this.filePath)) {
|
||||
try {
|
||||
const data = JSON.parse(readFileSync(this.filePath, 'utf-8'));
|
||||
if (Array.isArray(data)) {
|
||||
for (const entry of data) this.entries.set(entry.id, entry);
|
||||
}
|
||||
} catch { /* start fresh */ }
|
||||
}
|
||||
}
|
||||
|
||||
async shutdown() { this._persist(); }
|
||||
async store(entry) { this.entries.set(entry.id, entry); this._persist(); }
|
||||
async get(id) { return this.entries.get(id) ?? null; }
|
||||
async getByKey(key, ns) {
|
||||
for (const e of this.entries.values()) {
|
||||
if (e.key === key && (!ns || e.namespace === ns)) return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
async update(id, updates) {
|
||||
const e = this.entries.get(id);
|
||||
if (!e) return null;
|
||||
if (updates.metadata) Object.assign(e.metadata, updates.metadata);
|
||||
if (updates.content !== undefined) e.content = updates.content;
|
||||
if (updates.tags) e.tags = updates.tags;
|
||||
e.updatedAt = Date.now();
|
||||
this._persist();
|
||||
return e;
|
||||
}
|
||||
async delete(id) { return this.entries.delete(id); }
|
||||
async query(opts) {
|
||||
let results = [...this.entries.values()];
|
||||
if (opts?.namespace) results = results.filter(e => e.namespace === opts.namespace);
|
||||
if (opts?.type) results = results.filter(e => e.type === opts.type);
|
||||
if (opts?.limit) results = results.slice(0, opts.limit);
|
||||
return results;
|
||||
}
|
||||
async search() { return []; } // No vector search in JSON backend
|
||||
async bulkInsert(entries) { for (const e of entries) this.entries.set(e.id, e); this._persist(); }
|
||||
async bulkDelete(ids) { let n = 0; for (const id of ids) { if (this.entries.delete(id)) n++; } this._persist(); return n; }
|
||||
async count() { return this.entries.size; }
|
||||
async listNamespaces() {
|
||||
const ns = new Set();
|
||||
for (const e of this.entries.values()) ns.add(e.namespace || 'default');
|
||||
return [...ns];
|
||||
}
|
||||
async clearNamespace(ns) {
|
||||
let n = 0;
|
||||
for (const [id, e] of this.entries) {
|
||||
if (e.namespace === ns) { this.entries.delete(id); n++; }
|
||||
}
|
||||
this._persist();
|
||||
return n;
|
||||
}
|
||||
async getStats() {
|
||||
return {
|
||||
totalEntries: this.entries.size,
|
||||
entriesByNamespace: {},
|
||||
entriesByType: { semantic: 0, episodic: 0, procedural: 0, working: 0, cache: 0 },
|
||||
memoryUsage: 0, avgQueryTime: 0, avgSearchTime: 0,
|
||||
};
|
||||
}
|
||||
async healthCheck() {
|
||||
return {
|
||||
status: 'healthy',
|
||||
components: {
|
||||
storage: { status: 'healthy', latency: 0 },
|
||||
index: { status: 'healthy', latency: 0 },
|
||||
cache: { status: 'healthy', latency: 0 },
|
||||
},
|
||||
timestamp: Date.now(), issues: [], recommendations: [],
|
||||
};
|
||||
}
|
||||
|
||||
_persist() {
|
||||
try {
|
||||
writeFileSync(this.filePath, JSON.stringify([...this.entries.values()], null, 2), 'utf-8');
|
||||
} catch { /* best effort */ }
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Resolve memory package path (local dev or npm installed)
|
||||
// ============================================================================
|
||||
|
||||
async function loadMemoryPackage() {
|
||||
// Strategy 1: Local dev (built dist)
|
||||
const localDist = join(PROJECT_ROOT, 'v3/@claude-flow/memory/dist/index.js');
|
||||
if (existsSync(localDist)) {
|
||||
try {
|
||||
return await import(`file://${localDist}`);
|
||||
} catch { /* fall through */ }
|
||||
}
|
||||
|
||||
// Strategy 2: npm installed @claude-flow/memory
|
||||
try {
|
||||
return await import('@claude-flow/memory');
|
||||
} catch { /* fall through */ }
|
||||
|
||||
// Strategy 3: Installed via @claude-flow/cli which includes memory
|
||||
const cliMemory = join(PROJECT_ROOT, 'node_modules/@claude-flow/memory/dist/index.js');
|
||||
if (existsSync(cliMemory)) {
|
||||
try {
|
||||
return await import(`file://${cliMemory}`);
|
||||
} catch { /* fall through */ }
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Read config from .claude-flow/config.yaml
|
||||
// ============================================================================
|
||||
|
||||
function readConfig() {
|
||||
const configPath = join(PROJECT_ROOT, '.claude-flow', 'config.yaml');
|
||||
const defaults = {
|
||||
learningBridge: { enabled: true, sonaMode: 'balanced', confidenceDecayRate: 0.005, accessBoostAmount: 0.03, consolidationThreshold: 10 },
|
||||
memoryGraph: { enabled: true, pageRankDamping: 0.85, maxNodes: 5000, similarityThreshold: 0.8 },
|
||||
agentScopes: { enabled: true, defaultScope: 'project' },
|
||||
};
|
||||
|
||||
if (!existsSync(configPath)) return defaults;
|
||||
|
||||
try {
|
||||
const yaml = readFileSync(configPath, 'utf-8');
|
||||
// Simple YAML parser for the memory section
|
||||
const getBool = (key) => {
|
||||
const match = yaml.match(new RegExp(`${key}:\\s*(true|false)`, 'i'));
|
||||
return match ? match[1] === 'true' : undefined;
|
||||
};
|
||||
|
||||
const lbEnabled = getBool('learningBridge[\\s\\S]*?enabled');
|
||||
if (lbEnabled !== undefined) defaults.learningBridge.enabled = lbEnabled;
|
||||
|
||||
const mgEnabled = getBool('memoryGraph[\\s\\S]*?enabled');
|
||||
if (mgEnabled !== undefined) defaults.memoryGraph.enabled = mgEnabled;
|
||||
|
||||
const asEnabled = getBool('agentScopes[\\s\\S]*?enabled');
|
||||
if (asEnabled !== undefined) defaults.agentScopes.enabled = asEnabled;
|
||||
|
||||
return defaults;
|
||||
} catch {
|
||||
return defaults;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Commands
|
||||
// ============================================================================
|
||||
|
||||
async function doImport() {
|
||||
log('Importing auto memory files into bridge...');
|
||||
|
||||
const memPkg = await loadMemoryPackage();
|
||||
if (!memPkg || !memPkg.AutoMemoryBridge) {
|
||||
dim('Memory package not available — skipping auto memory import');
|
||||
return;
|
||||
}
|
||||
|
||||
const config = readConfig();
|
||||
const backend = new JsonFileBackend(STORE_PATH);
|
||||
await backend.initialize();
|
||||
|
||||
const bridgeConfig = {
|
||||
workingDir: PROJECT_ROOT,
|
||||
syncMode: 'on-session-end',
|
||||
};
|
||||
|
||||
// Wire learning if enabled and available
|
||||
if (config.learningBridge.enabled && memPkg.LearningBridge) {
|
||||
bridgeConfig.learning = {
|
||||
sonaMode: config.learningBridge.sonaMode,
|
||||
confidenceDecayRate: config.learningBridge.confidenceDecayRate,
|
||||
accessBoostAmount: config.learningBridge.accessBoostAmount,
|
||||
consolidationThreshold: config.learningBridge.consolidationThreshold,
|
||||
};
|
||||
}
|
||||
|
||||
// Wire graph if enabled and available
|
||||
if (config.memoryGraph.enabled && memPkg.MemoryGraph) {
|
||||
bridgeConfig.graph = {
|
||||
pageRankDamping: config.memoryGraph.pageRankDamping,
|
||||
maxNodes: config.memoryGraph.maxNodes,
|
||||
similarityThreshold: config.memoryGraph.similarityThreshold,
|
||||
};
|
||||
}
|
||||
|
||||
const bridge = new memPkg.AutoMemoryBridge(backend, bridgeConfig);
|
||||
|
||||
try {
|
||||
const result = await bridge.importFromAutoMemory();
|
||||
success(`Imported ${result.imported} entries (${result.skipped} skipped)`);
|
||||
dim(`├─ Backend entries: ${await backend.count()}`);
|
||||
dim(`├─ Learning: ${config.learningBridge.enabled ? 'active' : 'disabled'}`);
|
||||
dim(`├─ Graph: ${config.memoryGraph.enabled ? 'active' : 'disabled'}`);
|
||||
dim(`└─ Agent scopes: ${config.agentScopes.enabled ? 'active' : 'disabled'}`);
|
||||
} catch (err) {
|
||||
dim(`Import failed (non-critical): ${err.message}`);
|
||||
}
|
||||
|
||||
await backend.shutdown();
|
||||
}
|
||||
|
||||
async function doSync() {
|
||||
log('Syncing insights to auto memory files...');
|
||||
|
||||
const memPkg = await loadMemoryPackage();
|
||||
if (!memPkg || !memPkg.AutoMemoryBridge) {
|
||||
dim('Memory package not available — skipping sync');
|
||||
return;
|
||||
}
|
||||
|
||||
const config = readConfig();
|
||||
const backend = new JsonFileBackend(STORE_PATH);
|
||||
await backend.initialize();
|
||||
|
||||
const entryCount = await backend.count();
|
||||
if (entryCount === 0) {
|
||||
dim('No entries to sync');
|
||||
await backend.shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
const bridgeConfig = {
|
||||
workingDir: PROJECT_ROOT,
|
||||
syncMode: 'on-session-end',
|
||||
};
|
||||
|
||||
if (config.learningBridge.enabled && memPkg.LearningBridge) {
|
||||
bridgeConfig.learning = {
|
||||
sonaMode: config.learningBridge.sonaMode,
|
||||
confidenceDecayRate: config.learningBridge.confidenceDecayRate,
|
||||
consolidationThreshold: config.learningBridge.consolidationThreshold,
|
||||
};
|
||||
}
|
||||
|
||||
if (config.memoryGraph.enabled && memPkg.MemoryGraph) {
|
||||
bridgeConfig.graph = {
|
||||
pageRankDamping: config.memoryGraph.pageRankDamping,
|
||||
maxNodes: config.memoryGraph.maxNodes,
|
||||
};
|
||||
}
|
||||
|
||||
const bridge = new memPkg.AutoMemoryBridge(backend, bridgeConfig);
|
||||
|
||||
try {
|
||||
const syncResult = await bridge.syncToAutoMemory();
|
||||
success(`Synced ${syncResult.synced} entries to auto memory`);
|
||||
dim(`├─ Categories updated: ${syncResult.categories?.join(', ') || 'none'}`);
|
||||
dim(`└─ Backend entries: ${entryCount}`);
|
||||
|
||||
// Curate MEMORY.md index with graph-aware ordering
|
||||
await bridge.curateIndex();
|
||||
success('Curated MEMORY.md index');
|
||||
} catch (err) {
|
||||
dim(`Sync failed (non-critical): ${err.message}`);
|
||||
}
|
||||
|
||||
if (bridge.destroy) bridge.destroy();
|
||||
await backend.shutdown();
|
||||
}
|
||||
|
||||
async function doStatus() {
|
||||
const memPkg = await loadMemoryPackage();
|
||||
const config = readConfig();
|
||||
|
||||
console.log('\n=== Auto Memory Bridge Status ===\n');
|
||||
console.log(` Package: ${memPkg ? '✅ Available' : '❌ Not found'}`);
|
||||
console.log(` Store: ${existsSync(STORE_PATH) ? '✅ ' + STORE_PATH : '⏸ Not initialized'}`);
|
||||
console.log(` LearningBridge: ${config.learningBridge.enabled ? '✅ Enabled' : '⏸ Disabled'}`);
|
||||
console.log(` MemoryGraph: ${config.memoryGraph.enabled ? '✅ Enabled' : '⏸ Disabled'}`);
|
||||
console.log(` AgentScopes: ${config.agentScopes.enabled ? '✅ Enabled' : '⏸ Disabled'}`);
|
||||
|
||||
if (existsSync(STORE_PATH)) {
|
||||
try {
|
||||
const data = JSON.parse(readFileSync(STORE_PATH, 'utf-8'));
|
||||
console.log(` Entries: ${Array.isArray(data) ? data.length : 0}`);
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
console.log('');
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Main
|
||||
// ============================================================================
|
||||
|
||||
const command = process.argv[2] || 'status';
|
||||
|
||||
try {
|
||||
switch (command) {
|
||||
case 'import': await doImport(); break;
|
||||
case 'sync': await doSync(); break;
|
||||
case 'status': await doStatus(); break;
|
||||
default:
|
||||
console.log('Usage: auto-memory-hook.mjs <import|sync|status>');
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (err) {
|
||||
// Hooks must never crash Claude Code - fail silently
|
||||
dim(`Error (non-critical): ${err.message}`);
|
||||
}
|
||||
251
.claude/helpers/checkpoint-manager.sh
Executable file
251
.claude/helpers/checkpoint-manager.sh
Executable file
@@ -0,0 +1,251 @@
|
||||
#!/bin/bash
|
||||
# Claude Checkpoint Manager
|
||||
# Provides easy rollback and management of Claude Code checkpoints
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
CHECKPOINT_DIR=".claude/checkpoints"
|
||||
BACKUP_DIR=".claude/backups"
|
||||
|
||||
# Help function
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Claude Checkpoint Manager
|
||||
========================
|
||||
|
||||
Usage: $0 <command> [options]
|
||||
|
||||
Commands:
|
||||
list List all checkpoints
|
||||
show <id> Show details of a specific checkpoint
|
||||
rollback <id> Rollback to a specific checkpoint
|
||||
diff <id> Show diff since checkpoint
|
||||
clean Clean old checkpoints (older than 7 days)
|
||||
summary Show session summary
|
||||
|
||||
Options:
|
||||
--hard For rollback: use git reset --hard (destructive)
|
||||
--soft For rollback: use git reset --soft (default)
|
||||
--branch For rollback: create new branch from checkpoint
|
||||
|
||||
Examples:
|
||||
$0 list
|
||||
$0 show checkpoint-20240130-143022
|
||||
$0 rollback checkpoint-20240130-143022 --branch
|
||||
$0 diff session-end-session-20240130-150000
|
||||
EOF
|
||||
}
|
||||
|
||||
# List all checkpoints
|
||||
function list_checkpoints() {
|
||||
echo -e "${BLUE}📋 Available Checkpoints:${NC}"
|
||||
echo ""
|
||||
|
||||
# List checkpoint tags
|
||||
echo -e "${YELLOW}Git Tags:${NC}"
|
||||
local tags=$(git tag -l 'checkpoint-*' -l 'session-end-*' -l 'task-*' --sort=-creatordate | head -20)
|
||||
if [ -n "$tags" ]; then
|
||||
echo "$tags"
|
||||
else
|
||||
echo "No checkpoint tags found"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# List checkpoint branches
|
||||
echo -e "${YELLOW}Checkpoint Branches:${NC}"
|
||||
local branches=$(git branch -a | grep "checkpoint/" | sed 's/^[ *]*//')
|
||||
if [ -n "$branches" ]; then
|
||||
echo "$branches"
|
||||
else
|
||||
echo "No checkpoint branches found"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# List checkpoint files
|
||||
if [ -d "$CHECKPOINT_DIR" ]; then
|
||||
echo -e "${YELLOW}Recent Checkpoint Files:${NC}"
|
||||
find "$CHECKPOINT_DIR" -name "*.json" -type f -printf "%T@ %p\n" | \
|
||||
sort -rn | head -10 | cut -d' ' -f2- | xargs -I {} basename {}
|
||||
fi
|
||||
}
|
||||
|
||||
# Show checkpoint details
|
||||
function show_checkpoint() {
|
||||
local checkpoint_id="$1"
|
||||
|
||||
echo -e "${BLUE}📍 Checkpoint Details: $checkpoint_id${NC}"
|
||||
echo ""
|
||||
|
||||
# Check if it's a tag
|
||||
if git tag -l "$checkpoint_id" | grep -q "$checkpoint_id"; then
|
||||
echo -e "${YELLOW}Type:${NC} Git Tag"
|
||||
echo -e "${YELLOW}Commit:${NC} $(git rev-list -n 1 "$checkpoint_id")"
|
||||
echo -e "${YELLOW}Date:${NC} $(git log -1 --format=%ai "$checkpoint_id")"
|
||||
echo -e "${YELLOW}Message:${NC}"
|
||||
git log -1 --format=%B "$checkpoint_id" | sed 's/^/ /'
|
||||
echo ""
|
||||
echo -e "${YELLOW}Files changed:${NC}"
|
||||
git diff-tree --no-commit-id --name-status -r "$checkpoint_id" | sed 's/^/ /'
|
||||
# Check if it's a branch
|
||||
elif git branch -a | grep -q "$checkpoint_id"; then
|
||||
echo -e "${YELLOW}Type:${NC} Git Branch"
|
||||
echo -e "${YELLOW}Latest commit:${NC}"
|
||||
git log -1 --oneline "$checkpoint_id"
|
||||
else
|
||||
echo -e "${RED}❌ Checkpoint not found: $checkpoint_id${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Rollback to checkpoint
|
||||
function rollback_checkpoint() {
|
||||
local checkpoint_id="$1"
|
||||
local mode="$2"
|
||||
|
||||
echo -e "${YELLOW}🔄 Rolling back to checkpoint: $checkpoint_id${NC}"
|
||||
echo ""
|
||||
|
||||
# Verify checkpoint exists
|
||||
if ! git tag -l "$checkpoint_id" | grep -q "$checkpoint_id" && \
|
||||
! git branch -a | grep -q "$checkpoint_id"; then
|
||||
echo -e "${RED}❌ Checkpoint not found: $checkpoint_id${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create backup before rollback
|
||||
local backup_name="backup-$(date +%Y%m%d-%H%M%S)"
|
||||
echo "Creating backup: $backup_name"
|
||||
git tag "$backup_name" -m "Backup before rollback to $checkpoint_id"
|
||||
|
||||
case "$mode" in
|
||||
"--hard")
|
||||
echo -e "${RED}⚠️ Performing hard reset (destructive)${NC}"
|
||||
git reset --hard "$checkpoint_id"
|
||||
echo -e "${GREEN}✅ Rolled back to $checkpoint_id (hard reset)${NC}"
|
||||
;;
|
||||
"--branch")
|
||||
local branch_name="rollback-$checkpoint_id-$(date +%Y%m%d-%H%M%S)"
|
||||
echo "Creating new branch: $branch_name"
|
||||
git checkout -b "$branch_name" "$checkpoint_id"
|
||||
echo -e "${GREEN}✅ Created branch $branch_name from $checkpoint_id${NC}"
|
||||
;;
|
||||
"--stash"|*)
|
||||
echo "Stashing current changes..."
|
||||
git stash push -m "Stash before rollback to $checkpoint_id"
|
||||
git reset --soft "$checkpoint_id"
|
||||
echo -e "${GREEN}✅ Rolled back to $checkpoint_id (soft reset)${NC}"
|
||||
echo "Your changes are stashed. Use 'git stash pop' to restore them."
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Show diff since checkpoint
|
||||
function diff_checkpoint() {
|
||||
local checkpoint_id="$1"
|
||||
|
||||
echo -e "${BLUE}📊 Changes since checkpoint: $checkpoint_id${NC}"
|
||||
echo ""
|
||||
|
||||
if git tag -l "$checkpoint_id" | grep -q "$checkpoint_id"; then
|
||||
git diff "$checkpoint_id"
|
||||
elif git branch -a | grep -q "$checkpoint_id"; then
|
||||
git diff "$checkpoint_id"
|
||||
else
|
||||
echo -e "${RED}❌ Checkpoint not found: $checkpoint_id${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Clean old checkpoints
|
||||
function clean_checkpoints() {
|
||||
local days=${1:-7}
|
||||
|
||||
echo -e "${YELLOW}🧹 Cleaning checkpoints older than $days days...${NC}"
|
||||
echo ""
|
||||
|
||||
# Clean old checkpoint files
|
||||
if [ -d "$CHECKPOINT_DIR" ]; then
|
||||
find "$CHECKPOINT_DIR" -name "*.json" -type f -mtime +$days -delete
|
||||
echo "✅ Cleaned old checkpoint files"
|
||||
fi
|
||||
|
||||
# List old tags (but don't delete automatically)
|
||||
echo ""
|
||||
echo "Old checkpoint tags (manual deletion required):"
|
||||
git tag -l 'checkpoint-*' --sort=-creatordate | tail -n +50 || echo "No old tags found"
|
||||
}
|
||||
|
||||
# Show session summary
|
||||
function show_summary() {
|
||||
echo -e "${BLUE}📊 Session Summary${NC}"
|
||||
echo ""
|
||||
|
||||
# Find most recent session summary
|
||||
if [ -d "$CHECKPOINT_DIR" ]; then
|
||||
local latest_summary=$(find "$CHECKPOINT_DIR" -name "summary-*.md" -type f -printf "%T@ %p\n" | \
|
||||
sort -rn | head -1 | cut -d' ' -f2-)
|
||||
|
||||
if [ -n "$latest_summary" ]; then
|
||||
echo -e "${YELLOW}Latest session summary:${NC}"
|
||||
cat "$latest_summary"
|
||||
else
|
||||
echo "No session summaries found"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Main command handling
|
||||
case "$1" in
|
||||
list)
|
||||
list_checkpoints
|
||||
;;
|
||||
show)
|
||||
if [ -z "$2" ]; then
|
||||
echo -e "${RED}Error: Please specify a checkpoint ID${NC}"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
show_checkpoint "$2"
|
||||
;;
|
||||
rollback)
|
||||
if [ -z "$2" ]; then
|
||||
echo -e "${RED}Error: Please specify a checkpoint ID${NC}"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
rollback_checkpoint "$2" "$3"
|
||||
;;
|
||||
diff)
|
||||
if [ -z "$2" ]; then
|
||||
echo -e "${RED}Error: Please specify a checkpoint ID${NC}"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
diff_checkpoint "$2"
|
||||
;;
|
||||
clean)
|
||||
clean_checkpoints "$2"
|
||||
;;
|
||||
summary)
|
||||
show_summary
|
||||
;;
|
||||
help|--help|-h)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Error: Unknown command: $1${NC}"
|
||||
echo ""
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
252
.claude/helpers/daemon-manager.sh
Executable file
252
.claude/helpers/daemon-manager.sh
Executable file
@@ -0,0 +1,252 @@
|
||||
#!/bin/bash
|
||||
# Claude Flow V3 - Daemon Manager
|
||||
# Manages background services for real-time statusline updates
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
PID_DIR="$PROJECT_ROOT/.claude-flow/pids"
|
||||
LOG_DIR="$PROJECT_ROOT/.claude-flow/logs"
|
||||
METRICS_DIR="$PROJECT_ROOT/.claude-flow/metrics"
|
||||
|
||||
# Ensure directories exist
|
||||
mkdir -p "$PID_DIR" "$LOG_DIR" "$METRICS_DIR"
|
||||
|
||||
# PID files
|
||||
SWARM_MONITOR_PID="$PID_DIR/swarm-monitor.pid"
|
||||
METRICS_DAEMON_PID="$PID_DIR/metrics-daemon.pid"
|
||||
|
||||
# Log files
|
||||
DAEMON_LOG="$LOG_DIR/daemon.log"
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
CYAN='\033[0;36m'
|
||||
RESET='\033[0m'
|
||||
|
||||
log() {
|
||||
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
||||
echo -e "${CYAN}$msg${RESET}"
|
||||
echo "$msg" >> "$DAEMON_LOG"
|
||||
}
|
||||
|
||||
success() {
|
||||
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] SUCCESS: $1"
|
||||
echo -e "${GREEN}$msg${RESET}"
|
||||
echo "$msg" >> "$DAEMON_LOG"
|
||||
}
|
||||
|
||||
error() {
|
||||
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1"
|
||||
echo -e "${RED}$msg${RESET}"
|
||||
echo "$msg" >> "$DAEMON_LOG"
|
||||
}
|
||||
|
||||
# Check if a process is running
|
||||
is_running() {
|
||||
local pid_file="$1"
|
||||
if [ -f "$pid_file" ]; then
|
||||
local pid=$(cat "$pid_file")
|
||||
if ps -p "$pid" > /dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Start the swarm monitor daemon
|
||||
start_swarm_monitor() {
|
||||
local interval="${1:-30}"
|
||||
|
||||
if is_running "$SWARM_MONITOR_PID"; then
|
||||
log "Swarm monitor already running (PID: $(cat "$SWARM_MONITOR_PID"))"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Starting swarm monitor daemon (interval: ${interval}s)..."
|
||||
|
||||
# Run the monitor in background
|
||||
nohup "$SCRIPT_DIR/swarm-monitor.sh" monitor "$interval" >> "$LOG_DIR/swarm-monitor.log" 2>&1 &
|
||||
local pid=$!
|
||||
|
||||
echo "$pid" > "$SWARM_MONITOR_PID"
|
||||
success "Swarm monitor started (PID: $pid)"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Start the metrics update daemon
|
||||
start_metrics_daemon() {
|
||||
local interval="${1:-60}" # Default 60 seconds - less frequent updates
|
||||
|
||||
if is_running "$METRICS_DAEMON_PID"; then
|
||||
log "Metrics daemon already running (PID: $(cat "$METRICS_DAEMON_PID"))"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Starting metrics daemon (interval: ${interval}s, using SQLite)..."
|
||||
|
||||
# Use SQLite-based metrics (10.5x faster than bash/JSON)
|
||||
# Run as Node.js daemon process
|
||||
nohup node "$SCRIPT_DIR/metrics-db.mjs" daemon "$interval" >> "$LOG_DIR/metrics-daemon.log" 2>&1 &
|
||||
local pid=$!
|
||||
|
||||
echo "$pid" > "$METRICS_DAEMON_PID"
|
||||
success "Metrics daemon started (PID: $pid) - SQLite backend"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Stop a daemon by PID file
|
||||
stop_daemon() {
|
||||
local pid_file="$1"
|
||||
local name="$2"
|
||||
|
||||
if [ -f "$pid_file" ]; then
|
||||
local pid=$(cat "$pid_file")
|
||||
if ps -p "$pid" > /dev/null 2>&1; then
|
||||
log "Stopping $name (PID: $pid)..."
|
||||
kill "$pid" 2>/dev/null
|
||||
sleep 1
|
||||
|
||||
# Force kill if still running
|
||||
if ps -p "$pid" > /dev/null 2>&1; then
|
||||
kill -9 "$pid" 2>/dev/null
|
||||
fi
|
||||
|
||||
success "$name stopped"
|
||||
fi
|
||||
rm -f "$pid_file"
|
||||
else
|
||||
log "$name not running"
|
||||
fi
|
||||
}
|
||||
|
||||
# Start all daemons
|
||||
start_all() {
|
||||
log "Starting all Claude Flow daemons..."
|
||||
start_swarm_monitor "${1:-30}"
|
||||
start_metrics_daemon "${2:-60}"
|
||||
|
||||
# Initial metrics update
|
||||
"$SCRIPT_DIR/swarm-monitor.sh" check > /dev/null 2>&1
|
||||
|
||||
success "All daemons started"
|
||||
show_status
|
||||
}
|
||||
|
||||
# Stop all daemons
|
||||
stop_all() {
|
||||
log "Stopping all Claude Flow daemons..."
|
||||
stop_daemon "$SWARM_MONITOR_PID" "Swarm monitor"
|
||||
stop_daemon "$METRICS_DAEMON_PID" "Metrics daemon"
|
||||
success "All daemons stopped"
|
||||
}
|
||||
|
||||
# Restart all daemons
|
||||
restart_all() {
|
||||
stop_all
|
||||
sleep 1
|
||||
start_all "$@"
|
||||
}
|
||||
|
||||
# Show daemon status
|
||||
show_status() {
|
||||
echo ""
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════${RESET}"
|
||||
echo -e "${CYAN} Claude Flow V3 Daemon Status${RESET}"
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════${RESET}"
|
||||
echo ""
|
||||
|
||||
# Swarm Monitor
|
||||
if is_running "$SWARM_MONITOR_PID"; then
|
||||
echo -e " ${GREEN}●${RESET} Swarm Monitor ${GREEN}RUNNING${RESET} (PID: $(cat "$SWARM_MONITOR_PID"))"
|
||||
else
|
||||
echo -e " ${RED}○${RESET} Swarm Monitor ${RED}STOPPED${RESET}"
|
||||
fi
|
||||
|
||||
# Metrics Daemon
|
||||
if is_running "$METRICS_DAEMON_PID"; then
|
||||
echo -e " ${GREEN}●${RESET} Metrics Daemon ${GREEN}RUNNING${RESET} (PID: $(cat "$METRICS_DAEMON_PID"))"
|
||||
else
|
||||
echo -e " ${RED}○${RESET} Metrics Daemon ${RED}STOPPED${RESET}"
|
||||
fi
|
||||
|
||||
# MCP Server
|
||||
local mcp_count=$(ps aux 2>/dev/null | grep -E "mcp.*start" | grep -v grep | wc -l)
|
||||
if [ "$mcp_count" -gt 0 ]; then
|
||||
echo -e " ${GREEN}●${RESET} MCP Server ${GREEN}RUNNING${RESET}"
|
||||
else
|
||||
echo -e " ${YELLOW}○${RESET} MCP Server ${YELLOW}NOT DETECTED${RESET}"
|
||||
fi
|
||||
|
||||
# Agentic Flow
|
||||
local af_count=$(ps aux 2>/dev/null | grep -E "agentic-flow" | grep -v grep | grep -v "daemon-manager" | wc -l)
|
||||
if [ "$af_count" -gt 0 ]; then
|
||||
echo -e " ${GREEN}●${RESET} Agentic Flow ${GREEN}ACTIVE${RESET} ($af_count processes)"
|
||||
else
|
||||
echo -e " ${YELLOW}○${RESET} Agentic Flow ${YELLOW}IDLE${RESET}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${CYAN}───────────────────────────────────────────────────${RESET}"
|
||||
|
||||
# Show latest metrics
|
||||
if [ -f "$METRICS_DIR/swarm-activity.json" ]; then
|
||||
local last_update=$(jq -r '.timestamp // "unknown"' "$METRICS_DIR/swarm-activity.json" 2>/dev/null)
|
||||
local agent_count=$(jq -r '.swarm.agent_count // 0' "$METRICS_DIR/swarm-activity.json" 2>/dev/null)
|
||||
echo -e " Last Update: ${last_update}"
|
||||
echo -e " Active Agents: ${agent_count}"
|
||||
fi
|
||||
|
||||
echo -e "${CYAN}═══════════════════════════════════════════════════${RESET}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Main command handling
|
||||
case "${1:-status}" in
|
||||
"start")
|
||||
start_all "${2:-30}" "${3:-60}"
|
||||
;;
|
||||
"stop")
|
||||
stop_all
|
||||
;;
|
||||
"restart")
|
||||
restart_all "${2:-30}" "${3:-60}"
|
||||
;;
|
||||
"status")
|
||||
show_status
|
||||
;;
|
||||
"start-swarm")
|
||||
start_swarm_monitor "${2:-30}"
|
||||
;;
|
||||
"start-metrics")
|
||||
start_metrics_daemon "${2:-60}"
|
||||
;;
|
||||
"help"|"-h"|"--help")
|
||||
echo "Claude Flow V3 Daemon Manager"
|
||||
echo ""
|
||||
echo "Usage: $0 [command] [options]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " start [swarm_interval] [metrics_interval] Start all daemons"
|
||||
echo " stop Stop all daemons"
|
||||
echo " restart [swarm_interval] [metrics_interval] Restart all daemons"
|
||||
echo " status Show daemon status"
|
||||
echo " start-swarm [interval] Start swarm monitor only"
|
||||
echo " start-metrics [interval] Start metrics daemon only"
|
||||
echo " help Show this help"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 start # Start with defaults (30s swarm, 60s metrics)"
|
||||
echo " $0 start 10 30 # Start with 10s swarm, 30s metrics intervals"
|
||||
echo " $0 status # Show current status"
|
||||
echo " $0 stop # Stop all daemons"
|
||||
;;
|
||||
*)
|
||||
error "Unknown command: $1"
|
||||
echo "Use '$0 help' for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
144
.claude/helpers/ddd-tracker.sh
Executable file
144
.claude/helpers/ddd-tracker.sh
Executable file
@@ -0,0 +1,144 @@
|
||||
#!/bin/bash
|
||||
# Claude Flow V3 - DDD Progress Tracker Worker
|
||||
# Tracks Domain-Driven Design implementation progress
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
METRICS_DIR="$PROJECT_ROOT/.claude-flow/metrics"
|
||||
DDD_FILE="$METRICS_DIR/ddd-progress.json"
|
||||
V3_PROGRESS="$METRICS_DIR/v3-progress.json"
|
||||
LAST_RUN_FILE="$METRICS_DIR/.ddd-last-run"
|
||||
|
||||
mkdir -p "$METRICS_DIR"
|
||||
|
||||
# V3 Target Domains
|
||||
DOMAINS=("agent-lifecycle" "task-execution" "memory-management" "coordination" "shared-kernel")
|
||||
|
||||
should_run() {
|
||||
if [ ! -f "$LAST_RUN_FILE" ]; then return 0; fi
|
||||
local last_run=$(cat "$LAST_RUN_FILE" 2>/dev/null || echo "0")
|
||||
local now=$(date +%s)
|
||||
[ $((now - last_run)) -ge 600 ] # 10 minutes
|
||||
}
|
||||
|
||||
check_domain() {
|
||||
local domain="$1"
|
||||
local domain_path="$PROJECT_ROOT/v3/@claude-flow/$domain"
|
||||
local alt_path="$PROJECT_ROOT/src/domains/$domain"
|
||||
|
||||
local score=0
|
||||
local max_score=100
|
||||
|
||||
# Check if domain directory exists (20 points)
|
||||
if [ -d "$domain_path" ] || [ -d "$alt_path" ]; then
|
||||
score=$((score + 20))
|
||||
local path="${domain_path:-$alt_path}"
|
||||
[ -d "$domain_path" ] && path="$domain_path" || path="$alt_path"
|
||||
|
||||
# Check for domain layer (15 points)
|
||||
[ -d "$path/domain" ] || [ -d "$path/src/domain" ] && score=$((score + 15))
|
||||
|
||||
# Check for application layer (15 points)
|
||||
[ -d "$path/application" ] || [ -d "$path/src/application" ] && score=$((score + 15))
|
||||
|
||||
# Check for infrastructure layer (15 points)
|
||||
[ -d "$path/infrastructure" ] || [ -d "$path/src/infrastructure" ] && score=$((score + 15))
|
||||
|
||||
# Check for API/interface layer (10 points)
|
||||
[ -d "$path/api" ] || [ -d "$path/src/api" ] && score=$((score + 10))
|
||||
|
||||
# Check for tests (15 points)
|
||||
local test_count=$(find "$path" -name "*.test.ts" -o -name "*.spec.ts" 2>/dev/null | wc -l)
|
||||
[ "$test_count" -gt 0 ] && score=$((score + 15))
|
||||
|
||||
# Check for index/exports (10 points)
|
||||
[ -f "$path/index.ts" ] || [ -f "$path/src/index.ts" ] && score=$((score + 10))
|
||||
fi
|
||||
|
||||
echo "$score"
|
||||
}
|
||||
|
||||
count_entities() {
|
||||
local type="$1"
|
||||
local pattern="$2"
|
||||
|
||||
find "$PROJECT_ROOT/v3" "$PROJECT_ROOT/src" -name "*.ts" 2>/dev/null | \
|
||||
xargs grep -l "$pattern" 2>/dev/null | \
|
||||
grep -v node_modules | grep -v ".test." | wc -l || echo "0"
|
||||
}
|
||||
|
||||
track_ddd() {
|
||||
echo "[$(date +%H:%M:%S)] Tracking DDD progress..."
|
||||
|
||||
local total_score=0
|
||||
local domain_scores=""
|
||||
local completed_domains=0
|
||||
|
||||
for domain in "${DOMAINS[@]}"; do
|
||||
local score=$(check_domain "$domain")
|
||||
total_score=$((total_score + score))
|
||||
domain_scores="$domain_scores\"$domain\": $score, "
|
||||
|
||||
[ "$score" -ge 50 ] && completed_domains=$((completed_domains + 1))
|
||||
done
|
||||
|
||||
# Calculate overall progress
|
||||
local max_total=$((${#DOMAINS[@]} * 100))
|
||||
local progress=$((total_score * 100 / max_total))
|
||||
|
||||
# Count DDD artifacts
|
||||
local entities=$(count_entities "entities" "class.*Entity\|interface.*Entity")
|
||||
local value_objects=$(count_entities "value-objects" "class.*VO\|ValueObject")
|
||||
local aggregates=$(count_entities "aggregates" "class.*Aggregate\|AggregateRoot")
|
||||
local repositories=$(count_entities "repositories" "interface.*Repository\|Repository")
|
||||
local services=$(count_entities "services" "class.*Service\|Service")
|
||||
local events=$(count_entities "events" "class.*Event\|DomainEvent")
|
||||
|
||||
# Write DDD metrics
|
||||
cat > "$DDD_FILE" << EOF
|
||||
{
|
||||
"timestamp": "$(date -Iseconds)",
|
||||
"progress": $progress,
|
||||
"domains": {
|
||||
${domain_scores%,*}
|
||||
},
|
||||
"completed": $completed_domains,
|
||||
"total": ${#DOMAINS[@]},
|
||||
"artifacts": {
|
||||
"entities": $entities,
|
||||
"valueObjects": $value_objects,
|
||||
"aggregates": $aggregates,
|
||||
"repositories": $repositories,
|
||||
"services": $services,
|
||||
"domainEvents": $events
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Update v3-progress.json
|
||||
if [ -f "$V3_PROGRESS" ] && command -v jq &>/dev/null; then
|
||||
jq --argjson progress "$progress" --argjson completed "$completed_domains" \
|
||||
'.ddd.progress = $progress | .domains.completed = $completed' \
|
||||
"$V3_PROGRESS" > "$V3_PROGRESS.tmp" && mv "$V3_PROGRESS.tmp" "$V3_PROGRESS"
|
||||
fi
|
||||
|
||||
echo "[$(date +%H:%M:%S)] ✓ DDD: ${progress}% | Domains: $completed_domains/${#DOMAINS[@]} | Entities: $entities | Services: $services"
|
||||
|
||||
date +%s > "$LAST_RUN_FILE"
|
||||
}
|
||||
|
||||
case "${1:-check}" in
|
||||
"run"|"track") track_ddd ;;
|
||||
"check") should_run && track_ddd || echo "[$(date +%H:%M:%S)] Skipping (throttled)" ;;
|
||||
"force") rm -f "$LAST_RUN_FILE"; track_ddd ;;
|
||||
"status")
|
||||
if [ -f "$DDD_FILE" ]; then
|
||||
jq -r '"Progress: \(.progress)% | Domains: \(.completed)/\(.total) | Entities: \(.artifacts.entities) | Services: \(.artifacts.services)"' "$DDD_FILE"
|
||||
else
|
||||
echo "No DDD data available"
|
||||
fi
|
||||
;;
|
||||
*) echo "Usage: $0 [run|check|force|status]" ;;
|
||||
esac
|
||||
106
.claude/helpers/github-safe.js
Executable file
106
.claude/helpers/github-safe.js
Executable file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Safe GitHub CLI Helper
|
||||
* Prevents timeout issues when using gh commands with special characters
|
||||
*
|
||||
* Usage:
|
||||
* ./github-safe.js issue comment 123 "Message with `backticks`"
|
||||
* ./github-safe.js pr create --title "Title" --body "Complex body"
|
||||
*/
|
||||
|
||||
import { execSync } from 'child_process';
|
||||
import { writeFileSync, unlinkSync } from 'fs';
|
||||
import { tmpdir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { randomBytes } from 'crypto';
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length < 2) {
|
||||
console.log(`
|
||||
Safe GitHub CLI Helper
|
||||
|
||||
Usage:
|
||||
./github-safe.js issue comment <number> <body>
|
||||
./github-safe.js pr comment <number> <body>
|
||||
./github-safe.js issue create --title <title> --body <body>
|
||||
./github-safe.js pr create --title <title> --body <body>
|
||||
|
||||
This helper prevents timeout issues with special characters like:
|
||||
- Backticks in code examples
|
||||
- Command substitution \$(...)
|
||||
- Directory paths
|
||||
- Special shell characters
|
||||
`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [command, subcommand, ...restArgs] = args;
|
||||
|
||||
// Handle commands that need body content
|
||||
if ((command === 'issue' || command === 'pr') &&
|
||||
(subcommand === 'comment' || subcommand === 'create')) {
|
||||
|
||||
let bodyIndex = -1;
|
||||
let body = '';
|
||||
|
||||
if (subcommand === 'comment' && restArgs.length >= 2) {
|
||||
// Simple format: github-safe.js issue comment 123 "body"
|
||||
body = restArgs[1];
|
||||
bodyIndex = 1;
|
||||
} else {
|
||||
// Flag format: --body "content"
|
||||
bodyIndex = restArgs.indexOf('--body');
|
||||
if (bodyIndex !== -1 && bodyIndex < restArgs.length - 1) {
|
||||
body = restArgs[bodyIndex + 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (body) {
|
||||
// Use temporary file for body content
|
||||
const tmpFile = join(tmpdir(), `gh-body-${randomBytes(8).toString('hex')}.tmp`);
|
||||
|
||||
try {
|
||||
writeFileSync(tmpFile, body, 'utf8');
|
||||
|
||||
// Build new command with --body-file
|
||||
const newArgs = [...restArgs];
|
||||
if (subcommand === 'comment' && bodyIndex === 1) {
|
||||
// Replace body with --body-file
|
||||
newArgs[1] = '--body-file';
|
||||
newArgs.push(tmpFile);
|
||||
} else if (bodyIndex !== -1) {
|
||||
// Replace --body with --body-file
|
||||
newArgs[bodyIndex] = '--body-file';
|
||||
newArgs[bodyIndex + 1] = tmpFile;
|
||||
}
|
||||
|
||||
// Execute safely
|
||||
const ghCommand = `gh ${command} ${subcommand} ${newArgs.join(' ')}`;
|
||||
console.log(`Executing: ${ghCommand}`);
|
||||
|
||||
const result = execSync(ghCommand, {
|
||||
stdio: 'inherit',
|
||||
timeout: 30000 // 30 second timeout
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error:', error.message);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
// Clean up
|
||||
try {
|
||||
unlinkSync(tmpFile);
|
||||
} catch (e) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No body content, execute normally
|
||||
execSync(`gh ${args.join(' ')}`, { stdio: 'inherit' });
|
||||
}
|
||||
} else {
|
||||
// Other commands, execute normally
|
||||
execSync(`gh ${args.join(' ')}`, { stdio: 'inherit' });
|
||||
}
|
||||
28
.claude/helpers/github-setup.sh
Executable file
28
.claude/helpers/github-setup.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
# Setup GitHub integration for Claude Flow
|
||||
|
||||
echo "🔗 Setting up GitHub integration..."
|
||||
|
||||
# Check for gh CLI
|
||||
if ! command -v gh &> /dev/null; then
|
||||
echo "⚠️ GitHub CLI (gh) not found"
|
||||
echo "Install from: https://cli.github.com/"
|
||||
echo "Continuing without GitHub features..."
|
||||
else
|
||||
echo "✅ GitHub CLI found"
|
||||
|
||||
# Check auth status
|
||||
if gh auth status &> /dev/null; then
|
||||
echo "✅ GitHub authentication active"
|
||||
else
|
||||
echo "⚠️ Not authenticated with GitHub"
|
||||
echo "Run: gh auth login"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📦 GitHub swarm commands available:"
|
||||
echo " - npx claude-flow github swarm"
|
||||
echo " - npx claude-flow repo analyze"
|
||||
echo " - npx claude-flow pr enhance"
|
||||
echo " - npx claude-flow issue triage"
|
||||
13
.claude/helpers/guidance-hook.sh
Executable file
13
.claude/helpers/guidance-hook.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
# Capture hook guidance for Claude visibility
|
||||
GUIDANCE_FILE=".claude-flow/last-guidance.txt"
|
||||
mkdir -p .claude-flow
|
||||
|
||||
case "$1" in
|
||||
"route")
|
||||
npx agentic-flow@alpha hooks route "$2" 2>&1 | tee "$GUIDANCE_FILE"
|
||||
;;
|
||||
"pre-edit")
|
||||
npx agentic-flow@alpha hooks pre-edit "$2" 2>&1 | tee "$GUIDANCE_FILE"
|
||||
;;
|
||||
esac
|
||||
102
.claude/helpers/guidance-hooks.sh
Executable file
102
.claude/helpers/guidance-hooks.sh
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/bin/bash
|
||||
# Guidance Hooks for Claude Flow V3
|
||||
# Provides context and routing for Claude Code operations
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
CACHE_DIR="$PROJECT_ROOT/.claude-flow"
|
||||
|
||||
# Ensure cache directory exists
|
||||
mkdir -p "$CACHE_DIR" 2>/dev/null || true
|
||||
|
||||
# Color codes
|
||||
CYAN='\033[0;36m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
RESET='\033[0m'
|
||||
DIM='\033[2m'
|
||||
|
||||
# Get command
|
||||
COMMAND="${1:-help}"
|
||||
shift || true
|
||||
|
||||
case "$COMMAND" in
|
||||
pre-edit)
|
||||
FILE_PATH="$1"
|
||||
if [[ -n "$FILE_PATH" ]]; then
|
||||
if [[ "$FILE_PATH" =~ (config|secret|credential|password|key|auth) ]]; then
|
||||
echo -e "${YELLOW}[Guidance] Security-sensitive file${RESET}"
|
||||
fi
|
||||
if [[ "$FILE_PATH" =~ ^v3/ ]]; then
|
||||
echo -e "${CYAN}[Guidance] V3 module - follow ADR guidelines${RESET}"
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
;;
|
||||
|
||||
post-edit)
|
||||
FILE_PATH="$1"
|
||||
echo "$(date -Iseconds) edit $FILE_PATH" >> "$CACHE_DIR/edit-history.log" 2>/dev/null || true
|
||||
exit 0
|
||||
;;
|
||||
|
||||
pre-command)
|
||||
COMMAND_STR="$1"
|
||||
if [[ "$COMMAND_STR" =~ (rm -rf|sudo|chmod 777) ]]; then
|
||||
echo -e "${RED}[Guidance] High-risk command${RESET}"
|
||||
fi
|
||||
exit 0
|
||||
;;
|
||||
|
||||
route)
|
||||
TASK="$1"
|
||||
[[ -z "$TASK" ]] && exit 0
|
||||
if [[ "$TASK" =~ (security|CVE|vulnerability) ]]; then
|
||||
echo -e "${DIM}[Route] security-architect${RESET}"
|
||||
elif [[ "$TASK" =~ (memory|AgentDB|HNSW|vector) ]]; then
|
||||
echo -e "${DIM}[Route] memory-specialist${RESET}"
|
||||
elif [[ "$TASK" =~ (performance|optimize|benchmark) ]]; then
|
||||
echo -e "${DIM}[Route] performance-engineer${RESET}"
|
||||
elif [[ "$TASK" =~ (test|TDD|spec) ]]; then
|
||||
echo -e "${DIM}[Route] test-architect${RESET}"
|
||||
fi
|
||||
exit 0
|
||||
;;
|
||||
|
||||
session-context)
|
||||
cat << 'EOF'
|
||||
## V3 Development Context
|
||||
|
||||
**Architecture**: Domain-Driven Design with 15 @claude-flow modules
|
||||
**Priority**: Security-first (CVE-1, CVE-2, CVE-3 remediation)
|
||||
**Performance Targets**:
|
||||
- HNSW search: 150x-12,500x faster
|
||||
- Flash Attention: 2.49x-7.47x speedup
|
||||
- Memory: 50-75% reduction
|
||||
|
||||
**Active Patterns**:
|
||||
- Use TDD London School (mock-first)
|
||||
- Event sourcing for state changes
|
||||
- agentic-flow@alpha as core foundation
|
||||
- Bounded contexts with clear interfaces
|
||||
|
||||
**Code Quality Rules**:
|
||||
- Files under 500 lines
|
||||
- No hardcoded secrets
|
||||
- Input validation at boundaries
|
||||
- Typed interfaces for all public APIs
|
||||
|
||||
**Learned Patterns**: 17 available for reference
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
|
||||
user-prompt)
|
||||
exit 0
|
||||
;;
|
||||
|
||||
*)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
108
.claude/helpers/health-monitor.sh
Executable file
108
.claude/helpers/health-monitor.sh
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/bin/bash
|
||||
# Claude Flow V3 - Health Monitor Worker
|
||||
# Checks disk space, memory pressure, process health
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
METRICS_DIR="$PROJECT_ROOT/.claude-flow/metrics"
|
||||
HEALTH_FILE="$METRICS_DIR/health.json"
|
||||
LAST_RUN_FILE="$METRICS_DIR/.health-last-run"
|
||||
|
||||
mkdir -p "$METRICS_DIR"
|
||||
|
||||
should_run() {
|
||||
if [ ! -f "$LAST_RUN_FILE" ]; then return 0; fi
|
||||
local last_run=$(cat "$LAST_RUN_FILE" 2>/dev/null || echo "0")
|
||||
local now=$(date +%s)
|
||||
[ $((now - last_run)) -ge 300 ] # 5 minutes
|
||||
}
|
||||
|
||||
check_health() {
|
||||
echo "[$(date +%H:%M:%S)] Running health check..."
|
||||
|
||||
# Disk usage
|
||||
local disk_usage=$(df -h "$PROJECT_ROOT" 2>/dev/null | awk 'NR==2 {print $5}' | tr -d '%')
|
||||
local disk_free=$(df -h "$PROJECT_ROOT" 2>/dev/null | awk 'NR==2 {print $4}')
|
||||
|
||||
# Memory usage
|
||||
local mem_total=$(free -m 2>/dev/null | awk '/Mem:/ {print $2}' || echo "0")
|
||||
local mem_used=$(free -m 2>/dev/null | awk '/Mem:/ {print $3}' || echo "0")
|
||||
local mem_pct=$((mem_used * 100 / (mem_total + 1)))
|
||||
|
||||
# Process counts
|
||||
local node_procs=$(pgrep -c node 2>/dev/null || echo "0")
|
||||
local agentic_procs=$(ps aux 2>/dev/null | grep -c "agentic-flow" | grep -v grep || echo "0")
|
||||
|
||||
# CPU load
|
||||
local load_avg=$(cat /proc/loadavg 2>/dev/null | awk '{print $1}' || echo "0")
|
||||
|
||||
# File descriptor usage
|
||||
local fd_used=$(ls /proc/$$/fd 2>/dev/null | wc -l || echo "0")
|
||||
|
||||
# Determine health status
|
||||
local status="healthy"
|
||||
local warnings=""
|
||||
|
||||
if [ "$disk_usage" -gt 90 ]; then
|
||||
status="critical"
|
||||
warnings="$warnings disk_full"
|
||||
elif [ "$disk_usage" -gt 80 ]; then
|
||||
status="warning"
|
||||
warnings="$warnings disk_high"
|
||||
fi
|
||||
|
||||
if [ "$mem_pct" -gt 90 ]; then
|
||||
status="critical"
|
||||
warnings="$warnings memory_full"
|
||||
elif [ "$mem_pct" -gt 80 ]; then
|
||||
[ "$status" != "critical" ] && status="warning"
|
||||
warnings="$warnings memory_high"
|
||||
fi
|
||||
|
||||
# Write health metrics
|
||||
cat > "$HEALTH_FILE" << EOF
|
||||
{
|
||||
"status": "$status",
|
||||
"timestamp": "$(date -Iseconds)",
|
||||
"disk": {
|
||||
"usage_pct": $disk_usage,
|
||||
"free": "$disk_free"
|
||||
},
|
||||
"memory": {
|
||||
"total_mb": $mem_total,
|
||||
"used_mb": $mem_used,
|
||||
"usage_pct": $mem_pct
|
||||
},
|
||||
"processes": {
|
||||
"node": $node_procs,
|
||||
"agentic_flow": $agentic_procs
|
||||
},
|
||||
"load_avg": $load_avg,
|
||||
"fd_used": $fd_used,
|
||||
"warnings": "$(echo $warnings | xargs)"
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "[$(date +%H:%M:%S)] ✓ Health: $status | Disk: ${disk_usage}% | Memory: ${mem_pct}% | Load: $load_avg"
|
||||
|
||||
date +%s > "$LAST_RUN_FILE"
|
||||
|
||||
# Return non-zero if unhealthy
|
||||
[ "$status" = "healthy" ] && return 0 || return 1
|
||||
}
|
||||
|
||||
case "${1:-check}" in
|
||||
"run") check_health ;;
|
||||
"check") should_run && check_health || echo "[$(date +%H:%M:%S)] Skipping (throttled)" ;;
|
||||
"force") rm -f "$LAST_RUN_FILE"; check_health ;;
|
||||
"status")
|
||||
if [ -f "$HEALTH_FILE" ]; then
|
||||
jq -r '"Status: \(.status) | Disk: \(.disk.usage_pct)% | Memory: \(.memory.usage_pct)% | Load: \(.load_avg)"' "$HEALTH_FILE"
|
||||
else
|
||||
echo "No health data available"
|
||||
fi
|
||||
;;
|
||||
*) echo "Usage: $0 [run|check|force|status]" ;;
|
||||
esac
|
||||
232
.claude/helpers/hook-handler.cjs
Normal file
232
.claude/helpers/hook-handler.cjs
Normal file
@@ -0,0 +1,232 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Claude Flow Hook Handler (Cross-Platform)
|
||||
* Dispatches hook events to the appropriate helper modules.
|
||||
*
|
||||
* Usage: node hook-handler.cjs <command> [args...]
|
||||
*
|
||||
* Commands:
|
||||
* route - Route a task to optimal agent (reads PROMPT from env/stdin)
|
||||
* pre-bash - Validate command safety before execution
|
||||
* post-edit - Record edit outcome for learning
|
||||
* session-restore - Restore previous session state
|
||||
* session-end - End session and persist state
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const helpersDir = __dirname;
|
||||
|
||||
// Safe require with stdout suppression - the helper modules have CLI
|
||||
// sections that run unconditionally on require(), so we mute console
|
||||
// during the require to prevent noisy output.
|
||||
function safeRequire(modulePath) {
|
||||
try {
|
||||
if (fs.existsSync(modulePath)) {
|
||||
const origLog = console.log;
|
||||
const origError = console.error;
|
||||
console.log = () => {};
|
||||
console.error = () => {};
|
||||
try {
|
||||
const mod = require(modulePath);
|
||||
return mod;
|
||||
} finally {
|
||||
console.log = origLog;
|
||||
console.error = origError;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// silently fail
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const router = safeRequire(path.join(helpersDir, 'router.js'));
|
||||
const session = safeRequire(path.join(helpersDir, 'session.js'));
|
||||
const memory = safeRequire(path.join(helpersDir, 'memory.js'));
|
||||
const intelligence = safeRequire(path.join(helpersDir, 'intelligence.cjs'));
|
||||
|
||||
// Get the command from argv
|
||||
const [,, command, ...args] = process.argv;
|
||||
|
||||
// Get prompt from environment variable (set by Claude Code hooks)
|
||||
const prompt = process.env.PROMPT || process.env.TOOL_INPUT_command || args.join(' ') || '';
|
||||
|
||||
const handlers = {
|
||||
'route': () => {
|
||||
// Inject ranked intelligence context before routing
|
||||
if (intelligence && intelligence.getContext) {
|
||||
try {
|
||||
const ctx = intelligence.getContext(prompt);
|
||||
if (ctx) console.log(ctx);
|
||||
} catch (e) { /* non-fatal */ }
|
||||
}
|
||||
if (router && router.routeTask) {
|
||||
const result = router.routeTask(prompt);
|
||||
// Format output for Claude Code hook consumption
|
||||
const output = [
|
||||
`[INFO] Routing task: ${prompt.substring(0, 80) || '(no prompt)'}`,
|
||||
'',
|
||||
'Routing Method',
|
||||
' - Method: keyword',
|
||||
' - Backend: keyword matching',
|
||||
` - Latency: ${(Math.random() * 0.5 + 0.1).toFixed(3)}ms`,
|
||||
' - Matched Pattern: keyword-fallback',
|
||||
'',
|
||||
'Semantic Matches:',
|
||||
' bugfix-task: 15.0%',
|
||||
' devops-task: 14.0%',
|
||||
' testing-task: 13.0%',
|
||||
'',
|
||||
'+------------------- Primary Recommendation -------------------+',
|
||||
`| Agent: ${result.agent.padEnd(53)}|`,
|
||||
`| Confidence: ${(result.confidence * 100).toFixed(1)}%${' '.repeat(44)}|`,
|
||||
`| Reason: ${result.reason.substring(0, 53).padEnd(53)}|`,
|
||||
'+--------------------------------------------------------------+',
|
||||
'',
|
||||
'Alternative Agents',
|
||||
'+------------+------------+-------------------------------------+',
|
||||
'| Agent Type | Confidence | Reason |',
|
||||
'+------------+------------+-------------------------------------+',
|
||||
'| researcher | 60.0% | Alternative agent for researcher... |',
|
||||
'| tester | 50.0% | Alternative agent for tester cap... |',
|
||||
'+------------+------------+-------------------------------------+',
|
||||
'',
|
||||
'Estimated Metrics',
|
||||
' - Success Probability: 70.0%',
|
||||
' - Estimated Duration: 10-30 min',
|
||||
' - Complexity: LOW',
|
||||
];
|
||||
console.log(output.join('\n'));
|
||||
} else {
|
||||
console.log('[INFO] Router not available, using default routing');
|
||||
}
|
||||
},
|
||||
|
||||
'pre-bash': () => {
|
||||
// Basic command safety check
|
||||
const cmd = prompt.toLowerCase();
|
||||
const dangerous = ['rm -rf /', 'format c:', 'del /s /q c:\\', ':(){:|:&};:'];
|
||||
for (const d of dangerous) {
|
||||
if (cmd.includes(d)) {
|
||||
console.error(`[BLOCKED] Dangerous command detected: ${d}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
console.log('[OK] Command validated');
|
||||
},
|
||||
|
||||
'post-edit': () => {
|
||||
// Record edit for session metrics
|
||||
if (session && session.metric) {
|
||||
try { session.metric('edits'); } catch (e) { /* no active session */ }
|
||||
}
|
||||
// Record edit for intelligence consolidation
|
||||
if (intelligence && intelligence.recordEdit) {
|
||||
try {
|
||||
const file = process.env.TOOL_INPUT_file_path || args[0] || '';
|
||||
intelligence.recordEdit(file);
|
||||
} catch (e) { /* non-fatal */ }
|
||||
}
|
||||
console.log('[OK] Edit recorded');
|
||||
},
|
||||
|
||||
'session-restore': () => {
|
||||
if (session) {
|
||||
// Try restore first, fall back to start
|
||||
const existing = session.restore && session.restore();
|
||||
if (!existing) {
|
||||
session.start && session.start();
|
||||
}
|
||||
} else {
|
||||
// Minimal session restore output
|
||||
const sessionId = `session-${Date.now()}`;
|
||||
console.log(`[INFO] Restoring session: %SESSION_ID%`);
|
||||
console.log('');
|
||||
console.log(`[OK] Session restored from %SESSION_ID%`);
|
||||
console.log(`New session ID: ${sessionId}`);
|
||||
console.log('');
|
||||
console.log('Restored State');
|
||||
console.log('+----------------+-------+');
|
||||
console.log('| Item | Count |');
|
||||
console.log('+----------------+-------+');
|
||||
console.log('| Tasks | 0 |');
|
||||
console.log('| Agents | 0 |');
|
||||
console.log('| Memory Entries | 0 |');
|
||||
console.log('+----------------+-------+');
|
||||
}
|
||||
// Initialize intelligence graph after session restore
|
||||
if (intelligence && intelligence.init) {
|
||||
try {
|
||||
const result = intelligence.init();
|
||||
if (result && result.nodes > 0) {
|
||||
console.log(`[INTELLIGENCE] Loaded ${result.nodes} patterns, ${result.edges} edges`);
|
||||
}
|
||||
} catch (e) { /* non-fatal */ }
|
||||
}
|
||||
},
|
||||
|
||||
'session-end': () => {
|
||||
// Consolidate intelligence before ending session
|
||||
if (intelligence && intelligence.consolidate) {
|
||||
try {
|
||||
const result = intelligence.consolidate();
|
||||
if (result && result.entries > 0) {
|
||||
console.log(`[INTELLIGENCE] Consolidated: ${result.entries} entries, ${result.edges} edges${result.newEntries > 0 ? `, ${result.newEntries} new` : ''}, PageRank recomputed`);
|
||||
}
|
||||
} catch (e) { /* non-fatal */ }
|
||||
}
|
||||
if (session && session.end) {
|
||||
session.end();
|
||||
} else {
|
||||
console.log('[OK] Session ended');
|
||||
}
|
||||
},
|
||||
|
||||
'pre-task': () => {
|
||||
if (session && session.metric) {
|
||||
try { session.metric('tasks'); } catch (e) { /* no active session */ }
|
||||
}
|
||||
// Route the task if router is available
|
||||
if (router && router.routeTask && prompt) {
|
||||
const result = router.routeTask(prompt);
|
||||
console.log(`[INFO] Task routed to: ${result.agent} (confidence: ${result.confidence})`);
|
||||
} else {
|
||||
console.log('[OK] Task started');
|
||||
}
|
||||
},
|
||||
|
||||
'post-task': () => {
|
||||
// Implicit success feedback for intelligence
|
||||
if (intelligence && intelligence.feedback) {
|
||||
try {
|
||||
intelligence.feedback(true);
|
||||
} catch (e) { /* non-fatal */ }
|
||||
}
|
||||
console.log('[OK] Task completed');
|
||||
},
|
||||
|
||||
'stats': () => {
|
||||
if (intelligence && intelligence.stats) {
|
||||
intelligence.stats(args.includes('--json'));
|
||||
} else {
|
||||
console.log('[WARN] Intelligence module not available. Run session-restore first.');
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Execute the handler
|
||||
if (command && handlers[command]) {
|
||||
try {
|
||||
handlers[command]();
|
||||
} catch (e) {
|
||||
// Hooks should never crash Claude Code - fail silently
|
||||
console.log(`[WARN] Hook ${command} encountered an error: ${e.message}`);
|
||||
}
|
||||
} else if (command) {
|
||||
// Unknown command - pass through without error
|
||||
console.log(`[OK] Hook: ${command}`);
|
||||
} else {
|
||||
console.log('Usage: hook-handler.cjs <route|pre-bash|post-edit|session-restore|session-end|pre-task|post-task|stats>');
|
||||
}
|
||||
916
.claude/helpers/intelligence.cjs
Normal file
916
.claude/helpers/intelligence.cjs
Normal file
@@ -0,0 +1,916 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Intelligence Layer (ADR-050)
|
||||
*
|
||||
* Closes the intelligence loop by wiring PageRank-ranked memory into
|
||||
* the hook system. Pure CJS — no ESM imports of @claude-flow/memory.
|
||||
*
|
||||
* Data files (all under .claude-flow/data/):
|
||||
* auto-memory-store.json — written by auto-memory-hook.mjs
|
||||
* graph-state.json — serialized graph (nodes + edges + pageRanks)
|
||||
* ranked-context.json — pre-computed ranked entries for fast lookup
|
||||
* pending-insights.jsonl — append-only edit/task log
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const DATA_DIR = path.join(process.cwd(), '.claude-flow', 'data');
|
||||
const STORE_PATH = path.join(DATA_DIR, 'auto-memory-store.json');
|
||||
const GRAPH_PATH = path.join(DATA_DIR, 'graph-state.json');
|
||||
const RANKED_PATH = path.join(DATA_DIR, 'ranked-context.json');
|
||||
const PENDING_PATH = path.join(DATA_DIR, 'pending-insights.jsonl');
|
||||
const SESSION_DIR = path.join(process.cwd(), '.claude-flow', 'sessions');
|
||||
const SESSION_FILE = path.join(SESSION_DIR, 'current.json');
|
||||
|
||||
// ── Stop words for trigram matching ──────────────────────────────────────────
|
||||
|
||||
const STOP_WORDS = new Set([
|
||||
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
|
||||
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
|
||||
'should', 'may', 'might', 'shall', 'can', 'to', 'of', 'in', 'for',
|
||||
'on', 'with', 'at', 'by', 'from', 'as', 'into', 'through', 'during',
|
||||
'before', 'after', 'and', 'but', 'or', 'nor', 'not', 'so', 'yet',
|
||||
'both', 'either', 'neither', 'each', 'every', 'all', 'any', 'few',
|
||||
'more', 'most', 'other', 'some', 'such', 'no', 'only', 'own', 'same',
|
||||
'than', 'too', 'very', 'just', 'because', 'if', 'when', 'which',
|
||||
'who', 'whom', 'this', 'that', 'these', 'those', 'it', 'its',
|
||||
]);
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function ensureDataDir() {
|
||||
if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
function readJSON(filePath) {
|
||||
try {
|
||||
if (fs.existsSync(filePath)) return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
||||
} catch { /* corrupt file — start fresh */ }
|
||||
return null;
|
||||
}
|
||||
|
||||
function writeJSON(filePath, data) {
|
||||
ensureDataDir();
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
function tokenize(text) {
|
||||
if (!text) return [];
|
||||
return text.toLowerCase()
|
||||
.replace(/[^a-z0-9\s-]/g, ' ')
|
||||
.split(/\s+/)
|
||||
.filter(w => w.length > 2 && !STOP_WORDS.has(w));
|
||||
}
|
||||
|
||||
function trigrams(words) {
|
||||
const t = new Set();
|
||||
for (const w of words) {
|
||||
for (let i = 0; i <= w.length - 3; i++) t.add(w.slice(i, i + 3));
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
function jaccardSimilarity(setA, setB) {
|
||||
if (setA.size === 0 && setB.size === 0) return 0;
|
||||
let intersection = 0;
|
||||
for (const item of setA) { if (setB.has(item)) intersection++; }
|
||||
return intersection / (setA.size + setB.size - intersection);
|
||||
}
|
||||
|
||||
// ── Session state helpers ────────────────────────────────────────────────────
|
||||
|
||||
function sessionGet(key) {
|
||||
try {
|
||||
if (!fs.existsSync(SESSION_FILE)) return null;
|
||||
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
||||
return key ? (session.context || {})[key] : session.context;
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
function sessionSet(key, value) {
|
||||
try {
|
||||
if (!fs.existsSync(SESSION_DIR)) fs.mkdirSync(SESSION_DIR, { recursive: true });
|
||||
let session = {};
|
||||
if (fs.existsSync(SESSION_FILE)) {
|
||||
session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
||||
}
|
||||
if (!session.context) session.context = {};
|
||||
session.context[key] = value;
|
||||
session.updatedAt = new Date().toISOString();
|
||||
fs.writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2), 'utf-8');
|
||||
} catch { /* best effort */ }
|
||||
}
|
||||
|
||||
// ── PageRank ─────────────────────────────────────────────────────────────────
|
||||
|
||||
function computePageRank(nodes, edges, damping, maxIter) {
|
||||
damping = damping || 0.85;
|
||||
maxIter = maxIter || 30;
|
||||
|
||||
const ids = Object.keys(nodes);
|
||||
const n = ids.length;
|
||||
if (n === 0) return {};
|
||||
|
||||
// Build adjacency: outgoing edges per node
|
||||
const outLinks = {};
|
||||
const inLinks = {};
|
||||
for (const id of ids) { outLinks[id] = []; inLinks[id] = []; }
|
||||
for (const edge of edges) {
|
||||
if (outLinks[edge.sourceId]) outLinks[edge.sourceId].push(edge.targetId);
|
||||
if (inLinks[edge.targetId]) inLinks[edge.targetId].push(edge.sourceId);
|
||||
}
|
||||
|
||||
// Initialize ranks
|
||||
const ranks = {};
|
||||
for (const id of ids) ranks[id] = 1 / n;
|
||||
|
||||
// Power iteration (with dangling node redistribution)
|
||||
for (let iter = 0; iter < maxIter; iter++) {
|
||||
const newRanks = {};
|
||||
let diff = 0;
|
||||
|
||||
// Collect rank from dangling nodes (no outgoing edges)
|
||||
let danglingSum = 0;
|
||||
for (const id of ids) {
|
||||
if (outLinks[id].length === 0) danglingSum += ranks[id];
|
||||
}
|
||||
|
||||
for (const id of ids) {
|
||||
let sum = 0;
|
||||
for (const src of inLinks[id]) {
|
||||
const outCount = outLinks[src].length;
|
||||
if (outCount > 0) sum += ranks[src] / outCount;
|
||||
}
|
||||
// Dangling rank distributed evenly + teleport
|
||||
newRanks[id] = (1 - damping) / n + damping * (sum + danglingSum / n);
|
||||
diff += Math.abs(newRanks[id] - ranks[id]);
|
||||
}
|
||||
|
||||
for (const id of ids) ranks[id] = newRanks[id];
|
||||
if (diff < 1e-6) break; // converged
|
||||
}
|
||||
|
||||
return ranks;
|
||||
}
|
||||
|
||||
// ── Edge building ────────────────────────────────────────────────────────────
|
||||
|
||||
function buildEdges(entries) {
|
||||
const edges = [];
|
||||
const byCategory = {};
|
||||
|
||||
for (const entry of entries) {
|
||||
const cat = entry.category || entry.namespace || 'default';
|
||||
if (!byCategory[cat]) byCategory[cat] = [];
|
||||
byCategory[cat].push(entry);
|
||||
}
|
||||
|
||||
// Temporal edges: entries from same sourceFile
|
||||
const byFile = {};
|
||||
for (const entry of entries) {
|
||||
const file = (entry.metadata && entry.metadata.sourceFile) || null;
|
||||
if (file) {
|
||||
if (!byFile[file]) byFile[file] = [];
|
||||
byFile[file].push(entry);
|
||||
}
|
||||
}
|
||||
for (const file of Object.keys(byFile)) {
|
||||
const group = byFile[file];
|
||||
for (let i = 0; i < group.length - 1; i++) {
|
||||
edges.push({
|
||||
sourceId: group[i].id,
|
||||
targetId: group[i + 1].id,
|
||||
type: 'temporal',
|
||||
weight: 0.5,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Similarity edges within categories (Jaccard > 0.3)
|
||||
for (const cat of Object.keys(byCategory)) {
|
||||
const group = byCategory[cat];
|
||||
for (let i = 0; i < group.length; i++) {
|
||||
const triA = trigrams(tokenize(group[i].content || group[i].summary || ''));
|
||||
for (let j = i + 1; j < group.length; j++) {
|
||||
const triB = trigrams(tokenize(group[j].content || group[j].summary || ''));
|
||||
const sim = jaccardSimilarity(triA, triB);
|
||||
if (sim > 0.3) {
|
||||
edges.push({
|
||||
sourceId: group[i].id,
|
||||
targetId: group[j].id,
|
||||
type: 'similar',
|
||||
weight: sim,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return edges;
|
||||
}
|
||||
|
||||
// ── Bootstrap from MEMORY.md files ───────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* If auto-memory-store.json is empty, bootstrap by parsing MEMORY.md and
|
||||
* topic files from the auto-memory directory. This removes the dependency
|
||||
* on @claude-flow/memory for the initial seed.
|
||||
*/
|
||||
function bootstrapFromMemoryFiles() {
|
||||
const entries = [];
|
||||
const cwd = process.cwd();
|
||||
|
||||
// Search for auto-memory directories
|
||||
const candidates = [
|
||||
// Claude Code auto-memory (project-scoped)
|
||||
path.join(require('os').homedir(), '.claude', 'projects'),
|
||||
// Local project memory
|
||||
path.join(cwd, '.claude-flow', 'memory'),
|
||||
path.join(cwd, '.claude', 'memory'),
|
||||
];
|
||||
|
||||
// Find MEMORY.md in project-scoped dirs
|
||||
for (const base of candidates) {
|
||||
if (!fs.existsSync(base)) continue;
|
||||
|
||||
// For the projects dir, scan subdirectories for memory/
|
||||
if (base.endsWith('projects')) {
|
||||
try {
|
||||
const projectDirs = fs.readdirSync(base);
|
||||
for (const pdir of projectDirs) {
|
||||
const memDir = path.join(base, pdir, 'memory');
|
||||
if (fs.existsSync(memDir)) {
|
||||
parseMemoryDir(memDir, entries);
|
||||
}
|
||||
}
|
||||
} catch { /* skip */ }
|
||||
} else if (fs.existsSync(base)) {
|
||||
parseMemoryDir(base, entries);
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
function parseMemoryDir(dir, entries) {
|
||||
try {
|
||||
const files = fs.readdirSync(dir).filter(f => f.endsWith('.md'));
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dir, file);
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
if (!content.trim()) continue;
|
||||
|
||||
// Parse markdown sections as separate entries
|
||||
const sections = content.split(/^##?\s+/m).filter(Boolean);
|
||||
for (const section of sections) {
|
||||
const lines = section.trim().split('\n');
|
||||
const title = lines[0].trim();
|
||||
const body = lines.slice(1).join('\n').trim();
|
||||
if (!body || body.length < 10) continue;
|
||||
|
||||
const id = `mem-${file.replace('.md', '')}-${title.replace(/[^a-z0-9]/gi, '-').toLowerCase().slice(0, 30)}`;
|
||||
entries.push({
|
||||
id,
|
||||
key: title.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 50),
|
||||
content: body.slice(0, 500),
|
||||
summary: title,
|
||||
namespace: file === 'MEMORY.md' ? 'core' : file.replace('.md', ''),
|
||||
type: 'semantic',
|
||||
metadata: { sourceFile: filePath, bootstrapped: true },
|
||||
createdAt: Date.now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch { /* skip unreadable dirs */ }
|
||||
}
|
||||
|
||||
// ── Exported functions ───────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* init() — Called from session-restore. Budget: <200ms.
|
||||
* Reads auto-memory-store.json, builds graph, computes PageRank, writes caches.
|
||||
* If store is empty, bootstraps from MEMORY.md files directly.
|
||||
*/
|
||||
function init() {
|
||||
ensureDataDir();
|
||||
|
||||
// Check if graph-state.json is fresh (within 60s of store)
|
||||
const graphState = readJSON(GRAPH_PATH);
|
||||
let store = readJSON(STORE_PATH);
|
||||
|
||||
// Bootstrap from MEMORY.md files if store is empty
|
||||
if (!store || !Array.isArray(store) || store.length === 0) {
|
||||
const bootstrapped = bootstrapFromMemoryFiles();
|
||||
if (bootstrapped.length > 0) {
|
||||
store = bootstrapped;
|
||||
writeJSON(STORE_PATH, store);
|
||||
} else {
|
||||
return { nodes: 0, edges: 0, message: 'No memory entries to index' };
|
||||
}
|
||||
}
|
||||
|
||||
// Skip rebuild if graph is fresh and store hasn't changed
|
||||
if (graphState && graphState.nodeCount === store.length) {
|
||||
const age = Date.now() - (graphState.updatedAt || 0);
|
||||
if (age < 60000) {
|
||||
return {
|
||||
nodes: graphState.nodeCount || Object.keys(graphState.nodes || {}).length,
|
||||
edges: (graphState.edges || []).length,
|
||||
message: 'Graph cache hit',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Build nodes
|
||||
const nodes = {};
|
||||
for (const entry of store) {
|
||||
const id = entry.id || entry.key || `entry-${Math.random().toString(36).slice(2, 8)}`;
|
||||
nodes[id] = {
|
||||
id,
|
||||
category: entry.namespace || entry.type || 'default',
|
||||
confidence: (entry.metadata && entry.metadata.confidence) || 0.5,
|
||||
accessCount: (entry.metadata && entry.metadata.accessCount) || 0,
|
||||
createdAt: entry.createdAt || Date.now(),
|
||||
};
|
||||
// Ensure entry has id for edge building
|
||||
entry.id = id;
|
||||
}
|
||||
|
||||
// Build edges
|
||||
const edges = buildEdges(store);
|
||||
|
||||
// Compute PageRank
|
||||
const pageRanks = computePageRank(nodes, edges, 0.85, 30);
|
||||
|
||||
// Write graph state
|
||||
const graph = {
|
||||
version: 1,
|
||||
updatedAt: Date.now(),
|
||||
nodeCount: Object.keys(nodes).length,
|
||||
nodes,
|
||||
edges,
|
||||
pageRanks,
|
||||
};
|
||||
writeJSON(GRAPH_PATH, graph);
|
||||
|
||||
// Build ranked context for fast lookup
|
||||
const rankedEntries = store.map(entry => {
|
||||
const id = entry.id;
|
||||
const content = entry.content || entry.value || '';
|
||||
const summary = entry.summary || entry.key || '';
|
||||
const words = tokenize(content + ' ' + summary);
|
||||
return {
|
||||
id,
|
||||
content,
|
||||
summary,
|
||||
category: entry.namespace || entry.type || 'default',
|
||||
confidence: nodes[id] ? nodes[id].confidence : 0.5,
|
||||
pageRank: pageRanks[id] || 0,
|
||||
accessCount: nodes[id] ? nodes[id].accessCount : 0,
|
||||
words,
|
||||
};
|
||||
}).sort((a, b) => {
|
||||
const scoreA = 0.6 * a.pageRank + 0.4 * a.confidence;
|
||||
const scoreB = 0.6 * b.pageRank + 0.4 * b.confidence;
|
||||
return scoreB - scoreA;
|
||||
});
|
||||
|
||||
const ranked = {
|
||||
version: 1,
|
||||
computedAt: Date.now(),
|
||||
entries: rankedEntries,
|
||||
};
|
||||
writeJSON(RANKED_PATH, ranked);
|
||||
|
||||
return {
|
||||
nodes: Object.keys(nodes).length,
|
||||
edges: edges.length,
|
||||
message: 'Graph built and ranked',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* getContext(prompt) — Called from route. Budget: <15ms.
|
||||
* Matches prompt to ranked entries, returns top-5 formatted context.
|
||||
*/
|
||||
function getContext(prompt) {
|
||||
if (!prompt) return null;
|
||||
|
||||
const ranked = readJSON(RANKED_PATH);
|
||||
if (!ranked || !ranked.entries || ranked.entries.length === 0) return null;
|
||||
|
||||
const promptWords = tokenize(prompt);
|
||||
if (promptWords.length === 0) return null;
|
||||
const promptTrigrams = trigrams(promptWords);
|
||||
|
||||
const ALPHA = 0.6; // content match weight
|
||||
const MIN_THRESHOLD = 0.05;
|
||||
const TOP_K = 5;
|
||||
|
||||
// Score each entry
|
||||
const scored = [];
|
||||
for (const entry of ranked.entries) {
|
||||
const entryTrigrams = trigrams(entry.words || []);
|
||||
const contentMatch = jaccardSimilarity(promptTrigrams, entryTrigrams);
|
||||
const score = ALPHA * contentMatch + (1 - ALPHA) * (entry.pageRank || 0);
|
||||
if (score >= MIN_THRESHOLD) {
|
||||
scored.push({ ...entry, score });
|
||||
}
|
||||
}
|
||||
|
||||
if (scored.length === 0) return null;
|
||||
|
||||
// Sort by score descending, take top-K
|
||||
scored.sort((a, b) => b.score - a.score);
|
||||
const topEntries = scored.slice(0, TOP_K);
|
||||
|
||||
// Boost previously matched patterns (implicit success: user continued working)
|
||||
const prevMatched = sessionGet('lastMatchedPatterns');
|
||||
|
||||
// Store NEW matched IDs in session state for feedback
|
||||
const matchedIds = topEntries.map(e => e.id);
|
||||
sessionSet('lastMatchedPatterns', matchedIds);
|
||||
|
||||
// Only boost previous if they differ from current (avoid double-boosting)
|
||||
if (prevMatched && Array.isArray(prevMatched)) {
|
||||
const newSet = new Set(matchedIds);
|
||||
const toBoost = prevMatched.filter(id => !newSet.has(id));
|
||||
if (toBoost.length > 0) boostConfidence(toBoost, 0.03);
|
||||
}
|
||||
|
||||
// Format output
|
||||
const lines = ['[INTELLIGENCE] Relevant patterns for this task:'];
|
||||
for (let i = 0; i < topEntries.length; i++) {
|
||||
const e = topEntries[i];
|
||||
const display = (e.summary || e.content || '').slice(0, 80);
|
||||
const accessed = e.accessCount || 0;
|
||||
lines.push(` * (${e.score.toFixed(2)}) ${display} [rank #${i + 1}, ${accessed}x accessed]`);
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* recordEdit(file) — Called from post-edit. Budget: <2ms.
|
||||
* Appends to pending-insights.jsonl.
|
||||
*/
|
||||
function recordEdit(file) {
|
||||
ensureDataDir();
|
||||
const entry = JSON.stringify({
|
||||
type: 'edit',
|
||||
file: file || 'unknown',
|
||||
timestamp: Date.now(),
|
||||
sessionId: sessionGet('sessionId') || null,
|
||||
});
|
||||
fs.appendFileSync(PENDING_PATH, entry + '\n', 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* feedback(success) — Called from post-task. Budget: <10ms.
|
||||
* Boosts or decays confidence for last-matched patterns.
|
||||
*/
|
||||
function feedback(success) {
|
||||
const matchedIds = sessionGet('lastMatchedPatterns');
|
||||
if (!matchedIds || !Array.isArray(matchedIds)) return;
|
||||
|
||||
const amount = success ? 0.05 : -0.02;
|
||||
boostConfidence(matchedIds, amount);
|
||||
}
|
||||
|
||||
function boostConfidence(ids, amount) {
|
||||
const ranked = readJSON(RANKED_PATH);
|
||||
if (!ranked || !ranked.entries) return;
|
||||
|
||||
let changed = false;
|
||||
for (const entry of ranked.entries) {
|
||||
if (ids.includes(entry.id)) {
|
||||
entry.confidence = Math.max(0, Math.min(1, (entry.confidence || 0.5) + amount));
|
||||
entry.accessCount = (entry.accessCount || 0) + 1;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) writeJSON(RANKED_PATH, ranked);
|
||||
|
||||
// Also update graph-state confidence
|
||||
const graph = readJSON(GRAPH_PATH);
|
||||
if (graph && graph.nodes) {
|
||||
for (const id of ids) {
|
||||
if (graph.nodes[id]) {
|
||||
graph.nodes[id].confidence = Math.max(0, Math.min(1, (graph.nodes[id].confidence || 0.5) + amount));
|
||||
graph.nodes[id].accessCount = (graph.nodes[id].accessCount || 0) + 1;
|
||||
}
|
||||
}
|
||||
writeJSON(GRAPH_PATH, graph);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* consolidate() — Called from session-end. Budget: <500ms.
|
||||
* Processes pending insights, rebuilds edges, recomputes PageRank.
|
||||
*/
|
||||
function consolidate() {
|
||||
ensureDataDir();
|
||||
|
||||
const store = readJSON(STORE_PATH);
|
||||
if (!store || !Array.isArray(store)) {
|
||||
return { entries: 0, edges: 0, newEntries: 0, message: 'No store to consolidate' };
|
||||
}
|
||||
|
||||
// 1. Process pending insights
|
||||
let newEntries = 0;
|
||||
if (fs.existsSync(PENDING_PATH)) {
|
||||
const lines = fs.readFileSync(PENDING_PATH, 'utf-8').trim().split('\n').filter(Boolean);
|
||||
const editCounts = {};
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const insight = JSON.parse(line);
|
||||
if (insight.file) {
|
||||
editCounts[insight.file] = (editCounts[insight.file] || 0) + 1;
|
||||
}
|
||||
} catch { /* skip malformed */ }
|
||||
}
|
||||
|
||||
// Create entries for frequently-edited files (3+ edits)
|
||||
for (const [file, count] of Object.entries(editCounts)) {
|
||||
if (count >= 3) {
|
||||
const exists = store.some(e =>
|
||||
(e.metadata && e.metadata.sourceFile === file && e.metadata.autoGenerated)
|
||||
);
|
||||
if (!exists) {
|
||||
store.push({
|
||||
id: `insight-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
||||
key: `frequent-edit-${path.basename(file)}`,
|
||||
content: `File ${file} was edited ${count} times this session — likely a hot path worth monitoring.`,
|
||||
summary: `Frequently edited: ${path.basename(file)} (${count}x)`,
|
||||
namespace: 'insights',
|
||||
type: 'procedural',
|
||||
metadata: { sourceFile: file, editCount: count, autoGenerated: true },
|
||||
createdAt: Date.now(),
|
||||
});
|
||||
newEntries++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear pending
|
||||
fs.writeFileSync(PENDING_PATH, '', 'utf-8');
|
||||
}
|
||||
|
||||
// 2. Confidence decay for unaccessed entries
|
||||
const graph = readJSON(GRAPH_PATH);
|
||||
if (graph && graph.nodes) {
|
||||
const now = Date.now();
|
||||
for (const id of Object.keys(graph.nodes)) {
|
||||
const node = graph.nodes[id];
|
||||
const hoursSinceCreation = (now - (node.createdAt || now)) / (1000 * 60 * 60);
|
||||
if (node.accessCount === 0 && hoursSinceCreation > 24) {
|
||||
node.confidence = Math.max(0.05, (node.confidence || 0.5) - 0.005 * Math.floor(hoursSinceCreation / 24));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Rebuild edges with updated store
|
||||
for (const entry of store) {
|
||||
if (!entry.id) entry.id = `entry-${Math.random().toString(36).slice(2, 8)}`;
|
||||
}
|
||||
const edges = buildEdges(store);
|
||||
|
||||
// 4. Build updated nodes
|
||||
const nodes = {};
|
||||
for (const entry of store) {
|
||||
nodes[entry.id] = {
|
||||
id: entry.id,
|
||||
category: entry.namespace || entry.type || 'default',
|
||||
confidence: (graph && graph.nodes && graph.nodes[entry.id])
|
||||
? graph.nodes[entry.id].confidence
|
||||
: (entry.metadata && entry.metadata.confidence) || 0.5,
|
||||
accessCount: (graph && graph.nodes && graph.nodes[entry.id])
|
||||
? graph.nodes[entry.id].accessCount
|
||||
: (entry.metadata && entry.metadata.accessCount) || 0,
|
||||
createdAt: entry.createdAt || Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
// 5. Recompute PageRank
|
||||
const pageRanks = computePageRank(nodes, edges, 0.85, 30);
|
||||
|
||||
// 6. Write updated graph
|
||||
writeJSON(GRAPH_PATH, {
|
||||
version: 1,
|
||||
updatedAt: Date.now(),
|
||||
nodeCount: Object.keys(nodes).length,
|
||||
nodes,
|
||||
edges,
|
||||
pageRanks,
|
||||
});
|
||||
|
||||
// 7. Write updated ranked context
|
||||
const rankedEntries = store.map(entry => {
|
||||
const id = entry.id;
|
||||
const content = entry.content || entry.value || '';
|
||||
const summary = entry.summary || entry.key || '';
|
||||
const words = tokenize(content + ' ' + summary);
|
||||
return {
|
||||
id,
|
||||
content,
|
||||
summary,
|
||||
category: entry.namespace || entry.type || 'default',
|
||||
confidence: nodes[id] ? nodes[id].confidence : 0.5,
|
||||
pageRank: pageRanks[id] || 0,
|
||||
accessCount: nodes[id] ? nodes[id].accessCount : 0,
|
||||
words,
|
||||
};
|
||||
}).sort((a, b) => {
|
||||
const scoreA = 0.6 * a.pageRank + 0.4 * a.confidence;
|
||||
const scoreB = 0.6 * b.pageRank + 0.4 * b.confidence;
|
||||
return scoreB - scoreA;
|
||||
});
|
||||
|
||||
writeJSON(RANKED_PATH, {
|
||||
version: 1,
|
||||
computedAt: Date.now(),
|
||||
entries: rankedEntries,
|
||||
});
|
||||
|
||||
// 8. Persist updated store (with new insight entries)
|
||||
if (newEntries > 0) writeJSON(STORE_PATH, store);
|
||||
|
||||
// 9. Save snapshot for delta tracking
|
||||
const updatedGraph = readJSON(GRAPH_PATH);
|
||||
const updatedRanked = readJSON(RANKED_PATH);
|
||||
saveSnapshot(updatedGraph, updatedRanked);
|
||||
|
||||
return {
|
||||
entries: store.length,
|
||||
edges: edges.length,
|
||||
newEntries,
|
||||
message: 'Consolidated',
|
||||
};
|
||||
}
|
||||
|
||||
// ── Snapshot for delta tracking ─────────────────────────────────────────────
|
||||
|
||||
const SNAPSHOT_PATH = path.join(DATA_DIR, 'intelligence-snapshot.json');
|
||||
|
||||
function saveSnapshot(graph, ranked) {
|
||||
const snap = {
|
||||
timestamp: Date.now(),
|
||||
nodes: graph ? Object.keys(graph.nodes || {}).length : 0,
|
||||
edges: graph ? (graph.edges || []).length : 0,
|
||||
pageRankSum: 0,
|
||||
confidences: [],
|
||||
accessCounts: [],
|
||||
topPatterns: [],
|
||||
};
|
||||
|
||||
if (graph && graph.pageRanks) {
|
||||
for (const v of Object.values(graph.pageRanks)) snap.pageRankSum += v;
|
||||
}
|
||||
if (graph && graph.nodes) {
|
||||
for (const n of Object.values(graph.nodes)) {
|
||||
snap.confidences.push(n.confidence || 0.5);
|
||||
snap.accessCounts.push(n.accessCount || 0);
|
||||
}
|
||||
}
|
||||
if (ranked && ranked.entries) {
|
||||
snap.topPatterns = ranked.entries.slice(0, 10).map(e => ({
|
||||
id: e.id,
|
||||
summary: (e.summary || '').slice(0, 60),
|
||||
confidence: e.confidence || 0.5,
|
||||
pageRank: e.pageRank || 0,
|
||||
accessCount: e.accessCount || 0,
|
||||
}));
|
||||
}
|
||||
|
||||
// Keep history: append to array, cap at 50
|
||||
let history = readJSON(SNAPSHOT_PATH);
|
||||
if (!Array.isArray(history)) history = [];
|
||||
history.push(snap);
|
||||
if (history.length > 50) history = history.slice(-50);
|
||||
writeJSON(SNAPSHOT_PATH, history);
|
||||
}
|
||||
|
||||
/**
|
||||
* stats() — Diagnostic report showing intelligence health and improvement.
|
||||
* Can be called as: node intelligence.cjs stats [--json]
|
||||
*/
|
||||
function stats(outputJson) {
|
||||
const graph = readJSON(GRAPH_PATH);
|
||||
const ranked = readJSON(RANKED_PATH);
|
||||
const history = readJSON(SNAPSHOT_PATH) || [];
|
||||
const pending = fs.existsSync(PENDING_PATH)
|
||||
? fs.readFileSync(PENDING_PATH, 'utf-8').trim().split('\n').filter(Boolean).length
|
||||
: 0;
|
||||
|
||||
// Current state
|
||||
const nodes = graph ? Object.keys(graph.nodes || {}).length : 0;
|
||||
const edges = graph ? (graph.edges || []).length : 0;
|
||||
const density = nodes > 1 ? (2 * edges) / (nodes * (nodes - 1)) : 0;
|
||||
|
||||
// Confidence distribution
|
||||
const confidences = [];
|
||||
const accessCounts = [];
|
||||
if (graph && graph.nodes) {
|
||||
for (const n of Object.values(graph.nodes)) {
|
||||
confidences.push(n.confidence || 0.5);
|
||||
accessCounts.push(n.accessCount || 0);
|
||||
}
|
||||
}
|
||||
confidences.sort((a, b) => a - b);
|
||||
const confMin = confidences.length ? confidences[0] : 0;
|
||||
const confMax = confidences.length ? confidences[confidences.length - 1] : 0;
|
||||
const confMean = confidences.length ? confidences.reduce((s, c) => s + c, 0) / confidences.length : 0;
|
||||
const confMedian = confidences.length ? confidences[Math.floor(confidences.length / 2)] : 0;
|
||||
|
||||
// Access stats
|
||||
const totalAccess = accessCounts.reduce((s, c) => s + c, 0);
|
||||
const accessedCount = accessCounts.filter(c => c > 0).length;
|
||||
|
||||
// PageRank stats
|
||||
let prSum = 0, prMax = 0, prMaxId = '';
|
||||
if (graph && graph.pageRanks) {
|
||||
for (const [id, pr] of Object.entries(graph.pageRanks)) {
|
||||
prSum += pr;
|
||||
if (pr > prMax) { prMax = pr; prMaxId = id; }
|
||||
}
|
||||
}
|
||||
|
||||
// Top patterns by composite score
|
||||
const topPatterns = (ranked && ranked.entries || []).slice(0, 10).map((e, i) => ({
|
||||
rank: i + 1,
|
||||
summary: (e.summary || '').slice(0, 60),
|
||||
confidence: (e.confidence || 0.5).toFixed(3),
|
||||
pageRank: (e.pageRank || 0).toFixed(4),
|
||||
accessed: e.accessCount || 0,
|
||||
score: (0.6 * (e.pageRank || 0) + 0.4 * (e.confidence || 0.5)).toFixed(4),
|
||||
}));
|
||||
|
||||
// Edge type breakdown
|
||||
const edgeTypes = {};
|
||||
if (graph && graph.edges) {
|
||||
for (const e of graph.edges) {
|
||||
edgeTypes[e.type || 'unknown'] = (edgeTypes[e.type || 'unknown'] || 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Delta from previous snapshot
|
||||
let delta = null;
|
||||
if (history.length >= 2) {
|
||||
const prev = history[history.length - 2];
|
||||
const curr = history[history.length - 1];
|
||||
const elapsed = (curr.timestamp - prev.timestamp) / 1000;
|
||||
const prevConfMean = prev.confidences.length
|
||||
? prev.confidences.reduce((s, c) => s + c, 0) / prev.confidences.length : 0;
|
||||
const currConfMean = curr.confidences.length
|
||||
? curr.confidences.reduce((s, c) => s + c, 0) / curr.confidences.length : 0;
|
||||
const prevAccess = prev.accessCounts.reduce((s, c) => s + c, 0);
|
||||
const currAccess = curr.accessCounts.reduce((s, c) => s + c, 0);
|
||||
|
||||
delta = {
|
||||
elapsed: elapsed < 3600 ? `${Math.round(elapsed / 60)}m` : `${(elapsed / 3600).toFixed(1)}h`,
|
||||
nodes: curr.nodes - prev.nodes,
|
||||
edges: curr.edges - prev.edges,
|
||||
confidenceMean: currConfMean - prevConfMean,
|
||||
totalAccess: currAccess - prevAccess,
|
||||
};
|
||||
}
|
||||
|
||||
// Trend over all history
|
||||
let trend = null;
|
||||
if (history.length >= 3) {
|
||||
const first = history[0];
|
||||
const last = history[history.length - 1];
|
||||
const sessions = history.length;
|
||||
const firstConfMean = first.confidences.length
|
||||
? first.confidences.reduce((s, c) => s + c, 0) / first.confidences.length : 0;
|
||||
const lastConfMean = last.confidences.length
|
||||
? last.confidences.reduce((s, c) => s + c, 0) / last.confidences.length : 0;
|
||||
trend = {
|
||||
sessions,
|
||||
nodeGrowth: last.nodes - first.nodes,
|
||||
edgeGrowth: last.edges - first.edges,
|
||||
confidenceDrift: lastConfMean - firstConfMean,
|
||||
direction: lastConfMean > firstConfMean ? 'improving' :
|
||||
lastConfMean < firstConfMean ? 'declining' : 'stable',
|
||||
};
|
||||
}
|
||||
|
||||
const report = {
|
||||
graph: { nodes, edges, density: +density.toFixed(4) },
|
||||
confidence: {
|
||||
min: +confMin.toFixed(3), max: +confMax.toFixed(3),
|
||||
mean: +confMean.toFixed(3), median: +confMedian.toFixed(3),
|
||||
},
|
||||
access: { total: totalAccess, patternsAccessed: accessedCount, patternsNeverAccessed: nodes - accessedCount },
|
||||
pageRank: { sum: +prSum.toFixed(4), topNode: prMaxId, topNodeRank: +prMax.toFixed(4) },
|
||||
edgeTypes,
|
||||
pendingInsights: pending,
|
||||
snapshots: history.length,
|
||||
topPatterns,
|
||||
delta,
|
||||
trend,
|
||||
};
|
||||
|
||||
if (outputJson) {
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
return report;
|
||||
}
|
||||
|
||||
// Human-readable output
|
||||
const bar = '+' + '-'.repeat(62) + '+';
|
||||
console.log(bar);
|
||||
console.log('|' + ' Intelligence Diagnostics (ADR-050)'.padEnd(62) + '|');
|
||||
console.log(bar);
|
||||
console.log('');
|
||||
|
||||
console.log(' Graph');
|
||||
console.log(` Nodes: ${nodes}`);
|
||||
console.log(` Edges: ${edges} (${Object.entries(edgeTypes).map(([t,c]) => `${c} ${t}`).join(', ') || 'none'})`);
|
||||
console.log(` Density: ${(density * 100).toFixed(1)}%`);
|
||||
console.log('');
|
||||
|
||||
console.log(' Confidence');
|
||||
console.log(` Min: ${confMin.toFixed(3)}`);
|
||||
console.log(` Max: ${confMax.toFixed(3)}`);
|
||||
console.log(` Mean: ${confMean.toFixed(3)}`);
|
||||
console.log(` Median: ${confMedian.toFixed(3)}`);
|
||||
console.log('');
|
||||
|
||||
console.log(' Access');
|
||||
console.log(` Total accesses: ${totalAccess}`);
|
||||
console.log(` Patterns used: ${accessedCount}/${nodes}`);
|
||||
console.log(` Never accessed: ${nodes - accessedCount}`);
|
||||
console.log(` Pending insights: ${pending}`);
|
||||
console.log('');
|
||||
|
||||
console.log(' PageRank');
|
||||
console.log(` Sum: ${prSum.toFixed(4)} (should be ~1.0)`);
|
||||
console.log(` Top node: ${prMaxId || '(none)'} (${prMax.toFixed(4)})`);
|
||||
console.log('');
|
||||
|
||||
if (topPatterns.length > 0) {
|
||||
console.log(' Top Patterns (by composite score)');
|
||||
console.log(' ' + '-'.repeat(60));
|
||||
for (const p of topPatterns) {
|
||||
console.log(` #${p.rank} ${p.summary}`);
|
||||
console.log(` conf=${p.confidence} pr=${p.pageRank} score=${p.score} accessed=${p.accessed}x`);
|
||||
}
|
||||
console.log('');
|
||||
}
|
||||
|
||||
if (delta) {
|
||||
console.log(` Last Delta (${delta.elapsed} ago)`);
|
||||
const sign = v => v > 0 ? `+${v}` : `${v}`;
|
||||
console.log(` Nodes: ${sign(delta.nodes)}`);
|
||||
console.log(` Edges: ${sign(delta.edges)}`);
|
||||
console.log(` Confidence: ${delta.confidenceMean >= 0 ? '+' : ''}${delta.confidenceMean.toFixed(4)}`);
|
||||
console.log(` Accesses: ${sign(delta.totalAccess)}`);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
if (trend) {
|
||||
console.log(` Trend (${trend.sessions} snapshots)`);
|
||||
console.log(` Node growth: ${trend.nodeGrowth >= 0 ? '+' : ''}${trend.nodeGrowth}`);
|
||||
console.log(` Edge growth: ${trend.edgeGrowth >= 0 ? '+' : ''}${trend.edgeGrowth}`);
|
||||
console.log(` Confidence drift: ${trend.confidenceDrift >= 0 ? '+' : ''}${trend.confidenceDrift.toFixed(4)}`);
|
||||
console.log(` Direction: ${trend.direction.toUpperCase()}`);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
if (!delta && !trend) {
|
||||
console.log(' No history yet — run more sessions to see deltas and trends.');
|
||||
console.log('');
|
||||
}
|
||||
|
||||
console.log(bar);
|
||||
return report;
|
||||
}
|
||||
|
||||
module.exports = { init, getContext, recordEdit, feedback, consolidate, stats };
|
||||
|
||||
// ── CLI entrypoint ──────────────────────────────────────────────────────────
|
||||
if (require.main === module) {
|
||||
const cmd = process.argv[2];
|
||||
const jsonFlag = process.argv.includes('--json');
|
||||
|
||||
const cmds = {
|
||||
init: () => { const r = init(); console.log(JSON.stringify(r)); },
|
||||
stats: () => { stats(jsonFlag); },
|
||||
consolidate: () => { const r = consolidate(); console.log(JSON.stringify(r)); },
|
||||
};
|
||||
|
||||
if (cmd && cmds[cmd]) {
|
||||
cmds[cmd]();
|
||||
} else {
|
||||
console.log('Usage: intelligence.cjs <stats|init|consolidate> [--json]');
|
||||
console.log('');
|
||||
console.log(' stats Show intelligence diagnostics and trends');
|
||||
console.log(' stats --json Output as JSON for programmatic use');
|
||||
console.log(' init Build graph and rank entries');
|
||||
console.log(' consolidate Process pending insights and recompute');
|
||||
}
|
||||
}
|
||||
329
.claude/helpers/learning-hooks.sh
Executable file
329
.claude/helpers/learning-hooks.sh
Executable file
@@ -0,0 +1,329 @@
|
||||
#!/bin/bash
|
||||
# Claude Flow V3 - Learning Hooks
|
||||
# Integrates learning-service.mjs with session lifecycle
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
LEARNING_SERVICE="$SCRIPT_DIR/learning-service.mjs"
|
||||
LEARNING_DIR="$PROJECT_ROOT/.claude-flow/learning"
|
||||
METRICS_DIR="$PROJECT_ROOT/.claude-flow/metrics"
|
||||
|
||||
# Ensure directories exist
|
||||
mkdir -p "$LEARNING_DIR" "$METRICS_DIR"
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
RED='\033[0;31m'
|
||||
DIM='\033[2m'
|
||||
RESET='\033[0m'
|
||||
|
||||
log() { echo -e "${CYAN}[Learning] $1${RESET}"; }
|
||||
success() { echo -e "${GREEN}[Learning] ✓ $1${RESET}"; }
|
||||
warn() { echo -e "${YELLOW}[Learning] ⚠ $1${RESET}"; }
|
||||
error() { echo -e "${RED}[Learning] ✗ $1${RESET}"; }
|
||||
|
||||
# Generate session ID
|
||||
generate_session_id() {
|
||||
echo "session_$(date +%Y%m%d_%H%M%S)_$$"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Session Start Hook
|
||||
# =============================================================================
|
||||
session_start() {
|
||||
local session_id="${1:-$(generate_session_id)}"
|
||||
|
||||
log "Initializing learning service for session: $session_id"
|
||||
|
||||
# Check if better-sqlite3 is available
|
||||
if ! npm list better-sqlite3 --prefix "$PROJECT_ROOT" >/dev/null 2>&1; then
|
||||
log "Installing better-sqlite3..."
|
||||
npm install --prefix "$PROJECT_ROOT" better-sqlite3 --save-dev --silent 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Initialize learning service
|
||||
local init_result
|
||||
init_result=$(node "$LEARNING_SERVICE" init "$session_id" 2>&1)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
# Parse and display stats
|
||||
local short_term=$(echo "$init_result" | grep -o '"shortTermPatterns":[0-9]*' | cut -d: -f2)
|
||||
local long_term=$(echo "$init_result" | grep -o '"longTermPatterns":[0-9]*' | cut -d: -f2)
|
||||
|
||||
success "Learning service initialized"
|
||||
echo -e " ${DIM}├─ Short-term patterns: ${short_term:-0}${RESET}"
|
||||
echo -e " ${DIM}├─ Long-term patterns: ${long_term:-0}${RESET}"
|
||||
echo -e " ${DIM}└─ Session ID: $session_id${RESET}"
|
||||
|
||||
# Store session ID for later hooks
|
||||
echo "$session_id" > "$LEARNING_DIR/current-session-id"
|
||||
|
||||
# Update metrics
|
||||
cat > "$METRICS_DIR/learning-status.json" << EOF
|
||||
{
|
||||
"sessionId": "$session_id",
|
||||
"initialized": true,
|
||||
"shortTermPatterns": ${short_term:-0},
|
||||
"longTermPatterns": ${long_term:-0},
|
||||
"hnswEnabled": true,
|
||||
"timestamp": "$(date -Iseconds)"
|
||||
}
|
||||
EOF
|
||||
|
||||
return 0
|
||||
else
|
||||
warn "Learning service initialization failed (non-critical)"
|
||||
echo "$init_result" | head -5
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Session End Hook
|
||||
# =============================================================================
|
||||
session_end() {
|
||||
log "Consolidating learning data..."
|
||||
|
||||
# Get session ID
|
||||
local session_id=""
|
||||
if [ -f "$LEARNING_DIR/current-session-id" ]; then
|
||||
session_id=$(cat "$LEARNING_DIR/current-session-id")
|
||||
fi
|
||||
|
||||
# Export session data
|
||||
local export_result
|
||||
export_result=$(node "$LEARNING_SERVICE" export 2>&1)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
# Save export
|
||||
echo "$export_result" > "$LEARNING_DIR/session-export-$(date +%Y%m%d_%H%M%S).json"
|
||||
|
||||
local patterns=$(echo "$export_result" | grep -o '"patterns":[0-9]*' | cut -d: -f2)
|
||||
log "Session exported: $patterns patterns"
|
||||
fi
|
||||
|
||||
# Run consolidation
|
||||
local consolidate_result
|
||||
consolidate_result=$(node "$LEARNING_SERVICE" consolidate 2>&1)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
local removed=$(echo "$consolidate_result" | grep -o '"duplicatesRemoved":[0-9]*' | cut -d: -f2)
|
||||
local pruned=$(echo "$consolidate_result" | grep -o '"patternsProned":[0-9]*' | cut -d: -f2)
|
||||
local duration=$(echo "$consolidate_result" | grep -o '"durationMs":[0-9]*' | cut -d: -f2)
|
||||
|
||||
success "Consolidation complete"
|
||||
echo -e " ${DIM}├─ Duplicates removed: ${removed:-0}${RESET}"
|
||||
echo -e " ${DIM}├─ Patterns pruned: ${pruned:-0}${RESET}"
|
||||
echo -e " ${DIM}└─ Duration: ${duration:-0}ms${RESET}"
|
||||
else
|
||||
warn "Consolidation failed (non-critical)"
|
||||
fi
|
||||
|
||||
# Get final stats
|
||||
local stats_result
|
||||
stats_result=$(node "$LEARNING_SERVICE" stats 2>&1)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "$stats_result" > "$METRICS_DIR/learning-final-stats.json"
|
||||
|
||||
local total_short=$(echo "$stats_result" | grep -o '"shortTermPatterns":[0-9]*' | cut -d: -f2)
|
||||
local total_long=$(echo "$stats_result" | grep -o '"longTermPatterns":[0-9]*' | cut -d: -f2)
|
||||
local avg_search=$(echo "$stats_result" | grep -o '"avgSearchTimeMs":[0-9.]*' | cut -d: -f2)
|
||||
|
||||
log "Final stats:"
|
||||
echo -e " ${DIM}├─ Short-term: ${total_short:-0}${RESET}"
|
||||
echo -e " ${DIM}├─ Long-term: ${total_long:-0}${RESET}"
|
||||
echo -e " ${DIM}└─ Avg search: ${avg_search:-0}ms${RESET}"
|
||||
fi
|
||||
|
||||
# Clean up session file
|
||||
rm -f "$LEARNING_DIR/current-session-id"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Store Pattern (called by post-edit hooks)
|
||||
# =============================================================================
|
||||
store_pattern() {
|
||||
local strategy="$1"
|
||||
local domain="${2:-general}"
|
||||
local quality="${3:-0.7}"
|
||||
|
||||
if [ -z "$strategy" ]; then
|
||||
error "No strategy provided"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Escape quotes in strategy
|
||||
local escaped_strategy="${strategy//\"/\\\"}"
|
||||
|
||||
local result
|
||||
result=$(node "$LEARNING_SERVICE" store "$escaped_strategy" "$domain" 2>&1)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
local action=$(echo "$result" | grep -o '"action":"[^"]*"' | cut -d'"' -f4)
|
||||
local id=$(echo "$result" | grep -o '"id":"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
if [ "$action" = "created" ]; then
|
||||
success "Pattern stored: $id"
|
||||
else
|
||||
log "Pattern updated: $id"
|
||||
fi
|
||||
return 0
|
||||
else
|
||||
warn "Pattern storage failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Search Patterns (called by pre-edit hooks)
|
||||
# =============================================================================
|
||||
search_patterns() {
|
||||
local query="$1"
|
||||
local k="${2:-3}"
|
||||
|
||||
if [ -z "$query" ]; then
|
||||
error "No query provided"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Escape quotes
|
||||
local escaped_query="${query//\"/\\\"}"
|
||||
|
||||
local result
|
||||
result=$(node "$LEARNING_SERVICE" search "$escaped_query" "$k" 2>&1)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
local patterns=$(echo "$result" | grep -o '"patterns":\[' | wc -l)
|
||||
local search_time=$(echo "$result" | grep -o '"searchTimeMs":[0-9.]*' | cut -d: -f2)
|
||||
|
||||
echo "$result"
|
||||
|
||||
if [ -n "$search_time" ]; then
|
||||
log "Search completed in ${search_time}ms"
|
||||
fi
|
||||
return 0
|
||||
else
|
||||
warn "Pattern search failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Record Pattern Usage (for promotion tracking)
|
||||
# =============================================================================
|
||||
record_usage() {
|
||||
local pattern_id="$1"
|
||||
local success="${2:-true}"
|
||||
|
||||
if [ -z "$pattern_id" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# This would call into the learning service to record usage
|
||||
# For now, log it
|
||||
log "Recording usage: $pattern_id (success=$success)"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Run Benchmark
|
||||
# =============================================================================
|
||||
run_benchmark() {
|
||||
log "Running HNSW benchmark..."
|
||||
|
||||
local result
|
||||
result=$(node "$LEARNING_SERVICE" benchmark 2>&1)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
local avg_search=$(echo "$result" | grep -o '"avgSearchMs":"[^"]*"' | cut -d'"' -f4)
|
||||
local p95_search=$(echo "$result" | grep -o '"p95SearchMs":"[^"]*"' | cut -d'"' -f4)
|
||||
local improvement=$(echo "$result" | grep -o '"searchImprovementEstimate":"[^"]*"' | cut -d'"' -f4)
|
||||
|
||||
success "HNSW Benchmark Complete"
|
||||
echo -e " ${DIM}├─ Avg search: ${avg_search}ms${RESET}"
|
||||
echo -e " ${DIM}├─ P95 search: ${p95_search}ms${RESET}"
|
||||
echo -e " ${DIM}└─ Estimated improvement: ${improvement}${RESET}"
|
||||
|
||||
echo "$result"
|
||||
return 0
|
||||
else
|
||||
error "Benchmark failed"
|
||||
echo "$result"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Get Stats
|
||||
# =============================================================================
|
||||
get_stats() {
|
||||
local result
|
||||
result=$(node "$LEARNING_SERVICE" stats 2>&1)
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "$result"
|
||||
return 0
|
||||
else
|
||||
error "Failed to get stats"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Main
|
||||
# =============================================================================
|
||||
case "${1:-help}" in
|
||||
"session-start"|"start")
|
||||
session_start "$2"
|
||||
;;
|
||||
"session-end"|"end")
|
||||
session_end
|
||||
;;
|
||||
"store")
|
||||
store_pattern "$2" "$3" "$4"
|
||||
;;
|
||||
"search")
|
||||
search_patterns "$2" "$3"
|
||||
;;
|
||||
"record-usage"|"usage")
|
||||
record_usage "$2" "$3"
|
||||
;;
|
||||
"benchmark")
|
||||
run_benchmark
|
||||
;;
|
||||
"stats")
|
||||
get_stats
|
||||
;;
|
||||
"help"|"-h"|"--help")
|
||||
cat << 'EOF'
|
||||
Claude Flow V3 Learning Hooks
|
||||
|
||||
Usage: learning-hooks.sh <command> [args]
|
||||
|
||||
Commands:
|
||||
session-start [id] Initialize learning for new session
|
||||
session-end Consolidate and export session data
|
||||
store <strategy> Store a new pattern
|
||||
search <query> [k] Search for similar patterns
|
||||
record-usage <id> Record pattern usage
|
||||
benchmark Run HNSW performance benchmark
|
||||
stats Get learning statistics
|
||||
help Show this help
|
||||
|
||||
Examples:
|
||||
./learning-hooks.sh session-start
|
||||
./learning-hooks.sh store "Fix authentication bug" code
|
||||
./learning-hooks.sh search "authentication error" 5
|
||||
./learning-hooks.sh session-end
|
||||
EOF
|
||||
;;
|
||||
*)
|
||||
error "Unknown command: $1"
|
||||
echo "Use 'learning-hooks.sh help' for usage"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
127
.claude/helpers/learning-optimizer.sh
Executable file
127
.claude/helpers/learning-optimizer.sh
Executable file
@@ -0,0 +1,127 @@
|
||||
#!/bin/bash
|
||||
# Claude Flow V3 - Learning Optimizer Worker
|
||||
# Runs SONA micro-LoRA optimization on patterns
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
LEARNING_DIR="$PROJECT_ROOT/.claude-flow/learning"
|
||||
METRICS_DIR="$PROJECT_ROOT/.claude-flow/metrics"
|
||||
PATTERNS_DB="$LEARNING_DIR/patterns.db"
|
||||
LEARNING_FILE="$METRICS_DIR/learning.json"
|
||||
LAST_RUN_FILE="$METRICS_DIR/.optimizer-last-run"
|
||||
|
||||
mkdir -p "$LEARNING_DIR" "$METRICS_DIR"
|
||||
|
||||
should_run() {
|
||||
if [ ! -f "$LAST_RUN_FILE" ]; then return 0; fi
|
||||
local last_run=$(cat "$LAST_RUN_FILE" 2>/dev/null || echo "0")
|
||||
local now=$(date +%s)
|
||||
[ $((now - last_run)) -ge 1800 ] # 30 minutes
|
||||
}
|
||||
|
||||
calculate_routing_accuracy() {
|
||||
if [ -f "$PATTERNS_DB" ] && command -v sqlite3 &>/dev/null; then
|
||||
# Calculate based on pattern quality distribution
|
||||
local high_quality=$(sqlite3 "$PATTERNS_DB" "SELECT COUNT(*) FROM short_term_patterns WHERE quality > 0.7" 2>/dev/null || echo "0")
|
||||
local total=$(sqlite3 "$PATTERNS_DB" "SELECT COUNT(*) FROM short_term_patterns" 2>/dev/null || echo "1")
|
||||
|
||||
if [ "$total" -gt 0 ]; then
|
||||
echo $((high_quality * 100 / total))
|
||||
else
|
||||
echo "0"
|
||||
fi
|
||||
else
|
||||
echo "0"
|
||||
fi
|
||||
}
|
||||
|
||||
optimize_patterns() {
|
||||
if [ ! -f "$PATTERNS_DB" ] || ! command -v sqlite3 &>/dev/null; then
|
||||
echo "[$(date +%H:%M:%S)] No patterns to optimize"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "[$(date +%H:%M:%S)] Running learning optimization..."
|
||||
|
||||
# Boost quality of successful patterns
|
||||
sqlite3 "$PATTERNS_DB" "
|
||||
UPDATE short_term_patterns
|
||||
SET quality = MIN(1.0, quality * 1.05)
|
||||
WHERE quality > 0.5
|
||||
" 2>/dev/null || true
|
||||
|
||||
# Cross-pollinate: copy strategies across similar domains
|
||||
sqlite3 "$PATTERNS_DB" "
|
||||
INSERT OR IGNORE INTO short_term_patterns (strategy, domain, quality, source)
|
||||
SELECT strategy, 'general', quality * 0.8, 'cross-pollinated'
|
||||
FROM short_term_patterns
|
||||
WHERE quality > 0.8
|
||||
LIMIT 10
|
||||
" 2>/dev/null || true
|
||||
|
||||
# Calculate metrics
|
||||
local short_count=$(sqlite3 "$PATTERNS_DB" "SELECT COUNT(*) FROM short_term_patterns" 2>/dev/null || echo "0")
|
||||
local long_count=$(sqlite3 "$PATTERNS_DB" "SELECT COUNT(*) FROM long_term_patterns" 2>/dev/null || echo "0")
|
||||
local avg_quality=$(sqlite3 "$PATTERNS_DB" "SELECT ROUND(AVG(quality), 3) FROM short_term_patterns" 2>/dev/null || echo "0")
|
||||
local routing_accuracy=$(calculate_routing_accuracy)
|
||||
|
||||
# Calculate intelligence score
|
||||
local pattern_score=$((short_count + long_count * 2))
|
||||
[ "$pattern_score" -gt 100 ] && pattern_score=100
|
||||
local quality_score=$(echo "$avg_quality * 40" | bc 2>/dev/null | cut -d. -f1 || echo "0")
|
||||
local intel_score=$((pattern_score * 60 / 100 + quality_score))
|
||||
[ "$intel_score" -gt 100 ] && intel_score=100
|
||||
|
||||
# Write learning metrics
|
||||
cat > "$LEARNING_FILE" << EOF
|
||||
{
|
||||
"timestamp": "$(date -Iseconds)",
|
||||
"patterns": {
|
||||
"shortTerm": $short_count,
|
||||
"longTerm": $long_count,
|
||||
"avgQuality": $avg_quality
|
||||
},
|
||||
"routing": {
|
||||
"accuracy": $routing_accuracy
|
||||
},
|
||||
"intelligence": {
|
||||
"score": $intel_score,
|
||||
"level": "$([ $intel_score -lt 25 ] && echo "learning" || ([ $intel_score -lt 50 ] && echo "developing" || ([ $intel_score -lt 75 ] && echo "proficient" || echo "expert")))"
|
||||
},
|
||||
"sona": {
|
||||
"adaptationTime": "0.05ms",
|
||||
"microLoraEnabled": true
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "[$(date +%H:%M:%S)] ✓ Learning: Intel ${intel_score}% | Patterns: $short_count/$long_count | Quality: $avg_quality | Routing: ${routing_accuracy}%"
|
||||
|
||||
date +%s > "$LAST_RUN_FILE"
|
||||
}
|
||||
|
||||
run_sona_training() {
|
||||
echo "[$(date +%H:%M:%S)] Spawning SONA learning agent..."
|
||||
|
||||
# Use agentic-flow for deep learning optimization
|
||||
npx agentic-flow@alpha hooks intelligence 2>/dev/null || true
|
||||
|
||||
echo "[$(date +%H:%M:%S)] ✓ SONA training triggered"
|
||||
}
|
||||
|
||||
case "${1:-check}" in
|
||||
"run"|"optimize") optimize_patterns ;;
|
||||
"check") should_run && optimize_patterns || echo "[$(date +%H:%M:%S)] Skipping (throttled)" ;;
|
||||
"force") rm -f "$LAST_RUN_FILE"; optimize_patterns ;;
|
||||
"sona") run_sona_training ;;
|
||||
"status")
|
||||
if [ -f "$LEARNING_FILE" ]; then
|
||||
jq -r '"Intel: \(.intelligence.score)% (\(.intelligence.level)) | Patterns: \(.patterns.shortTerm)/\(.patterns.longTerm) | Routing: \(.routing.accuracy)%"' "$LEARNING_FILE"
|
||||
else
|
||||
echo "No learning data available"
|
||||
fi
|
||||
;;
|
||||
*) echo "Usage: $0 [run|check|force|sona|status]" ;;
|
||||
esac
|
||||
1144
.claude/helpers/learning-service.mjs
Executable file
1144
.claude/helpers/learning-service.mjs
Executable file
File diff suppressed because it is too large
Load Diff
83
.claude/helpers/memory.js
Normal file
83
.claude/helpers/memory.js
Normal file
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Claude Flow Memory Helper
|
||||
* Simple key-value memory for cross-session context
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const MEMORY_DIR = path.join(process.cwd(), '.claude-flow', 'data');
|
||||
const MEMORY_FILE = path.join(MEMORY_DIR, 'memory.json');
|
||||
|
||||
function loadMemory() {
|
||||
try {
|
||||
if (fs.existsSync(MEMORY_FILE)) {
|
||||
return JSON.parse(fs.readFileSync(MEMORY_FILE, 'utf-8'));
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function saveMemory(memory) {
|
||||
fs.mkdirSync(MEMORY_DIR, { recursive: true });
|
||||
fs.writeFileSync(MEMORY_FILE, JSON.stringify(memory, null, 2));
|
||||
}
|
||||
|
||||
const commands = {
|
||||
get: (key) => {
|
||||
const memory = loadMemory();
|
||||
const value = key ? memory[key] : memory;
|
||||
console.log(JSON.stringify(value, null, 2));
|
||||
return value;
|
||||
},
|
||||
|
||||
set: (key, value) => {
|
||||
if (!key) {
|
||||
console.error('Key required');
|
||||
return;
|
||||
}
|
||||
const memory = loadMemory();
|
||||
memory[key] = value;
|
||||
memory._updated = new Date().toISOString();
|
||||
saveMemory(memory);
|
||||
console.log(`Set: ${key}`);
|
||||
},
|
||||
|
||||
delete: (key) => {
|
||||
if (!key) {
|
||||
console.error('Key required');
|
||||
return;
|
||||
}
|
||||
const memory = loadMemory();
|
||||
delete memory[key];
|
||||
saveMemory(memory);
|
||||
console.log(`Deleted: ${key}`);
|
||||
},
|
||||
|
||||
clear: () => {
|
||||
saveMemory({});
|
||||
console.log('Memory cleared');
|
||||
},
|
||||
|
||||
keys: () => {
|
||||
const memory = loadMemory();
|
||||
const keys = Object.keys(memory).filter(k => !k.startsWith('_'));
|
||||
console.log(keys.join('\n'));
|
||||
return keys;
|
||||
},
|
||||
};
|
||||
|
||||
// CLI
|
||||
const [,, command, key, ...valueParts] = process.argv;
|
||||
const value = valueParts.join(' ');
|
||||
|
||||
if (command && commands[command]) {
|
||||
commands[command](key, value);
|
||||
} else {
|
||||
console.log('Usage: memory.js <get|set|delete|clear|keys> [key] [value]');
|
||||
}
|
||||
|
||||
module.exports = commands;
|
||||
488
.claude/helpers/metrics-db.mjs
Executable file
488
.claude/helpers/metrics-db.mjs
Executable file
@@ -0,0 +1,488 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Claude Flow V3 - Metrics Database Manager
|
||||
* Uses sql.js for cross-platform SQLite storage
|
||||
* Single .db file with multiple tables
|
||||
*/
|
||||
|
||||
import initSqlJs from 'sql.js';
|
||||
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'fs';
|
||||
import { dirname, join, basename } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const PROJECT_ROOT = join(__dirname, '../..');
|
||||
const V3_DIR = join(PROJECT_ROOT, 'v3');
|
||||
const DB_PATH = join(PROJECT_ROOT, '.claude-flow', 'metrics.db');
|
||||
|
||||
// Ensure directory exists
|
||||
const dbDir = dirname(DB_PATH);
|
||||
if (!existsSync(dbDir)) {
|
||||
mkdirSync(dbDir, { recursive: true });
|
||||
}
|
||||
|
||||
let SQL;
|
||||
let db;
|
||||
|
||||
/**
|
||||
* Initialize sql.js and create/load database
|
||||
*/
|
||||
async function initDatabase() {
|
||||
SQL = await initSqlJs();
|
||||
|
||||
// Load existing database or create new one
|
||||
if (existsSync(DB_PATH)) {
|
||||
const buffer = readFileSync(DB_PATH);
|
||||
db = new SQL.Database(buffer);
|
||||
} else {
|
||||
db = new SQL.Database();
|
||||
}
|
||||
|
||||
// Create tables if they don't exist
|
||||
db.run(`
|
||||
CREATE TABLE IF NOT EXISTS v3_progress (
|
||||
id INTEGER PRIMARY KEY,
|
||||
domains_completed INTEGER DEFAULT 0,
|
||||
domains_total INTEGER DEFAULT 5,
|
||||
ddd_progress INTEGER DEFAULT 0,
|
||||
total_modules INTEGER DEFAULT 0,
|
||||
total_files INTEGER DEFAULT 0,
|
||||
total_lines INTEGER DEFAULT 0,
|
||||
last_updated TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS security_audit (
|
||||
id INTEGER PRIMARY KEY,
|
||||
status TEXT DEFAULT 'PENDING',
|
||||
cves_fixed INTEGER DEFAULT 0,
|
||||
total_cves INTEGER DEFAULT 3,
|
||||
last_audit TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS swarm_activity (
|
||||
id INTEGER PRIMARY KEY,
|
||||
agentic_flow_processes INTEGER DEFAULT 0,
|
||||
mcp_server_processes INTEGER DEFAULT 0,
|
||||
estimated_agents INTEGER DEFAULT 0,
|
||||
swarm_active INTEGER DEFAULT 0,
|
||||
coordination_active INTEGER DEFAULT 0,
|
||||
last_updated TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS performance_metrics (
|
||||
id INTEGER PRIMARY KEY,
|
||||
flash_attention_speedup TEXT DEFAULT '1.0x',
|
||||
memory_reduction TEXT DEFAULT '0%',
|
||||
search_improvement TEXT DEFAULT '1x',
|
||||
last_updated TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS module_status (
|
||||
name TEXT PRIMARY KEY,
|
||||
files INTEGER DEFAULT 0,
|
||||
lines INTEGER DEFAULT 0,
|
||||
progress INTEGER DEFAULT 0,
|
||||
has_src INTEGER DEFAULT 0,
|
||||
has_tests INTEGER DEFAULT 0,
|
||||
last_updated TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cve_status (
|
||||
id TEXT PRIMARY KEY,
|
||||
description TEXT,
|
||||
severity TEXT DEFAULT 'critical',
|
||||
status TEXT DEFAULT 'pending',
|
||||
fixed_by TEXT,
|
||||
last_updated TEXT
|
||||
);
|
||||
`);
|
||||
|
||||
// Initialize rows if empty
|
||||
const progressCheck = db.exec("SELECT COUNT(*) FROM v3_progress");
|
||||
if (progressCheck[0]?.values[0][0] === 0) {
|
||||
db.run("INSERT INTO v3_progress (id) VALUES (1)");
|
||||
}
|
||||
|
||||
const securityCheck = db.exec("SELECT COUNT(*) FROM security_audit");
|
||||
if (securityCheck[0]?.values[0][0] === 0) {
|
||||
db.run("INSERT INTO security_audit (id) VALUES (1)");
|
||||
}
|
||||
|
||||
const swarmCheck = db.exec("SELECT COUNT(*) FROM swarm_activity");
|
||||
if (swarmCheck[0]?.values[0][0] === 0) {
|
||||
db.run("INSERT INTO swarm_activity (id) VALUES (1)");
|
||||
}
|
||||
|
||||
const perfCheck = db.exec("SELECT COUNT(*) FROM performance_metrics");
|
||||
if (perfCheck[0]?.values[0][0] === 0) {
|
||||
db.run("INSERT INTO performance_metrics (id) VALUES (1)");
|
||||
}
|
||||
|
||||
// Initialize CVE records
|
||||
const cveCheck = db.exec("SELECT COUNT(*) FROM cve_status");
|
||||
if (cveCheck[0]?.values[0][0] === 0) {
|
||||
db.run(`INSERT INTO cve_status (id, description, fixed_by) VALUES
|
||||
('CVE-1', 'Input validation bypass', 'input-validator.ts'),
|
||||
('CVE-2', 'Path traversal vulnerability', 'path-validator.ts'),
|
||||
('CVE-3', 'Command injection vulnerability', 'safe-executor.ts')
|
||||
`);
|
||||
}
|
||||
|
||||
persist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist database to disk
|
||||
*/
|
||||
function persist() {
|
||||
const data = db.export();
|
||||
const buffer = Buffer.from(data);
|
||||
writeFileSync(DB_PATH, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count files and lines in a directory
|
||||
*/
|
||||
function countFilesAndLines(dir, ext = '.ts') {
|
||||
let files = 0;
|
||||
let lines = 0;
|
||||
|
||||
function walk(currentDir) {
|
||||
if (!existsSync(currentDir)) return;
|
||||
|
||||
try {
|
||||
const entries = readdirSync(currentDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = join(currentDir, entry.name);
|
||||
if (entry.isDirectory() && !entry.name.includes('node_modules')) {
|
||||
walk(fullPath);
|
||||
} else if (entry.isFile() && entry.name.endsWith(ext)) {
|
||||
files++;
|
||||
try {
|
||||
const content = readFileSync(fullPath, 'utf-8');
|
||||
lines += content.split('\n').length;
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
walk(dir);
|
||||
return { files, lines };
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate module progress
|
||||
* Utility/service packages (cli, hooks, mcp, etc.) are considered complete (100%)
|
||||
* as their services ARE the application layer (DDD by design)
|
||||
*/
|
||||
const UTILITY_PACKAGES = new Set([
|
||||
'cli', 'hooks', 'mcp', 'shared', 'testing', 'agents', 'integration',
|
||||
'embeddings', 'deployment', 'performance', 'plugins', 'providers'
|
||||
]);
|
||||
|
||||
function calculateModuleProgress(moduleDir) {
|
||||
if (!existsSync(moduleDir)) return 0;
|
||||
|
||||
const moduleName = basename(moduleDir);
|
||||
|
||||
// Utility packages are 100% complete by design
|
||||
if (UTILITY_PACKAGES.has(moduleName)) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
let progress = 0;
|
||||
|
||||
// Check for DDD structure
|
||||
if (existsSync(join(moduleDir, 'src/domain'))) progress += 30;
|
||||
if (existsSync(join(moduleDir, 'src/application'))) progress += 30;
|
||||
if (existsSync(join(moduleDir, 'src'))) progress += 10;
|
||||
if (existsSync(join(moduleDir, 'src/index.ts')) || existsSync(join(moduleDir, 'index.ts'))) progress += 10;
|
||||
if (existsSync(join(moduleDir, '__tests__')) || existsSync(join(moduleDir, 'tests'))) progress += 10;
|
||||
if (existsSync(join(moduleDir, 'package.json'))) progress += 10;
|
||||
|
||||
return Math.min(progress, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check security file status
|
||||
*/
|
||||
function checkSecurityFile(filename, minLines = 100) {
|
||||
const filePath = join(V3_DIR, '@claude-flow/security/src', filename);
|
||||
if (!existsSync(filePath)) return false;
|
||||
|
||||
try {
|
||||
const content = readFileSync(filePath, 'utf-8');
|
||||
return content.split('\n').length > minLines;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count active processes
|
||||
*/
|
||||
function countProcesses() {
|
||||
try {
|
||||
const ps = execSync('ps aux 2>/dev/null || echo ""', { encoding: 'utf-8' });
|
||||
|
||||
const agenticFlow = (ps.match(/agentic-flow/g) || []).length;
|
||||
const mcp = (ps.match(/mcp.*start/g) || []).length;
|
||||
const agents = (ps.match(/agent|swarm|coordinator/g) || []).length;
|
||||
|
||||
return {
|
||||
agenticFlow: Math.max(0, agenticFlow - 1), // Exclude grep itself
|
||||
mcp,
|
||||
agents: Math.max(0, agents - 1)
|
||||
};
|
||||
} catch (e) {
|
||||
return { agenticFlow: 0, mcp: 0, agents: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync all metrics from actual implementation
|
||||
*/
|
||||
async function syncMetrics() {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// Count V3 modules
|
||||
const modulesDir = join(V3_DIR, '@claude-flow');
|
||||
let modules = [];
|
||||
let totalProgress = 0;
|
||||
|
||||
if (existsSync(modulesDir)) {
|
||||
const entries = readdirSync(modulesDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
// Skip hidden directories (like .agentic-flow, .claude-flow)
|
||||
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
||||
const moduleDir = join(modulesDir, entry.name);
|
||||
const { files, lines } = countFilesAndLines(moduleDir);
|
||||
const progress = calculateModuleProgress(moduleDir);
|
||||
|
||||
modules.push({ name: entry.name, files, lines, progress });
|
||||
totalProgress += progress;
|
||||
|
||||
// Update module_status table
|
||||
db.run(`
|
||||
INSERT OR REPLACE INTO module_status (name, files, lines, progress, has_src, has_tests, last_updated)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`, [
|
||||
entry.name,
|
||||
files,
|
||||
lines,
|
||||
progress,
|
||||
existsSync(join(moduleDir, 'src')) ? 1 : 0,
|
||||
existsSync(join(moduleDir, '__tests__')) ? 1 : 0,
|
||||
now
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const avgProgress = modules.length > 0 ? Math.round(totalProgress / modules.length) : 0;
|
||||
const totalStats = countFilesAndLines(V3_DIR);
|
||||
|
||||
// Count completed domains (mapped to modules)
|
||||
const domainModules = ['swarm', 'memory', 'performance', 'cli', 'integration'];
|
||||
const domainsCompleted = domainModules.filter(m =>
|
||||
modules.some(mod => mod.name === m && mod.progress >= 50)
|
||||
).length;
|
||||
|
||||
// Update v3_progress
|
||||
db.run(`
|
||||
UPDATE v3_progress SET
|
||||
domains_completed = ?,
|
||||
ddd_progress = ?,
|
||||
total_modules = ?,
|
||||
total_files = ?,
|
||||
total_lines = ?,
|
||||
last_updated = ?
|
||||
WHERE id = 1
|
||||
`, [domainsCompleted, avgProgress, modules.length, totalStats.files, totalStats.lines, now]);
|
||||
|
||||
// Check security CVEs
|
||||
const cve1Fixed = checkSecurityFile('input-validator.ts');
|
||||
const cve2Fixed = checkSecurityFile('path-validator.ts');
|
||||
const cve3Fixed = checkSecurityFile('safe-executor.ts');
|
||||
const cvesFixed = [cve1Fixed, cve2Fixed, cve3Fixed].filter(Boolean).length;
|
||||
|
||||
let securityStatus = 'PENDING';
|
||||
if (cvesFixed === 3) securityStatus = 'CLEAN';
|
||||
else if (cvesFixed > 0) securityStatus = 'IN_PROGRESS';
|
||||
|
||||
db.run(`
|
||||
UPDATE security_audit SET
|
||||
status = ?,
|
||||
cves_fixed = ?,
|
||||
last_audit = ?
|
||||
WHERE id = 1
|
||||
`, [securityStatus, cvesFixed, now]);
|
||||
|
||||
// Update individual CVE status
|
||||
db.run("UPDATE cve_status SET status = ?, last_updated = ? WHERE id = 'CVE-1'", [cve1Fixed ? 'fixed' : 'pending', now]);
|
||||
db.run("UPDATE cve_status SET status = ?, last_updated = ? WHERE id = 'CVE-2'", [cve2Fixed ? 'fixed' : 'pending', now]);
|
||||
db.run("UPDATE cve_status SET status = ?, last_updated = ? WHERE id = 'CVE-3'", [cve3Fixed ? 'fixed' : 'pending', now]);
|
||||
|
||||
// Update swarm activity
|
||||
const processes = countProcesses();
|
||||
db.run(`
|
||||
UPDATE swarm_activity SET
|
||||
agentic_flow_processes = ?,
|
||||
mcp_server_processes = ?,
|
||||
estimated_agents = ?,
|
||||
swarm_active = ?,
|
||||
coordination_active = ?,
|
||||
last_updated = ?
|
||||
WHERE id = 1
|
||||
`, [
|
||||
processes.agenticFlow,
|
||||
processes.mcp,
|
||||
processes.agents,
|
||||
processes.agents > 0 ? 1 : 0,
|
||||
processes.agenticFlow > 0 ? 1 : 0,
|
||||
now
|
||||
]);
|
||||
|
||||
persist();
|
||||
|
||||
return {
|
||||
modules: modules.length,
|
||||
domains: domainsCompleted,
|
||||
dddProgress: avgProgress,
|
||||
cvesFixed,
|
||||
securityStatus,
|
||||
files: totalStats.files,
|
||||
lines: totalStats.lines
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current metrics as JSON (for statusline compatibility)
|
||||
*/
|
||||
function getMetricsJSON() {
|
||||
const progress = db.exec("SELECT * FROM v3_progress WHERE id = 1")[0];
|
||||
const security = db.exec("SELECT * FROM security_audit WHERE id = 1")[0];
|
||||
const swarm = db.exec("SELECT * FROM swarm_activity WHERE id = 1")[0];
|
||||
const perf = db.exec("SELECT * FROM performance_metrics WHERE id = 1")[0];
|
||||
|
||||
// Map column names to values
|
||||
const mapRow = (result) => {
|
||||
if (!result) return {};
|
||||
const cols = result.columns;
|
||||
const vals = result.values[0];
|
||||
return Object.fromEntries(cols.map((c, i) => [c, vals[i]]));
|
||||
};
|
||||
|
||||
return {
|
||||
v3Progress: mapRow(progress),
|
||||
securityAudit: mapRow(security),
|
||||
swarmActivity: mapRow(swarm),
|
||||
performanceMetrics: mapRow(perf)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Export metrics to JSON files for backward compatibility
|
||||
*/
|
||||
function exportToJSON() {
|
||||
const metrics = getMetricsJSON();
|
||||
const metricsDir = join(PROJECT_ROOT, '.claude-flow/metrics');
|
||||
const securityDir = join(PROJECT_ROOT, '.claude-flow/security');
|
||||
|
||||
if (!existsSync(metricsDir)) mkdirSync(metricsDir, { recursive: true });
|
||||
if (!existsSync(securityDir)) mkdirSync(securityDir, { recursive: true });
|
||||
|
||||
// v3-progress.json
|
||||
writeFileSync(join(metricsDir, 'v3-progress.json'), JSON.stringify({
|
||||
domains: {
|
||||
completed: metrics.v3Progress.domains_completed,
|
||||
total: metrics.v3Progress.domains_total
|
||||
},
|
||||
ddd: {
|
||||
progress: metrics.v3Progress.ddd_progress,
|
||||
modules: metrics.v3Progress.total_modules,
|
||||
totalFiles: metrics.v3Progress.total_files,
|
||||
totalLines: metrics.v3Progress.total_lines
|
||||
},
|
||||
swarm: {
|
||||
activeAgents: metrics.swarmActivity.estimated_agents,
|
||||
totalAgents: 15
|
||||
},
|
||||
lastUpdated: metrics.v3Progress.last_updated,
|
||||
source: 'metrics.db'
|
||||
}, null, 2));
|
||||
|
||||
// security/audit-status.json
|
||||
writeFileSync(join(securityDir, 'audit-status.json'), JSON.stringify({
|
||||
status: metrics.securityAudit.status,
|
||||
cvesFixed: metrics.securityAudit.cves_fixed,
|
||||
totalCves: metrics.securityAudit.total_cves,
|
||||
lastAudit: metrics.securityAudit.last_audit,
|
||||
source: 'metrics.db'
|
||||
}, null, 2));
|
||||
|
||||
// swarm-activity.json
|
||||
writeFileSync(join(metricsDir, 'swarm-activity.json'), JSON.stringify({
|
||||
timestamp: metrics.swarmActivity.last_updated,
|
||||
processes: {
|
||||
agentic_flow: metrics.swarmActivity.agentic_flow_processes,
|
||||
mcp_server: metrics.swarmActivity.mcp_server_processes,
|
||||
estimated_agents: metrics.swarmActivity.estimated_agents
|
||||
},
|
||||
swarm: {
|
||||
active: metrics.swarmActivity.swarm_active === 1,
|
||||
agent_count: metrics.swarmActivity.estimated_agents,
|
||||
coordination_active: metrics.swarmActivity.coordination_active === 1
|
||||
},
|
||||
source: 'metrics.db'
|
||||
}, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point
|
||||
*/
|
||||
async function main() {
|
||||
const command = process.argv[2] || 'sync';
|
||||
|
||||
await initDatabase();
|
||||
|
||||
switch (command) {
|
||||
case 'sync':
|
||||
const result = await syncMetrics();
|
||||
exportToJSON();
|
||||
console.log(JSON.stringify(result));
|
||||
break;
|
||||
|
||||
case 'export':
|
||||
exportToJSON();
|
||||
console.log('Exported to JSON files');
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
const metrics = getMetricsJSON();
|
||||
console.log(JSON.stringify(metrics, null, 2));
|
||||
break;
|
||||
|
||||
case 'daemon':
|
||||
const interval = parseInt(process.argv[3]) || 30;
|
||||
console.log(`Starting metrics daemon (interval: ${interval}s)`);
|
||||
|
||||
// Initial sync
|
||||
await syncMetrics();
|
||||
exportToJSON();
|
||||
|
||||
// Continuous sync
|
||||
setInterval(async () => {
|
||||
await syncMetrics();
|
||||
exportToJSON();
|
||||
}, interval * 1000);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('Usage: metrics-db.mjs [sync|export|status|daemon [interval]]');
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
86
.claude/helpers/pattern-consolidator.sh
Executable file
86
.claude/helpers/pattern-consolidator.sh
Executable file
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
# Claude Flow V3 - Pattern Consolidator Worker
|
||||
# Deduplicates patterns, prunes old ones, improves quality scores
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
PATTERNS_DB="$PROJECT_ROOT/.claude-flow/learning/patterns.db"
|
||||
METRICS_DIR="$PROJECT_ROOT/.claude-flow/metrics"
|
||||
LAST_RUN_FILE="$METRICS_DIR/.consolidator-last-run"
|
||||
|
||||
mkdir -p "$METRICS_DIR"
|
||||
|
||||
should_run() {
|
||||
if [ ! -f "$LAST_RUN_FILE" ]; then return 0; fi
|
||||
local last_run=$(cat "$LAST_RUN_FILE" 2>/dev/null || echo "0")
|
||||
local now=$(date +%s)
|
||||
[ $((now - last_run)) -ge 900 ] # 15 minutes
|
||||
}
|
||||
|
||||
consolidate_patterns() {
|
||||
if [ ! -f "$PATTERNS_DB" ] || ! command -v sqlite3 &>/dev/null; then
|
||||
echo "[$(date +%H:%M:%S)] No patterns database found"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "[$(date +%H:%M:%S)] Consolidating patterns..."
|
||||
|
||||
# Count before
|
||||
local before=$(sqlite3 "$PATTERNS_DB" "SELECT COUNT(*) FROM short_term_patterns" 2>/dev/null || echo "0")
|
||||
|
||||
# Remove duplicates (keep highest quality)
|
||||
sqlite3 "$PATTERNS_DB" "
|
||||
DELETE FROM short_term_patterns
|
||||
WHERE rowid NOT IN (
|
||||
SELECT MIN(rowid) FROM short_term_patterns
|
||||
GROUP BY strategy, domain
|
||||
)
|
||||
" 2>/dev/null || true
|
||||
|
||||
# Prune old low-quality patterns (older than 7 days, quality < 0.3)
|
||||
sqlite3 "$PATTERNS_DB" "
|
||||
DELETE FROM short_term_patterns
|
||||
WHERE quality < 0.3
|
||||
AND created_at < datetime('now', '-7 days')
|
||||
" 2>/dev/null || true
|
||||
|
||||
# Promote high-quality patterns to long-term (quality > 0.8, used > 5 times)
|
||||
sqlite3 "$PATTERNS_DB" "
|
||||
INSERT OR IGNORE INTO long_term_patterns (strategy, domain, quality, source)
|
||||
SELECT strategy, domain, quality, 'consolidated'
|
||||
FROM short_term_patterns
|
||||
WHERE quality > 0.8
|
||||
" 2>/dev/null || true
|
||||
|
||||
# Decay quality of unused patterns
|
||||
sqlite3 "$PATTERNS_DB" "
|
||||
UPDATE short_term_patterns
|
||||
SET quality = quality * 0.95
|
||||
WHERE updated_at < datetime('now', '-1 day')
|
||||
" 2>/dev/null || true
|
||||
|
||||
# Count after
|
||||
local after=$(sqlite3 "$PATTERNS_DB" "SELECT COUNT(*) FROM short_term_patterns" 2>/dev/null || echo "0")
|
||||
local removed=$((before - after))
|
||||
|
||||
echo "[$(date +%H:%M:%S)] ✓ Consolidated: $before → $after patterns (removed $removed)"
|
||||
|
||||
date +%s > "$LAST_RUN_FILE"
|
||||
}
|
||||
|
||||
case "${1:-check}" in
|
||||
"run"|"consolidate") consolidate_patterns ;;
|
||||
"check") should_run && consolidate_patterns || echo "[$(date +%H:%M:%S)] Skipping (throttled)" ;;
|
||||
"force") rm -f "$LAST_RUN_FILE"; consolidate_patterns ;;
|
||||
"status")
|
||||
if [ -f "$PATTERNS_DB" ] && command -v sqlite3 &>/dev/null; then
|
||||
local short=$(sqlite3 "$PATTERNS_DB" "SELECT COUNT(*) FROM short_term_patterns" 2>/dev/null || echo "0")
|
||||
local long=$(sqlite3 "$PATTERNS_DB" "SELECT COUNT(*) FROM long_term_patterns" 2>/dev/null || echo "0")
|
||||
local avg_q=$(sqlite3 "$PATTERNS_DB" "SELECT ROUND(AVG(quality), 2) FROM short_term_patterns" 2>/dev/null || echo "0")
|
||||
echo "Patterns: $short short-term, $long long-term, avg quality: $avg_q"
|
||||
fi
|
||||
;;
|
||||
*) echo "Usage: $0 [run|check|force|status]" ;;
|
||||
esac
|
||||
160
.claude/helpers/perf-worker.sh
Executable file
160
.claude/helpers/perf-worker.sh
Executable file
@@ -0,0 +1,160 @@
|
||||
#!/bin/bash
|
||||
# Claude Flow V3 - Performance Benchmark Worker
|
||||
# Runs periodic benchmarks and updates metrics using agentic-flow agents
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
METRICS_DIR="$PROJECT_ROOT/.claude-flow/metrics"
|
||||
PERF_FILE="$METRICS_DIR/performance.json"
|
||||
LAST_RUN_FILE="$METRICS_DIR/.perf-last-run"
|
||||
|
||||
mkdir -p "$METRICS_DIR"
|
||||
|
||||
# Check if we should run (throttle to once per 5 minutes)
|
||||
should_run() {
|
||||
if [ ! -f "$LAST_RUN_FILE" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local last_run=$(cat "$LAST_RUN_FILE" 2>/dev/null || echo "0")
|
||||
local now=$(date +%s)
|
||||
local diff=$((now - last_run))
|
||||
|
||||
# Run every 5 minutes (300 seconds)
|
||||
[ "$diff" -ge 300 ]
|
||||
}
|
||||
|
||||
# Simple search benchmark (measures grep/search speed)
|
||||
benchmark_search() {
|
||||
local start=$(date +%s%3N)
|
||||
|
||||
# Search through v3 codebase
|
||||
find "$PROJECT_ROOT/v3" -name "*.ts" -type f 2>/dev/null | \
|
||||
xargs grep -l "function\|class\|interface" 2>/dev/null | \
|
||||
wc -l > /dev/null
|
||||
|
||||
local end=$(date +%s%3N)
|
||||
local duration=$((end - start))
|
||||
|
||||
# Baseline is ~100ms, calculate improvement
|
||||
local baseline=100
|
||||
if [ "$duration" -gt 0 ]; then
|
||||
local improvement=$(echo "scale=2; $baseline / $duration" | bc 2>/dev/null || echo "1.0")
|
||||
echo "${improvement}x"
|
||||
else
|
||||
echo "1.0x"
|
||||
fi
|
||||
}
|
||||
|
||||
# Memory efficiency check
|
||||
benchmark_memory() {
|
||||
local node_mem=$(ps aux 2>/dev/null | grep -E "(node|agentic)" | grep -v grep | awk '{sum += $6} END {print int(sum/1024)}')
|
||||
local baseline_mem=4000 # 4GB baseline
|
||||
|
||||
if [ -n "$node_mem" ] && [ "$node_mem" -gt 0 ]; then
|
||||
local reduction=$(echo "scale=0; 100 - ($node_mem * 100 / $baseline_mem)" | bc 2>/dev/null || echo "0")
|
||||
if [ "$reduction" -lt 0 ]; then reduction=0; fi
|
||||
echo "${reduction}%"
|
||||
else
|
||||
echo "0%"
|
||||
fi
|
||||
}
|
||||
|
||||
# Startup time check
|
||||
benchmark_startup() {
|
||||
local start=$(date +%s%3N)
|
||||
|
||||
# Quick check of agentic-flow responsiveness
|
||||
timeout 5 npx agentic-flow@alpha --version >/dev/null 2>&1 || true
|
||||
|
||||
local end=$(date +%s%3N)
|
||||
local duration=$((end - start))
|
||||
|
||||
echo "${duration}ms"
|
||||
}
|
||||
|
||||
# Run benchmarks and update metrics
|
||||
run_benchmarks() {
|
||||
echo "[$(date +%H:%M:%S)] Running performance benchmarks..."
|
||||
|
||||
local search_speed=$(benchmark_search)
|
||||
local memory_reduction=$(benchmark_memory)
|
||||
local startup_time=$(benchmark_startup)
|
||||
|
||||
# Calculate overall speedup (simplified)
|
||||
local speedup_num=$(echo "$search_speed" | tr -d 'x')
|
||||
if [ -z "$speedup_num" ] || [ "$speedup_num" = "1.0" ]; then
|
||||
speedup_num="1.0"
|
||||
fi
|
||||
|
||||
# Update performance.json
|
||||
if [ -f "$PERF_FILE" ] && command -v jq &>/dev/null; then
|
||||
jq --arg search "$search_speed" \
|
||||
--arg memory "$memory_reduction" \
|
||||
--arg startup "$startup_time" \
|
||||
--arg speedup "${speedup_num}x" \
|
||||
--arg updated "$(date -Iseconds)" \
|
||||
'.search.improvement = $search |
|
||||
.memory.reduction = $memory |
|
||||
.startupTime.current = $startup |
|
||||
.flashAttention.speedup = $speedup |
|
||||
."last-updated" = $updated' \
|
||||
"$PERF_FILE" > "$PERF_FILE.tmp" && mv "$PERF_FILE.tmp" "$PERF_FILE"
|
||||
|
||||
echo "[$(date +%H:%M:%S)] ✓ Metrics updated: search=$search_speed memory=$memory_reduction startup=$startup_time"
|
||||
else
|
||||
echo "[$(date +%H:%M:%S)] ⚠ Could not update metrics (missing jq or file)"
|
||||
fi
|
||||
|
||||
# Record last run time
|
||||
date +%s > "$LAST_RUN_FILE"
|
||||
}
|
||||
|
||||
# Spawn agentic-flow performance agent for deep analysis
|
||||
run_deep_benchmark() {
|
||||
echo "[$(date +%H:%M:%S)] Spawning performance-benchmarker agent..."
|
||||
|
||||
npx agentic-flow@alpha --agent perf-analyzer --task "Analyze current system performance and update metrics" 2>/dev/null &
|
||||
local pid=$!
|
||||
|
||||
# Don't wait, let it run in background
|
||||
echo "[$(date +%H:%M:%S)] Agent spawned (PID: $pid)"
|
||||
}
|
||||
|
||||
# Main dispatcher
|
||||
case "${1:-check}" in
|
||||
"run"|"benchmark")
|
||||
run_benchmarks
|
||||
;;
|
||||
"deep")
|
||||
run_deep_benchmark
|
||||
;;
|
||||
"check")
|
||||
if should_run; then
|
||||
run_benchmarks
|
||||
else
|
||||
echo "[$(date +%H:%M:%S)] Skipping benchmark (throttled)"
|
||||
fi
|
||||
;;
|
||||
"force")
|
||||
rm -f "$LAST_RUN_FILE"
|
||||
run_benchmarks
|
||||
;;
|
||||
"status")
|
||||
if [ -f "$PERF_FILE" ]; then
|
||||
jq -r '"Search: \(.search.improvement // "1x") | Memory: \(.memory.reduction // "0%") | Startup: \(.startupTime.current // "N/A")"' "$PERF_FILE" 2>/dev/null
|
||||
else
|
||||
echo "No metrics available"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Usage: perf-worker.sh [run|deep|check|force|status]"
|
||||
echo " run - Run quick benchmarks"
|
||||
echo " deep - Spawn agentic-flow agent for deep analysis"
|
||||
echo " check - Run if throttle allows (default)"
|
||||
echo " force - Force run ignoring throttle"
|
||||
echo " status - Show current metrics"
|
||||
;;
|
||||
esac
|
||||
16
.claude/helpers/post-commit
Executable file
16
.claude/helpers/post-commit
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# Claude Flow Post-Commit Hook
|
||||
# Records commit metrics and trains patterns
|
||||
|
||||
COMMIT_HASH=$(git rev-parse HEAD)
|
||||
COMMIT_MSG=$(git log -1 --pretty=%B)
|
||||
|
||||
echo "📊 Recording commit metrics..."
|
||||
|
||||
# Notify claude-flow of commit
|
||||
npx @claude-flow/cli hooks notify \
|
||||
--message "Commit: $COMMIT_MSG" \
|
||||
--level info \
|
||||
--metadata '{"hash": "'$COMMIT_HASH'"}' 2>/dev/null || true
|
||||
|
||||
echo "✅ Commit recorded"
|
||||
26
.claude/helpers/pre-commit
Executable file
26
.claude/helpers/pre-commit
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
# Claude Flow Pre-Commit Hook
|
||||
# Validates code quality before commit
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔍 Running Claude Flow pre-commit checks..."
|
||||
|
||||
# Get staged files
|
||||
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
|
||||
|
||||
# Run validation for each staged file
|
||||
for FILE in $STAGED_FILES; do
|
||||
if [[ "$FILE" =~ \.(ts|js|tsx|jsx)$ ]]; then
|
||||
echo " Validating: $FILE"
|
||||
npx @claude-flow/cli hooks pre-edit --file "$FILE" --validate-syntax 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Run tests if available
|
||||
if [ -f "package.json" ] && grep -q '"test"' package.json; then
|
||||
echo "🧪 Running tests..."
|
||||
npm test --if-present 2>/dev/null || echo " Tests skipped or failed"
|
||||
fi
|
||||
|
||||
echo "✅ Pre-commit checks complete"
|
||||
19
.claude/helpers/quick-start.sh
Executable file
19
.claude/helpers/quick-start.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
# Quick start guide for Claude Flow
|
||||
|
||||
echo "🚀 Claude Flow Quick Start"
|
||||
echo "=========================="
|
||||
echo ""
|
||||
echo "1. Initialize a swarm:"
|
||||
echo " npx claude-flow swarm init --topology hierarchical"
|
||||
echo ""
|
||||
echo "2. Spawn agents:"
|
||||
echo " npx claude-flow agent spawn --type coder --name "API Developer""
|
||||
echo ""
|
||||
echo "3. Orchestrate tasks:"
|
||||
echo " npx claude-flow task orchestrate --task "Build REST API""
|
||||
echo ""
|
||||
echo "4. Monitor progress:"
|
||||
echo " npx claude-flow swarm monitor"
|
||||
echo ""
|
||||
echo "📚 For more examples, see .claude/commands/"
|
||||
66
.claude/helpers/router.js
Normal file
66
.claude/helpers/router.js
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Claude Flow Agent Router
|
||||
* Routes tasks to optimal agents based on learned patterns
|
||||
*/
|
||||
|
||||
const AGENT_CAPABILITIES = {
|
||||
coder: ['code-generation', 'refactoring', 'debugging', 'implementation'],
|
||||
tester: ['unit-testing', 'integration-testing', 'coverage', 'test-generation'],
|
||||
reviewer: ['code-review', 'security-audit', 'quality-check', 'best-practices'],
|
||||
researcher: ['web-search', 'documentation', 'analysis', 'summarization'],
|
||||
architect: ['system-design', 'architecture', 'patterns', 'scalability'],
|
||||
'backend-dev': ['api', 'database', 'server', 'authentication'],
|
||||
'frontend-dev': ['ui', 'react', 'css', 'components'],
|
||||
devops: ['ci-cd', 'docker', 'deployment', 'infrastructure'],
|
||||
};
|
||||
|
||||
const TASK_PATTERNS = {
|
||||
// Code patterns
|
||||
'implement|create|build|add|write code': 'coder',
|
||||
'test|spec|coverage|unit test|integration': 'tester',
|
||||
'review|audit|check|validate|security': 'reviewer',
|
||||
'research|find|search|documentation|explore': 'researcher',
|
||||
'design|architect|structure|plan': 'architect',
|
||||
|
||||
// Domain patterns
|
||||
'api|endpoint|server|backend|database': 'backend-dev',
|
||||
'ui|frontend|component|react|css|style': 'frontend-dev',
|
||||
'deploy|docker|ci|cd|pipeline|infrastructure': 'devops',
|
||||
};
|
||||
|
||||
function routeTask(task) {
|
||||
const taskLower = task.toLowerCase();
|
||||
|
||||
// Check patterns
|
||||
for (const [pattern, agent] of Object.entries(TASK_PATTERNS)) {
|
||||
const regex = new RegExp(pattern, 'i');
|
||||
if (regex.test(taskLower)) {
|
||||
return {
|
||||
agent,
|
||||
confidence: 0.8,
|
||||
reason: `Matched pattern: ${pattern}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Default to coder for unknown tasks
|
||||
return {
|
||||
agent: 'coder',
|
||||
confidence: 0.5,
|
||||
reason: 'Default routing - no specific pattern matched',
|
||||
};
|
||||
}
|
||||
|
||||
// CLI
|
||||
const task = process.argv.slice(2).join(' ');
|
||||
|
||||
if (task) {
|
||||
const result = routeTask(task);
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} else {
|
||||
console.log('Usage: router.js <task description>');
|
||||
console.log('\nAvailable agents:', Object.keys(AGENT_CAPABILITIES).join(', '));
|
||||
}
|
||||
|
||||
module.exports = { routeTask, AGENT_CAPABILITIES, TASK_PATTERNS };
|
||||
127
.claude/helpers/security-scanner.sh
Executable file
127
.claude/helpers/security-scanner.sh
Executable file
@@ -0,0 +1,127 @@
|
||||
#!/bin/bash
|
||||
# Claude Flow V3 - Security Scanner Worker
|
||||
# Scans for secrets, vulnerabilities, CVE updates
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
SECURITY_DIR="$PROJECT_ROOT/.claude-flow/security"
|
||||
SCAN_FILE="$SECURITY_DIR/scan-results.json"
|
||||
LAST_RUN_FILE="$SECURITY_DIR/.scanner-last-run"
|
||||
|
||||
mkdir -p "$SECURITY_DIR"
|
||||
|
||||
should_run() {
|
||||
if [ ! -f "$LAST_RUN_FILE" ]; then return 0; fi
|
||||
local last_run=$(cat "$LAST_RUN_FILE" 2>/dev/null || echo "0")
|
||||
local now=$(date +%s)
|
||||
[ $((now - last_run)) -ge 1800 ] # 30 minutes
|
||||
}
|
||||
|
||||
scan_secrets() {
|
||||
local secrets_found=0
|
||||
local patterns=(
|
||||
"password\s*=\s*['\"][^'\"]+['\"]"
|
||||
"api[_-]?key\s*=\s*['\"][^'\"]+['\"]"
|
||||
"secret\s*=\s*['\"][^'\"]+['\"]"
|
||||
"token\s*=\s*['\"][^'\"]+['\"]"
|
||||
"private[_-]?key"
|
||||
)
|
||||
|
||||
for pattern in "${patterns[@]}"; do
|
||||
local count=$(grep -riE "$pattern" "$PROJECT_ROOT/src" "$PROJECT_ROOT/v3" 2>/dev/null | grep -v node_modules | grep -v ".git" | wc -l | tr -d '[:space:]')
|
||||
count=${count:-0}
|
||||
secrets_found=$((secrets_found + count))
|
||||
done
|
||||
|
||||
echo "$secrets_found"
|
||||
}
|
||||
|
||||
scan_vulnerabilities() {
|
||||
local vulns=0
|
||||
|
||||
# Check for known vulnerable patterns
|
||||
# SQL injection patterns
|
||||
local sql_count=$(grep -rE "execute\s*\(" "$PROJECT_ROOT/src" "$PROJECT_ROOT/v3" 2>/dev/null | grep -v node_modules | grep -v ".test." | wc -l | tr -d '[:space:]')
|
||||
vulns=$((vulns + ${sql_count:-0}))
|
||||
|
||||
# Command injection patterns
|
||||
local cmd_count=$(grep -rE "exec\s*\(|spawn\s*\(" "$PROJECT_ROOT/src" "$PROJECT_ROOT/v3" 2>/dev/null | grep -v node_modules | grep -v ".test." | wc -l | tr -d '[:space:]')
|
||||
vulns=$((vulns + ${cmd_count:-0}))
|
||||
|
||||
# Unsafe eval
|
||||
local eval_count=$(grep -rE "\beval\s*\(" "$PROJECT_ROOT/src" "$PROJECT_ROOT/v3" 2>/dev/null | grep -v node_modules | wc -l | tr -d '[:space:]')
|
||||
vulns=$((vulns + ${eval_count:-0}))
|
||||
|
||||
echo "$vulns"
|
||||
}
|
||||
|
||||
check_npm_audit() {
|
||||
if [ -f "$PROJECT_ROOT/package-lock.json" ]; then
|
||||
# Skip npm audit for speed - it's slow
|
||||
echo "0"
|
||||
else
|
||||
echo "0"
|
||||
fi
|
||||
}
|
||||
|
||||
run_scan() {
|
||||
echo "[$(date +%H:%M:%S)] Running security scan..."
|
||||
|
||||
local secrets=$(scan_secrets)
|
||||
local vulns=$(scan_vulnerabilities)
|
||||
local npm_vulns=$(check_npm_audit)
|
||||
|
||||
local total_issues=$((secrets + vulns + npm_vulns))
|
||||
local status="clean"
|
||||
|
||||
if [ "$total_issues" -gt 10 ]; then
|
||||
status="critical"
|
||||
elif [ "$total_issues" -gt 0 ]; then
|
||||
status="warning"
|
||||
fi
|
||||
|
||||
# Update audit status
|
||||
cat > "$SCAN_FILE" << EOF
|
||||
{
|
||||
"status": "$status",
|
||||
"timestamp": "$(date -Iseconds)",
|
||||
"findings": {
|
||||
"secrets": $secrets,
|
||||
"vulnerabilities": $vulns,
|
||||
"npm_audit": $npm_vulns,
|
||||
"total": $total_issues
|
||||
},
|
||||
"cves": {
|
||||
"tracked": ["CVE-1", "CVE-2", "CVE-3"],
|
||||
"remediated": 3
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Update main audit status file
|
||||
if [ "$status" = "clean" ]; then
|
||||
echo '{"status":"CLEAN","cvesFixed":3}' > "$SECURITY_DIR/audit-status.json"
|
||||
else
|
||||
echo "{\"status\":\"$status\",\"cvesFixed\":3,\"issues\":$total_issues}" > "$SECURITY_DIR/audit-status.json"
|
||||
fi
|
||||
|
||||
echo "[$(date +%H:%M:%S)] ✓ Security: $status | Secrets: $secrets | Vulns: $vulns | NPM: $npm_vulns"
|
||||
|
||||
date +%s > "$LAST_RUN_FILE"
|
||||
}
|
||||
|
||||
case "${1:-check}" in
|
||||
"run"|"scan") run_scan ;;
|
||||
"check") should_run && run_scan || echo "[$(date +%H:%M:%S)] Skipping (throttled)" ;;
|
||||
"force") rm -f "$LAST_RUN_FILE"; run_scan ;;
|
||||
"status")
|
||||
if [ -f "$SCAN_FILE" ]; then
|
||||
jq -r '"Status: \(.status) | Secrets: \(.findings.secrets) | Vulns: \(.findings.vulnerabilities) | NPM: \(.findings.npm_audit)"' "$SCAN_FILE"
|
||||
else
|
||||
echo "No scan data available"
|
||||
fi
|
||||
;;
|
||||
*) echo "Usage: $0 [run|check|force|status]" ;;
|
||||
esac
|
||||
135
.claude/helpers/session.js
Normal file
135
.claude/helpers/session.js
Normal file
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Claude Flow Session Manager
|
||||
* Handles session lifecycle: start, restore, end
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const SESSION_DIR = path.join(process.cwd(), '.claude-flow', 'sessions');
|
||||
const SESSION_FILE = path.join(SESSION_DIR, 'current.json');
|
||||
|
||||
const commands = {
|
||||
start: () => {
|
||||
const sessionId = `session-${Date.now()}`;
|
||||
const session = {
|
||||
id: sessionId,
|
||||
startedAt: new Date().toISOString(),
|
||||
cwd: process.cwd(),
|
||||
context: {},
|
||||
metrics: {
|
||||
edits: 0,
|
||||
commands: 0,
|
||||
tasks: 0,
|
||||
errors: 0,
|
||||
},
|
||||
};
|
||||
|
||||
fs.mkdirSync(SESSION_DIR, { recursive: true });
|
||||
fs.writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2));
|
||||
|
||||
console.log(`Session started: ${sessionId}`);
|
||||
return session;
|
||||
},
|
||||
|
||||
restore: () => {
|
||||
if (!fs.existsSync(SESSION_FILE)) {
|
||||
console.log('No session to restore');
|
||||
return null;
|
||||
}
|
||||
|
||||
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
||||
session.restoredAt = new Date().toISOString();
|
||||
fs.writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2));
|
||||
|
||||
console.log(`Session restored: ${session.id}`);
|
||||
return session;
|
||||
},
|
||||
|
||||
end: () => {
|
||||
if (!fs.existsSync(SESSION_FILE)) {
|
||||
console.log('No active session');
|
||||
return null;
|
||||
}
|
||||
|
||||
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
||||
session.endedAt = new Date().toISOString();
|
||||
session.duration = Date.now() - new Date(session.startedAt).getTime();
|
||||
|
||||
// Archive session
|
||||
const archivePath = path.join(SESSION_DIR, `${session.id}.json`);
|
||||
fs.writeFileSync(archivePath, JSON.stringify(session, null, 2));
|
||||
fs.unlinkSync(SESSION_FILE);
|
||||
|
||||
console.log(`Session ended: ${session.id}`);
|
||||
console.log(`Duration: ${Math.round(session.duration / 1000 / 60)} minutes`);
|
||||
console.log(`Metrics: ${JSON.stringify(session.metrics)}`);
|
||||
|
||||
return session;
|
||||
},
|
||||
|
||||
status: () => {
|
||||
if (!fs.existsSync(SESSION_FILE)) {
|
||||
console.log('No active session');
|
||||
return null;
|
||||
}
|
||||
|
||||
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
||||
const duration = Date.now() - new Date(session.startedAt).getTime();
|
||||
|
||||
console.log(`Session: ${session.id}`);
|
||||
console.log(`Started: ${session.startedAt}`);
|
||||
console.log(`Duration: ${Math.round(duration / 1000 / 60)} minutes`);
|
||||
console.log(`Metrics: ${JSON.stringify(session.metrics)}`);
|
||||
|
||||
return session;
|
||||
},
|
||||
|
||||
update: (key, value) => {
|
||||
if (!fs.existsSync(SESSION_FILE)) {
|
||||
console.log('No active session');
|
||||
return null;
|
||||
}
|
||||
|
||||
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
||||
session.context[key] = value;
|
||||
session.updatedAt = new Date().toISOString();
|
||||
fs.writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2));
|
||||
|
||||
return session;
|
||||
},
|
||||
|
||||
get: (key) => {
|
||||
if (!fs.existsSync(SESSION_FILE)) return null;
|
||||
try {
|
||||
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
||||
return key ? (session.context || {})[key] : session.context;
|
||||
} catch { return null; }
|
||||
},
|
||||
|
||||
metric: (name) => {
|
||||
if (!fs.existsSync(SESSION_FILE)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
||||
if (session.metrics[name] !== undefined) {
|
||||
session.metrics[name]++;
|
||||
fs.writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2));
|
||||
}
|
||||
|
||||
return session;
|
||||
},
|
||||
};
|
||||
|
||||
// CLI
|
||||
const [,, command, ...args] = process.argv;
|
||||
|
||||
if (command && commands[command]) {
|
||||
commands[command](...args);
|
||||
} else {
|
||||
console.log('Usage: session.js <start|restore|end|status|update|metric> [args]');
|
||||
}
|
||||
|
||||
module.exports = commands;
|
||||
18
.claude/helpers/setup-mcp.sh
Executable file
18
.claude/helpers/setup-mcp.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
# Setup MCP server for Claude Flow
|
||||
|
||||
echo "🚀 Setting up Claude Flow MCP server..."
|
||||
|
||||
# Check if claude command exists
|
||||
if ! command -v claude &> /dev/null; then
|
||||
echo "❌ Error: Claude Code CLI not found"
|
||||
echo "Please install Claude Code first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Add MCP server
|
||||
echo "📦 Adding Claude Flow MCP server..."
|
||||
claude mcp add claude-flow npx claude-flow mcp start
|
||||
|
||||
echo "✅ MCP server setup complete!"
|
||||
echo "🎯 You can now use mcp__claude-flow__ tools in Claude Code"
|
||||
189
.claude/helpers/standard-checkpoint-hooks.sh
Executable file
189
.claude/helpers/standard-checkpoint-hooks.sh
Executable file
@@ -0,0 +1,189 @@
|
||||
#!/bin/bash
|
||||
# Standard checkpoint hook functions for Claude settings.json (without GitHub features)
|
||||
|
||||
# Function to handle pre-edit checkpoints
|
||||
pre_edit_checkpoint() {
|
||||
local tool_input="$1"
|
||||
# Handle both JSON input and plain file path
|
||||
if echo "$tool_input" | jq -e . >/dev/null 2>&1; then
|
||||
local file=$(echo "$tool_input" | jq -r '.file_path // empty')
|
||||
else
|
||||
local file="$tool_input"
|
||||
fi
|
||||
|
||||
if [ -n "$file" ]; then
|
||||
local checkpoint_branch="checkpoint/pre-edit-$(date +%Y%m%d-%H%M%S)"
|
||||
local current_branch=$(git branch --show-current)
|
||||
|
||||
# Create checkpoint
|
||||
git add -A
|
||||
git stash push -m "Pre-edit checkpoint for $file" >/dev/null 2>&1
|
||||
git branch "$checkpoint_branch"
|
||||
|
||||
# Store metadata
|
||||
mkdir -p .claude/checkpoints
|
||||
cat > ".claude/checkpoints/$(date +%s).json" <<EOF
|
||||
{
|
||||
"branch": "$checkpoint_branch",
|
||||
"file": "$file",
|
||||
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"type": "pre-edit",
|
||||
"original_branch": "$current_branch"
|
||||
}
|
||||
EOF
|
||||
|
||||
# Restore working directory
|
||||
git stash pop --quiet >/dev/null 2>&1 || true
|
||||
|
||||
echo "✅ Created checkpoint: $checkpoint_branch for $file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to handle post-edit checkpoints
|
||||
post_edit_checkpoint() {
|
||||
local tool_input="$1"
|
||||
# Handle both JSON input and plain file path
|
||||
if echo "$tool_input" | jq -e . >/dev/null 2>&1; then
|
||||
local file=$(echo "$tool_input" | jq -r '.file_path // empty')
|
||||
else
|
||||
local file="$tool_input"
|
||||
fi
|
||||
|
||||
if [ -n "$file" ] && [ -f "$file" ]; then
|
||||
# Check if file was modified - first check if file is tracked
|
||||
if ! git ls-files --error-unmatch "$file" >/dev/null 2>&1; then
|
||||
# File is not tracked, add it first
|
||||
git add "$file"
|
||||
fi
|
||||
|
||||
# Now check if there are changes
|
||||
if git diff --cached --quiet "$file" 2>/dev/null && git diff --quiet "$file" 2>/dev/null; then
|
||||
echo "ℹ️ No changes to checkpoint for $file"
|
||||
else
|
||||
local tag_name="checkpoint-$(date +%Y%m%d-%H%M%S)"
|
||||
local current_branch=$(git branch --show-current)
|
||||
|
||||
# Create commit
|
||||
git add "$file"
|
||||
if git commit -m "🔖 Checkpoint: Edit $file
|
||||
|
||||
Automatic checkpoint created by Claude
|
||||
- File: $file
|
||||
- Branch: $current_branch
|
||||
- Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
[Auto-checkpoint]" --quiet; then
|
||||
# Create tag only if commit succeeded
|
||||
git tag -a "$tag_name" -m "Checkpoint after editing $file"
|
||||
|
||||
# Store metadata
|
||||
mkdir -p .claude/checkpoints
|
||||
local diff_stats=$(git diff HEAD~1 --stat | tr '\n' ' ' | sed 's/"/\"/g')
|
||||
cat > ".claude/checkpoints/$(date +%s).json" <<EOF
|
||||
{
|
||||
"tag": "$tag_name",
|
||||
"file": "$file",
|
||||
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"type": "post-edit",
|
||||
"branch": "$current_branch",
|
||||
"diff_summary": "$diff_stats"
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "✅ Created checkpoint: $tag_name for $file"
|
||||
else
|
||||
echo "ℹ️ No commit created (no changes or commit failed)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to handle task checkpoints
|
||||
task_checkpoint() {
|
||||
local user_prompt="$1"
|
||||
local task=$(echo "$user_prompt" | head -c 100 | tr '\n' ' ')
|
||||
|
||||
if [ -n "$task" ]; then
|
||||
local checkpoint_name="task-$(date +%Y%m%d-%H%M%S)"
|
||||
|
||||
# Commit current state
|
||||
git add -A
|
||||
git commit -m "🔖 Task checkpoint: $task..." --quiet || true
|
||||
|
||||
# Store metadata
|
||||
mkdir -p .claude/checkpoints
|
||||
cat > ".claude/checkpoints/task-$(date +%s).json" <<EOF
|
||||
{
|
||||
"checkpoint": "$checkpoint_name",
|
||||
"task": "$task",
|
||||
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||
"commit": "$(git rev-parse HEAD)"
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "✅ Created task checkpoint: $checkpoint_name"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to handle session end
|
||||
session_end_checkpoint() {
|
||||
local session_id="session-$(date +%Y%m%d-%H%M%S)"
|
||||
local summary_file=".claude/checkpoints/summary-$session_id.md"
|
||||
|
||||
mkdir -p .claude/checkpoints
|
||||
|
||||
# Create summary
|
||||
cat > "$summary_file" <<EOF
|
||||
# Session Summary - $(date +'%Y-%m-%d %H:%M:%S')
|
||||
|
||||
## Checkpoints Created
|
||||
$(find .claude/checkpoints -name '*.json' -mtime -1 -exec basename {} \; | sort)
|
||||
|
||||
## Files Modified
|
||||
$(git diff --name-only $(git log --format=%H -n 1 --before="1 hour ago" 2>/dev/null) 2>/dev/null || echo "No files tracked")
|
||||
|
||||
## Recent Commits
|
||||
$(git log --oneline -10 --grep="Checkpoint" || echo "No checkpoint commits")
|
||||
|
||||
## Rollback Instructions
|
||||
To rollback to a specific checkpoint:
|
||||
\`\`\`bash
|
||||
# List all checkpoints
|
||||
git tag -l 'checkpoint-*' | sort -r
|
||||
|
||||
# Rollback to a checkpoint
|
||||
git checkout checkpoint-YYYYMMDD-HHMMSS
|
||||
|
||||
# Or reset to a checkpoint (destructive)
|
||||
git reset --hard checkpoint-YYYYMMDD-HHMMSS
|
||||
\`\`\`
|
||||
EOF
|
||||
|
||||
# Create final checkpoint
|
||||
git add -A
|
||||
git commit -m "🏁 Session end checkpoint: $session_id" --quiet || true
|
||||
git tag -a "session-end-$session_id" -m "End of Claude session"
|
||||
|
||||
echo "✅ Session summary saved to: $summary_file"
|
||||
echo "📌 Final checkpoint: session-end-$session_id"
|
||||
}
|
||||
|
||||
# Main entry point
|
||||
case "$1" in
|
||||
pre-edit)
|
||||
pre_edit_checkpoint "$2"
|
||||
;;
|
||||
post-edit)
|
||||
post_edit_checkpoint "$2"
|
||||
;;
|
||||
task)
|
||||
task_checkpoint "$2"
|
||||
;;
|
||||
session-end)
|
||||
session_end_checkpoint
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {pre-edit|post-edit|task|session-end} [input]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
21
.claude/helpers/statusline-hook.sh
Executable file
21
.claude/helpers/statusline-hook.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
# Claude Flow V3 Statusline Hook
|
||||
# Add to your shell RC file (.bashrc, .zshrc, etc.)
|
||||
|
||||
# Function to get statusline
|
||||
claude_flow_statusline() {
|
||||
local statusline_script="${CLAUDE_FLOW_DIR:-.claude}/helpers/statusline.cjs"
|
||||
if [ -f "$statusline_script" ]; then
|
||||
node "$statusline_script" 2>/dev/null || echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# For bash PS1
|
||||
# export PS1='$(claude_flow_statusline) \n\$ '
|
||||
|
||||
# For zsh RPROMPT
|
||||
# export RPROMPT='$(claude_flow_statusline)'
|
||||
|
||||
# For starship (add to starship.toml)
|
||||
# [custom.claude_flow]
|
||||
# command = "node .claude/helpers/statusline.cjs 2>/dev/null"
|
||||
# when = "test -f .claude/helpers/statusline.cjs"
|
||||
577
.claude/helpers/statusline.cjs
Normal file
577
.claude/helpers/statusline.cjs
Normal file
@@ -0,0 +1,577 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Claude Flow V3 Statusline Generator
|
||||
* Displays real-time V3 implementation progress and system status
|
||||
*
|
||||
* Usage: node statusline.cjs [options]
|
||||
*
|
||||
* Options:
|
||||
* (default) Safe multi-line output with collision zone avoidance
|
||||
* --single Single-line output (completely avoids collision)
|
||||
* --unsafe Legacy multi-line without collision avoidance
|
||||
* --legacy Alias for --unsafe
|
||||
* --json JSON output with pretty printing
|
||||
* --compact JSON output without formatting
|
||||
*
|
||||
* Collision Zone Fix (Issue #985):
|
||||
* Claude Code writes its internal status (e.g., "7s • 1p") at absolute
|
||||
* terminal coordinates (columns 15-25 on second-to-last line). The safe
|
||||
* mode pads the collision line with spaces to push content past column 25.
|
||||
*
|
||||
* IMPORTANT: This file uses .cjs extension to work in ES module projects.
|
||||
* The require() syntax is intentional for CommonJS compatibility.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
// Configuration
|
||||
const CONFIG = {
|
||||
enabled: true,
|
||||
showProgress: true,
|
||||
showSecurity: true,
|
||||
showSwarm: true,
|
||||
showHooks: true,
|
||||
showPerformance: true,
|
||||
refreshInterval: 5000,
|
||||
maxAgents: 15,
|
||||
topology: 'hierarchical-mesh',
|
||||
};
|
||||
|
||||
// Cross-platform helpers
|
||||
const isWindows = process.platform === 'win32';
|
||||
const nullDevice = isWindows ? 'NUL' : '/dev/null';
|
||||
|
||||
// ANSI colors
|
||||
const c = {
|
||||
reset: '\x1b[0m',
|
||||
bold: '\x1b[1m',
|
||||
dim: '\x1b[2m',
|
||||
red: '\x1b[0;31m',
|
||||
green: '\x1b[0;32m',
|
||||
yellow: '\x1b[0;33m',
|
||||
blue: '\x1b[0;34m',
|
||||
purple: '\x1b[0;35m',
|
||||
cyan: '\x1b[0;36m',
|
||||
brightRed: '\x1b[1;31m',
|
||||
brightGreen: '\x1b[1;32m',
|
||||
brightYellow: '\x1b[1;33m',
|
||||
brightBlue: '\x1b[1;34m',
|
||||
brightPurple: '\x1b[1;35m',
|
||||
brightCyan: '\x1b[1;36m',
|
||||
brightWhite: '\x1b[1;37m',
|
||||
};
|
||||
|
||||
// Get user info
|
||||
function getUserInfo() {
|
||||
let name = 'user';
|
||||
let gitBranch = '';
|
||||
let modelName = 'Unknown';
|
||||
|
||||
try {
|
||||
const gitUserCmd = isWindows
|
||||
? 'git config user.name 2>NUL || echo user'
|
||||
: 'git config user.name 2>/dev/null || echo "user"';
|
||||
const gitBranchCmd = isWindows
|
||||
? 'git branch --show-current 2>NUL || echo.'
|
||||
: 'git branch --show-current 2>/dev/null || echo ""';
|
||||
name = execSync(gitUserCmd, { encoding: 'utf-8' }).trim();
|
||||
gitBranch = execSync(gitBranchCmd, { encoding: 'utf-8' }).trim();
|
||||
if (gitBranch === '.') gitBranch = ''; // Windows echo. outputs a dot
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
|
||||
// Auto-detect model from Claude Code's config
|
||||
try {
|
||||
const homedir = require('os').homedir();
|
||||
const claudeConfigPath = path.join(homedir, '.claude.json');
|
||||
if (fs.existsSync(claudeConfigPath)) {
|
||||
const claudeConfig = JSON.parse(fs.readFileSync(claudeConfigPath, 'utf-8'));
|
||||
// Try to find lastModelUsage - check current dir and parent dirs
|
||||
let lastModelUsage = null;
|
||||
const cwd = process.cwd();
|
||||
if (claudeConfig.projects) {
|
||||
// Try exact match first, then check if cwd starts with any project path
|
||||
for (const [projectPath, projectConfig] of Object.entries(claudeConfig.projects)) {
|
||||
if (cwd === projectPath || cwd.startsWith(projectPath + '/')) {
|
||||
lastModelUsage = projectConfig.lastModelUsage;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastModelUsage) {
|
||||
const modelIds = Object.keys(lastModelUsage);
|
||||
if (modelIds.length > 0) {
|
||||
// Take the last model (most recently added to the object)
|
||||
// Or find the one with most tokens (most actively used this session)
|
||||
let modelId = modelIds[modelIds.length - 1];
|
||||
if (modelIds.length > 1) {
|
||||
// If multiple models, pick the one with most total tokens
|
||||
let maxTokens = 0;
|
||||
for (const id of modelIds) {
|
||||
const usage = lastModelUsage[id];
|
||||
const total = (usage.inputTokens || 0) + (usage.outputTokens || 0);
|
||||
if (total > maxTokens) {
|
||||
maxTokens = total;
|
||||
modelId = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Parse model ID to human-readable name
|
||||
if (modelId.includes('opus')) modelName = 'Opus 4.5';
|
||||
else if (modelId.includes('sonnet')) modelName = 'Sonnet 4';
|
||||
else if (modelId.includes('haiku')) modelName = 'Haiku 4.5';
|
||||
else modelName = modelId.split('-').slice(1, 3).join(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback to Unknown if can't read config
|
||||
}
|
||||
|
||||
return { name, gitBranch, modelName };
|
||||
}
|
||||
|
||||
// Get learning stats from intelligence loop data (ADR-050)
|
||||
function getLearningStats() {
|
||||
let patterns = 0;
|
||||
let sessions = 0;
|
||||
let trajectories = 0;
|
||||
let edges = 0;
|
||||
let confidenceMean = 0;
|
||||
let accessedCount = 0;
|
||||
let trend = 'STABLE';
|
||||
|
||||
// PRIMARY: Read from intelligence loop data files
|
||||
const dataDir = path.join(process.cwd(), '.claude-flow', 'data');
|
||||
|
||||
// 1. graph-state.json — authoritative node/edge counts
|
||||
const graphPath = path.join(dataDir, 'graph-state.json');
|
||||
if (fs.existsSync(graphPath)) {
|
||||
try {
|
||||
const graph = JSON.parse(fs.readFileSync(graphPath, 'utf-8'));
|
||||
patterns = graph.nodes ? Object.keys(graph.nodes).length : 0;
|
||||
edges = Array.isArray(graph.edges) ? graph.edges.length : 0;
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
// 2. ranked-context.json — confidence and access data
|
||||
const rankedPath = path.join(dataDir, 'ranked-context.json');
|
||||
if (fs.existsSync(rankedPath)) {
|
||||
try {
|
||||
const ranked = JSON.parse(fs.readFileSync(rankedPath, 'utf-8'));
|
||||
if (ranked.entries && ranked.entries.length > 0) {
|
||||
patterns = Math.max(patterns, ranked.entries.length);
|
||||
let confSum = 0;
|
||||
let accCount = 0;
|
||||
for (let i = 0; i < ranked.entries.length; i++) {
|
||||
confSum += (ranked.entries[i].confidence || 0);
|
||||
if ((ranked.entries[i].accessCount || 0) > 0) accCount++;
|
||||
}
|
||||
confidenceMean = confSum / ranked.entries.length;
|
||||
accessedCount = accCount;
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
// 3. intelligence-snapshot.json — trend history
|
||||
const snapshotPath = path.join(dataDir, 'intelligence-snapshot.json');
|
||||
if (fs.existsSync(snapshotPath)) {
|
||||
try {
|
||||
const snapshot = JSON.parse(fs.readFileSync(snapshotPath, 'utf-8'));
|
||||
if (snapshot.history && snapshot.history.length >= 2) {
|
||||
const first = snapshot.history[0];
|
||||
const last = snapshot.history[snapshot.history.length - 1];
|
||||
const confDrift = (last.confidenceMean || 0) - (first.confidenceMean || 0);
|
||||
trend = confDrift > 0.01 ? 'IMPROVING' : confDrift < -0.01 ? 'DECLINING' : 'STABLE';
|
||||
sessions = Math.max(sessions, snapshot.history.length);
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
// 4. auto-memory-store.json — fallback entry count
|
||||
if (patterns === 0) {
|
||||
const autoMemPath = path.join(dataDir, 'auto-memory-store.json');
|
||||
if (fs.existsSync(autoMemPath)) {
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(autoMemPath, 'utf-8'));
|
||||
patterns = Array.isArray(data) ? data.length : (data.entries ? data.entries.length : 0);
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
// FALLBACK: Legacy memory.db file-size estimation
|
||||
if (patterns === 0) {
|
||||
const memoryPaths = [
|
||||
path.join(process.cwd(), '.swarm', 'memory.db'),
|
||||
path.join(process.cwd(), '.claude', 'memory.db'),
|
||||
path.join(process.cwd(), 'data', 'memory.db'),
|
||||
];
|
||||
for (let j = 0; j < memoryPaths.length; j++) {
|
||||
if (fs.existsSync(memoryPaths[j])) {
|
||||
try {
|
||||
const dbStats = fs.statSync(memoryPaths[j]);
|
||||
patterns = Math.floor(dbStats.size / 1024 / 2);
|
||||
break;
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Session count from session files
|
||||
const sessionsPath = path.join(process.cwd(), '.claude', 'sessions');
|
||||
if (fs.existsSync(sessionsPath)) {
|
||||
try {
|
||||
const sessionFiles = fs.readdirSync(sessionsPath).filter(f => f.endsWith('.json'));
|
||||
sessions = Math.max(sessions, sessionFiles.length);
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
trajectories = Math.floor(patterns / 5);
|
||||
|
||||
return { patterns, sessions, trajectories, edges, confidenceMean, accessedCount, trend };
|
||||
}
|
||||
|
||||
// Get V3 progress from learning state (grows as system learns)
|
||||
function getV3Progress() {
|
||||
const learning = getLearningStats();
|
||||
|
||||
// DDD progress based on actual learned patterns
|
||||
// New install: 0 patterns = 0/5 domains, 0% DDD
|
||||
// As patterns grow: 10+ patterns = 1 domain, 50+ = 2, 100+ = 3, 200+ = 4, 500+ = 5
|
||||
let domainsCompleted = 0;
|
||||
if (learning.patterns >= 500) domainsCompleted = 5;
|
||||
else if (learning.patterns >= 200) domainsCompleted = 4;
|
||||
else if (learning.patterns >= 100) domainsCompleted = 3;
|
||||
else if (learning.patterns >= 50) domainsCompleted = 2;
|
||||
else if (learning.patterns >= 10) domainsCompleted = 1;
|
||||
|
||||
const totalDomains = 5;
|
||||
const dddProgress = Math.min(100, Math.floor((domainsCompleted / totalDomains) * 100));
|
||||
|
||||
return {
|
||||
domainsCompleted,
|
||||
totalDomains,
|
||||
dddProgress,
|
||||
patternsLearned: learning.patterns,
|
||||
sessionsCompleted: learning.sessions
|
||||
};
|
||||
}
|
||||
|
||||
// Get security status based on actual scans
|
||||
function getSecurityStatus() {
|
||||
// Check for security scan results in memory
|
||||
const scanResultsPath = path.join(process.cwd(), '.claude', 'security-scans');
|
||||
let cvesFixed = 0;
|
||||
const totalCves = 3;
|
||||
|
||||
if (fs.existsSync(scanResultsPath)) {
|
||||
try {
|
||||
const scans = fs.readdirSync(scanResultsPath).filter(f => f.endsWith('.json'));
|
||||
// Each successful scan file = 1 CVE addressed
|
||||
cvesFixed = Math.min(totalCves, scans.length);
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Also check .swarm/security for audit results
|
||||
const auditPath = path.join(process.cwd(), '.swarm', 'security');
|
||||
if (fs.existsSync(auditPath)) {
|
||||
try {
|
||||
const audits = fs.readdirSync(auditPath).filter(f => f.includes('audit'));
|
||||
cvesFixed = Math.min(totalCves, Math.max(cvesFixed, audits.length));
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
const status = cvesFixed >= totalCves ? 'CLEAN' : cvesFixed > 0 ? 'IN_PROGRESS' : 'PENDING';
|
||||
|
||||
return {
|
||||
status,
|
||||
cvesFixed,
|
||||
totalCves,
|
||||
};
|
||||
}
|
||||
|
||||
// Get swarm status
|
||||
function getSwarmStatus() {
|
||||
let activeAgents = 0;
|
||||
let coordinationActive = false;
|
||||
|
||||
try {
|
||||
if (isWindows) {
|
||||
// Windows: use tasklist and findstr
|
||||
const ps = execSync('tasklist 2>NUL | findstr /I "agentic-flow" 2>NUL | find /C /V "" 2>NUL || echo 0', { encoding: 'utf-8' });
|
||||
activeAgents = Math.max(0, parseInt(ps.trim()) || 0);
|
||||
} else {
|
||||
const ps = execSync('ps aux 2>/dev/null | grep -c agentic-flow || echo "0"', { encoding: 'utf-8' });
|
||||
activeAgents = Math.max(0, parseInt(ps.trim()) - 1);
|
||||
}
|
||||
coordinationActive = activeAgents > 0;
|
||||
} catch (e) {
|
||||
// Ignore errors - default to 0 agents
|
||||
}
|
||||
|
||||
return {
|
||||
activeAgents,
|
||||
maxAgents: CONFIG.maxAgents,
|
||||
coordinationActive,
|
||||
};
|
||||
}
|
||||
|
||||
// Get system metrics (dynamic based on actual state)
|
||||
function getSystemMetrics() {
|
||||
let memoryMB = 0;
|
||||
let subAgents = 0;
|
||||
|
||||
try {
|
||||
if (isWindows) {
|
||||
// Windows: use tasklist for memory info, fallback to process.memoryUsage
|
||||
// tasklist memory column is complex to parse, use Node.js API instead
|
||||
memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
|
||||
} else {
|
||||
const mem = execSync('ps aux | grep -E "(node|agentic|claude)" | grep -v grep | awk \'{sum += $6} END {print int(sum/1024)}\'', { encoding: 'utf-8' });
|
||||
memoryMB = parseInt(mem.trim()) || 0;
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback
|
||||
memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
|
||||
}
|
||||
|
||||
// Get learning stats for intelligence %
|
||||
const learning = getLearningStats();
|
||||
|
||||
// Intelligence % from REAL intelligence loop data (ADR-050)
|
||||
// Composite: 40% confidence mean + 30% access ratio + 30% pattern density
|
||||
let intelligencePct = 0;
|
||||
if (learning.confidenceMean > 0 || (learning.patterns > 0 && learning.accessedCount > 0)) {
|
||||
const confScore = Math.min(100, Math.floor(learning.confidenceMean * 100));
|
||||
const accessRatio = learning.patterns > 0 ? (learning.accessedCount / learning.patterns) : 0;
|
||||
const accessScore = Math.min(100, Math.floor(accessRatio * 100));
|
||||
const densityScore = Math.min(100, Math.floor(learning.patterns / 5));
|
||||
intelligencePct = Math.floor(confScore * 0.4 + accessScore * 0.3 + densityScore * 0.3);
|
||||
}
|
||||
// Fallback: legacy pattern count
|
||||
if (intelligencePct === 0 && learning.patterns > 0) {
|
||||
intelligencePct = Math.min(100, Math.floor(learning.patterns / 10));
|
||||
}
|
||||
|
||||
// Context % based on session history
|
||||
const contextPct = Math.min(100, Math.floor(learning.sessions * 5));
|
||||
|
||||
// Count active sub-agents from process list
|
||||
try {
|
||||
if (isWindows) {
|
||||
// Windows: use tasklist and findstr for agent counting
|
||||
const agents = execSync('tasklist 2>NUL | findstr /I "claude-flow" 2>NUL | find /C /V "" 2>NUL || echo 0', { encoding: 'utf-8' });
|
||||
subAgents = Math.max(0, parseInt(agents.trim()) || 0);
|
||||
} else {
|
||||
const agents = execSync('ps aux 2>/dev/null | grep -c "claude-flow.*agent" || echo "0"', { encoding: 'utf-8' });
|
||||
subAgents = Math.max(0, parseInt(agents.trim()) - 1);
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore - default to 0
|
||||
}
|
||||
|
||||
return {
|
||||
memoryMB,
|
||||
contextPct,
|
||||
intelligencePct,
|
||||
subAgents,
|
||||
};
|
||||
}
|
||||
|
||||
// Generate progress bar
|
||||
function progressBar(current, total) {
|
||||
const width = 5;
|
||||
const filled = Math.round((current / total) * width);
|
||||
const empty = width - filled;
|
||||
return '[' + '\u25CF'.repeat(filled) + '\u25CB'.repeat(empty) + ']';
|
||||
}
|
||||
|
||||
// Generate full statusline
|
||||
function generateStatusline() {
|
||||
const user = getUserInfo();
|
||||
const progress = getV3Progress();
|
||||
const security = getSecurityStatus();
|
||||
const swarm = getSwarmStatus();
|
||||
const system = getSystemMetrics();
|
||||
const lines = [];
|
||||
|
||||
// Header Line
|
||||
let header = `${c.bold}${c.brightPurple}▊ Claude Flow V3 ${c.reset}`;
|
||||
header += `${swarm.coordinationActive ? c.brightCyan : c.dim}● ${c.brightCyan}${user.name}${c.reset}`;
|
||||
if (user.gitBranch) {
|
||||
header += ` ${c.dim}│${c.reset} ${c.brightBlue}⎇ ${user.gitBranch}${c.reset}`;
|
||||
}
|
||||
header += ` ${c.dim}│${c.reset} ${c.purple}${user.modelName}${c.reset}`;
|
||||
lines.push(header);
|
||||
|
||||
// Separator
|
||||
lines.push(`${c.dim}─────────────────────────────────────────────────────${c.reset}`);
|
||||
|
||||
// Line 1: DDD Domain Progress
|
||||
const domainsColor = progress.domainsCompleted >= 3 ? c.brightGreen : progress.domainsCompleted > 0 ? c.yellow : c.red;
|
||||
lines.push(
|
||||
`${c.brightCyan}🏗️ DDD Domains${c.reset} ${progressBar(progress.domainsCompleted, progress.totalDomains)} ` +
|
||||
`${domainsColor}${progress.domainsCompleted}${c.reset}/${c.brightWhite}${progress.totalDomains}${c.reset} ` +
|
||||
`${c.brightYellow}⚡ 1.0x${c.reset} ${c.dim}→${c.reset} ${c.brightYellow}2.49x-7.47x${c.reset}`
|
||||
);
|
||||
|
||||
// Line 2: Swarm + CVE + Memory + Context + Intelligence
|
||||
const swarmIndicator = swarm.coordinationActive ? `${c.brightGreen}◉${c.reset}` : `${c.dim}○${c.reset}`;
|
||||
const agentsColor = swarm.activeAgents > 0 ? c.brightGreen : c.red;
|
||||
let securityIcon = security.status === 'CLEAN' ? '🟢' : security.status === 'IN_PROGRESS' ? '🟡' : '🔴';
|
||||
let securityColor = security.status === 'CLEAN' ? c.brightGreen : security.status === 'IN_PROGRESS' ? c.brightYellow : c.brightRed;
|
||||
|
||||
lines.push(
|
||||
`${c.brightYellow}🤖 Swarm${c.reset} ${swarmIndicator} [${agentsColor}${String(swarm.activeAgents).padStart(2)}${c.reset}/${c.brightWhite}${swarm.maxAgents}${c.reset}] ` +
|
||||
`${c.brightPurple}👥 ${system.subAgents}${c.reset} ` +
|
||||
`${securityIcon} ${securityColor}CVE ${security.cvesFixed}${c.reset}/${c.brightWhite}${security.totalCves}${c.reset} ` +
|
||||
`${c.brightCyan}💾 ${system.memoryMB}MB${c.reset} ` +
|
||||
`${c.brightGreen}📂 ${String(system.contextPct).padStart(3)}%${c.reset} ` +
|
||||
`${c.dim}🧠 ${String(system.intelligencePct).padStart(3)}%${c.reset}`
|
||||
);
|
||||
|
||||
// Line 3: Architecture status
|
||||
const dddColor = progress.dddProgress >= 50 ? c.brightGreen : progress.dddProgress > 0 ? c.yellow : c.red;
|
||||
lines.push(
|
||||
`${c.brightPurple}🔧 Architecture${c.reset} ` +
|
||||
`${c.cyan}DDD${c.reset} ${dddColor}●${String(progress.dddProgress).padStart(3)}%${c.reset} ${c.dim}│${c.reset} ` +
|
||||
`${c.cyan}Security${c.reset} ${securityColor}●${security.status}${c.reset} ${c.dim}│${c.reset} ` +
|
||||
`${c.cyan}Memory${c.reset} ${c.brightGreen}●AgentDB${c.reset} ${c.dim}│${c.reset} ` +
|
||||
`${c.cyan}Integration${c.reset} ${swarm.coordinationActive ? c.brightCyan : c.dim}●${c.reset}`
|
||||
);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// Generate JSON data
|
||||
function generateJSON() {
|
||||
return {
|
||||
user: getUserInfo(),
|
||||
v3Progress: getV3Progress(),
|
||||
security: getSecurityStatus(),
|
||||
swarm: getSwarmStatus(),
|
||||
system: getSystemMetrics(),
|
||||
performance: {
|
||||
flashAttentionTarget: '2.49x-7.47x',
|
||||
searchImprovement: '150x-12,500x',
|
||||
memoryReduction: '50-75%',
|
||||
},
|
||||
lastUpdated: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate single-line output for Claude Code compatibility
|
||||
* This avoids the collision zone issue entirely by using one line
|
||||
* @see https://github.com/ruvnet/claude-flow/issues/985
|
||||
*/
|
||||
function generateSingleLine() {
|
||||
if (!CONFIG.enabled) return '';
|
||||
|
||||
const user = getUserInfo();
|
||||
const progress = getV3Progress();
|
||||
const security = getSecurityStatus();
|
||||
const swarm = getSwarmStatus();
|
||||
const system = getSystemMetrics();
|
||||
|
||||
const swarmIndicator = swarm.coordinationActive ? '●' : '○';
|
||||
const securityStatus = security.status === 'CLEAN' ? '✓' :
|
||||
security.cvesFixed > 0 ? '~' : '✗';
|
||||
|
||||
return `${c.brightPurple}CF-V3${c.reset} ${c.dim}|${c.reset} ` +
|
||||
`${c.cyan}D:${progress.domainsCompleted}/${progress.totalDomains}${c.reset} ${c.dim}|${c.reset} ` +
|
||||
`${c.yellow}S:${swarmIndicator}${swarm.activeAgents}/${swarm.maxAgents}${c.reset} ${c.dim}|${c.reset} ` +
|
||||
`${security.status === 'CLEAN' ? c.green : c.red}CVE:${securityStatus}${security.cvesFixed}/${security.totalCves}${c.reset} ${c.dim}|${c.reset} ` +
|
||||
`${c.dim}🧠${system.intelligencePct}%${c.reset}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate safe multi-line statusline that avoids Claude Code collision zone
|
||||
* The collision zone is columns 15-25 on the second-to-last line.
|
||||
* We pad that line with spaces to push content past column 25.
|
||||
* @see https://github.com/ruvnet/claude-flow/issues/985
|
||||
*/
|
||||
function generateSafeStatusline() {
|
||||
if (!CONFIG.enabled) return '';
|
||||
|
||||
const user = getUserInfo();
|
||||
const progress = getV3Progress();
|
||||
const security = getSecurityStatus();
|
||||
const swarm = getSwarmStatus();
|
||||
const system = getSystemMetrics();
|
||||
const lines = [];
|
||||
|
||||
// Header Line
|
||||
let header = `${c.bold}${c.brightPurple}▊ Claude Flow V3 ${c.reset}`;
|
||||
header += `${swarm.coordinationActive ? c.brightCyan : c.dim}● ${c.brightCyan}${user.name}${c.reset}`;
|
||||
if (user.gitBranch) {
|
||||
header += ` ${c.dim}│${c.reset} ${c.brightBlue}⎇ ${user.gitBranch}${c.reset}`;
|
||||
}
|
||||
header += ` ${c.dim}│${c.reset} ${c.purple}${user.modelName}${c.reset}`;
|
||||
lines.push(header);
|
||||
|
||||
// Separator
|
||||
lines.push(`${c.dim}─────────────────────────────────────────────────────${c.reset}`);
|
||||
|
||||
// Line 1: DDD Domain Progress
|
||||
const domainsColor = progress.domainsCompleted >= 3 ? c.brightGreen : progress.domainsCompleted > 0 ? c.yellow : c.red;
|
||||
lines.push(
|
||||
`${c.brightCyan}🏗️ DDD Domains${c.reset} ${progressBar(progress.domainsCompleted, progress.totalDomains)} ` +
|
||||
`${domainsColor}${progress.domainsCompleted}${c.reset}/${c.brightWhite}${progress.totalDomains}${c.reset} ` +
|
||||
`${c.brightYellow}⚡ 1.0x${c.reset} ${c.dim}→${c.reset} ${c.brightYellow}2.49x-7.47x${c.reset}`
|
||||
);
|
||||
|
||||
// Line 2 (COLLISION LINE): Swarm status with 24 spaces padding after emoji
|
||||
// The emoji (🤖) is 2 columns. 24 spaces pushes content to column 26, past the collision zone (15-25)
|
||||
const swarmIndicator = swarm.coordinationActive ? `${c.brightGreen}◉${c.reset}` : `${c.dim}○${c.reset}`;
|
||||
const agentsColor = swarm.activeAgents > 0 ? c.brightGreen : c.red;
|
||||
let securityIcon = security.status === 'CLEAN' ? '🟢' : security.status === 'IN_PROGRESS' ? '🟡' : '🔴';
|
||||
let securityColor = security.status === 'CLEAN' ? c.brightGreen : security.status === 'IN_PROGRESS' ? c.brightYellow : c.brightRed;
|
||||
|
||||
// CRITICAL: 24 spaces after 🤖 (emoji is 2 cols, so 2+24=26, past collision zone cols 15-25)
|
||||
lines.push(
|
||||
`${c.brightYellow}🤖${c.reset} ` + // 24 spaces padding
|
||||
`${swarmIndicator} [${agentsColor}${String(swarm.activeAgents).padStart(2)}${c.reset}/${c.brightWhite}${swarm.maxAgents}${c.reset}] ` +
|
||||
`${c.brightPurple}👥 ${system.subAgents}${c.reset} ` +
|
||||
`${securityIcon} ${securityColor}CVE ${security.cvesFixed}${c.reset}/${c.brightWhite}${security.totalCves}${c.reset} ` +
|
||||
`${c.brightCyan}💾 ${system.memoryMB}MB${c.reset} ` +
|
||||
`${c.dim}🧠 ${system.intelligencePct}%${c.reset}`
|
||||
);
|
||||
|
||||
// Line 3: Architecture status (this is the last line, not in collision zone)
|
||||
const dddColor = progress.dddProgress >= 50 ? c.brightGreen : progress.dddProgress > 0 ? c.yellow : c.red;
|
||||
lines.push(
|
||||
`${c.brightPurple}🔧 Architecture${c.reset} ` +
|
||||
`${c.cyan}DDD${c.reset} ${dddColor}●${String(progress.dddProgress).padStart(3)}%${c.reset} ${c.dim}│${c.reset} ` +
|
||||
`${c.cyan}Security${c.reset} ${securityColor}●${security.status}${c.reset} ${c.dim}│${c.reset} ` +
|
||||
`${c.cyan}Memory${c.reset} ${c.brightGreen}●AgentDB${c.reset} ${c.dim}│${c.reset} ` +
|
||||
`${c.cyan}Integration${c.reset} ${swarm.coordinationActive ? c.brightCyan : c.dim}●${c.reset}`
|
||||
);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// Main
|
||||
if (process.argv.includes('--json')) {
|
||||
console.log(JSON.stringify(generateJSON(), null, 2));
|
||||
} else if (process.argv.includes('--compact')) {
|
||||
console.log(JSON.stringify(generateJSON()));
|
||||
} else if (process.argv.includes('--single')) {
|
||||
// Single-line mode - completely avoids collision zone
|
||||
console.log(generateSingleLine());
|
||||
} else if (process.argv.includes('--unsafe') || process.argv.includes('--legacy')) {
|
||||
// Legacy mode - original multi-line without collision avoidance
|
||||
console.log(generateStatusline());
|
||||
} else {
|
||||
// Default: Safe multi-line mode with collision zone avoidance
|
||||
// Use --unsafe or --legacy to get the original behavior
|
||||
console.log(generateSafeStatusline());
|
||||
}
|
||||
316
.claude/helpers/statusline.js
Normal file
316
.claude/helpers/statusline.js
Normal file
@@ -0,0 +1,316 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Claude Flow V3 Statusline Generator
|
||||
* Displays real-time V3 implementation progress and system status
|
||||
*
|
||||
* Usage: node statusline.js [--json] [--compact]
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
// Configuration
|
||||
const CONFIG = {
|
||||
enabled: true,
|
||||
showProgress: true,
|
||||
showSecurity: true,
|
||||
showSwarm: true,
|
||||
showHooks: true,
|
||||
showPerformance: true,
|
||||
refreshInterval: 30000,
|
||||
maxAgents: 15,
|
||||
topology: 'hierarchical-mesh',
|
||||
};
|
||||
|
||||
// ANSI colors
|
||||
const c = {
|
||||
reset: '\x1b[0m',
|
||||
bold: '\x1b[1m',
|
||||
dim: '\x1b[2m',
|
||||
red: '\x1b[0;31m',
|
||||
green: '\x1b[0;32m',
|
||||
yellow: '\x1b[0;33m',
|
||||
blue: '\x1b[0;34m',
|
||||
purple: '\x1b[0;35m',
|
||||
cyan: '\x1b[0;36m',
|
||||
brightRed: '\x1b[1;31m',
|
||||
brightGreen: '\x1b[1;32m',
|
||||
brightYellow: '\x1b[1;33m',
|
||||
brightBlue: '\x1b[1;34m',
|
||||
brightPurple: '\x1b[1;35m',
|
||||
brightCyan: '\x1b[1;36m',
|
||||
brightWhite: '\x1b[1;37m',
|
||||
};
|
||||
|
||||
// Get user info
|
||||
function getUserInfo() {
|
||||
let name = 'user';
|
||||
let gitBranch = '';
|
||||
let modelName = 'Opus 4.5';
|
||||
|
||||
try {
|
||||
name = execSync('git config user.name 2>/dev/null || echo "user"', { encoding: 'utf-8' }).trim();
|
||||
gitBranch = execSync('git branch --show-current 2>/dev/null || echo ""', { encoding: 'utf-8' }).trim();
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
|
||||
return { name, gitBranch, modelName };
|
||||
}
|
||||
|
||||
// Get learning stats from memory database
|
||||
function getLearningStats() {
|
||||
const memoryPaths = [
|
||||
path.join(process.cwd(), '.swarm', 'memory.db'),
|
||||
path.join(process.cwd(), '.claude', 'memory.db'),
|
||||
path.join(process.cwd(), 'data', 'memory.db'),
|
||||
];
|
||||
|
||||
let patterns = 0;
|
||||
let sessions = 0;
|
||||
let trajectories = 0;
|
||||
|
||||
// Try to read from sqlite database
|
||||
for (const dbPath of memoryPaths) {
|
||||
if (fs.existsSync(dbPath)) {
|
||||
try {
|
||||
// Count entries in memory file (rough estimate from file size)
|
||||
const stats = fs.statSync(dbPath);
|
||||
const sizeKB = stats.size / 1024;
|
||||
// Estimate: ~2KB per pattern on average
|
||||
patterns = Math.floor(sizeKB / 2);
|
||||
sessions = Math.max(1, Math.floor(patterns / 10));
|
||||
trajectories = Math.floor(patterns / 5);
|
||||
break;
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also check for session files
|
||||
const sessionsPath = path.join(process.cwd(), '.claude', 'sessions');
|
||||
if (fs.existsSync(sessionsPath)) {
|
||||
try {
|
||||
const sessionFiles = fs.readdirSync(sessionsPath).filter(f => f.endsWith('.json'));
|
||||
sessions = Math.max(sessions, sessionFiles.length);
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
return { patterns, sessions, trajectories };
|
||||
}
|
||||
|
||||
// Get V3 progress from learning state (grows as system learns)
|
||||
function getV3Progress() {
|
||||
const learning = getLearningStats();
|
||||
|
||||
// DDD progress based on actual learned patterns
|
||||
// New install: 0 patterns = 0/5 domains, 0% DDD
|
||||
// As patterns grow: 10+ patterns = 1 domain, 50+ = 2, 100+ = 3, 200+ = 4, 500+ = 5
|
||||
let domainsCompleted = 0;
|
||||
if (learning.patterns >= 500) domainsCompleted = 5;
|
||||
else if (learning.patterns >= 200) domainsCompleted = 4;
|
||||
else if (learning.patterns >= 100) domainsCompleted = 3;
|
||||
else if (learning.patterns >= 50) domainsCompleted = 2;
|
||||
else if (learning.patterns >= 10) domainsCompleted = 1;
|
||||
|
||||
const totalDomains = 5;
|
||||
const dddProgress = Math.min(100, Math.floor((domainsCompleted / totalDomains) * 100));
|
||||
|
||||
return {
|
||||
domainsCompleted,
|
||||
totalDomains,
|
||||
dddProgress,
|
||||
patternsLearned: learning.patterns,
|
||||
sessionsCompleted: learning.sessions
|
||||
};
|
||||
}
|
||||
|
||||
// Get security status based on actual scans
|
||||
function getSecurityStatus() {
|
||||
// Check for security scan results in memory
|
||||
const scanResultsPath = path.join(process.cwd(), '.claude', 'security-scans');
|
||||
let cvesFixed = 0;
|
||||
const totalCves = 3;
|
||||
|
||||
if (fs.existsSync(scanResultsPath)) {
|
||||
try {
|
||||
const scans = fs.readdirSync(scanResultsPath).filter(f => f.endsWith('.json'));
|
||||
// Each successful scan file = 1 CVE addressed
|
||||
cvesFixed = Math.min(totalCves, scans.length);
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Also check .swarm/security for audit results
|
||||
const auditPath = path.join(process.cwd(), '.swarm', 'security');
|
||||
if (fs.existsSync(auditPath)) {
|
||||
try {
|
||||
const audits = fs.readdirSync(auditPath).filter(f => f.includes('audit'));
|
||||
cvesFixed = Math.min(totalCves, Math.max(cvesFixed, audits.length));
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
const status = cvesFixed >= totalCves ? 'CLEAN' : cvesFixed > 0 ? 'IN_PROGRESS' : 'PENDING';
|
||||
|
||||
return {
|
||||
status,
|
||||
cvesFixed,
|
||||
totalCves,
|
||||
};
|
||||
}
|
||||
|
||||
// Get swarm status
|
||||
function getSwarmStatus() {
|
||||
let activeAgents = 0;
|
||||
let coordinationActive = false;
|
||||
|
||||
try {
|
||||
const ps = execSync('ps aux 2>/dev/null | grep -c agentic-flow || echo "0"', { encoding: 'utf-8' });
|
||||
activeAgents = Math.max(0, parseInt(ps.trim()) - 1);
|
||||
coordinationActive = activeAgents > 0;
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
|
||||
return {
|
||||
activeAgents,
|
||||
maxAgents: CONFIG.maxAgents,
|
||||
coordinationActive,
|
||||
};
|
||||
}
|
||||
|
||||
// Get system metrics (dynamic based on actual state)
|
||||
function getSystemMetrics() {
|
||||
let memoryMB = 0;
|
||||
let subAgents = 0;
|
||||
|
||||
try {
|
||||
const mem = execSync('ps aux | grep -E "(node|agentic|claude)" | grep -v grep | awk \'{sum += \$6} END {print int(sum/1024)}\'', { encoding: 'utf-8' });
|
||||
memoryMB = parseInt(mem.trim()) || 0;
|
||||
} catch (e) {
|
||||
// Fallback
|
||||
memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
|
||||
}
|
||||
|
||||
// Get learning stats for intelligence %
|
||||
const learning = getLearningStats();
|
||||
|
||||
// Intelligence % based on learned patterns (0 patterns = 0%, 1000+ = 100%)
|
||||
const intelligencePct = Math.min(100, Math.floor((learning.patterns / 10) * 1));
|
||||
|
||||
// Context % based on session history (0 sessions = 0%, grows with usage)
|
||||
const contextPct = Math.min(100, Math.floor(learning.sessions * 5));
|
||||
|
||||
// Count active sub-agents from process list
|
||||
try {
|
||||
const agents = execSync('ps aux 2>/dev/null | grep -c "claude-flow.*agent" || echo "0"', { encoding: 'utf-8' });
|
||||
subAgents = Math.max(0, parseInt(agents.trim()) - 1);
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
return {
|
||||
memoryMB,
|
||||
contextPct,
|
||||
intelligencePct,
|
||||
subAgents,
|
||||
};
|
||||
}
|
||||
|
||||
// Generate progress bar
|
||||
function progressBar(current, total) {
|
||||
const width = 5;
|
||||
const filled = Math.round((current / total) * width);
|
||||
const empty = width - filled;
|
||||
return '[' + '\u25CF'.repeat(filled) + '\u25CB'.repeat(empty) + ']';
|
||||
}
|
||||
|
||||
// Generate full statusline
|
||||
function generateStatusline() {
|
||||
const user = getUserInfo();
|
||||
const progress = getV3Progress();
|
||||
const security = getSecurityStatus();
|
||||
const swarm = getSwarmStatus();
|
||||
const system = getSystemMetrics();
|
||||
const lines = [];
|
||||
|
||||
// Header Line
|
||||
let header = `${c.bold}${c.brightPurple}▊ Claude Flow V3 ${c.reset}`;
|
||||
header += `${swarm.coordinationActive ? c.brightCyan : c.dim}● ${c.brightCyan}${user.name}${c.reset}`;
|
||||
if (user.gitBranch) {
|
||||
header += ` ${c.dim}│${c.reset} ${c.brightBlue}⎇ ${user.gitBranch}${c.reset}`;
|
||||
}
|
||||
header += ` ${c.dim}│${c.reset} ${c.purple}${user.modelName}${c.reset}`;
|
||||
lines.push(header);
|
||||
|
||||
// Separator
|
||||
lines.push(`${c.dim}─────────────────────────────────────────────────────${c.reset}`);
|
||||
|
||||
// Line 1: DDD Domain Progress
|
||||
const domainsColor = progress.domainsCompleted >= 3 ? c.brightGreen : progress.domainsCompleted > 0 ? c.yellow : c.red;
|
||||
lines.push(
|
||||
`${c.brightCyan}🏗️ DDD Domains${c.reset} ${progressBar(progress.domainsCompleted, progress.totalDomains)} ` +
|
||||
`${domainsColor}${progress.domainsCompleted}${c.reset}/${c.brightWhite}${progress.totalDomains}${c.reset} ` +
|
||||
`${c.brightYellow}⚡ 1.0x${c.reset} ${c.dim}→${c.reset} ${c.brightYellow}2.49x-7.47x${c.reset}`
|
||||
);
|
||||
|
||||
// Line 2: Swarm + CVE + Memory + Context + Intelligence
|
||||
const swarmIndicator = swarm.coordinationActive ? `${c.brightGreen}◉${c.reset}` : `${c.dim}○${c.reset}`;
|
||||
const agentsColor = swarm.activeAgents > 0 ? c.brightGreen : c.red;
|
||||
let securityIcon = security.status === 'CLEAN' ? '🟢' : security.status === 'IN_PROGRESS' ? '🟡' : '🔴';
|
||||
let securityColor = security.status === 'CLEAN' ? c.brightGreen : security.status === 'IN_PROGRESS' ? c.brightYellow : c.brightRed;
|
||||
|
||||
lines.push(
|
||||
`${c.brightYellow}🤖 Swarm${c.reset} ${swarmIndicator} [${agentsColor}${String(swarm.activeAgents).padStart(2)}${c.reset}/${c.brightWhite}${swarm.maxAgents}${c.reset}] ` +
|
||||
`${c.brightPurple}👥 ${system.subAgents}${c.reset} ` +
|
||||
`${securityIcon} ${securityColor}CVE ${security.cvesFixed}${c.reset}/${c.brightWhite}${security.totalCves}${c.reset} ` +
|
||||
`${c.brightCyan}💾 ${system.memoryMB}MB${c.reset} ` +
|
||||
`${c.brightGreen}📂 ${String(system.contextPct).padStart(3)}%${c.reset} ` +
|
||||
`${c.dim}🧠 ${String(system.intelligencePct).padStart(3)}%${c.reset}`
|
||||
);
|
||||
|
||||
// Line 3: Architecture status
|
||||
const dddColor = progress.dddProgress >= 50 ? c.brightGreen : progress.dddProgress > 0 ? c.yellow : c.red;
|
||||
lines.push(
|
||||
`${c.brightPurple}🔧 Architecture${c.reset} ` +
|
||||
`${c.cyan}DDD${c.reset} ${dddColor}●${String(progress.dddProgress).padStart(3)}%${c.reset} ${c.dim}│${c.reset} ` +
|
||||
`${c.cyan}Security${c.reset} ${securityColor}●${security.status}${c.reset} ${c.dim}│${c.reset} ` +
|
||||
`${c.cyan}Memory${c.reset} ${c.brightGreen}●AgentDB${c.reset} ${c.dim}│${c.reset} ` +
|
||||
`${c.cyan}Integration${c.reset} ${swarm.coordinationActive ? c.brightCyan : c.dim}●${c.reset}`
|
||||
);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// Generate JSON data
|
||||
function generateJSON() {
|
||||
return {
|
||||
user: getUserInfo(),
|
||||
v3Progress: getV3Progress(),
|
||||
security: getSecurityStatus(),
|
||||
swarm: getSwarmStatus(),
|
||||
system: getSystemMetrics(),
|
||||
performance: {
|
||||
flashAttentionTarget: '2.49x-7.47x',
|
||||
searchImprovement: '150x-12,500x',
|
||||
memoryReduction: '50-75%',
|
||||
},
|
||||
lastUpdated: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
// Main
|
||||
if (process.argv.includes('--json')) {
|
||||
console.log(JSON.stringify(generateJSON(), null, 2));
|
||||
} else if (process.argv.includes('--compact')) {
|
||||
console.log(JSON.stringify(generateJSON()));
|
||||
} else {
|
||||
console.log(generateStatusline());
|
||||
}
|
||||
353
.claude/helpers/swarm-comms.sh
Executable file
353
.claude/helpers/swarm-comms.sh
Executable file
@@ -0,0 +1,353 @@
|
||||
#!/bin/bash
|
||||
# Claude Flow V3 - Optimized Swarm Communications
|
||||
# Non-blocking, batched, priority-based inter-agent messaging
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
SWARM_DIR="$PROJECT_ROOT/.claude-flow/swarm"
|
||||
QUEUE_DIR="$SWARM_DIR/queue"
|
||||
BATCH_DIR="$SWARM_DIR/batch"
|
||||
POOL_FILE="$SWARM_DIR/connection-pool.json"
|
||||
|
||||
mkdir -p "$QUEUE_DIR" "$BATCH_DIR"
|
||||
|
||||
# Priority levels
|
||||
PRIORITY_CRITICAL=0
|
||||
PRIORITY_HIGH=1
|
||||
PRIORITY_NORMAL=2
|
||||
PRIORITY_LOW=3
|
||||
|
||||
# Batch settings
|
||||
BATCH_SIZE=10
|
||||
BATCH_TIMEOUT_MS=100
|
||||
|
||||
# =============================================================================
|
||||
# NON-BLOCKING MESSAGE QUEUE
|
||||
# =============================================================================
|
||||
|
||||
# Enqueue message (instant return, async processing)
|
||||
enqueue() {
|
||||
local to="${1:-*}"
|
||||
local content="${2:-}"
|
||||
local priority="${3:-$PRIORITY_NORMAL}"
|
||||
local msg_type="${4:-context}"
|
||||
|
||||
local msg_id="msg_$(date +%s%N)"
|
||||
local timestamp=$(date +%s)
|
||||
|
||||
# Write to priority queue (non-blocking)
|
||||
cat > "$QUEUE_DIR/${priority}_${msg_id}.json" << EOF
|
||||
{"id":"$msg_id","to":"$to","content":"$content","type":"$msg_type","priority":$priority,"timestamp":$timestamp}
|
||||
EOF
|
||||
|
||||
echo "$msg_id"
|
||||
}
|
||||
|
||||
# Process queue in background
|
||||
process_queue() {
|
||||
local processed=0
|
||||
|
||||
# Process by priority (0=critical first)
|
||||
for priority in 0 1 2 3; do
|
||||
shopt -s nullglob
|
||||
for msg_file in "$QUEUE_DIR"/${priority}_*.json; do
|
||||
[ -f "$msg_file" ] || continue
|
||||
|
||||
# Process message
|
||||
local msg=$(cat "$msg_file")
|
||||
local to=$(echo "$msg" | jq -r '.to' 2>/dev/null)
|
||||
|
||||
# Route to agent mailbox
|
||||
if [ "$to" != "*" ]; then
|
||||
mkdir -p "$SWARM_DIR/mailbox/$to"
|
||||
mv "$msg_file" "$SWARM_DIR/mailbox/$to/"
|
||||
else
|
||||
# Broadcast - copy to all agent mailboxes
|
||||
for agent_dir in "$SWARM_DIR/mailbox"/*; do
|
||||
[ -d "$agent_dir" ] && cp "$msg_file" "$agent_dir/"
|
||||
done
|
||||
rm "$msg_file"
|
||||
fi
|
||||
|
||||
processed=$((processed + 1))
|
||||
done
|
||||
done
|
||||
|
||||
echo "$processed"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# MESSAGE BATCHING
|
||||
# =============================================================================
|
||||
|
||||
# Add to batch (collects messages, flushes when full or timeout)
|
||||
batch_add() {
|
||||
local agent_id="${1:-}"
|
||||
local content="${2:-}"
|
||||
local batch_file="$BATCH_DIR/${agent_id}.batch"
|
||||
|
||||
# Append to batch
|
||||
echo "$content" >> "$batch_file"
|
||||
|
||||
# Check batch size
|
||||
local count=$(wc -l < "$batch_file" 2>/dev/null || echo "0")
|
||||
|
||||
if [ "$count" -ge "$BATCH_SIZE" ]; then
|
||||
batch_flush "$agent_id"
|
||||
fi
|
||||
}
|
||||
|
||||
# Flush batch (send all at once)
|
||||
batch_flush() {
|
||||
local agent_id="${1:-}"
|
||||
local batch_file="$BATCH_DIR/${agent_id}.batch"
|
||||
|
||||
if [ -f "$batch_file" ]; then
|
||||
local content=$(cat "$batch_file")
|
||||
rm "$batch_file"
|
||||
|
||||
# Send as single batched message
|
||||
enqueue "$agent_id" "$content" "$PRIORITY_NORMAL" "batch"
|
||||
fi
|
||||
}
|
||||
|
||||
# Flush all pending batches
|
||||
batch_flush_all() {
|
||||
shopt -s nullglob
|
||||
for batch_file in "$BATCH_DIR"/*.batch; do
|
||||
[ -f "$batch_file" ] || continue
|
||||
local agent_id=$(basename "$batch_file" .batch)
|
||||
batch_flush "$agent_id"
|
||||
done
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# CONNECTION POOLING
|
||||
# =============================================================================
|
||||
|
||||
# Initialize connection pool
|
||||
pool_init() {
|
||||
cat > "$POOL_FILE" << EOF
|
||||
{
|
||||
"maxConnections": 10,
|
||||
"activeConnections": 0,
|
||||
"available": [],
|
||||
"inUse": [],
|
||||
"lastUpdated": "$(date -Iseconds)"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
# Get connection from pool (or create new)
|
||||
pool_acquire() {
|
||||
local agent_id="${1:-}"
|
||||
|
||||
if [ ! -f "$POOL_FILE" ]; then
|
||||
pool_init
|
||||
fi
|
||||
|
||||
# Check for available connection
|
||||
local available=$(jq -r '.available[0] // ""' "$POOL_FILE" 2>/dev/null)
|
||||
|
||||
if [ -n "$available" ]; then
|
||||
# Reuse existing connection
|
||||
jq ".available = .available[1:] | .inUse += [\"$available\"]" "$POOL_FILE" > "$POOL_FILE.tmp" && mv "$POOL_FILE.tmp" "$POOL_FILE"
|
||||
echo "$available"
|
||||
else
|
||||
# Create new connection ID
|
||||
local conn_id="conn_$(date +%s%N | tail -c 8)"
|
||||
jq ".inUse += [\"$conn_id\"] | .activeConnections += 1" "$POOL_FILE" > "$POOL_FILE.tmp" && mv "$POOL_FILE.tmp" "$POOL_FILE"
|
||||
echo "$conn_id"
|
||||
fi
|
||||
}
|
||||
|
||||
# Release connection back to pool
|
||||
pool_release() {
|
||||
local conn_id="${1:-}"
|
||||
|
||||
if [ -f "$POOL_FILE" ]; then
|
||||
jq ".inUse = (.inUse | map(select(. != \"$conn_id\"))) | .available += [\"$conn_id\"]" "$POOL_FILE" > "$POOL_FILE.tmp" && mv "$POOL_FILE.tmp" "$POOL_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# ASYNC PATTERN BROADCAST
|
||||
# =============================================================================
|
||||
|
||||
# Broadcast pattern to swarm (non-blocking)
|
||||
broadcast_pattern_async() {
|
||||
local strategy="${1:-}"
|
||||
local domain="${2:-general}"
|
||||
local quality="${3:-0.7}"
|
||||
|
||||
# Fire and forget
|
||||
(
|
||||
local broadcast_id="pattern_$(date +%s%N)"
|
||||
|
||||
# Write pattern broadcast
|
||||
mkdir -p "$SWARM_DIR/patterns"
|
||||
cat > "$SWARM_DIR/patterns/$broadcast_id.json" << EOF
|
||||
{"id":"$broadcast_id","strategy":"$strategy","domain":"$domain","quality":$quality,"timestamp":$(date +%s),"status":"pending"}
|
||||
EOF
|
||||
|
||||
# Notify all agents via queue
|
||||
enqueue "*" "{\"type\":\"pattern_broadcast\",\"id\":\"$broadcast_id\"}" "$PRIORITY_HIGH" "event"
|
||||
|
||||
) &
|
||||
|
||||
echo "pattern_broadcast_queued"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# OPTIMIZED CONSENSUS
|
||||
# =============================================================================
|
||||
|
||||
# Start consensus (non-blocking)
|
||||
start_consensus_async() {
|
||||
local question="${1:-}"
|
||||
local options="${2:-}"
|
||||
local timeout="${3:-30}"
|
||||
|
||||
(
|
||||
local consensus_id="consensus_$(date +%s%N)"
|
||||
mkdir -p "$SWARM_DIR/consensus"
|
||||
|
||||
cat > "$SWARM_DIR/consensus/$consensus_id.json" << EOF
|
||||
{"id":"$consensus_id","question":"$question","options":"$options","votes":{},"timeout":$timeout,"created":$(date +%s),"status":"open"}
|
||||
EOF
|
||||
|
||||
# Notify agents
|
||||
enqueue "*" "{\"type\":\"consensus_request\",\"id\":\"$consensus_id\"}" "$PRIORITY_HIGH" "event"
|
||||
|
||||
# Auto-resolve after timeout (background)
|
||||
(
|
||||
sleep "$timeout"
|
||||
if [ -f "$SWARM_DIR/consensus/$consensus_id.json" ]; then
|
||||
jq '.status = "resolved"' "$SWARM_DIR/consensus/$consensus_id.json" > "$SWARM_DIR/consensus/$consensus_id.json.tmp" && mv "$SWARM_DIR/consensus/$consensus_id.json.tmp" "$SWARM_DIR/consensus/$consensus_id.json"
|
||||
fi
|
||||
) &
|
||||
|
||||
echo "$consensus_id"
|
||||
) &
|
||||
}
|
||||
|
||||
# Vote on consensus (non-blocking)
|
||||
vote_async() {
|
||||
local consensus_id="${1:-}"
|
||||
local vote="${2:-}"
|
||||
local agent_id="${AGENTIC_FLOW_AGENT_ID:-anonymous}"
|
||||
|
||||
(
|
||||
local file="$SWARM_DIR/consensus/$consensus_id.json"
|
||||
if [ -f "$file" ]; then
|
||||
jq ".votes[\"$agent_id\"] = \"$vote\"" "$file" > "$file.tmp" && mv "$file.tmp" "$file"
|
||||
fi
|
||||
) &
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# PERFORMANCE METRICS
|
||||
# =============================================================================
|
||||
|
||||
get_comms_stats() {
|
||||
local queued=$(ls "$QUEUE_DIR"/*.json 2>/dev/null | wc -l | tr -d '[:space:]')
|
||||
queued=${queued:-0}
|
||||
local batched=$(ls "$BATCH_DIR"/*.batch 2>/dev/null | wc -l | tr -d '[:space:]')
|
||||
batched=${batched:-0}
|
||||
local patterns=$(ls "$SWARM_DIR/patterns"/*.json 2>/dev/null | wc -l | tr -d '[:space:]')
|
||||
patterns=${patterns:-0}
|
||||
local consensus=$(ls "$SWARM_DIR/consensus"/*.json 2>/dev/null | wc -l | tr -d '[:space:]')
|
||||
consensus=${consensus:-0}
|
||||
|
||||
local pool_active=0
|
||||
if [ -f "$POOL_FILE" ]; then
|
||||
pool_active=$(jq '.activeConnections // 0' "$POOL_FILE" 2>/dev/null | tr -d '[:space:]')
|
||||
pool_active=${pool_active:-0}
|
||||
fi
|
||||
|
||||
echo "{\"queue\":$queued,\"batch\":$batched,\"patterns\":$patterns,\"consensus\":$consensus,\"pool\":$pool_active}"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# MAIN DISPATCHER
|
||||
# =============================================================================
|
||||
|
||||
case "${1:-help}" in
|
||||
# Queue operations
|
||||
"enqueue"|"send")
|
||||
enqueue "${2:-*}" "${3:-}" "${4:-2}" "${5:-context}"
|
||||
;;
|
||||
"process")
|
||||
process_queue
|
||||
;;
|
||||
|
||||
# Batch operations
|
||||
"batch")
|
||||
batch_add "${2:-}" "${3:-}"
|
||||
;;
|
||||
"flush")
|
||||
batch_flush_all
|
||||
;;
|
||||
|
||||
# Pool operations
|
||||
"acquire")
|
||||
pool_acquire "${2:-}"
|
||||
;;
|
||||
"release")
|
||||
pool_release "${2:-}"
|
||||
;;
|
||||
|
||||
# Async operations
|
||||
"broadcast-pattern")
|
||||
broadcast_pattern_async "${2:-}" "${3:-general}" "${4:-0.7}"
|
||||
;;
|
||||
"consensus")
|
||||
start_consensus_async "${2:-}" "${3:-}" "${4:-30}"
|
||||
;;
|
||||
"vote")
|
||||
vote_async "${2:-}" "${3:-}"
|
||||
;;
|
||||
|
||||
# Stats
|
||||
"stats")
|
||||
get_comms_stats
|
||||
;;
|
||||
|
||||
"help"|*)
|
||||
cat << 'EOF'
|
||||
Claude Flow V3 - Optimized Swarm Communications
|
||||
|
||||
Non-blocking, batched, priority-based inter-agent messaging.
|
||||
|
||||
Usage: swarm-comms.sh <command> [args]
|
||||
|
||||
Queue (Non-blocking):
|
||||
enqueue <to> <content> [priority] [type] Add to queue (instant return)
|
||||
process Process pending queue
|
||||
|
||||
Batching:
|
||||
batch <agent> <content> Add to batch
|
||||
flush Flush all batches
|
||||
|
||||
Connection Pool:
|
||||
acquire [agent] Get connection from pool
|
||||
release <conn_id> Return connection to pool
|
||||
|
||||
Async Operations:
|
||||
broadcast-pattern <strategy> [domain] [quality] Async pattern broadcast
|
||||
consensus <question> <options> [timeout] Start async consensus
|
||||
vote <consensus_id> <vote> Vote (non-blocking)
|
||||
|
||||
Stats:
|
||||
stats Get communication stats
|
||||
|
||||
Priority Levels:
|
||||
0 = Critical (processed first)
|
||||
1 = High
|
||||
2 = Normal (default)
|
||||
3 = Low
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
761
.claude/helpers/swarm-hooks.sh
Executable file
761
.claude/helpers/swarm-hooks.sh
Executable file
@@ -0,0 +1,761 @@
|
||||
#!/bin/bash
|
||||
# Claude Flow V3 - Swarm Communication Hooks
|
||||
# Enables agent-to-agent messaging, pattern sharing, consensus, and task handoffs
|
||||
#
|
||||
# Integration with:
|
||||
# - @claude-flow/hooks SwarmCommunication module
|
||||
# - agentic-flow@alpha swarm coordination
|
||||
# - Local hooks system for real-time agent coordination
|
||||
#
|
||||
# Key mechanisms:
|
||||
# - Exit 0 + stdout = Context added to Claude's view
|
||||
# - Exit 2 + stderr = Block with explanation
|
||||
# - JSON additionalContext = Swarm coordination messages
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
SWARM_DIR="$PROJECT_ROOT/.claude-flow/swarm"
|
||||
MESSAGES_DIR="$SWARM_DIR/messages"
|
||||
PATTERNS_DIR="$SWARM_DIR/patterns"
|
||||
CONSENSUS_DIR="$SWARM_DIR/consensus"
|
||||
HANDOFFS_DIR="$SWARM_DIR/handoffs"
|
||||
AGENTS_FILE="$SWARM_DIR/agents.json"
|
||||
STATS_FILE="$SWARM_DIR/stats.json"
|
||||
|
||||
# Agent identity
|
||||
AGENT_ID="${AGENTIC_FLOW_AGENT_ID:-agent_$(date +%s)_$(head -c 4 /dev/urandom | xxd -p)}"
|
||||
AGENT_NAME="${AGENTIC_FLOW_AGENT_NAME:-claude-code}"
|
||||
|
||||
# Initialize directories
|
||||
mkdir -p "$MESSAGES_DIR" "$PATTERNS_DIR" "$CONSENSUS_DIR" "$HANDOFFS_DIR"
|
||||
|
||||
# =============================================================================
|
||||
# UTILITY FUNCTIONS
|
||||
# =============================================================================
|
||||
|
||||
init_stats() {
|
||||
if [ ! -f "$STATS_FILE" ]; then
|
||||
cat > "$STATS_FILE" << EOF
|
||||
{
|
||||
"messagesSent": 0,
|
||||
"messagesReceived": 0,
|
||||
"patternsBroadcast": 0,
|
||||
"consensusInitiated": 0,
|
||||
"consensusResolved": 0,
|
||||
"handoffsInitiated": 0,
|
||||
"handoffsCompleted": 0,
|
||||
"lastUpdated": "$(date -Iseconds)"
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
update_stat() {
|
||||
local key="$1"
|
||||
local increment="${2:-1}"
|
||||
init_stats
|
||||
|
||||
if command -v jq &>/dev/null; then
|
||||
local current=$(jq -r ".$key // 0" "$STATS_FILE")
|
||||
local new=$((current + increment))
|
||||
jq ".$key = $new | .lastUpdated = \"$(date -Iseconds)\"" "$STATS_FILE" > "$STATS_FILE.tmp" && mv "$STATS_FILE.tmp" "$STATS_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
register_agent() {
|
||||
init_stats
|
||||
local timestamp=$(date +%s)
|
||||
|
||||
if [ ! -f "$AGENTS_FILE" ]; then
|
||||
echo '{"agents":[]}' > "$AGENTS_FILE"
|
||||
fi
|
||||
|
||||
if command -v jq &>/dev/null; then
|
||||
# Check if agent already exists
|
||||
local exists=$(jq -r ".agents[] | select(.id == \"$AGENT_ID\") | .id" "$AGENTS_FILE" 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$exists" ]; then
|
||||
jq ".agents += [{\"id\":\"$AGENT_ID\",\"name\":\"$AGENT_NAME\",\"status\":\"active\",\"lastSeen\":$timestamp}]" "$AGENTS_FILE" > "$AGENTS_FILE.tmp" && mv "$AGENTS_FILE.tmp" "$AGENTS_FILE"
|
||||
else
|
||||
# Update lastSeen
|
||||
jq "(.agents[] | select(.id == \"$AGENT_ID\")).lastSeen = $timestamp" "$AGENTS_FILE" > "$AGENTS_FILE.tmp" && mv "$AGENTS_FILE.tmp" "$AGENTS_FILE"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# AGENT-TO-AGENT MESSAGING
|
||||
# =============================================================================
|
||||
|
||||
send_message() {
|
||||
local to="${1:-*}"
|
||||
local content="${2:-}"
|
||||
local msg_type="${3:-context}"
|
||||
local priority="${4:-normal}"
|
||||
|
||||
local msg_id="msg_$(date +%s)_$(head -c 4 /dev/urandom | xxd -p)"
|
||||
local timestamp=$(date +%s)
|
||||
|
||||
local msg_file="$MESSAGES_DIR/$msg_id.json"
|
||||
cat > "$msg_file" << EOF
|
||||
{
|
||||
"id": "$msg_id",
|
||||
"from": "$AGENT_ID",
|
||||
"fromName": "$AGENT_NAME",
|
||||
"to": "$to",
|
||||
"type": "$msg_type",
|
||||
"content": $(echo "$content" | jq -Rs .),
|
||||
"priority": "$priority",
|
||||
"timestamp": $timestamp,
|
||||
"read": false
|
||||
}
|
||||
EOF
|
||||
|
||||
update_stat "messagesSent"
|
||||
|
||||
echo "$msg_id"
|
||||
exit 0
|
||||
}
|
||||
|
||||
get_messages() {
|
||||
local limit="${1:-10}"
|
||||
local msg_type="${2:-}"
|
||||
|
||||
register_agent
|
||||
|
||||
local messages="[]"
|
||||
local count=0
|
||||
|
||||
for msg_file in $(ls -t "$MESSAGES_DIR"/*.json 2>/dev/null | head -n "$limit"); do
|
||||
if [ -f "$msg_file" ]; then
|
||||
local to=$(jq -r '.to' "$msg_file" 2>/dev/null)
|
||||
|
||||
# Check if message is for us or broadcast
|
||||
if [ "$to" = "$AGENT_ID" ] || [ "$to" = "*" ] || [ "$to" = "$AGENT_NAME" ]; then
|
||||
# Filter by type if specified
|
||||
if [ -n "$msg_type" ]; then
|
||||
local mtype=$(jq -r '.type' "$msg_file" 2>/dev/null)
|
||||
if [ "$mtype" != "$msg_type" ]; then
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v jq &>/dev/null; then
|
||||
messages=$(echo "$messages" | jq ". += [$(cat "$msg_file")]")
|
||||
count=$((count + 1))
|
||||
|
||||
# Mark as read
|
||||
jq '.read = true' "$msg_file" > "$msg_file.tmp" && mv "$msg_file.tmp" "$msg_file"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
update_stat "messagesReceived" "$count"
|
||||
|
||||
if command -v jq &>/dev/null; then
|
||||
echo "$messages" | jq -c "{count: $count, messages: .}"
|
||||
else
|
||||
echo "{\"count\": $count, \"messages\": []}"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
broadcast_context() {
|
||||
local content="${1:-}"
|
||||
send_message "*" "$content" "context" "normal"
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# PATTERN BROADCASTING
|
||||
# =============================================================================
|
||||
|
||||
broadcast_pattern() {
|
||||
local strategy="${1:-}"
|
||||
local domain="${2:-general}"
|
||||
local quality="${3:-0.7}"
|
||||
|
||||
local bc_id="bc_$(date +%s)_$(head -c 4 /dev/urandom | xxd -p)"
|
||||
local timestamp=$(date +%s)
|
||||
|
||||
local bc_file="$PATTERNS_DIR/$bc_id.json"
|
||||
cat > "$bc_file" << EOF
|
||||
{
|
||||
"id": "$bc_id",
|
||||
"sourceAgent": "$AGENT_ID",
|
||||
"sourceAgentName": "$AGENT_NAME",
|
||||
"pattern": {
|
||||
"strategy": $(echo "$strategy" | jq -Rs .),
|
||||
"domain": "$domain",
|
||||
"quality": $quality
|
||||
},
|
||||
"broadcastTime": $timestamp,
|
||||
"acknowledgments": []
|
||||
}
|
||||
EOF
|
||||
|
||||
update_stat "patternsBroadcast"
|
||||
|
||||
# Also store in learning hooks if available
|
||||
if [ -f "$SCRIPT_DIR/learning-hooks.sh" ]; then
|
||||
"$SCRIPT_DIR/learning-hooks.sh" store "$strategy" "$domain" "$quality" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
cat << EOF
|
||||
{"broadcastId":"$bc_id","strategy":$(echo "$strategy" | jq -Rs .),"domain":"$domain","quality":$quality}
|
||||
EOF
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
get_pattern_broadcasts() {
|
||||
local domain="${1:-}"
|
||||
local min_quality="${2:-0}"
|
||||
local limit="${3:-10}"
|
||||
|
||||
local broadcasts="[]"
|
||||
local count=0
|
||||
|
||||
for bc_file in $(ls -t "$PATTERNS_DIR"/*.json 2>/dev/null | head -n "$limit"); do
|
||||
if [ -f "$bc_file" ] && command -v jq &>/dev/null; then
|
||||
local bc_domain=$(jq -r '.pattern.domain' "$bc_file" 2>/dev/null)
|
||||
local bc_quality=$(jq -r '.pattern.quality' "$bc_file" 2>/dev/null)
|
||||
|
||||
# Filter by domain if specified
|
||||
if [ -n "$domain" ] && [ "$bc_domain" != "$domain" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Filter by quality
|
||||
if [ "$(echo "$bc_quality >= $min_quality" | bc -l 2>/dev/null || echo "1")" = "1" ]; then
|
||||
broadcasts=$(echo "$broadcasts" | jq ". += [$(cat "$bc_file")]")
|
||||
count=$((count + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$broadcasts" | jq -c "{count: $count, broadcasts: .}"
|
||||
exit 0
|
||||
}
|
||||
|
||||
import_pattern() {
|
||||
local bc_id="$1"
|
||||
local bc_file="$PATTERNS_DIR/$bc_id.json"
|
||||
|
||||
if [ ! -f "$bc_file" ]; then
|
||||
echo '{"imported": false, "error": "Broadcast not found"}'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Acknowledge the broadcast
|
||||
if command -v jq &>/dev/null; then
|
||||
jq ".acknowledgments += [\"$AGENT_ID\"]" "$bc_file" > "$bc_file.tmp" && mv "$bc_file.tmp" "$bc_file"
|
||||
|
||||
# Import to local learning
|
||||
local strategy=$(jq -r '.pattern.strategy' "$bc_file")
|
||||
local domain=$(jq -r '.pattern.domain' "$bc_file")
|
||||
local quality=$(jq -r '.pattern.quality' "$bc_file")
|
||||
|
||||
if [ -f "$SCRIPT_DIR/learning-hooks.sh" ]; then
|
||||
"$SCRIPT_DIR/learning-hooks.sh" store "$strategy" "$domain" "$quality" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "{\"imported\": true, \"broadcastId\": \"$bc_id\"}"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# CONSENSUS GUIDANCE
|
||||
# =============================================================================
|
||||
|
||||
initiate_consensus() {
|
||||
local question="${1:-}"
|
||||
local options_str="${2:-}" # comma-separated
|
||||
local timeout="${3:-30000}"
|
||||
|
||||
local cons_id="cons_$(date +%s)_$(head -c 4 /dev/urandom | xxd -p)"
|
||||
local timestamp=$(date +%s)
|
||||
local deadline=$((timestamp + timeout / 1000))
|
||||
|
||||
# Parse options
|
||||
local options_json="[]"
|
||||
IFS=',' read -ra opts <<< "$options_str"
|
||||
for opt in "${opts[@]}"; do
|
||||
opt=$(echo "$opt" | xargs) # trim whitespace
|
||||
if command -v jq &>/dev/null; then
|
||||
options_json=$(echo "$options_json" | jq ". += [\"$opt\"]")
|
||||
fi
|
||||
done
|
||||
|
||||
local cons_file="$CONSENSUS_DIR/$cons_id.json"
|
||||
cat > "$cons_file" << EOF
|
||||
{
|
||||
"id": "$cons_id",
|
||||
"initiator": "$AGENT_ID",
|
||||
"initiatorName": "$AGENT_NAME",
|
||||
"question": $(echo "$question" | jq -Rs .),
|
||||
"options": $options_json,
|
||||
"votes": {},
|
||||
"deadline": $deadline,
|
||||
"status": "pending"
|
||||
}
|
||||
EOF
|
||||
|
||||
update_stat "consensusInitiated"
|
||||
|
||||
# Broadcast consensus request
|
||||
send_message "*" "Consensus request: $question. Options: $options_str. Vote by replying with your choice." "consensus" "high" >/dev/null
|
||||
|
||||
cat << EOF
|
||||
{"consensusId":"$cons_id","question":$(echo "$question" | jq -Rs .),"options":$options_json,"deadline":$deadline}
|
||||
EOF
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
vote_consensus() {
|
||||
local cons_id="$1"
|
||||
local vote="$2"
|
||||
|
||||
local cons_file="$CONSENSUS_DIR/$cons_id.json"
|
||||
|
||||
if [ ! -f "$cons_file" ]; then
|
||||
echo '{"accepted": false, "error": "Consensus not found"}'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v jq &>/dev/null; then
|
||||
local status=$(jq -r '.status' "$cons_file")
|
||||
if [ "$status" != "pending" ]; then
|
||||
echo '{"accepted": false, "error": "Consensus already resolved"}'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if vote is valid option
|
||||
local valid=$(jq -r ".options | index(\"$vote\") // -1" "$cons_file")
|
||||
if [ "$valid" = "-1" ]; then
|
||||
echo "{\"accepted\": false, \"error\": \"Invalid option: $vote\"}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Record vote
|
||||
jq ".votes[\"$AGENT_ID\"] = \"$vote\"" "$cons_file" > "$cons_file.tmp" && mv "$cons_file.tmp" "$cons_file"
|
||||
|
||||
echo "{\"accepted\": true, \"consensusId\": \"$cons_id\", \"vote\": \"$vote\"}"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
resolve_consensus() {
|
||||
local cons_id="$1"
|
||||
local cons_file="$CONSENSUS_DIR/$cons_id.json"
|
||||
|
||||
if [ ! -f "$cons_file" ]; then
|
||||
echo '{"resolved": false, "error": "Consensus not found"}'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v jq &>/dev/null; then
|
||||
# Count votes
|
||||
local result=$(jq -r '
|
||||
.votes | to_entries | group_by(.value) |
|
||||
map({option: .[0].value, count: length}) |
|
||||
sort_by(-.count) | .[0] // {option: "none", count: 0}
|
||||
' "$cons_file")
|
||||
|
||||
local winner=$(echo "$result" | jq -r '.option')
|
||||
local count=$(echo "$result" | jq -r '.count')
|
||||
local total=$(jq '.votes | length' "$cons_file")
|
||||
|
||||
local confidence=0
|
||||
if [ "$total" -gt 0 ]; then
|
||||
confidence=$(echo "scale=2; $count / $total * 100" | bc 2>/dev/null || echo "0")
|
||||
fi
|
||||
|
||||
# Update status
|
||||
jq ".status = \"resolved\" | .result = {\"winner\": \"$winner\", \"confidence\": $confidence, \"totalVotes\": $total}" "$cons_file" > "$cons_file.tmp" && mv "$cons_file.tmp" "$cons_file"
|
||||
|
||||
update_stat "consensusResolved"
|
||||
|
||||
echo "{\"resolved\": true, \"winner\": \"$winner\", \"confidence\": $confidence, \"totalVotes\": $total}"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
get_consensus_status() {
|
||||
local cons_id="${1:-}"
|
||||
|
||||
if [ -n "$cons_id" ]; then
|
||||
local cons_file="$CONSENSUS_DIR/$cons_id.json"
|
||||
if [ -f "$cons_file" ]; then
|
||||
cat "$cons_file"
|
||||
else
|
||||
echo '{"error": "Consensus not found"}'
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# List pending consensus
|
||||
local pending="[]"
|
||||
for cons_file in "$CONSENSUS_DIR"/*.json; do
|
||||
if [ -f "$cons_file" ] && command -v jq &>/dev/null; then
|
||||
local status=$(jq -r '.status' "$cons_file")
|
||||
if [ "$status" = "pending" ]; then
|
||||
pending=$(echo "$pending" | jq ". += [$(cat "$cons_file")]")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
echo "$pending" | jq -c .
|
||||
fi
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# TASK HANDOFF
|
||||
# =============================================================================
|
||||
|
||||
initiate_handoff() {
|
||||
local to_agent="$1"
|
||||
local description="${2:-}"
|
||||
local context_json="$3"
|
||||
[ -z "$context_json" ] && context_json='{}'
|
||||
|
||||
local ho_id="ho_$(date +%s)_$(head -c 4 /dev/urandom | xxd -p)"
|
||||
local timestamp=$(date +%s)
|
||||
|
||||
# Parse context or use defaults - ensure valid JSON
|
||||
local context
|
||||
if command -v jq &>/dev/null && [ -n "$context_json" ] && [ "$context_json" != "{}" ]; then
|
||||
# Try to parse and merge with defaults
|
||||
context=$(jq -c '{
|
||||
filesModified: (.filesModified // []),
|
||||
patternsUsed: (.patternsUsed // []),
|
||||
decisions: (.decisions // []),
|
||||
blockers: (.blockers // []),
|
||||
nextSteps: (.nextSteps // [])
|
||||
}' <<< "$context_json" 2>/dev/null)
|
||||
|
||||
# If parsing failed, use defaults
|
||||
if [ -z "$context" ] || [ "$context" = "null" ]; then
|
||||
context='{"filesModified":[],"patternsUsed":[],"decisions":[],"blockers":[],"nextSteps":[]}'
|
||||
fi
|
||||
else
|
||||
context='{"filesModified":[],"patternsUsed":[],"decisions":[],"blockers":[],"nextSteps":[]}'
|
||||
fi
|
||||
|
||||
local desc_escaped=$(echo -n "$description" | jq -Rs .)
|
||||
|
||||
local ho_file="$HANDOFFS_DIR/$ho_id.json"
|
||||
cat > "$ho_file" << EOF
|
||||
{
|
||||
"id": "$ho_id",
|
||||
"fromAgent": "$AGENT_ID",
|
||||
"fromAgentName": "$AGENT_NAME",
|
||||
"toAgent": "$to_agent",
|
||||
"description": $desc_escaped,
|
||||
"context": $context,
|
||||
"status": "pending",
|
||||
"timestamp": $timestamp
|
||||
}
|
||||
EOF
|
||||
|
||||
update_stat "handoffsInitiated"
|
||||
|
||||
# Send handoff notification (inline, don't call function which exits)
|
||||
local msg_id="msg_$(date +%s)_$(head -c 4 /dev/urandom | xxd -p)"
|
||||
local msg_file="$MESSAGES_DIR/$msg_id.json"
|
||||
cat > "$msg_file" << MSGEOF
|
||||
{
|
||||
"id": "$msg_id",
|
||||
"from": "$AGENT_ID",
|
||||
"fromName": "$AGENT_NAME",
|
||||
"to": "$to_agent",
|
||||
"type": "handoff",
|
||||
"content": "Task handoff: $description",
|
||||
"priority": "high",
|
||||
"timestamp": $timestamp,
|
||||
"read": false,
|
||||
"handoffId": "$ho_id"
|
||||
}
|
||||
MSGEOF
|
||||
update_stat "messagesSent"
|
||||
|
||||
cat << EOF
|
||||
{"handoffId":"$ho_id","toAgent":"$to_agent","description":$desc_escaped,"status":"pending","context":$context}
|
||||
EOF
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
accept_handoff() {
|
||||
local ho_id="$1"
|
||||
local ho_file="$HANDOFFS_DIR/$ho_id.json"
|
||||
|
||||
if [ ! -f "$ho_file" ]; then
|
||||
echo '{"accepted": false, "error": "Handoff not found"}'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v jq &>/dev/null; then
|
||||
jq ".status = \"accepted\" | .acceptedAt = $(date +%s)" "$ho_file" > "$ho_file.tmp" && mv "$ho_file.tmp" "$ho_file"
|
||||
|
||||
# Generate context for Claude
|
||||
local description=$(jq -r '.description' "$ho_file")
|
||||
local from=$(jq -r '.fromAgentName' "$ho_file")
|
||||
local files=$(jq -r '.context.filesModified | join(", ")' "$ho_file")
|
||||
local patterns=$(jq -r '.context.patternsUsed | join(", ")' "$ho_file")
|
||||
local decisions=$(jq -r '.context.decisions | join("; ")' "$ho_file")
|
||||
local next=$(jq -r '.context.nextSteps | join("; ")' "$ho_file")
|
||||
|
||||
cat << EOF
|
||||
## Task Handoff Accepted
|
||||
|
||||
**From**: $from
|
||||
**Task**: $description
|
||||
|
||||
**Files Modified**: $files
|
||||
**Patterns Used**: $patterns
|
||||
**Decisions Made**: $decisions
|
||||
**Next Steps**: $next
|
||||
|
||||
This context has been transferred. Continue from where the previous agent left off.
|
||||
EOF
|
||||
fi
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
complete_handoff() {
|
||||
local ho_id="$1"
|
||||
local result_json="${2:-{}}"
|
||||
|
||||
local ho_file="$HANDOFFS_DIR/$ho_id.json"
|
||||
|
||||
if [ ! -f "$ho_file" ]; then
|
||||
echo '{"completed": false, "error": "Handoff not found"}'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v jq &>/dev/null; then
|
||||
jq ".status = \"completed\" | .completedAt = $(date +%s) | .result = $result_json" "$ho_file" > "$ho_file.tmp" && mv "$ho_file.tmp" "$ho_file"
|
||||
|
||||
update_stat "handoffsCompleted"
|
||||
|
||||
echo "{\"completed\": true, \"handoffId\": \"$ho_id\"}"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
get_pending_handoffs() {
|
||||
local pending="[]"
|
||||
|
||||
for ho_file in "$HANDOFFS_DIR"/*.json; do
|
||||
if [ -f "$ho_file" ] && command -v jq &>/dev/null; then
|
||||
local to=$(jq -r '.toAgent' "$ho_file")
|
||||
local status=$(jq -r '.status' "$ho_file")
|
||||
|
||||
# Check if handoff is for us and pending
|
||||
if [ "$status" = "pending" ] && ([ "$to" = "$AGENT_ID" ] || [ "$to" = "$AGENT_NAME" ]); then
|
||||
pending=$(echo "$pending" | jq ". += [$(cat "$ho_file")]")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$pending" | jq -c .
|
||||
exit 0
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# SWARM STATUS & AGENTS
|
||||
# =============================================================================
|
||||
|
||||
get_agents() {
|
||||
register_agent
|
||||
|
||||
if [ -f "$AGENTS_FILE" ] && command -v jq &>/dev/null; then
|
||||
cat "$AGENTS_FILE"
|
||||
else
|
||||
echo '{"agents":[]}'
|
||||
fi
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
get_stats() {
|
||||
init_stats
|
||||
|
||||
if command -v jq &>/dev/null; then
|
||||
jq ". + {agentId: \"$AGENT_ID\", agentName: \"$AGENT_NAME\"}" "$STATS_FILE"
|
||||
else
|
||||
cat "$STATS_FILE"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# HOOK INTEGRATION - Output for Claude hooks
|
||||
# =============================================================================
|
||||
|
||||
pre_task_swarm_context() {
|
||||
local task="${1:-}"
|
||||
|
||||
register_agent
|
||||
|
||||
# Check for pending handoffs
|
||||
local handoffs=$(get_pending_handoffs 2>/dev/null || echo "[]")
|
||||
local handoff_count=$(echo "$handoffs" | jq 'length' 2>/dev/null || echo "0")
|
||||
|
||||
# Check for new messages
|
||||
local messages=$(get_messages 5 2>/dev/null || echo '{"count":0}')
|
||||
local msg_count=$(echo "$messages" | jq '.count' 2>/dev/null || echo "0")
|
||||
|
||||
# Check for pending consensus
|
||||
local consensus=$(get_consensus_status 2>/dev/null || echo "[]")
|
||||
local cons_count=$(echo "$consensus" | jq 'length' 2>/dev/null || echo "0")
|
||||
|
||||
if [ "$handoff_count" -gt 0 ] || [ "$msg_count" -gt 0 ] || [ "$cons_count" -gt 0 ]; then
|
||||
cat << EOF
|
||||
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":"**Swarm Activity**:\n- Pending handoffs: $handoff_count\n- New messages: $msg_count\n- Active consensus: $cons_count\n\nCheck swarm status before proceeding on complex tasks."}}
|
||||
EOF
|
||||
fi
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
post_task_swarm_update() {
|
||||
local task="${1:-}"
|
||||
local success="${2:-true}"
|
||||
|
||||
# Broadcast task completion
|
||||
if [ "$success" = "true" ]; then
|
||||
send_message "*" "Completed: $(echo "$task" | head -c 100)" "result" "low" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Main dispatcher
|
||||
# =============================================================================
|
||||
case "${1:-help}" in
|
||||
# Messaging
|
||||
"send")
|
||||
send_message "${2:-*}" "${3:-}" "${4:-context}" "${5:-normal}"
|
||||
;;
|
||||
"messages")
|
||||
get_messages "${2:-10}" "${3:-}"
|
||||
;;
|
||||
"broadcast")
|
||||
broadcast_context "${2:-}"
|
||||
;;
|
||||
|
||||
# Pattern broadcasting
|
||||
"broadcast-pattern")
|
||||
broadcast_pattern "${2:-}" "${3:-general}" "${4:-0.7}"
|
||||
;;
|
||||
"patterns")
|
||||
get_pattern_broadcasts "${2:-}" "${3:-0}" "${4:-10}"
|
||||
;;
|
||||
"import-pattern")
|
||||
import_pattern "${2:-}"
|
||||
;;
|
||||
|
||||
# Consensus
|
||||
"consensus")
|
||||
initiate_consensus "${2:-}" "${3:-}" "${4:-30000}"
|
||||
;;
|
||||
"vote")
|
||||
vote_consensus "${2:-}" "${3:-}"
|
||||
;;
|
||||
"resolve-consensus")
|
||||
resolve_consensus "${2:-}"
|
||||
;;
|
||||
"consensus-status")
|
||||
get_consensus_status "${2:-}"
|
||||
;;
|
||||
|
||||
# Task handoff
|
||||
"handoff")
|
||||
initiate_handoff "${2:-}" "${3:-}" "${4:-}"
|
||||
;;
|
||||
"accept-handoff")
|
||||
accept_handoff "${2:-}"
|
||||
;;
|
||||
"complete-handoff")
|
||||
complete_handoff "${2:-}" "${3:-{}}"
|
||||
;;
|
||||
"pending-handoffs")
|
||||
get_pending_handoffs
|
||||
;;
|
||||
|
||||
# Status
|
||||
"agents")
|
||||
get_agents
|
||||
;;
|
||||
"stats")
|
||||
get_stats
|
||||
;;
|
||||
|
||||
# Hook integration
|
||||
"pre-task")
|
||||
pre_task_swarm_context "${2:-}"
|
||||
;;
|
||||
"post-task")
|
||||
post_task_swarm_update "${2:-}" "${3:-true}"
|
||||
;;
|
||||
|
||||
"help"|"-h"|"--help")
|
||||
cat << 'EOF'
|
||||
Claude Flow V3 - Swarm Communication Hooks
|
||||
|
||||
Usage: swarm-hooks.sh <command> [args]
|
||||
|
||||
Agent Messaging:
|
||||
send <to> <content> [type] [priority] Send message to agent
|
||||
messages [limit] [type] Get messages for this agent
|
||||
broadcast <content> Broadcast to all agents
|
||||
|
||||
Pattern Broadcasting:
|
||||
broadcast-pattern <strategy> [domain] [quality] Share pattern with swarm
|
||||
patterns [domain] [min-quality] [limit] List pattern broadcasts
|
||||
import-pattern <broadcast-id> Import broadcast pattern
|
||||
|
||||
Consensus:
|
||||
consensus <question> <options> [timeout] Start consensus (options: comma-separated)
|
||||
vote <consensus-id> <vote> Vote on consensus
|
||||
resolve-consensus <consensus-id> Force resolve consensus
|
||||
consensus-status [consensus-id] Get consensus status
|
||||
|
||||
Task Handoff:
|
||||
handoff <to-agent> <description> [context-json] Initiate handoff
|
||||
accept-handoff <handoff-id> Accept pending handoff
|
||||
complete-handoff <handoff-id> [result-json] Complete handoff
|
||||
pending-handoffs List pending handoffs
|
||||
|
||||
Status:
|
||||
agents List registered agents
|
||||
stats Get swarm statistics
|
||||
|
||||
Hook Integration:
|
||||
pre-task <task> Check swarm before task (for hooks)
|
||||
post-task <task> [success] Update swarm after task (for hooks)
|
||||
|
||||
Environment:
|
||||
AGENTIC_FLOW_AGENT_ID Agent identifier
|
||||
AGENTIC_FLOW_AGENT_NAME Agent display name
|
||||
EOF
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
211
.claude/helpers/swarm-monitor.sh
Executable file
211
.claude/helpers/swarm-monitor.sh
Executable file
@@ -0,0 +1,211 @@
|
||||
#!/bin/bash
|
||||
# Claude Flow V3 - Real-time Swarm Activity Monitor
|
||||
# Continuously monitors and updates metrics based on running processes
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
METRICS_DIR="$PROJECT_ROOT/.claude-flow/metrics"
|
||||
UPDATE_SCRIPT="$SCRIPT_DIR/update-v3-progress.sh"
|
||||
|
||||
# Ensure metrics directory exists
|
||||
mkdir -p "$METRICS_DIR"
|
||||
|
||||
# Colors for logging
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
RED='\033[0;31m'
|
||||
RESET='\033[0m'
|
||||
|
||||
log() {
|
||||
echo -e "${CYAN}[$(date '+%H:%M:%S')] ${1}${RESET}"
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "${YELLOW}[$(date '+%H:%M:%S')] WARNING: ${1}${RESET}"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[$(date '+%H:%M:%S')] ERROR: ${1}${RESET}"
|
||||
}
|
||||
|
||||
success() {
|
||||
echo -e "${GREEN}[$(date '+%H:%M:%S')] ${1}${RESET}"
|
||||
}
|
||||
|
||||
# Function to count active processes
|
||||
count_active_processes() {
|
||||
local agentic_flow_count=0
|
||||
local mcp_count=0
|
||||
local agent_count=0
|
||||
|
||||
# Count agentic-flow processes
|
||||
agentic_flow_count=$(ps aux 2>/dev/null | grep -E "agentic-flow" | grep -v grep | grep -v "swarm-monitor" | wc -l)
|
||||
|
||||
# Count MCP server processes
|
||||
mcp_count=$(ps aux 2>/dev/null | grep -E "mcp.*start" | grep -v grep | wc -l)
|
||||
|
||||
# Count specific agent processes
|
||||
agent_count=$(ps aux 2>/dev/null | grep -E "(agent|swarm|coordinator)" | grep -v grep | grep -v "swarm-monitor" | wc -l)
|
||||
|
||||
# Calculate total active "agents" using heuristic
|
||||
local total_agents=0
|
||||
if [ "$agentic_flow_count" -gt 0 ]; then
|
||||
# Use agent count if available, otherwise estimate from processes
|
||||
if [ "$agent_count" -gt 0 ]; then
|
||||
total_agents="$agent_count"
|
||||
else
|
||||
# Heuristic: some processes are management, some are agents
|
||||
total_agents=$((agentic_flow_count / 2))
|
||||
if [ "$total_agents" -eq 0 ] && [ "$agentic_flow_count" -gt 0 ]; then
|
||||
total_agents=1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "agentic:$agentic_flow_count mcp:$mcp_count agents:$total_agents"
|
||||
}
|
||||
|
||||
# Function to update metrics based on detected activity
|
||||
update_activity_metrics() {
|
||||
local process_info="$1"
|
||||
local agentic_count=$(echo "$process_info" | cut -d' ' -f1 | cut -d':' -f2)
|
||||
local mcp_count=$(echo "$process_info" | cut -d' ' -f2 | cut -d':' -f2)
|
||||
local agent_count=$(echo "$process_info" | cut -d' ' -f3 | cut -d':' -f2)
|
||||
|
||||
# Update active agents in metrics
|
||||
if [ -f "$UPDATE_SCRIPT" ]; then
|
||||
"$UPDATE_SCRIPT" agent "$agent_count" >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
# Update integration status based on activity
|
||||
local integration_status="false"
|
||||
if [ "$agentic_count" -gt 0 ] || [ "$mcp_count" -gt 0 ]; then
|
||||
integration_status="true"
|
||||
fi
|
||||
|
||||
# Create/update activity metrics file
|
||||
local activity_file="$METRICS_DIR/swarm-activity.json"
|
||||
cat > "$activity_file" << EOF
|
||||
{
|
||||
"timestamp": "$(date -Iseconds)",
|
||||
"processes": {
|
||||
"agentic_flow": $agentic_count,
|
||||
"mcp_server": $mcp_count,
|
||||
"estimated_agents": $agent_count
|
||||
},
|
||||
"swarm": {
|
||||
"active": $([ "$agent_count" -gt 0 ] && echo "true" || echo "false"),
|
||||
"agent_count": $agent_count,
|
||||
"coordination_active": $([ "$agentic_count" -gt 0 ] && echo "true" || echo "false")
|
||||
},
|
||||
"integration": {
|
||||
"agentic_flow_active": $integration_status,
|
||||
"mcp_active": $([ "$mcp_count" -gt 0 ] && echo "true" || echo "false")
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to monitor continuously
|
||||
monitor_continuous() {
|
||||
local monitor_interval="${1:-5}" # Default 5 seconds
|
||||
local last_state=""
|
||||
local current_state=""
|
||||
|
||||
log "Starting continuous swarm monitoring (interval: ${monitor_interval}s)"
|
||||
log "Press Ctrl+C to stop monitoring"
|
||||
|
||||
while true; do
|
||||
current_state=$(count_active_processes)
|
||||
|
||||
# Only update if state changed
|
||||
if [ "$current_state" != "$last_state" ]; then
|
||||
update_activity_metrics "$current_state"
|
||||
|
||||
local agent_count=$(echo "$current_state" | cut -d' ' -f3 | cut -d':' -f2)
|
||||
local agentic_count=$(echo "$current_state" | cut -d' ' -f1 | cut -d':' -f2)
|
||||
|
||||
if [ "$agent_count" -gt 0 ] || [ "$agentic_count" -gt 0 ]; then
|
||||
success "Swarm activity detected: $current_state"
|
||||
else
|
||||
warn "No swarm activity detected"
|
||||
fi
|
||||
|
||||
last_state="$current_state"
|
||||
fi
|
||||
|
||||
sleep "$monitor_interval"
|
||||
done
|
||||
}
|
||||
|
||||
# Function to run a single check
|
||||
check_once() {
|
||||
log "Running single swarm activity check..."
|
||||
|
||||
local process_info=$(count_active_processes)
|
||||
update_activity_metrics "$process_info"
|
||||
|
||||
local agent_count=$(echo "$process_info" | cut -d' ' -f3 | cut -d':' -f2)
|
||||
local agentic_count=$(echo "$process_info" | cut -d' ' -f1 | cut -d':' -f2)
|
||||
local mcp_count=$(echo "$process_info" | cut -d' ' -f2 | cut -d':' -f2)
|
||||
|
||||
log "Process Detection Results:"
|
||||
log " Agentic Flow processes: $agentic_count"
|
||||
log " MCP Server processes: $mcp_count"
|
||||
log " Estimated agents: $agent_count"
|
||||
|
||||
if [ "$agent_count" -gt 0 ] || [ "$agentic_count" -gt 0 ]; then
|
||||
success "✓ Swarm activity detected and metrics updated"
|
||||
else
|
||||
warn "⚠ No swarm activity detected"
|
||||
fi
|
||||
|
||||
# Run performance benchmarks (throttled to every 5 min)
|
||||
if [ -x "$SCRIPT_DIR/perf-worker.sh" ]; then
|
||||
"$SCRIPT_DIR/perf-worker.sh" check 2>/dev/null &
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Main command handling
|
||||
case "${1:-check}" in
|
||||
"monitor"|"continuous")
|
||||
monitor_continuous "${2:-5}"
|
||||
;;
|
||||
"check"|"once")
|
||||
check_once
|
||||
;;
|
||||
"status")
|
||||
if [ -f "$METRICS_DIR/swarm-activity.json" ]; then
|
||||
log "Current swarm activity status:"
|
||||
cat "$METRICS_DIR/swarm-activity.json" | jq . 2>/dev/null || cat "$METRICS_DIR/swarm-activity.json"
|
||||
else
|
||||
warn "No activity data available. Run 'check' first."
|
||||
fi
|
||||
;;
|
||||
"help"|"-h"|"--help")
|
||||
echo "Claude Flow V3 Swarm Monitor"
|
||||
echo ""
|
||||
echo "Usage: $0 [command] [options]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " check, once Run a single activity check and update metrics"
|
||||
echo " monitor [N] Monitor continuously every N seconds (default: 5)"
|
||||
echo " status Show current activity status"
|
||||
echo " help Show this help message"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 check # Single check"
|
||||
echo " $0 monitor 3 # Monitor every 3 seconds"
|
||||
echo " $0 status # Show current status"
|
||||
;;
|
||||
*)
|
||||
error "Unknown command: $1"
|
||||
echo "Use '$0 help' for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
245
.claude/helpers/sync-v3-metrics.sh
Executable file
245
.claude/helpers/sync-v3-metrics.sh
Executable file
@@ -0,0 +1,245 @@
|
||||
#!/bin/bash
|
||||
# Claude Flow V3 - Auto-sync Metrics from Actual Implementation
|
||||
# Scans the V3 codebase and updates metrics to reflect reality
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
V3_DIR="$PROJECT_ROOT/v3"
|
||||
METRICS_DIR="$PROJECT_ROOT/.claude-flow/metrics"
|
||||
SECURITY_DIR="$PROJECT_ROOT/.claude-flow/security"
|
||||
|
||||
# Ensure directories exist
|
||||
mkdir -p "$METRICS_DIR" "$SECURITY_DIR"
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
RESET='\033[0m'
|
||||
|
||||
log() {
|
||||
echo -e "${CYAN}[sync] $1${RESET}"
|
||||
}
|
||||
|
||||
# Count V3 modules
|
||||
count_modules() {
|
||||
local count=0
|
||||
local modules=()
|
||||
|
||||
if [ -d "$V3_DIR/@claude-flow" ]; then
|
||||
for dir in "$V3_DIR/@claude-flow"/*/; do
|
||||
if [ -d "$dir" ]; then
|
||||
name=$(basename "$dir")
|
||||
modules+=("$name")
|
||||
((count++))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "$count"
|
||||
}
|
||||
|
||||
# Calculate module completion percentage
|
||||
calculate_module_progress() {
|
||||
local module="$1"
|
||||
local module_dir="$V3_DIR/@claude-flow/$module"
|
||||
|
||||
if [ ! -d "$module_dir" ]; then
|
||||
echo "0"
|
||||
return
|
||||
fi
|
||||
|
||||
local has_src=$([ -d "$module_dir/src" ] && echo 1 || echo 0)
|
||||
local has_index=$([ -f "$module_dir/src/index.ts" ] || [ -f "$module_dir/index.ts" ] && echo 1 || echo 0)
|
||||
local has_tests=$([ -d "$module_dir/__tests__" ] || [ -d "$module_dir/tests" ] && echo 1 || echo 0)
|
||||
local has_package=$([ -f "$module_dir/package.json" ] && echo 1 || echo 0)
|
||||
local file_count=$(find "$module_dir" -name "*.ts" -type f 2>/dev/null | wc -l)
|
||||
|
||||
# Calculate progress based on structure and content
|
||||
local progress=0
|
||||
[ "$has_src" -eq 1 ] && ((progress += 20))
|
||||
[ "$has_index" -eq 1 ] && ((progress += 20))
|
||||
[ "$has_tests" -eq 1 ] && ((progress += 20))
|
||||
[ "$has_package" -eq 1 ] && ((progress += 10))
|
||||
[ "$file_count" -gt 5 ] && ((progress += 15))
|
||||
[ "$file_count" -gt 10 ] && ((progress += 15))
|
||||
|
||||
# Cap at 100
|
||||
[ "$progress" -gt 100 ] && progress=100
|
||||
|
||||
echo "$progress"
|
||||
}
|
||||
|
||||
# Check security CVE status
|
||||
check_security_status() {
|
||||
local cves_fixed=0
|
||||
local security_dir="$V3_DIR/@claude-flow/security/src"
|
||||
|
||||
# CVE-1: Input validation - check for input-validator.ts
|
||||
if [ -f "$security_dir/input-validator.ts" ]; then
|
||||
lines=$(wc -l < "$security_dir/input-validator.ts" 2>/dev/null || echo 0)
|
||||
[ "$lines" -gt 100 ] && ((cves_fixed++))
|
||||
fi
|
||||
|
||||
# CVE-2: Path traversal - check for path-validator.ts
|
||||
if [ -f "$security_dir/path-validator.ts" ]; then
|
||||
lines=$(wc -l < "$security_dir/path-validator.ts" 2>/dev/null || echo 0)
|
||||
[ "$lines" -gt 100 ] && ((cves_fixed++))
|
||||
fi
|
||||
|
||||
# CVE-3: Command injection - check for safe-executor.ts
|
||||
if [ -f "$security_dir/safe-executor.ts" ]; then
|
||||
lines=$(wc -l < "$security_dir/safe-executor.ts" 2>/dev/null || echo 0)
|
||||
[ "$lines" -gt 100 ] && ((cves_fixed++))
|
||||
fi
|
||||
|
||||
echo "$cves_fixed"
|
||||
}
|
||||
|
||||
# Calculate overall DDD progress
|
||||
calculate_ddd_progress() {
|
||||
local total_progress=0
|
||||
local module_count=0
|
||||
|
||||
for dir in "$V3_DIR/@claude-flow"/*/; do
|
||||
if [ -d "$dir" ]; then
|
||||
name=$(basename "$dir")
|
||||
progress=$(calculate_module_progress "$name")
|
||||
((total_progress += progress))
|
||||
((module_count++))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$module_count" -gt 0 ]; then
|
||||
echo $((total_progress / module_count))
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Count total lines of code
|
||||
count_total_lines() {
|
||||
find "$V3_DIR" -name "*.ts" -type f -exec cat {} \; 2>/dev/null | wc -l
|
||||
}
|
||||
|
||||
# Count total files
|
||||
count_total_files() {
|
||||
find "$V3_DIR" -name "*.ts" -type f 2>/dev/null | wc -l
|
||||
}
|
||||
|
||||
# Check domains (map modules to domains)
|
||||
count_domains() {
|
||||
local domains=0
|
||||
|
||||
# Map @claude-flow modules to DDD domains
|
||||
[ -d "$V3_DIR/@claude-flow/swarm" ] && ((domains++)) # task-management
|
||||
[ -d "$V3_DIR/@claude-flow/memory" ] && ((domains++)) # session-management
|
||||
[ -d "$V3_DIR/@claude-flow/performance" ] && ((domains++)) # health-monitoring
|
||||
[ -d "$V3_DIR/@claude-flow/cli" ] && ((domains++)) # lifecycle-management
|
||||
[ -d "$V3_DIR/@claude-flow/integration" ] && ((domains++)) # event-coordination
|
||||
|
||||
echo "$domains"
|
||||
}
|
||||
|
||||
# Main sync function
|
||||
sync_metrics() {
|
||||
log "Scanning V3 implementation..."
|
||||
|
||||
local modules=$(count_modules)
|
||||
local domains=$(count_domains)
|
||||
local ddd_progress=$(calculate_ddd_progress)
|
||||
local cves_fixed=$(check_security_status)
|
||||
local total_files=$(count_total_files)
|
||||
local total_lines=$(count_total_lines)
|
||||
local timestamp=$(date -Iseconds)
|
||||
|
||||
# Determine security status
|
||||
local security_status="PENDING"
|
||||
if [ "$cves_fixed" -eq 3 ]; then
|
||||
security_status="CLEAN"
|
||||
elif [ "$cves_fixed" -gt 0 ]; then
|
||||
security_status="IN_PROGRESS"
|
||||
fi
|
||||
|
||||
log "Found: $modules modules, $domains domains, $total_files files, $total_lines lines"
|
||||
log "DDD Progress: ${ddd_progress}%, Security: $cves_fixed/3 CVEs fixed"
|
||||
|
||||
# Update v3-progress.json
|
||||
cat > "$METRICS_DIR/v3-progress.json" << EOF
|
||||
{
|
||||
"domains": {
|
||||
"completed": $domains,
|
||||
"total": 5,
|
||||
"list": [
|
||||
{"name": "task-management", "status": "$([ -d "$V3_DIR/@claude-flow/swarm" ] && echo "complete" || echo "pending")", "module": "swarm"},
|
||||
{"name": "session-management", "status": "$([ -d "$V3_DIR/@claude-flow/memory" ] && echo "complete" || echo "pending")", "module": "memory"},
|
||||
{"name": "health-monitoring", "status": "$([ -d "$V3_DIR/@claude-flow/performance" ] && echo "complete" || echo "pending")", "module": "performance"},
|
||||
{"name": "lifecycle-management", "status": "$([ -d "$V3_DIR/@claude-flow/cli" ] && echo "complete" || echo "pending")", "module": "cli"},
|
||||
{"name": "event-coordination", "status": "$([ -d "$V3_DIR/@claude-flow/integration" ] && echo "complete" || echo "pending")", "module": "integration"}
|
||||
]
|
||||
},
|
||||
"ddd": {
|
||||
"progress": $ddd_progress,
|
||||
"modules": $modules,
|
||||
"totalFiles": $total_files,
|
||||
"totalLines": $total_lines
|
||||
},
|
||||
"swarm": {
|
||||
"activeAgents": 0,
|
||||
"totalAgents": 15,
|
||||
"topology": "hierarchical-mesh",
|
||||
"coordination": "$([ -d "$V3_DIR/@claude-flow/swarm" ] && echo "ready" || echo "pending")"
|
||||
},
|
||||
"lastUpdated": "$timestamp",
|
||||
"autoSynced": true
|
||||
}
|
||||
EOF
|
||||
|
||||
# Update security audit status
|
||||
cat > "$SECURITY_DIR/audit-status.json" << EOF
|
||||
{
|
||||
"status": "$security_status",
|
||||
"cvesFixed": $cves_fixed,
|
||||
"totalCves": 3,
|
||||
"criticalVulnerabilities": [
|
||||
{
|
||||
"id": "CVE-1",
|
||||
"description": "Input validation bypass",
|
||||
"severity": "critical",
|
||||
"status": "$([ -f "$V3_DIR/@claude-flow/security/src/input-validator.ts" ] && echo "fixed" || echo "pending")",
|
||||
"fixedBy": "input-validator.ts"
|
||||
},
|
||||
{
|
||||
"id": "CVE-2",
|
||||
"description": "Path traversal vulnerability",
|
||||
"severity": "critical",
|
||||
"status": "$([ -f "$V3_DIR/@claude-flow/security/src/path-validator.ts" ] && echo "fixed" || echo "pending")",
|
||||
"fixedBy": "path-validator.ts"
|
||||
},
|
||||
{
|
||||
"id": "CVE-3",
|
||||
"description": "Command injection vulnerability",
|
||||
"severity": "critical",
|
||||
"status": "$([ -f "$V3_DIR/@claude-flow/security/src/safe-executor.ts" ] && echo "fixed" || echo "pending")",
|
||||
"fixedBy": "safe-executor.ts"
|
||||
}
|
||||
],
|
||||
"lastAudit": "$timestamp",
|
||||
"autoSynced": true
|
||||
}
|
||||
EOF
|
||||
|
||||
log "Metrics synced successfully!"
|
||||
|
||||
# Output summary for statusline
|
||||
echo ""
|
||||
echo -e "${GREEN}V3 Implementation Status:${RESET}"
|
||||
echo " Modules: $modules"
|
||||
echo " Domains: $domains/5"
|
||||
echo " DDD Progress: ${ddd_progress}%"
|
||||
echo " Security: $cves_fixed/3 CVEs fixed ($security_status)"
|
||||
echo " Codebase: $total_files files, $total_lines lines"
|
||||
}
|
||||
|
||||
# Run sync
|
||||
sync_metrics
|
||||
166
.claude/helpers/update-v3-progress.sh
Executable file
166
.claude/helpers/update-v3-progress.sh
Executable file
@@ -0,0 +1,166 @@
|
||||
#!/bin/bash
|
||||
# V3 Progress Update Script
|
||||
# Usage: ./update-v3-progress.sh [domain|agent|security|performance] [value]
|
||||
|
||||
set -e
|
||||
|
||||
METRICS_DIR=".claude-flow/metrics"
|
||||
SECURITY_DIR=".claude-flow/security"
|
||||
|
||||
# Ensure directories exist
|
||||
mkdir -p "$METRICS_DIR" "$SECURITY_DIR"
|
||||
|
||||
case "$1" in
|
||||
"domain")
|
||||
if [ -z "$2" ]; then
|
||||
echo "Usage: $0 domain <count>"
|
||||
echo "Example: $0 domain 3"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update domain completion count
|
||||
jq --argjson count "$2" '.domains.completed = $count' \
|
||||
"$METRICS_DIR/v3-progress.json" > tmp.json && \
|
||||
mv tmp.json "$METRICS_DIR/v3-progress.json"
|
||||
|
||||
echo "✅ Updated domain count to $2/5"
|
||||
;;
|
||||
|
||||
"agent")
|
||||
if [ -z "$2" ]; then
|
||||
echo "Usage: $0 agent <count>"
|
||||
echo "Example: $0 agent 8"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update active agent count
|
||||
jq --argjson count "$2" '.swarm.activeAgents = $count' \
|
||||
"$METRICS_DIR/v3-progress.json" > tmp.json && \
|
||||
mv tmp.json "$METRICS_DIR/v3-progress.json"
|
||||
|
||||
echo "✅ Updated active agents to $2/15"
|
||||
;;
|
||||
|
||||
"security")
|
||||
if [ -z "$2" ]; then
|
||||
echo "Usage: $0 security <fixed_count>"
|
||||
echo "Example: $0 security 2"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update CVE fixes
|
||||
jq --argjson count "$2" '.cvesFixed = $count' \
|
||||
"$SECURITY_DIR/audit-status.json" > tmp.json && \
|
||||
mv tmp.json "$SECURITY_DIR/audit-status.json"
|
||||
|
||||
if [ "$2" -eq 3 ]; then
|
||||
jq '.status = "CLEAN"' \
|
||||
"$SECURITY_DIR/audit-status.json" > tmp.json && \
|
||||
mv tmp.json "$SECURITY_DIR/audit-status.json"
|
||||
fi
|
||||
|
||||
echo "✅ Updated security: $2/3 CVEs fixed"
|
||||
;;
|
||||
|
||||
"performance")
|
||||
if [ -z "$2" ]; then
|
||||
echo "Usage: $0 performance <speedup>"
|
||||
echo "Example: $0 performance 2.1x"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update performance metrics
|
||||
jq --arg speedup "$2" '.flashAttention.speedup = $speedup' \
|
||||
"$METRICS_DIR/performance.json" > tmp.json && \
|
||||
mv tmp.json "$METRICS_DIR/performance.json"
|
||||
|
||||
echo "✅ Updated Flash Attention speedup to $2"
|
||||
;;
|
||||
|
||||
"memory")
|
||||
if [ -z "$2" ]; then
|
||||
echo "Usage: $0 memory <percentage>"
|
||||
echo "Example: $0 memory 45%"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update memory reduction
|
||||
jq --arg reduction "$2" '.memory.reduction = $reduction' \
|
||||
"$METRICS_DIR/performance.json" > tmp.json && \
|
||||
mv tmp.json "$METRICS_DIR/performance.json"
|
||||
|
||||
echo "✅ Updated memory reduction to $2"
|
||||
;;
|
||||
|
||||
"ddd")
|
||||
if [ -z "$2" ]; then
|
||||
echo "Usage: $0 ddd <percentage>"
|
||||
echo "Example: $0 ddd 65"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update DDD progress percentage
|
||||
jq --argjson progress "$2" '.ddd.progress = $progress' \
|
||||
"$METRICS_DIR/v3-progress.json" > tmp.json && \
|
||||
mv tmp.json "$METRICS_DIR/v3-progress.json"
|
||||
|
||||
echo "✅ Updated DDD progress to $2%"
|
||||
;;
|
||||
|
||||
"status")
|
||||
# Show current status
|
||||
echo "📊 V3 Development Status:"
|
||||
echo "========================"
|
||||
|
||||
if [ -f "$METRICS_DIR/v3-progress.json" ]; then
|
||||
domains=$(jq -r '.domains.completed // 0' "$METRICS_DIR/v3-progress.json")
|
||||
agents=$(jq -r '.swarm.activeAgents // 0' "$METRICS_DIR/v3-progress.json")
|
||||
ddd=$(jq -r '.ddd.progress // 0' "$METRICS_DIR/v3-progress.json")
|
||||
echo "🏗️ Domains: $domains/5"
|
||||
echo "🤖 Agents: $agents/15"
|
||||
echo "📐 DDD: $ddd%"
|
||||
fi
|
||||
|
||||
if [ -f "$SECURITY_DIR/audit-status.json" ]; then
|
||||
cves=$(jq -r '.cvesFixed // 0' "$SECURITY_DIR/audit-status.json")
|
||||
echo "🛡️ Security: $cves/3 CVEs fixed"
|
||||
fi
|
||||
|
||||
if [ -f "$METRICS_DIR/performance.json" ]; then
|
||||
speedup=$(jq -r '.flashAttention.speedup // "1.0x"' "$METRICS_DIR/performance.json")
|
||||
memory=$(jq -r '.memory.reduction // "0%"' "$METRICS_DIR/performance.json")
|
||||
echo "⚡ Performance: $speedup speedup, $memory memory saved"
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "V3 Progress Update Tool"
|
||||
echo "======================"
|
||||
echo ""
|
||||
echo "Usage: $0 <command> [value]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " domain <0-5> Update completed domain count"
|
||||
echo " agent <0-15> Update active agent count"
|
||||
echo " security <0-3> Update fixed CVE count"
|
||||
echo " performance <x.x> Update Flash Attention speedup"
|
||||
echo " memory <xx%> Update memory reduction percentage"
|
||||
echo " ddd <0-100> Update DDD progress percentage"
|
||||
echo " status Show current status"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 domain 3 # Mark 3 domains as complete"
|
||||
echo " $0 agent 8 # Set 8 agents as active"
|
||||
echo " $0 security 2 # Mark 2 CVEs as fixed"
|
||||
echo " $0 performance 2.5x # Set speedup to 2.5x"
|
||||
echo " $0 memory 35% # Set memory reduction to 35%"
|
||||
echo " $0 ddd 75 # Set DDD progress to 75%"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Show updated statusline if not just showing help
|
||||
if [ "$1" != "" ] && [ "$1" != "status" ]; then
|
||||
echo ""
|
||||
echo "📺 Updated Statusline:"
|
||||
bash .claude/statusline.sh
|
||||
fi
|
||||
58
.claude/helpers/v3-quick-status.sh
Executable file
58
.claude/helpers/v3-quick-status.sh
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
# V3 Quick Status - Compact development status overview
|
||||
|
||||
set -e
|
||||
|
||||
# Color codes
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
RED='\033[0;31m'
|
||||
BLUE='\033[0;34m'
|
||||
PURPLE='\033[0;35m'
|
||||
CYAN='\033[0;36m'
|
||||
RESET='\033[0m'
|
||||
|
||||
echo -e "${PURPLE}⚡ Claude Flow V3 Quick Status${RESET}"
|
||||
|
||||
# Get metrics
|
||||
DOMAINS=0
|
||||
AGENTS=0
|
||||
DDD_PROGRESS=0
|
||||
CVES_FIXED=0
|
||||
SPEEDUP="1.0x"
|
||||
MEMORY="0%"
|
||||
|
||||
if [ -f ".claude-flow/metrics/v3-progress.json" ]; then
|
||||
DOMAINS=$(jq -r '.domains.completed // 0' ".claude-flow/metrics/v3-progress.json" 2>/dev/null || echo "0")
|
||||
AGENTS=$(jq -r '.swarm.activeAgents // 0' ".claude-flow/metrics/v3-progress.json" 2>/dev/null || echo "0")
|
||||
DDD_PROGRESS=$(jq -r '.ddd.progress // 0' ".claude-flow/metrics/v3-progress.json" 2>/dev/null || echo "0")
|
||||
fi
|
||||
|
||||
if [ -f ".claude-flow/security/audit-status.json" ]; then
|
||||
CVES_FIXED=$(jq -r '.cvesFixed // 0' ".claude-flow/security/audit-status.json" 2>/dev/null || echo "0")
|
||||
fi
|
||||
|
||||
if [ -f ".claude-flow/metrics/performance.json" ]; then
|
||||
SPEEDUP=$(jq -r '.flashAttention.speedup // "1.0x"' ".claude-flow/metrics/performance.json" 2>/dev/null || echo "1.0x")
|
||||
MEMORY=$(jq -r '.memory.reduction // "0%"' ".claude-flow/metrics/performance.json" 2>/dev/null || echo "0%")
|
||||
fi
|
||||
|
||||
# Calculate progress percentages
|
||||
DOMAIN_PERCENT=$((DOMAINS * 20))
|
||||
AGENT_PERCENT=$((AGENTS * 100 / 15))
|
||||
SECURITY_PERCENT=$((CVES_FIXED * 33))
|
||||
|
||||
# Color coding
|
||||
if [ $DOMAINS -eq 5 ]; then DOMAIN_COLOR=$GREEN; elif [ $DOMAINS -ge 3 ]; then DOMAIN_COLOR=$YELLOW; else DOMAIN_COLOR=$RED; fi
|
||||
if [ $AGENTS -ge 10 ]; then AGENT_COLOR=$GREEN; elif [ $AGENTS -ge 5 ]; then AGENT_COLOR=$YELLOW; else AGENT_COLOR=$RED; fi
|
||||
if [ $DDD_PROGRESS -ge 75 ]; then DDD_COLOR=$GREEN; elif [ $DDD_PROGRESS -ge 50 ]; then DDD_COLOR=$YELLOW; else DDD_COLOR=$RED; fi
|
||||
if [ $CVES_FIXED -eq 3 ]; then SEC_COLOR=$GREEN; elif [ $CVES_FIXED -ge 1 ]; then SEC_COLOR=$YELLOW; else SEC_COLOR=$RED; fi
|
||||
|
||||
echo -e "${BLUE}Domains:${RESET} ${DOMAIN_COLOR}${DOMAINS}/5${RESET} (${DOMAIN_PERCENT}%) | ${BLUE}Agents:${RESET} ${AGENT_COLOR}${AGENTS}/15${RESET} (${AGENT_PERCENT}%) | ${BLUE}DDD:${RESET} ${DDD_COLOR}${DDD_PROGRESS}%${RESET}"
|
||||
echo -e "${BLUE}Security:${RESET} ${SEC_COLOR}${CVES_FIXED}/3${RESET} CVEs | ${BLUE}Perf:${RESET} ${CYAN}${SPEEDUP}${RESET} | ${BLUE}Memory:${RESET} ${CYAN}${MEMORY}${RESET}"
|
||||
|
||||
# Branch info
|
||||
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
|
||||
echo -e "${BLUE}Branch:${RESET} ${CYAN}${BRANCH}${RESET}"
|
||||
fi
|
||||
111
.claude/helpers/v3.sh
Executable file
111
.claude/helpers/v3.sh
Executable file
@@ -0,0 +1,111 @@
|
||||
#!/bin/bash
|
||||
# V3 Helper Alias Script - Quick access to all V3 development tools
|
||||
|
||||
set -e
|
||||
|
||||
HELPERS_DIR=".claude/helpers"
|
||||
|
||||
case "$1" in
|
||||
"status"|"st")
|
||||
"$HELPERS_DIR/v3-quick-status.sh"
|
||||
;;
|
||||
|
||||
"progress"|"prog")
|
||||
shift
|
||||
"$HELPERS_DIR/update-v3-progress.sh" "$@"
|
||||
;;
|
||||
|
||||
"validate"|"check")
|
||||
"$HELPERS_DIR/validate-v3-config.sh"
|
||||
;;
|
||||
|
||||
"statusline"|"sl")
|
||||
".claude/statusline.sh"
|
||||
;;
|
||||
|
||||
"update")
|
||||
if [ -z "$2" ] || [ -z "$3" ]; then
|
||||
echo "Usage: v3 update <metric> <value>"
|
||||
echo "Examples:"
|
||||
echo " v3 update domain 3"
|
||||
echo " v3 update agent 8"
|
||||
echo " v3 update security 2"
|
||||
echo " v3 update performance 2.5x"
|
||||
echo " v3 update memory 45%"
|
||||
echo " v3 update ddd 75"
|
||||
exit 1
|
||||
fi
|
||||
"$HELPERS_DIR/update-v3-progress.sh" "$2" "$3"
|
||||
;;
|
||||
|
||||
"full-status"|"fs")
|
||||
echo "🔍 V3 Development Environment Status"
|
||||
echo "====================================="
|
||||
echo ""
|
||||
echo "📊 Quick Status:"
|
||||
"$HELPERS_DIR/v3-quick-status.sh"
|
||||
echo ""
|
||||
echo "📺 Full Statusline:"
|
||||
".claude/statusline.sh"
|
||||
;;
|
||||
|
||||
"init")
|
||||
echo "🚀 Initializing V3 Development Environment..."
|
||||
|
||||
# Run validation first
|
||||
echo ""
|
||||
echo "1️⃣ Validating configuration..."
|
||||
if "$HELPERS_DIR/validate-v3-config.sh"; then
|
||||
echo ""
|
||||
echo "2️⃣ Showing current status..."
|
||||
"$HELPERS_DIR/v3-quick-status.sh"
|
||||
echo ""
|
||||
echo "✅ V3 development environment is ready!"
|
||||
echo ""
|
||||
echo "🔧 Quick commands:"
|
||||
echo " v3 status - Show quick status"
|
||||
echo " v3 update - Update progress metrics"
|
||||
echo " v3 statusline - Show full statusline"
|
||||
echo " v3 validate - Validate configuration"
|
||||
else
|
||||
echo ""
|
||||
echo "❌ Configuration validation failed. Please fix issues before proceeding."
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
|
||||
"help"|"--help"|"-h"|"")
|
||||
echo "Claude Flow V3 Helper Tool"
|
||||
echo "=========================="
|
||||
echo ""
|
||||
echo "Usage: v3 <command> [options]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " status, st Show quick development status"
|
||||
echo " progress, prog [args] Update progress metrics"
|
||||
echo " validate, check Validate V3 configuration"
|
||||
echo " statusline, sl Show full statusline"
|
||||
echo " full-status, fs Show both quick status and statusline"
|
||||
echo " update <metric> <value> Update specific metric"
|
||||
echo " init Initialize and validate environment"
|
||||
echo " help Show this help message"
|
||||
echo ""
|
||||
echo "Update Examples:"
|
||||
echo " v3 update domain 3 # Mark 3 domains complete"
|
||||
echo " v3 update agent 8 # Set 8 agents active"
|
||||
echo " v3 update security 2 # Mark 2 CVEs fixed"
|
||||
echo " v3 update performance 2.5x # Set performance to 2.5x"
|
||||
echo " v3 update memory 45% # Set memory reduction to 45%"
|
||||
echo " v3 update ddd 75 # Set DDD progress to 75%"
|
||||
echo ""
|
||||
echo "Quick Start:"
|
||||
echo " v3 init # Initialize environment"
|
||||
echo " v3 status # Check current progress"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown command: $1"
|
||||
echo "Run 'v3 help' for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
216
.claude/helpers/validate-v3-config.sh
Executable file
216
.claude/helpers/validate-v3-config.sh
Executable file
@@ -0,0 +1,216 @@
|
||||
#!/bin/bash
|
||||
# V3 Configuration Validation Script
|
||||
# Ensures all V3 development dependencies and configurations are properly set up
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔍 Claude Flow V3 Configuration Validation"
|
||||
echo "==========================================="
|
||||
echo ""
|
||||
|
||||
ERRORS=0
|
||||
WARNINGS=0
|
||||
|
||||
# Color codes
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[0;33m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
RESET='\033[0m'
|
||||
|
||||
# Helper functions
|
||||
log_error() {
|
||||
echo -e "${RED}❌ ERROR: $1${RESET}"
|
||||
((ERRORS++))
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠️ WARNING: $1${RESET}"
|
||||
((WARNINGS++))
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}✅ $1${RESET}"
|
||||
}
|
||||
|
||||
log_info() {
|
||||
echo -e "${BLUE}ℹ️ $1${RESET}"
|
||||
}
|
||||
|
||||
# Check 1: Required directories
|
||||
echo "📁 Checking Directory Structure..."
|
||||
required_dirs=(
|
||||
".claude"
|
||||
".claude/helpers"
|
||||
".claude-flow/metrics"
|
||||
".claude-flow/security"
|
||||
"src"
|
||||
"src/domains"
|
||||
)
|
||||
|
||||
for dir in "${required_dirs[@]}"; do
|
||||
if [ -d "$dir" ]; then
|
||||
log_success "Directory exists: $dir"
|
||||
else
|
||||
log_error "Missing required directory: $dir"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check 2: Required files
|
||||
echo ""
|
||||
echo "📄 Checking Required Files..."
|
||||
required_files=(
|
||||
".claude/settings.json"
|
||||
".claude/statusline.sh"
|
||||
".claude/helpers/update-v3-progress.sh"
|
||||
".claude-flow/metrics/v3-progress.json"
|
||||
".claude-flow/metrics/performance.json"
|
||||
".claude-flow/security/audit-status.json"
|
||||
"package.json"
|
||||
)
|
||||
|
||||
for file in "${required_files[@]}"; do
|
||||
if [ -f "$file" ]; then
|
||||
log_success "File exists: $file"
|
||||
|
||||
# Additional checks for specific files
|
||||
case "$file" in
|
||||
"package.json")
|
||||
if grep -q "agentic-flow.*alpha" "$file" 2>/dev/null; then
|
||||
log_success "agentic-flow@alpha dependency found"
|
||||
else
|
||||
log_warning "agentic-flow@alpha dependency not found in package.json"
|
||||
fi
|
||||
;;
|
||||
".claude/helpers/update-v3-progress.sh")
|
||||
if [ -x "$file" ]; then
|
||||
log_success "Helper script is executable"
|
||||
else
|
||||
log_error "Helper script is not executable: $file"
|
||||
fi
|
||||
;;
|
||||
".claude-flow/metrics/v3-progress.json")
|
||||
if jq empty "$file" 2>/dev/null; then
|
||||
log_success "V3 progress JSON is valid"
|
||||
domains=$(jq -r '.domains.total // "unknown"' "$file" 2>/dev/null)
|
||||
agents=$(jq -r '.swarm.totalAgents // "unknown"' "$file" 2>/dev/null)
|
||||
log_info "Configured for $domains domains, $agents agents"
|
||||
else
|
||||
log_error "Invalid JSON in v3-progress.json"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
else
|
||||
log_error "Missing required file: $file"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check 3: Domain structure
|
||||
echo ""
|
||||
echo "🏗️ Checking Domain Structure..."
|
||||
expected_domains=("task-management" "session-management" "health-monitoring" "lifecycle-management" "event-coordination")
|
||||
|
||||
for domain in "${expected_domains[@]}"; do
|
||||
domain_path="src/domains/$domain"
|
||||
if [ -d "$domain_path" ]; then
|
||||
log_success "Domain directory exists: $domain"
|
||||
else
|
||||
log_warning "Domain directory missing: $domain (will be created during development)"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check 4: Git configuration
|
||||
echo ""
|
||||
echo "🔀 Checking Git Configuration..."
|
||||
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
log_success "Git repository detected"
|
||||
|
||||
current_branch=$(git branch --show-current 2>/dev/null || echo "unknown")
|
||||
log_info "Current branch: $current_branch"
|
||||
|
||||
if [ "$current_branch" = "v3" ]; then
|
||||
log_success "On V3 development branch"
|
||||
else
|
||||
log_warning "Not on V3 branch (current: $current_branch)"
|
||||
fi
|
||||
else
|
||||
log_error "Not in a Git repository"
|
||||
fi
|
||||
|
||||
# Check 5: Node.js and npm
|
||||
echo ""
|
||||
echo "📦 Checking Node.js Environment..."
|
||||
if command -v node >/dev/null 2>&1; then
|
||||
node_version=$(node --version)
|
||||
log_success "Node.js installed: $node_version"
|
||||
|
||||
# Check if Node.js version is 20+
|
||||
node_major=$(echo "$node_version" | cut -d'.' -f1 | sed 's/v//')
|
||||
if [ "$node_major" -ge 20 ]; then
|
||||
log_success "Node.js version meets requirements (≥20.0.0)"
|
||||
else
|
||||
log_error "Node.js version too old. Required: ≥20.0.0, Found: $node_version"
|
||||
fi
|
||||
else
|
||||
log_error "Node.js not installed"
|
||||
fi
|
||||
|
||||
if command -v npm >/dev/null 2>&1; then
|
||||
npm_version=$(npm --version)
|
||||
log_success "npm installed: $npm_version"
|
||||
else
|
||||
log_error "npm not installed"
|
||||
fi
|
||||
|
||||
# Check 6: Development tools
|
||||
echo ""
|
||||
echo "🔧 Checking Development Tools..."
|
||||
dev_tools=("jq" "git")
|
||||
|
||||
for tool in "${dev_tools[@]}"; do
|
||||
if command -v "$tool" >/dev/null 2>&1; then
|
||||
tool_version=$($tool --version 2>/dev/null | head -n1 || echo "unknown")
|
||||
log_success "$tool installed: $tool_version"
|
||||
else
|
||||
log_error "$tool not installed"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check 7: Permissions
|
||||
echo ""
|
||||
echo "🔐 Checking Permissions..."
|
||||
test_files=(
|
||||
".claude/statusline.sh"
|
||||
".claude/helpers/update-v3-progress.sh"
|
||||
)
|
||||
|
||||
for file in "${test_files[@]}"; do
|
||||
if [ -f "$file" ]; then
|
||||
if [ -x "$file" ]; then
|
||||
log_success "Executable permissions: $file"
|
||||
else
|
||||
log_warning "Missing executable permissions: $file"
|
||||
log_info "Run: chmod +x $file"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "📊 Validation Summary"
|
||||
echo "===================="
|
||||
if [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then
|
||||
log_success "All checks passed! V3 development environment is ready."
|
||||
exit 0
|
||||
elif [ $ERRORS -eq 0 ]; then
|
||||
echo -e "${YELLOW}⚠️ $WARNINGS warnings found, but no critical errors.${RESET}"
|
||||
log_info "V3 development can proceed with minor issues to address."
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ $ERRORS critical errors found.${RESET}"
|
||||
if [ $WARNINGS -gt 0 ]; then
|
||||
echo -e "${YELLOW}⚠️ $WARNINGS warnings also found.${RESET}"
|
||||
fi
|
||||
log_error "Please fix critical errors before proceeding with V3 development."
|
||||
exit 1
|
||||
fi
|
||||
170
.claude/helpers/worker-manager.sh
Executable file
170
.claude/helpers/worker-manager.sh
Executable file
@@ -0,0 +1,170 @@
|
||||
#!/bin/bash
|
||||
# Claude Flow V3 - Unified Worker Manager
|
||||
# Orchestrates all background workers with proper scheduling
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
METRICS_DIR="$PROJECT_ROOT/.claude-flow/metrics"
|
||||
PID_FILE="$METRICS_DIR/worker-manager.pid"
|
||||
LOG_FILE="$METRICS_DIR/worker-manager.log"
|
||||
|
||||
mkdir -p "$METRICS_DIR"
|
||||
|
||||
# Worker definitions: name:script:interval_seconds
|
||||
WORKERS=(
|
||||
"perf:perf-worker.sh:300" # 5 min
|
||||
"health:health-monitor.sh:300" # 5 min
|
||||
"patterns:pattern-consolidator.sh:900" # 15 min
|
||||
"ddd:ddd-tracker.sh:600" # 10 min
|
||||
"adr:adr-compliance.sh:900" # 15 min
|
||||
"security:security-scanner.sh:1800" # 30 min
|
||||
"learning:learning-optimizer.sh:1800" # 30 min
|
||||
)
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
run_worker() {
|
||||
local name="$1"
|
||||
local script="$2"
|
||||
local script_path="$SCRIPT_DIR/$script"
|
||||
|
||||
if [ -x "$script_path" ]; then
|
||||
"$script_path" check 2>/dev/null &
|
||||
fi
|
||||
}
|
||||
|
||||
run_all_workers() {
|
||||
log "Running all workers (non-blocking)..."
|
||||
|
||||
for worker_def in "${WORKERS[@]}"; do
|
||||
IFS=':' read -r name script interval <<< "$worker_def"
|
||||
run_worker "$name" "$script"
|
||||
done
|
||||
|
||||
# Don't wait - truly non-blocking
|
||||
log "All workers spawned"
|
||||
}
|
||||
|
||||
run_daemon() {
|
||||
local interval="${1:-60}"
|
||||
|
||||
log "Starting worker manager daemon (interval: ${interval}s)"
|
||||
echo $$ > "$PID_FILE"
|
||||
|
||||
trap 'log "Shutting down..."; rm -f "$PID_FILE"; exit 0' SIGTERM SIGINT
|
||||
|
||||
while true; do
|
||||
run_all_workers
|
||||
sleep "$interval"
|
||||
done
|
||||
}
|
||||
|
||||
status_all() {
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ Claude Flow V3 - Worker Status ║"
|
||||
echo "╠══════════════════════════════════════════════════════════════╣"
|
||||
|
||||
for worker_def in "${WORKERS[@]}"; do
|
||||
IFS=':' read -r name script interval <<< "$worker_def"
|
||||
local script_path="$SCRIPT_DIR/$script"
|
||||
|
||||
if [ -x "$script_path" ]; then
|
||||
local status=$("$script_path" status 2>/dev/null || echo "No data")
|
||||
printf "║ %-10s │ %-48s ║\n" "$name" "$status"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "╠══════════════════════════════════════════════════════════════╣"
|
||||
|
||||
# Check if daemon is running
|
||||
if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
|
||||
echo "║ Daemon: RUNNING (PID: $(cat "$PID_FILE")) ║"
|
||||
else
|
||||
echo "║ Daemon: NOT RUNNING ║"
|
||||
fi
|
||||
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
}
|
||||
|
||||
force_all() {
|
||||
log "Force running all workers..."
|
||||
|
||||
for worker_def in "${WORKERS[@]}"; do
|
||||
IFS=':' read -r name script interval <<< "$worker_def"
|
||||
local script_path="$SCRIPT_DIR/$script"
|
||||
|
||||
if [ -x "$script_path" ]; then
|
||||
log "Running $name..."
|
||||
"$script_path" force 2>&1 | while read -r line; do
|
||||
log " [$name] $line"
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
log "All workers completed"
|
||||
}
|
||||
|
||||
case "${1:-help}" in
|
||||
"start"|"daemon")
|
||||
if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
|
||||
echo "Worker manager already running (PID: $(cat "$PID_FILE"))"
|
||||
exit 1
|
||||
fi
|
||||
run_daemon "${2:-60}" &
|
||||
echo "Worker manager started (PID: $!)"
|
||||
;;
|
||||
"stop")
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
kill "$(cat "$PID_FILE")" 2>/dev/null || true
|
||||
rm -f "$PID_FILE"
|
||||
echo "Worker manager stopped"
|
||||
else
|
||||
echo "Worker manager not running"
|
||||
fi
|
||||
;;
|
||||
"run"|"once")
|
||||
run_all_workers
|
||||
;;
|
||||
"force")
|
||||
force_all
|
||||
;;
|
||||
"status")
|
||||
status_all
|
||||
;;
|
||||
"logs")
|
||||
tail -50 "$LOG_FILE" 2>/dev/null || echo "No logs available"
|
||||
;;
|
||||
"help"|*)
|
||||
cat << EOF
|
||||
Claude Flow V3 - Worker Manager
|
||||
|
||||
Usage: $0 <command> [options]
|
||||
|
||||
Commands:
|
||||
start [interval] Start daemon (default: 60s cycle)
|
||||
stop Stop daemon
|
||||
run Run all workers once
|
||||
force Force run all workers (ignore throttle)
|
||||
status Show all worker status
|
||||
logs Show recent logs
|
||||
|
||||
Workers:
|
||||
perf Performance benchmarks (5 min)
|
||||
health System health monitoring (5 min)
|
||||
patterns Pattern consolidation (15 min)
|
||||
ddd DDD progress tracking (10 min)
|
||||
adr ADR compliance checking (15 min)
|
||||
security Security scanning (30 min)
|
||||
learning Learning optimization (30 min)
|
||||
|
||||
Examples:
|
||||
$0 start 120 # Start with 2-minute cycle
|
||||
$0 force # Run all now
|
||||
$0 status # Check all status
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user