claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
1,111 lines (1,009 loc) โข 38.3 kB
JavaScript
/**
* Helpers Generator
* Creates utility scripts in .claude/helpers/
*/
import { generateStatuslineScript, generateStatuslineHook } from './statusline-generator.js';
/**
* Generate pre-commit hook script
*/
export function generatePreCommitHook() {
return `#!/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"
`;
}
/**
* Generate post-commit hook script
*/
export function generatePostCommitHook() {
return `#!/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"
`;
}
/**
* Generate session manager script
*/
export function generateSessionManager() {
return `#!/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;
},
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;
`;
}
/**
* Generate agent router script
*/
export function generateAgentRouter() {
return `#!/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 };
`;
}
/**
* Generate memory helper script
*/
export function generateMemoryHelper() {
return `#!/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;
`;
}
/**
* Generate hook-handler.cjs (cross-platform hook dispatcher)
* This is the inline fallback when file copy from the package fails.
* Uses string concatenation instead of template literals to avoid escaping issues.
*/
export function generateHookHandler() {
// Build as array of lines to avoid template-in-template escaping nightmares
const lines = [
'#!/usr/bin/env node',
'/**',
' * Claude Flow Hook Handler (Cross-Platform)',
' * Dispatches hook events to the appropriate helper modules.',
' */',
'',
"const path = require('path');",
"const fs = require('fs');",
'',
'const helpersDir = __dirname;',
'',
'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'));",
'',
'const [,, command, ...args] = process.argv;',
"const prompt = process.env.PROMPT || process.env.TOOL_INPUT_command || args.join(' ') || '';",
'',
'const handlers = {',
" 'route': () => {",
' 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);',
' var output = [];',
" output.push('[INFO] Routing task: ' + (prompt.substring(0, 80) || '(no prompt)'));",
" output.push('');",
" output.push('+------------------- Primary Recommendation -------------------+');",
" output.push('| Agent: ' + result.agent.padEnd(53) + '|');",
" output.push('| Confidence: ' + (result.confidence * 100).toFixed(1) + '%' + ' '.repeat(44) + '|');",
" output.push('| Reason: ' + result.reason.substring(0, 53).padEnd(53) + '|');",
" output.push('+--------------------------------------------------------------+');",
" console.log(output.join('\\n'));",
' } else {',
" console.log('[INFO] Router not available, using default routing');",
' }',
' },',
'',
" 'pre-bash': () => {",
' var cmd = prompt.toLowerCase();',
" var dangerous = ['rm -rf /', 'format c:', 'del /s /q c:\\\\', ':(){:|:&};:'];",
' for (var i = 0; i < dangerous.length; i++) {',
' if (cmd.includes(dangerous[i])) {',
" console.error('[BLOCKED] Dangerous command detected: ' + dangerous[i]);",
' process.exit(1);',
' }',
' }',
" console.log('[OK] Command validated');",
' },',
'',
" 'post-edit': () => {",
' if (session && session.metric) {',
" try { session.metric('edits'); } catch (e) { /* no active session */ }",
' }',
' if (intelligence && intelligence.recordEdit) {',
' try {',
" var 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) {',
' var existing = session.restore && session.restore();',
' if (!existing) {',
' session.start && session.start();',
' }',
' } else {',
" console.log('[OK] Session restored: session-' + Date.now());",
' }',
' if (intelligence && intelligence.init) {',
' try {',
' var result = intelligence.init();',
' if (result && result.nodes > 0) {',
" console.log('[INTELLIGENCE] Loaded ' + result.nodes + ' patterns, ' + result.edges + ' edges');",
' }',
' } catch (e) { /* non-fatal */ }',
' }',
' },',
'',
" 'session-end': () => {",
' if (intelligence && intelligence.consolidate) {',
' try {',
' var result = intelligence.consolidate();',
' if (result && result.entries > 0) {',
" var msg = '[INTELLIGENCE] Consolidated: ' + result.entries + ' entries, ' + result.edges + ' edges';",
" if (result.newEntries > 0) msg += ', ' + result.newEntries + ' new';",
" msg += ', PageRank recomputed';",
' console.log(msg);',
' }',
' } 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 */ }",
' }',
' if (router && router.routeTask && prompt) {',
' var result = router.routeTask(prompt);',
" console.log('[INFO] Task routed to: ' + result.agent + ' (confidence: ' + result.confidence + ')');",
' } else {',
" console.log('[OK] Task started');",
' }',
' },',
'',
" 'post-task': () => {",
' if (intelligence && intelligence.feedback) {',
' try {',
' intelligence.feedback(true);',
' } catch (e) { /* non-fatal */ }',
' }',
" console.log('[OK] Task completed');",
' },',
'',
" 'compact-manual': () => {",
" console.log('PreCompact Guidance:');",
" console.log('IMPORTANT: Review CLAUDE.md in project root for:');",
" console.log(' - Available agents and concurrent usage patterns');",
" console.log(' - Swarm coordination strategies (hierarchical, mesh, adaptive)');",
" console.log(' - Critical concurrent execution rules (1 MESSAGE = ALL OPERATIONS)');",
" console.log('Ready for compact operation');",
' },',
'',
" 'compact-auto': () => {",
" console.log('Auto-Compact Guidance (Context Window Full):');",
" console.log('CRITICAL: Before compacting, ensure you understand:');",
" console.log(' - All agents available in .claude/agents/ directory');",
" console.log(' - Concurrent execution patterns from CLAUDE.md');",
" console.log(' - Swarm coordination strategies for complex tasks');",
" console.log('Apply GOLDEN RULE: Always batch operations in single messages');",
" console.log('Auto-compact proceeding with full agent context');",
' },',
'',
" 'status': () => {",
" console.log('[OK] Status check');",
' },',
'',
" 'stats': () => {",
' if (intelligence && intelligence.stats) {',
" intelligence.stats(args.includes('--json'));",
' } else {',
" console.log('[WARN] Intelligence module not available. Run session-restore first.');",
' }',
' },',
'};',
'',
'if (command && handlers[command]) {',
' try {',
' handlers[command]();',
' } catch (e) {',
" console.log('[WARN] Hook ' + command + ' encountered an error: ' + e.message);",
' }',
'} else if (command) {',
" 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|compact-manual|compact-auto|status|stats>');",
'}',
];
return lines.join('\n') + '\n';
}
/**
* Generate a minimal intelligence.cjs stub for fallback installs.
* Provides the same API as the full intelligence.cjs but with simplified logic.
* Gets overwritten when source copy succeeds (full version has PageRank, Jaccard, etc.)
*/
export function generateIntelligenceStub() {
const lines = [
'#!/usr/bin/env node',
'/**',
' * Intelligence Layer Stub (ADR-050)',
' * Minimal fallback โ full version is copied from package source.',
' * Provides: init, getContext, recordEdit, feedback, consolidate',
' */',
"'use strict';",
'',
"const fs = require('fs');",
"const path = require('path');",
"const os = require('os');",
'',
"const DATA_DIR = path.join(process.cwd(), '.claude-flow', 'data');",
"const STORE_PATH = path.join(DATA_DIR, 'auto-memory-store.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');",
'',
'function ensureDir(dir) {',
' if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });',
'}',
'',
'function readJSON(p) {',
' try { return fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, "utf-8")) : null; }',
' catch { return null; }',
'}',
'',
'function writeJSON(p, data) {',
' ensureDir(path.dirname(p));',
' fs.writeFileSync(p, JSON.stringify(data, null, 2), "utf-8");',
'}',
'',
'// Read session context key',
'function sessionGet(key) {',
' var session = readJSON(SESSION_FILE);',
' if (!session) return null;',
' return key ? (session.context || {})[key] : session.context;',
'}',
'',
'// Write session context key',
'function sessionSet(key, value) {',
' var session = readJSON(SESSION_FILE);',
' if (!session) return;',
' if (!session.context) session.context = {};',
' session.context[key] = value;',
' writeJSON(SESSION_FILE, session);',
'}',
'',
'// Tokenize text into words',
'function tokenize(text) {',
' if (!text) return [];',
' return text.toLowerCase().replace(/[^a-z0-9\\s]/g, " ").split(/\\s+/).filter(function(w) { return w.length > 2; });',
'}',
'',
'// Bootstrap entries from MEMORY.md files when store is empty',
'function bootstrapFromMemoryFiles() {',
' var entries = [];',
' var candidates = [',
' path.join(os.homedir(), ".claude", "projects"),',
' path.join(process.cwd(), ".claude-flow", "memory"),',
' path.join(process.cwd(), ".claude", "memory"),',
' ];',
' for (var i = 0; i < candidates.length; i++) {',
' try {',
' if (!fs.existsSync(candidates[i])) continue;',
' var files = [];',
' try {',
' var items = fs.readdirSync(candidates[i], { withFileTypes: true, recursive: true });',
' for (var j = 0; j < items.length; j++) {',
' if (items[j].name === "MEMORY.md") {',
' var fp = items[j].path ? path.join(items[j].path, items[j].name) : path.join(candidates[i], items[j].name);',
' files.push(fp);',
' }',
' }',
' } catch (e) { continue; }',
' for (var k = 0; k < files.length; k++) {',
' try {',
' var content = fs.readFileSync(files[k], "utf-8");',
' var sections = content.split(/^##\\s+/m).filter(function(s) { return s.trim().length > 20; });',
' for (var s = 0; s < sections.length; s++) {',
' var lines2 = sections[s].split("\\n");',
' var title = lines2[0] ? lines2[0].trim() : "section-" + s;',
' entries.push({',
' id: "mem-" + entries.length,',
' content: sections[s].substring(0, 500),',
' summary: title.substring(0, 100),',
' category: "memory",',
' confidence: 0.5,',
' sourceFile: files[k],',
' words: tokenize(sections[s].substring(0, 500)),',
' });',
' }',
' } catch (e) { /* skip */ }',
' }',
' } catch (e) { /* skip */ }',
' }',
' return entries;',
'}',
'',
'// Load entries from auto-memory-store or bootstrap from MEMORY.md',
'function loadEntries() {',
' var store = readJSON(STORE_PATH);',
' if (store && store.entries && store.entries.length > 0) {',
' return store.entries.map(function(e, i) {',
' return {',
' id: e.id || ("entry-" + i),',
' content: e.content || e.value || "",',
' summary: e.summary || e.key || "",',
' category: e.category || e.namespace || "default",',
' confidence: e.confidence || 0.5,',
' sourceFile: e.sourceFile || "",',
' words: tokenize((e.content || e.value || "") + " " + (e.summary || e.key || "")),',
' };',
' });',
' }',
' return bootstrapFromMemoryFiles();',
'}',
'',
'// Simple keyword match score',
'function matchScore(promptWords, entryWords) {',
' if (!promptWords.length || !entryWords.length) return 0;',
' var entrySet = {};',
' for (var i = 0; i < entryWords.length; i++) entrySet[entryWords[i]] = true;',
' var overlap = 0;',
' for (var j = 0; j < promptWords.length; j++) {',
' if (entrySet[promptWords[j]]) overlap++;',
' }',
' var union = Object.keys(entrySet).length + promptWords.length - overlap;',
' return union > 0 ? overlap / union : 0;',
'}',
'',
'var cachedEntries = null;',
'',
'module.exports = {',
' init: function() {',
' cachedEntries = loadEntries();',
' var ranked = cachedEntries.map(function(e) {',
' return { id: e.id, content: e.content, summary: e.summary, category: e.category, confidence: e.confidence, words: e.words };',
' });',
' writeJSON(RANKED_PATH, { version: 1, computedAt: Date.now(), entries: ranked });',
' return { nodes: cachedEntries.length, edges: 0 };',
' },',
'',
' getContext: function(prompt) {',
' if (!prompt) return null;',
' var ranked = readJSON(RANKED_PATH);',
' var entries = (ranked && ranked.entries) || (cachedEntries || []);',
' if (!entries.length) return null;',
' var promptWords = tokenize(prompt);',
' if (!promptWords.length) return null;',
' var scored = entries.map(function(e) {',
' return { entry: e, score: matchScore(promptWords, e.words || tokenize(e.content + " " + e.summary)) };',
' }).filter(function(s) { return s.score > 0.05; });',
' scored.sort(function(a, b) { return b.score - a.score; });',
' var top = scored.slice(0, 5);',
' if (!top.length) return null;',
' var prevMatched = sessionGet("lastMatchedPatterns");',
' var matchedIds = top.map(function(s) { return s.entry.id; });',
' sessionSet("lastMatchedPatterns", matchedIds);',
' if (prevMatched && Array.isArray(prevMatched)) {',
' var newSet = {};',
' for (var i = 0; i < matchedIds.length; i++) newSet[matchedIds[i]] = true;',
' }',
' var lines2 = ["[INTELLIGENCE] Relevant patterns for this task:"];',
' for (var j = 0; j < top.length; j++) {',
' var e = top[j];',
' var conf = e.entry.confidence || 0.5;',
' var summary = (e.entry.summary || e.entry.content || "").substring(0, 80);',
' lines2.push(" * (" + conf.toFixed(2) + ") " + summary);',
' }',
' return lines2.join("\\n");',
' },',
'',
' recordEdit: function(file) {',
' if (!file) return;',
' ensureDir(DATA_DIR);',
' var line = JSON.stringify({ type: "edit", file: file, timestamp: Date.now() }) + "\\n";',
' fs.appendFileSync(PENDING_PATH, line, "utf-8");',
' },',
'',
' feedback: function(success) {',
' // Stub: no-op in minimal version',
' },',
'',
' consolidate: function() {',
' var count = 0;',
' if (fs.existsSync(PENDING_PATH)) {',
' try {',
' var content = fs.readFileSync(PENDING_PATH, "utf-8").trim();',
' count = content ? content.split("\\n").length : 0;',
' fs.writeFileSync(PENDING_PATH, "", "utf-8");',
' } catch (e) { /* skip */ }',
' }',
' return { entries: count, edges: 0, newEntries: 0 };',
' },',
'};',
];
return lines.join('\n') + '\n';
}
/**
* Generate a minimal auto-memory-hook.mjs fallback for fresh installs.
* This ESM script handles import/sync/status commands gracefully when
* @claude-flow/memory is not installed. Gets overwritten when source copy succeeds.
*/
export function generateAutoMemoryHook() {
return `#!/usr/bin/env node
/**
* Auto Memory Bridge Hook (ADR-048/049) โ Minimal Fallback
* Full version is copied from package source when available.
*
* Usage:
* node auto-memory-hook.mjs import # SessionStart
* node auto-memory-hook.mjs sync # SessionEnd / Stop
* 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');
const DIM = '\\x1b[2m';
const RESET = '\\x1b[0m';
const dim = (msg) => console.log(\` \${DIM}\${msg}\${RESET}\`);
// Ensure data dir
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
async function doImport() {
// Try loading @claude-flow/memory for full functionality
let memPkg = null;
try { memPkg = await import('@claude-flow/memory'); } catch {}
if (!memPkg || !memPkg.AutoMemoryBridge) {
dim('Memory package not available โ auto memory import skipped (non-critical)');
return;
}
// Full implementation deferred to copied version
dim('Auto memory import available โ run init --upgrade for full support');
}
async function doSync() {
if (!existsSync(STORE_PATH)) {
dim('No entries to sync');
return;
}
let memPkg = null;
try { memPkg = await import('@claude-flow/memory'); } catch {}
if (!memPkg || !memPkg.AutoMemoryBridge) {
dim('Memory package not available โ sync skipped (non-critical)');
return;
}
dim('Auto memory sync available โ run init --upgrade for full support');
}
function doStatus() {
console.log('\\n=== Auto Memory Bridge Status ===\\n');
console.log(' Package: Fallback mode (run init --upgrade for full)');
console.log(\` Store: \${existsSync(STORE_PATH) ? 'Initialized' : 'Not initialized'}\`);
console.log('');
}
const command = process.argv[2] || 'status';
try {
switch (command) {
case 'import': await doImport(); break;
case 'sync': await doSync(); break;
case 'status': 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}\`);
}
`;
}
/**
* Generate Windows PowerShell daemon manager
*/
export function generateWindowsDaemonManager() {
return `# Claude Flow V3 Daemon Manager for Windows
# PowerShell script for managing background processes
param(
[Parameter(Position=0)]
[ValidateSet('start', 'stop', 'status', 'restart')]
[string]$Action = 'status'
)
$ErrorActionPreference = 'SilentlyContinue'
$ClaudeFlowDir = Join-Path $PWD '.claude-flow'
$PidDir = Join-Path $ClaudeFlowDir 'pids'
# Ensure directories exist
if (-not (Test-Path $PidDir)) {
New-Item -ItemType Directory -Path $PidDir -Force | Out-Null
}
function Get-DaemonStatus {
param([string]$Name, [string]$PidFile)
if (Test-Path $PidFile) {
$pid = Get-Content $PidFile
$process = Get-Process -Id $pid -ErrorAction SilentlyContinue
if ($process) {
return @{ Running = $true; Pid = $pid }
}
}
return @{ Running = $false; Pid = $null }
}
function Start-SwarmMonitor {
$pidFile = Join-Path $PidDir 'swarm-monitor.pid'
$status = Get-DaemonStatus -Name 'swarm-monitor' -PidFile $pidFile
if ($status.Running) {
Write-Host "Swarm monitor already running (PID: $($status.Pid))" -ForegroundColor Yellow
return
}
Write-Host "Starting swarm monitor..." -ForegroundColor Cyan
$process = Start-Process -FilePath 'node' -ArgumentList @(
'-e',
'setInterval(() => { require("fs").writeFileSync(".claude-flow/metrics/swarm-activity.json", JSON.stringify({swarm:{active:true,agent_count:0},timestamp:Date.now()})) }, 5000)'
) -PassThru -WindowStyle Hidden
$process.Id | Out-File $pidFile
Write-Host "Swarm monitor started (PID: $($process.Id))" -ForegroundColor Green
}
function Stop-SwarmMonitor {
$pidFile = Join-Path $PidDir 'swarm-monitor.pid'
$status = Get-DaemonStatus -Name 'swarm-monitor' -PidFile $pidFile
if (-not $status.Running) {
Write-Host "Swarm monitor not running" -ForegroundColor Yellow
return
}
Stop-Process -Id $status.Pid -Force
Remove-Item $pidFile -Force
Write-Host "Swarm monitor stopped" -ForegroundColor Green
}
function Show-Status {
Write-Host ""
Write-Host "Claude Flow V3 Daemon Status" -ForegroundColor Cyan
Write-Host "=============================" -ForegroundColor Cyan
$swarmPid = Join-Path $PidDir 'swarm-monitor.pid'
$swarmStatus = Get-DaemonStatus -Name 'swarm-monitor' -PidFile $swarmPid
if ($swarmStatus.Running) {
Write-Host " Swarm Monitor: RUNNING (PID: $($swarmStatus.Pid))" -ForegroundColor Green
} else {
Write-Host " Swarm Monitor: STOPPED" -ForegroundColor Red
}
Write-Host ""
}
switch ($Action) {
'start' {
Start-SwarmMonitor
Show-Status
}
'stop' {
Stop-SwarmMonitor
Show-Status
}
'restart' {
Stop-SwarmMonitor
Start-Sleep -Seconds 1
Start-SwarmMonitor
Show-Status
}
'status' {
Show-Status
}
}
`;
}
/**
* Generate Windows batch file wrapper
*/
export function generateWindowsBatchWrapper() {
return `@echo off
REM Claude Flow V3 - Windows Batch Wrapper
REM Routes to PowerShell daemon manager
PowerShell -ExecutionPolicy Bypass -File "%~dp0daemon-manager.ps1" %*
`;
}
/**
* Generate cross-platform session manager
*/
export function generateCrossPlatformSessionManager() {
return `#!/usr/bin/env node
/**
* Claude Flow Cross-Platform Session Manager
* Works on Windows, macOS, and Linux
*/
const fs = require('fs');
const path = require('path');
const os = require('os');
// Platform-specific paths
const platform = os.platform();
const homeDir = os.homedir();
// Get data directory based on platform
function getDataDir() {
const localDir = path.join(process.cwd(), '.claude-flow', 'sessions');
if (fs.existsSync(path.dirname(localDir))) {
return localDir;
}
switch (platform) {
case 'win32':
return path.join(process.env.APPDATA || homeDir, 'claude-flow', 'sessions');
case 'darwin':
return path.join(homeDir, 'Library', 'Application Support', 'claude-flow', 'sessions');
default:
return path.join(homeDir, '.claude-flow', 'sessions');
}
}
const SESSION_DIR = getDataDir();
const SESSION_FILE = path.join(SESSION_DIR, 'current.json');
// Ensure directory exists
function ensureDir(dir) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
const commands = {
start: () => {
ensureDir(SESSION_DIR);
const sessionId = \`session-\${Date.now()}\`;
const session = {
id: sessionId,
startedAt: new Date().toISOString(),
platform: platform,
cwd: process.cwd(),
context: {},
metrics: { edits: 0, commands: 0, tasks: 0, errors: 0 }
};
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();
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\`);
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(\`Platform: \${session.platform}\`);
console.log(\`Started: \${session.startedAt}\`);
console.log(\`Duration: \${Math.round(duration / 1000 / 60)} minutes\`);
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>');
console.log(\`Platform: \${platform}\`);
console.log(\`Data dir: \${SESSION_DIR}\`);
}
module.exports = commands;
`;
}
/**
* Generate all helper files
*/
export function generateHelpers(options) {
const helpers = {};
if (options.components.helpers) {
// Unix/macOS shell scripts
helpers['pre-commit'] = generatePreCommitHook();
helpers['post-commit'] = generatePostCommitHook();
// Cross-platform Node.js scripts
helpers['session.js'] = generateCrossPlatformSessionManager();
helpers['router.js'] = generateAgentRouter();
helpers['memory.js'] = generateMemoryHelper();
// Windows-specific scripts
helpers['daemon-manager.ps1'] = generateWindowsDaemonManager();
helpers['daemon-manager.cmd'] = generateWindowsBatchWrapper();
}
if (options.components.statusline) {
helpers['statusline.cjs'] = generateStatuslineScript(options); // .cjs for ES module compatibility
helpers['statusline-hook.sh'] = generateStatuslineHook(options);
}
return helpers;
}
//# sourceMappingURL=helpers-generator.js.map