claude-flow-novice
Version:
Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes CodeSearch (hybrid SQLite + pgvector), mem0/memgraph specialists, and all CFN skills.
991 lines (987 loc) • 40.4 kB
JavaScript
/**
* Skills Database CLI Tool
* Phase 4: CLI Tooling with Approval Workflow
*
* Commands:
* - list: List skills with filtering
* - assign: Assign skills to agents
* - create: Create new skills
* - update: Update skill metadata
* - deprecate: Deprecate skills
* - approve: Approve pending skills
* - escalate: Escalate skills for review
* - pending: List pending approvals
* - approval-status: Check skill approval status
* - analytics: Skill effectiveness analytics
*/ import { createRequire } from 'module';
import { existsSync, readFileSync } from 'fs';
import { createHash } from 'crypto';
import { fileURLToPath } from 'url';
import path from 'path';
const require = createRequire(import.meta.url);
const Database = require('better-sqlite3');
// ============================================================================
// ANSI Color Codes
// ============================================================================
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
dim: '\x1b[2m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
white: '\x1b[37m'
};
const chalk = {
red: (text)=>`${colors.red}${text}${colors.reset}`,
green: (text)=>`${colors.green}${text}${colors.reset}`,
yellow: (text)=>`${colors.yellow}${text}${colors.reset}`,
blue: (text)=>`${colors.blue}${text}${colors.reset}`,
magenta: (text)=>`${colors.magenta}${text}${colors.reset}`,
cyan: (text)=>`${colors.cyan}${text}${colors.reset}`,
bold: (text)=>`${colors.bright}${text}${colors.reset}`,
dim: (text)=>`${colors.dim}${text}${colors.reset}`
};
// ============================================================================
// Database Connection
// ============================================================================
const DB_PATH = process.env.CFN_SKILLS_DB_PATH || './.claude/skills-database/skills.db';
function getDb() {
if (!existsSync(DB_PATH)) {
console.error(chalk.red(`Error: Skills database not found at ${DB_PATH}`));
process.exit(1);
}
return new Database(DB_PATH);
}
// ============================================================================
// Utility Functions
// ============================================================================
function calculateHash(content) {
return createHash('sha256').update(content).digest('hex');
}
// Strip ANSI escape codes for accurate string length measurement
function stripAnsi(str) {
return str.replace(/\x1b\[[0-9;]*m/g, '');
}
function formatTable(headers, rows) {
if (rows.length === 0) {
return 'No results found.';
}
// Calculate column widths - strip ANSI codes before measuring
const colWidths = headers.map((header, i)=>{
const maxDataWidth = Math.max(...rows.map((row)=>stripAnsi((row[i] || '').toString()).length));
return Math.max(stripAnsi(header).length, maxDataWidth);
});
// Build separator
const separator = colWidths.map((w)=>'-'.repeat(w)).join('-+-');
// Build header - use stripped length for padding calculation
const headerRow = headers.map((h, i)=>{
const stripped = stripAnsi(h);
const padding = colWidths[i] - stripped.length;
return h + ' '.repeat(Math.max(0, padding));
}).join(' | ');
// Build data rows - use stripped length for padding calculation
const dataRows = rows.map((row)=>row.map((cell, i)=>{
const cellStr = (cell || '').toString();
const stripped = stripAnsi(cellStr);
const padding = colWidths[i] - stripped.length;
return cellStr + ' '.repeat(Math.max(0, padding));
}).join(' | '));
return [
headerRow,
separator,
...dataRows
].join('\n');
}
function parseArgs(args) {
const parsed = {};
for(let i = 0; i < args.length; i++){
const arg = args[i];
if (arg.startsWith('--')) {
// Handle --key=value format
if (arg.includes('=')) {
const [key, ...valueParts] = arg.slice(2).split('=');
parsed[key] = valueParts.join('='); // Rejoin in case value contains '='
} else {
// Handle --key value format
const key = arg.slice(2);
const nextArg = args[i + 1];
if (nextArg && !nextArg.startsWith('--')) {
parsed[key] = nextArg;
i++;
} else {
parsed[key] = true;
}
}
}
}
return parsed;
}
// ============================================================================
// Command: list
// ============================================================================
async function cmdList(options) {
const db = getDb();
let query = 'SELECT * FROM skills WHERE 1=1';
const params = [];
// Filter by approval level
if (options.approval) {
query += ' AND approval_level = ?';
params.push(options.approval);
}
// Filter by category
if (options.category) {
query += ' AND category = ?';
params.push(options.category);
}
// Filter by team
if (options.team) {
query += ' AND team = ?';
params.push(options.team);
}
// Filter by status
if (options.status) {
query += ' AND status = ?';
params.push(options.status);
} else {
// Default to active only
query += ' AND status = ?';
params.push('active');
}
// Filter by pending approval
if (options['pending-approval']) {
query = `
SELECT s.* FROM skills s
LEFT JOIN approval_history ah ON ah.skill_id = s.id AND ah.version = s.version AND ah.decision = 'approved'
WHERE ah.id IS NULL
AND s.approval_level IN ('human', 'escalate')
AND s.status = 'active'
`;
params.length = 0; // Clear params
}
// Filter by agent
if (options.agent) {
query = `
SELECT s.* FROM skills s
JOIN agent_skill_mappings m ON m.skill_id = s.id
WHERE m.agent_type = ?
AND s.status = 'active'
`;
params.length = 0;
params.push(options.agent);
}
query += ' ORDER BY id ASC';
const skills = db.prepare(query).all(...params);
if (skills.length === 0) {
console.log(chalk.yellow('No skills found matching criteria.'));
db.close();
return;
}
// Count agents for each skill
const skillsWithAgentCount = skills.map((skill)=>{
const count = db.prepare('SELECT COUNT(*) as count FROM agent_skill_mappings WHERE skill_id = ?').get(skill.id);
return {
...skill,
agent_count: count.count
};
});
// Format as table
const headers = [
'ID',
'Name',
'Category',
'Approval',
'Version',
'Status',
'Agents'
];
const rows = skillsWithAgentCount.map((s)=>[
s.id.toString(),
s.name,
s.category,
s.approval_level === 'auto' ? chalk.green(s.approval_level) : s.approval_level === 'escalate' ? chalk.yellow(s.approval_level) : chalk.red(s.approval_level),
s.version,
s.status === 'active' ? chalk.green(s.status) : chalk.dim(s.status),
s.agent_count.toString()
]);
console.log(formatTable(headers, rows));
console.log(`\nTotal: ${skills.length} skill(s)`);
db.close();
}
// ============================================================================
// Command: assign
// ============================================================================
async function cmdAssign(options) {
const db = getDb();
const { agent, skill, priority, required, condition } = options;
if (!agent || !skill) {
console.error(chalk.red('Error: --agent and --skill are required'));
process.exit(1);
}
// Get skill ID
const skillRecord = db.prepare('SELECT id FROM skills WHERE name = ?').get(skill);
if (!skillRecord) {
console.error(chalk.red(`Error: Skill not found: ${skill}`));
db.close();
process.exit(1);
}
// Check if mapping already exists
const existing = db.prepare('SELECT id FROM agent_skill_mappings WHERE agent_type = ? AND skill_id = ?').get(agent, skillRecord.id);
if (existing) {
console.error(chalk.yellow(`Warning: Mapping already exists for ${agent} → ${skill}`));
db.close();
return;
}
// Insert mapping
db.prepare(`
INSERT INTO agent_skill_mappings (agent_type, skill_id, priority, required, conditions)
VALUES (?, ?, ?, ?, ?)
`).run(agent, skillRecord.id, priority ? parseInt(priority) : 5, required ? 1 : 0, condition ? JSON.stringify({
taskContext: [
condition
]
}) : null);
console.log(chalk.green(`✓ Assigned skill "${skill}" to agent "${agent}"`));
console.log(` Priority: ${priority || 5}`);
console.log(` Required: ${required ? 'Yes' : 'No'}`);
if (condition) {
console.log(` Condition: taskContext contains "${condition}"`);
}
db.close();
}
// ============================================================================
// Command: create
// ============================================================================
async function cmdCreate(options) {
const db = getDb();
const { name, category, team, 'content-path': contentPath, tags, version, 'approval-level': approvalLevel, owner } = options;
if (!name || !category || !contentPath) {
console.error(chalk.red('Error: --name, --category, and --content-path are required'));
process.exit(1);
}
// Validate content path exists
const fullPath = path.resolve(contentPath);
if (!existsSync(fullPath)) {
console.error(chalk.red(`Error: Content file not found: ${fullPath}`));
process.exit(1);
}
// Calculate hash
const content = readFileSync(fullPath, 'utf-8');
const hash = calculateHash(content);
// Parse tags
const tagArray = tags ? tags.split(',').map((t)=>t.trim()) : [];
// Insert skill
try {
db.prepare(`
INSERT INTO skills (
name, category, team, content_path, content_hash, tags, version, status,
approval_level, owner, generated_by
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(name, category, team || 'default', contentPath, hash, JSON.stringify(tagArray), version || '1.0.0', 'active', approvalLevel || 'human', owner || 'unknown', 'manual');
console.log(chalk.green(`✓ Created skill: ${name}`));
console.log(` Category: ${category}`);
console.log(` Version: ${version || '1.0.0'}`);
console.log(` Approval: ${approvalLevel || 'human'}`);
console.log(` Path: ${contentPath}`);
console.log(` Hash: ${hash.slice(0, 16)}...`);
} catch (error) {
if (error.message.includes('UNIQUE constraint failed')) {
console.error(chalk.red(`Error: Skill "${name}" already exists`));
} else {
console.error(chalk.red(`Error: ${error.message}`));
}
db.close();
process.exit(1);
}
db.close();
}
// ============================================================================
// Command: update
// ============================================================================
async function cmdUpdate(options) {
const db = getDb();
const { skill, version, tags, 'recalculate-hash': recalcHash, 'approval-level': approvalLevel } = options;
if (!skill) {
console.error(chalk.red('Error: --skill is required'));
process.exit(1);
}
// Get skill
const skillRecord = db.prepare('SELECT * FROM skills WHERE name = ?').get(skill);
if (!skillRecord) {
console.error(chalk.red(`Error: Skill not found: ${skill}`));
db.close();
process.exit(1);
}
const updates = [];
const params = [];
if (version) {
updates.push('version = ?');
params.push(version);
}
if (tags) {
const tagArray = tags.split(',').map((t)=>t.trim());
updates.push('tags = ?');
params.push(JSON.stringify(tagArray));
}
if (approvalLevel) {
updates.push('approval_level = ?');
params.push(approvalLevel);
}
if (recalcHash) {
const content = readFileSync(skillRecord.content_path, 'utf-8');
const newHash = calculateHash(content);
updates.push('content_hash = ?');
params.push(newHash);
console.log(chalk.blue(`New hash: ${newHash.slice(0, 16)}...`));
}
if (updates.length === 0) {
console.log(chalk.yellow('No updates specified'));
db.close();
return;
}
updates.push('updated_at = datetime(\'now\')');
params.push(skill);
db.prepare(`UPDATE skills SET ${updates.join(', ')} WHERE name = ?`).run(...params);
console.log(chalk.green(`✓ Updated skill: ${skill}`));
db.close();
}
// ============================================================================
// Command: deprecate
// ============================================================================
async function cmdDeprecate(options) {
const db = getDb();
const { skill, replacement, note } = options;
if (!skill) {
console.error(chalk.red('Error: --skill is required'));
process.exit(1);
}
// Get skill ID
const skillRecord = db.prepare('SELECT id FROM skills WHERE name = ?').get(skill);
if (!skillRecord) {
console.error(chalk.red(`Error: Skill not found: ${skill}`));
db.close();
process.exit(1);
}
// Get replacement ID if specified
let replacementId = null;
if (replacement) {
const replacementRecord = db.prepare('SELECT id FROM skills WHERE name = ?').get(replacement);
if (replacementRecord) {
replacementId = replacementRecord.id;
}
}
// Update skill
db.prepare(`
UPDATE skills
SET status = 'deprecated',
deprecation_note = ?,
replacement_id = ?,
updated_at = datetime('now')
WHERE id = ?
`).run(note || 'Deprecated', replacementId, skillRecord.id);
console.log(chalk.yellow(`⚠ Deprecated skill: ${skill}`));
if (replacement) {
console.log(` Replacement: ${replacement}`);
}
if (note) {
console.log(` Note: ${note}`);
}
db.close();
}
// ============================================================================
// Command: approve
// ============================================================================
async function cmdApprove(options) {
const db = getDb();
const { skill, version, approver, decision, reasoning } = options;
if (!skill || !decision) {
console.error(chalk.red('Error: --skill and --decision are required'));
process.exit(1);
}
if (![
'approved',
'rejected'
].includes(decision)) {
console.error(chalk.red('Error: --decision must be "approved" or "rejected"'));
process.exit(1);
}
// Get skill
const skillRecord = db.prepare('SELECT * FROM skills WHERE name = ?').get(skill);
if (!skillRecord) {
console.error(chalk.red(`Error: Skill not found: ${skill}`));
db.close();
process.exit(1);
}
// Record approval
db.prepare(`
INSERT INTO approval_history (skill_id, version, approval_level, approver, decision, reasoning, timestamp)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
`).run(skillRecord.id, version || skillRecord.version, skillRecord.approval_level, approver || 'system', decision, reasoning || null);
// Update skill status
if (decision === 'approved') {
db.prepare('UPDATE skills SET status = ? WHERE id = ?').run('active', skillRecord.id);
console.log(chalk.green(`✓ Skill approved: ${skill} (v${version || skillRecord.version})`));
} else {
db.prepare('UPDATE skills SET status = ? WHERE id = ?').run('archived', skillRecord.id);
console.log(chalk.red(`✗ Skill rejected: ${skill}`));
}
if (reasoning) {
console.log(` Reasoning: ${reasoning}`);
}
db.close();
}
// ============================================================================
// Command: escalate
// ============================================================================
async function cmdEscalate(options) {
const db = getDb();
const { skill, version, reason } = options;
if (!skill || !reason) {
console.error(chalk.red('Error: --skill and --reason are required'));
process.exit(1);
}
// Get skill
const skillRecord = db.prepare('SELECT * FROM skills WHERE name = ?').get(skill);
if (!skillRecord) {
console.error(chalk.red(`Error: Skill not found: ${skill}`));
db.close();
process.exit(1);
}
// Update approval level to escalate
db.prepare('UPDATE skills SET approval_level = ? WHERE id = ?').run('escalate', skillRecord.id);
// Record escalation in approval history
db.prepare(`
INSERT INTO approval_history (skill_id, version, approval_level, approver, decision, reasoning, timestamp)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
`).run(skillRecord.id, version || skillRecord.version, 'escalate', 'system', 'escalated', reason);
console.log(chalk.yellow(`⚠ Skill escalated: ${skill}`));
console.log(` Reason: ${reason}`);
console.log(` Awaiting expert review...`);
db.close();
}
// ============================================================================
// Command: pending
// ============================================================================
async function cmdPending(options) {
const db = getDb();
const approvalLevel = options['approval-level'];
let query = `
SELECT s.* FROM skills s
LEFT JOIN approval_history ah ON ah.skill_id = s.id AND ah.version = s.version AND ah.decision = 'approved'
WHERE ah.id IS NULL
AND s.approval_level != 'auto'
AND s.status = 'active'
`;
const params = [];
if (approvalLevel) {
query += ' AND s.approval_level = ?';
params.push(approvalLevel);
}
query += ' ORDER BY s.created_at DESC';
const skills = db.prepare(query).all(...params);
if (skills.length === 0) {
console.log(chalk.green('✓ No pending approvals'));
db.close();
return;
}
const headers = [
'ID',
'Name',
'Category',
'Approval Level',
'Version',
'Created'
];
const rows = skills.map((s)=>[
s.id.toString(),
s.name,
s.category,
s.approval_level === 'escalate' ? chalk.yellow(s.approval_level) : chalk.red(s.approval_level),
s.version,
s.created_at.split('T')[0]
]);
console.log(formatTable(headers, rows));
console.log(`\n${chalk.yellow(`Pending: ${skills.length} skill(s)`)}`);
db.close();
}
// ============================================================================
// Command: approval-status
// ============================================================================
async function cmdApprovalStatus(options) {
const db = getDb();
const { skill } = options;
if (!skill) {
console.error(chalk.red('Error: --skill is required'));
process.exit(1);
}
// Get skill
const skillRecord = db.prepare('SELECT * FROM skills WHERE name = ?').get(skill);
if (!skillRecord) {
console.error(chalk.red(`Error: Skill not found: ${skill}`));
db.close();
process.exit(1);
}
console.log(chalk.bold(`\nApproval Status: ${skill}`));
console.log(`${'─'.repeat(50)}`);
console.log(`Approval Level: ${skillRecord.approval_level}`);
console.log(`Current Status: ${skillRecord.status}`);
// Get approval history
const history = db.prepare(`
SELECT * FROM approval_history
WHERE skill_id = ?
ORDER BY timestamp DESC
`).all(skillRecord.id);
if (history.length === 0) {
console.log(chalk.yellow('\nNo approval history'));
} else {
console.log(chalk.bold('\nApproval History:'));
history.forEach((entry, i)=>{
console.log(`\n${i + 1}. ${entry.decision.toUpperCase()} (${entry.timestamp.split('T')[0]})`);
console.log(` Version: ${entry.version}`);
console.log(` Approver: ${entry.approver || 'system'}`);
if (entry.reasoning) {
console.log(` Reasoning: ${entry.reasoning}`);
}
});
}
db.close();
}
// ============================================================================
// Command: analytics
// ============================================================================
async function cmdAnalytics(options, subcommand) {
const db = getDb();
if (!subcommand) {
console.error(chalk.red('Error: Analytics subcommand required'));
console.log('\nAvailable subcommands:');
console.log(' effectiveness - Skill effectiveness by approval level');
console.log(' velocity - Approval velocity and SLA compliance');
console.log(' bottlenecks - Identify approval bottlenecks');
console.log(' by-approval - Skills grouped by approval level');
console.log(' effectiveness-by-approval - Effectiveness metrics grouped by approval level');
console.log(' phase4-performance - Performance of Phase4-generated skills');
console.log(' approval-efficiency - Approval workflow efficiency metrics');
process.exit(1);
}
switch(subcommand){
case 'effectiveness':
await analyticsEffectiveness(db, options);
break;
case 'velocity':
await analyticsVelocity(db, options);
break;
case 'bottlenecks':
await analyticsBottlenecks(db, options);
break;
case 'by-approval':
await analyticsByApproval(db, options);
break;
case 'effectiveness-by-approval':
await analyticsEffectivenessByApproval(db, options);
break;
case 'phase4-performance':
await analyticsPhase4Performance(db, options);
break;
case 'approval-efficiency':
await analyticsApprovalEfficiency(db, options);
break;
default:
console.error(chalk.red(`Error: Unknown analytics subcommand: ${subcommand}`));
process.exit(1);
}
db.close();
}
async function analyticsEffectiveness(db, options) {
const days = parseInt(options.days || '30');
console.log(chalk.bold(`\nSkill Effectiveness by Approval Level (${days} days)`));
console.log(`${'─'.repeat(60)}`);
const approvalLevels = [
'auto',
'escalate',
'human'
];
for (const level of approvalLevels){
const stats = db.prepare(`
SELECT
COUNT(DISTINCT sul.skill_id) as skill_count,
COUNT(*) as usage_count,
AVG(sul.confidence_after - sul.confidence_before) as avg_impact,
AVG(sul.execution_time_ms) as avg_time
FROM skill_usage_log sul
JOIN skills s ON s.id = sul.skill_id
WHERE s.approval_level = ?
AND sul.loaded_at >= datetime('now', '-${days} days')
AND sul.confidence_before IS NOT NULL
AND sul.confidence_after IS NOT NULL
`).get(level);
console.log(`\n${chalk.bold(level.toUpperCase())} skills:`);
console.log(` Skills: ${stats.skill_count || 0}`);
console.log(` Usages: ${stats.usage_count || 0}`);
console.log(` Avg Confidence Impact: ${stats.avg_impact ? `+${stats.avg_impact.toFixed(3)}` : 'N/A'}`);
console.log(` Avg Execution Time: ${stats.avg_time ? `${stats.avg_time.toFixed(1)}ms` : 'N/A'}`);
}
}
async function analyticsVelocity(db, options) {
const days = parseInt(options.days || '30');
console.log(chalk.bold(`\nApproval Velocity (${days} days)`));
console.log(`${'─'.repeat(50)}`);
const approvals = db.prepare(`
SELECT approval_level, decision, COUNT(*) as count, AVG(julianday('now') - julianday(timestamp)) as avg_days
FROM approval_history
WHERE timestamp >= datetime('now', '-${days} days')
GROUP BY approval_level, decision
`).all();
if (approvals.length === 0) {
console.log(chalk.yellow('\nNo approvals in the specified timeframe'));
return;
}
approvals.forEach((stat)=>{
console.log(`\n${stat.approval_level.toUpperCase()} - ${stat.decision}:`);
console.log(` Count: ${stat.count}`);
console.log(` Avg Time: ${stat.avg_days.toFixed(1)} days`);
});
// SLA compliance (example: human approvals should be within 7 days)
const slaTarget = 7;
const humanApprovals = db.prepare(`
SELECT COUNT(*) as total,
SUM(CASE WHEN julianday('now') - julianday(timestamp) <= ${slaTarget} THEN 1 ELSE 0 END) as within_sla
FROM approval_history
WHERE approval_level = 'human'
AND timestamp >= datetime('now', '-${days} days')
`).get();
if (humanApprovals && humanApprovals.total > 0) {
const compliance = humanApprovals.within_sla / humanApprovals.total * 100;
console.log(`\n${chalk.bold('SLA Compliance')} (${slaTarget} days):`);
console.log(` Human Approvals: ${compliance.toFixed(1)}% (${humanApprovals.within_sla}/${humanApprovals.total})`);
}
}
async function analyticsBottlenecks(db, options) {
console.log(chalk.bold('\nApproval Bottlenecks'));
console.log(`${'─'.repeat(50)}`);
// Skills waiting longest for approval
const pending = db.prepare(`
SELECT s.name, s.approval_level, s.created_at,
julianday('now') - julianday(s.created_at) as days_waiting
FROM skills s
LEFT JOIN approval_history ah ON ah.skill_id = s.id AND ah.version = s.version AND ah.decision = 'approved'
WHERE ah.id IS NULL
AND s.approval_level != 'auto'
AND s.status = 'active'
ORDER BY days_waiting DESC
LIMIT 10
`).all();
if (pending.length === 0) {
console.log(chalk.green('\n✓ No pending approvals'));
return;
}
console.log('\nLongest Pending Approvals:');
pending.forEach((skill, i)=>{
console.log(`\n${i + 1}. ${skill.name} (${skill.approval_level})`);
console.log(` Waiting: ${Math.floor(skill.days_waiting)} days`);
});
}
async function analyticsByApproval(db, options) {
console.log(chalk.bold('\nSkills by Approval Level'));
console.log(`${'─'.repeat(50)}`);
const stats = db.prepare(`
SELECT approval_level, status, COUNT(*) as count
FROM skills
GROUP BY approval_level, status
ORDER BY approval_level, status
`).all();
const grouped = {};
stats.forEach((stat)=>{
if (!grouped[stat.approval_level]) {
grouped[stat.approval_level] = [];
}
grouped[stat.approval_level].push(stat);
});
Object.keys(grouped).forEach((level)=>{
console.log(`\n${chalk.bold(level.toUpperCase())}:`);
grouped[level].forEach((stat)=>{
console.log(` ${stat.status}: ${stat.count}`);
});
});
}
// ============================================================================
// Command: analytics - Phase 6.2 New Subcommands
// ============================================================================
async function analyticsEffectivenessByApproval(db, options) {
const days = parseInt(options.days || '30');
console.log(chalk.bold(`\nSkill Effectiveness by Approval Level (${days} days)`));
console.log(`${'─'.repeat(70)}\n`);
const approvalLevels = [
'auto',
'human',
'escalate'
];
for (const level of approvalLevels){
// Get stats for this approval level
const stats = db.prepare(`
SELECT
COUNT(DISTINCT sul.skill_id) as skill_count,
COUNT(*) as usage_count,
AVG(sul.confidence_after - sul.confidence_before) as avg_confidence_impact,
AVG(sul.execution_time_ms) as avg_execution_time,
SUM(CASE WHEN (sul.confidence_after - sul.confidence_before) > 0.05 THEN 1 ELSE 0 END) as success_count
FROM skill_usage_log sul
JOIN skills s ON s.id = sul.skill_id
WHERE s.approval_level = ?
AND sul.loaded_at >= datetime('now', '-${days} days')
AND sul.confidence_before IS NOT NULL
AND sul.confidence_after IS NOT NULL
`).get(level);
if (!stats || stats.usage_count === 0) {
console.log(chalk.bold(`${level.charAt(0).toUpperCase() + level.slice(1)}-approved skills:`));
console.log(chalk.dim(' No usage data available\n'));
continue;
}
const successRate = stats.usage_count > 0 ? stats.success_count / stats.usage_count * 100 : 0;
console.log(chalk.bold(`${level.charAt(0).toUpperCase() + level.slice(1)}-approved skills:`));
console.log(` Avg confidence impact: ${stats.avg_confidence_impact ? chalk.green(`+${stats.avg_confidence_impact.toFixed(2)}`) : 'N/A'}`);
console.log(` Usage count: ${chalk.cyan(stats.usage_count.toLocaleString())}`);
console.log(` Success rate: ${chalk.green(`${successRate.toFixed(1)}%`)} (${stats.success_count}/${stats.usage_count} usages)`);
console.log(` Avg execution time: ${stats.avg_execution_time ? `${stats.avg_execution_time.toFixed(1)}ms` : 'N/A'}\n`);
}
}
async function analyticsPhase4Performance(db, options) {
const days = parseInt(options.days || '30');
console.log(chalk.bold(`\nPhase 4 Generated Skills Performance (${days} days)`));
console.log(`${'─'.repeat(70)}\n`);
// Get overall Phase4 stats
const overallStats = db.prepare(`
SELECT
COUNT(DISTINCT sul.skill_id) as skill_count,
COUNT(*) as total_usages,
AVG(sul.execution_time_ms) as avg_execution_time,
AVG(sul.confidence_after - sul.confidence_before) as avg_confidence_impact
FROM skill_usage_log sul
JOIN skills s ON s.id = sul.skill_id
WHERE s.is_auto_generated = 1
AND s.generated_by = 'phase4'
AND sul.loaded_at >= datetime('now', '-${days} days')
AND sul.confidence_before IS NOT NULL
AND sul.confidence_after IS NOT NULL
`).get();
if (!overallStats || overallStats.total_usages === 0) {
console.log(chalk.yellow('No Phase4 skill usage data found in the specified timeframe.'));
console.log(chalk.dim('Phase4 skills may not have been used recently, or usage logging may not be enabled.\n'));
return;
}
console.log(chalk.bold('Overall Metrics:'));
console.log(` Total Phase4 skill usages: ${chalk.cyan(overallStats.total_usages.toLocaleString())}`);
console.log(` Unique Phase4 skills used: ${chalk.cyan(overallStats.skill_count)}`);
console.log(` Avg execution time: ${overallStats.avg_execution_time ? `${overallStats.avg_execution_time.toFixed(1)}ms` : 'N/A'}`);
console.log(` Avg confidence impact: ${overallStats.avg_confidence_impact ? chalk.green(`+${overallStats.avg_confidence_impact.toFixed(2)}`) : 'N/A'}`);
console.log(chalk.dim(' Cost savings: N/A (requires cost tracking implementation)\n'));
// Get top 5 Phase4 skills
const topSkills = db.prepare(`
SELECT
s.name,
COUNT(*) as usage_count,
AVG(sul.confidence_after - sul.confidence_before) as avg_confidence_impact,
AVG(sul.execution_time_ms) as avg_execution_time
FROM skill_usage_log sul
JOIN skills s ON s.id = sul.skill_id
WHERE s.is_auto_generated = 1
AND s.generated_by = 'phase4'
AND sul.loaded_at >= datetime('now', '-${days} days')
AND sul.confidence_before IS NOT NULL
AND sul.confidence_after IS NOT NULL
GROUP BY s.id, s.name
ORDER BY usage_count DESC
LIMIT 5
`).all();
if (topSkills.length > 0) {
console.log(chalk.bold('Top 5 Phase4 Skills:'));
topSkills.forEach((skill, index)=>{
const impact = skill.avg_confidence_impact ? `+${skill.avg_confidence_impact.toFixed(2)}` : 'N/A';
console.log(` ${index + 1}. ${chalk.cyan(skill.name)} (${skill.usage_count} uses, ${chalk.green(impact)} confidence)`);
});
console.log('');
}
}
async function analyticsApprovalEfficiency(db, options) {
console.log(chalk.bold('\nApproval Workflow Efficiency'));
console.log(`${'─'.repeat(70)}\n`);
// Get approval counts and timing by level
const approvalStats = db.prepare(`
SELECT
approval_level,
COUNT(*) as total_count,
AVG(review_duration_minutes) as avg_review_time_minutes,
SUM(CASE WHEN decision = 'approved' THEN 1 ELSE 0 END) as approved_count,
SUM(CASE WHEN decision = 'rejected' THEN 1 ELSE 0 END) as rejected_count,
SUM(CASE WHEN decision = 'escalated' THEN 1 ELSE 0 END) as escalated_count
FROM approval_history
GROUP BY approval_level
ORDER BY approval_level
`).all();
if (approvalStats.length === 0) {
console.log(chalk.yellow('No approval history found in the database.'));
console.log(chalk.dim('Approvals may not have been logged yet.\n'));
return;
}
// Display approval stats by level
console.log(chalk.bold('Approval Statistics by Level:\n'));
const slaTargets = {
'auto': 0,
'human': 10080,
'escalate': 2880 // 2 days in minutes
};
approvalStats.forEach((stat)=>{
const approvalRate = stat.total_count > 0 ? stat.approved_count / stat.total_count * 100 : 0;
const rejectionRate = stat.total_count > 0 ? stat.rejected_count / stat.total_count * 100 : 0;
let avgTimeDisplay = 'N/A';
if (stat.avg_review_time_minutes !== null) {
if (stat.approval_level === 'auto') {
avgTimeDisplay = 'instant';
} else if (stat.avg_review_time_minutes < 60) {
avgTimeDisplay = `${stat.avg_review_time_minutes.toFixed(1)} minutes`;
} else if (stat.avg_review_time_minutes < 1440) {
avgTimeDisplay = `${(stat.avg_review_time_minutes / 60).toFixed(1)} hours`;
} else {
avgTimeDisplay = `${(stat.avg_review_time_minutes / 1440).toFixed(1)} days`;
}
}
const slaTarget = slaTargets[stat.approval_level];
const slaDisplay = stat.approval_level === 'auto' ? 'instant' : stat.approval_level === 'human' ? '7 days' : '2 days';
console.log(chalk.bold(`${stat.approval_level.charAt(0).toUpperCase() + stat.approval_level.slice(1)}-approved:`));
console.log(` Total: ${chalk.cyan(stat.total_count.toString())} (avg time: ${avgTimeDisplay}, SLA: ${slaDisplay})`);
console.log(` Approval rate: ${chalk.green(`${approvalRate.toFixed(1)}%`)} (${stat.approved_count} approved)`);
if (stat.rejected_count > 0) {
console.log(` Rejection rate: ${chalk.red(`${rejectionRate.toFixed(1)}%`)} (${stat.rejected_count} rejected)`);
}
if (stat.escalated_count > 0) {
console.log(` Escalated: ${chalk.yellow(stat.escalated_count.toString())}`);
}
console.log('');
});
// Check for bottlenecks (pending approvals exceeding SLA)
const bottlenecks = db.prepare(`
SELECT
s.name,
s.approval_level,
julianday('now') - julianday(s.created_at) as days_pending,
s.created_at
FROM skills s
LEFT JOIN approval_history ah ON ah.skill_id = s.id AND ah.decision = 'approved'
WHERE ah.id IS NULL
AND s.approval_level != 'auto'
AND s.status = 'active'
AND (
(s.approval_level = 'human' AND julianday('now') - julianday(s.created_at) > 7)
OR
(s.approval_level = 'escalate' AND julianday('now') - julianday(s.created_at) > 2)
)
ORDER BY days_pending DESC
`).all();
if (bottlenecks.length > 0) {
console.log(chalk.bold('Bottlenecks (Approvals Exceeding SLA):\n'));
bottlenecks.forEach((skill, index)=>{
const daysOver = skill.approval_level === 'human' ? skill.days_pending - 7 : skill.days_pending - 2;
console.log(` ${index + 1}. ${chalk.yellow(skill.name)} (${skill.approval_level})`);
console.log(` Pending: ${Math.floor(skill.days_pending)} days (${chalk.red(`${daysOver.toFixed(1)} days over SLA`)})`);
});
console.log('');
const humanOverdue = bottlenecks.filter((b)=>b.approval_level === 'human').length;
const escalateOverdue = bottlenecks.filter((b)=>b.approval_level === 'escalate').length;
if (humanOverdue > 0) {
console.log(chalk.yellow(`⚠ ${humanOverdue} human approval${humanOverdue > 1 ? 's' : ''} > 7 days (escalate recommended)`));
}
if (escalateOverdue > 0) {
console.log(chalk.red(`⚠ ${escalateOverdue} escalated approval${escalateOverdue > 1 ? 's' : ''} > 2 days (expert review needed)`));
}
console.log('');
} else {
console.log(chalk.green('✓ No bottlenecks found - all pending approvals are within SLA\n'));
}
}
// ============================================================================
// Main CLI Entry Point
// ============================================================================
async function main() {
const args = process.argv.slice(2);
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
console.log(`
${chalk.bold('Skills Database CLI Tool')}
Usage: npx cfn skill <command> [options]
Commands:
list List skills with filtering
assign Assign skill to agent
create Create new skill
update Update skill metadata
deprecate Deprecate a skill
approve Approve/reject a skill
escalate Escalate skill for expert review
pending List pending approvals
approval-status Check skill approval status
analytics Skill analytics and metrics
Examples:
npx cfn skill list --approval=auto
npx cfn skill list --pending-approval
npx cfn skill assign --agent=backend-developer --skill=jwt-auth --priority=3
npx cfn skill create --name=new-skill --category=domain --content-path=./skill.md
npx cfn skill approve --skill=jwt-auth --decision=approved --approver=expert@example.com
npx cfn skill pending --approval-level=human
npx cfn skill analytics effectiveness --days=30
`);
process.exit(0);
}
const command = args[0];
const options = parseArgs(args.slice(1));
try {
switch(command){
case 'list':
await cmdList(options);
break;
case 'assign':
await cmdAssign(options);
break;
case 'create':
await cmdCreate(options);
break;
case 'update':
await cmdUpdate(options);
break;
case 'deprecate':
await cmdDeprecate(options);
break;
case 'approve':
await cmdApprove(options);
break;
case 'escalate':
await cmdEscalate(options);
break;
case 'pending':
await cmdPending(options);
break;
case 'approval-status':
await cmdApprovalStatus(options);
break;
case 'analytics':
await cmdAnalytics(options, args[1]);
break;
default:
console.error(chalk.red(`Error: Unknown command: ${command}`));
console.log('Run "npx cfn skill --help" for usage information');
process.exit(1);
}
} catch (error) {
console.error(chalk.red(`Error: ${error.message}`));
process.exit(1);
}
}
// Run CLI (ESM-compatible check)
if (process.argv[1] === fileURLToPath(import.meta.url)) {
main().catch((error)=>{
console.error(chalk.red(`Fatal error: ${error.message}`));
process.exit(1);
});
}
export { main };
//# sourceMappingURL=skill-cli.js.map