Files
everything-claude-code/tests/lib/skill-dashboard.test.js
Aryan Tejani 89044e8c33 feat(design): skill health dashboard mockup (#518)
* feat(Design): skill health dashboard mockup

* fix(comments): code according to comments
2026-03-16 14:01:41 -07:00

455 lines
16 KiB
JavaScript

/**
* Tests for skill health dashboard.
*
* Run with: node tests/lib/skill-dashboard.test.js
*/
const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { spawnSync } = require('child_process');
const dashboard = require('../../scripts/lib/skill-evolution/dashboard');
const versioning = require('../../scripts/lib/skill-evolution/versioning');
const provenance = require('../../scripts/lib/skill-evolution/provenance');
const HEALTH_SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'skills-health.js');
function test(name, fn) {
try {
fn();
console.log(` \u2713 ${name}`);
return true;
} catch (error) {
console.log(` \u2717 ${name}`);
console.log(` Error: ${error.message}`);
return false;
}
}
function createTempDir(prefix) {
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
}
function cleanupTempDir(dirPath) {
fs.rmSync(dirPath, { recursive: true, force: true });
}
function createSkill(skillRoot, name, content) {
const skillDir = path.join(skillRoot, name);
fs.mkdirSync(skillDir, { recursive: true });
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content);
return skillDir;
}
function appendJsonl(filePath, rows) {
const lines = rows.map(row => JSON.stringify(row)).join('\n');
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, `${lines}\n`);
}
function runCli(args) {
return spawnSync(process.execPath, [HEALTH_SCRIPT, ...args], {
encoding: 'utf8',
});
}
function runTests() {
console.log('\n=== Testing skill dashboard ===\n');
let passed = 0;
let failed = 0;
const repoRoot = createTempDir('skill-dashboard-repo-');
const homeDir = createTempDir('skill-dashboard-home-');
const skillsRoot = path.join(repoRoot, 'skills');
const learnedRoot = path.join(homeDir, '.claude', 'skills', 'learned');
const importedRoot = path.join(homeDir, '.claude', 'skills', 'imported');
const runsFile = path.join(homeDir, '.claude', 'state', 'skill-runs.jsonl');
const now = '2026-03-15T12:00:00.000Z';
fs.mkdirSync(skillsRoot, { recursive: true });
fs.mkdirSync(learnedRoot, { recursive: true });
fs.mkdirSync(importedRoot, { recursive: true });
try {
console.log('Chart primitives:');
if (test('sparkline maps float values to Unicode block characters', () => {
const result = dashboard.sparkline([1, 0.5, 0]);
assert.strictEqual(result.length, 3);
assert.strictEqual(result[0], '\u2588');
assert.strictEqual(result[2], '\u2581');
})) passed++; else failed++;
if (test('sparkline returns empty string for empty array', () => {
assert.strictEqual(dashboard.sparkline([]), '');
})) passed++; else failed++;
if (test('sparkline renders null values as empty block', () => {
const result = dashboard.sparkline([null, 0.5, null]);
assert.strictEqual(result[0], '\u2591');
assert.strictEqual(result[2], '\u2591');
assert.strictEqual(result.length, 3);
})) passed++; else failed++;
if (test('horizontalBar renders correct fill ratio', () => {
const result = dashboard.horizontalBar(5, 10, 10);
const filled = (result.match(/\u2588/g) || []).length;
const empty = (result.match(/\u2591/g) || []).length;
assert.strictEqual(filled, 5);
assert.strictEqual(empty, 5);
assert.strictEqual(result.length, 10);
})) passed++; else failed++;
if (test('horizontalBar handles zero value', () => {
const result = dashboard.horizontalBar(0, 10, 10);
const filled = (result.match(/\u2588/g) || []).length;
assert.strictEqual(filled, 0);
assert.strictEqual(result.length, 10);
})) passed++; else failed++;
if (test('panelBox renders box-drawing characters with title', () => {
const result = dashboard.panelBox('Test Panel', ['line one', 'line two'], 30);
assert.match(result, /\u250C/);
assert.match(result, /\u2510/);
assert.match(result, /\u2514/);
assert.match(result, /\u2518/);
assert.match(result, /Test Panel/);
assert.match(result, /line one/);
assert.match(result, /line two/);
})) passed++; else failed++;
console.log('\nTime-series bucketing:');
if (test('bucketByDay groups records into daily bins', () => {
const nowMs = Date.parse(now);
const records = [
{ skill_id: 'alpha', outcome: 'success', recorded_at: '2026-03-15T10:00:00.000Z' },
{ skill_id: 'alpha', outcome: 'failure', recorded_at: '2026-03-15T08:00:00.000Z' },
{ skill_id: 'alpha', outcome: 'success', recorded_at: '2026-03-14T10:00:00.000Z' },
];
const buckets = dashboard.bucketByDay(records, nowMs, 3);
assert.strictEqual(buckets.length, 3);
const todayBucket = buckets[buckets.length - 1];
assert.strictEqual(todayBucket.runs, 2);
assert.strictEqual(todayBucket.rate, 0.5);
})) passed++; else failed++;
if (test('bucketByDay returns null rate for empty days', () => {
const nowMs = Date.parse(now);
const buckets = dashboard.bucketByDay([], nowMs, 5);
assert.strictEqual(buckets.length, 5);
for (const bucket of buckets) {
assert.strictEqual(bucket.rate, null);
assert.strictEqual(bucket.runs, 0);
}
})) passed++; else failed++;
console.log('\nPanel renderers:');
const alphaSkillDir = createSkill(skillsRoot, 'alpha', '# Alpha\n');
const betaSkillDir = createSkill(learnedRoot, 'beta', '# Beta\n');
versioning.createVersion(alphaSkillDir, {
timestamp: '2026-03-14T11:00:00.000Z',
author: 'observer',
reason: 'bootstrap',
});
fs.writeFileSync(path.join(alphaSkillDir, 'SKILL.md'), '# Alpha v2\n');
versioning.createVersion(alphaSkillDir, {
timestamp: '2026-03-15T11:00:00.000Z',
author: 'observer',
reason: 'accepted-amendment',
});
versioning.createVersion(betaSkillDir, {
timestamp: '2026-03-14T11:00:00.000Z',
author: 'observer',
reason: 'bootstrap',
});
const { appendFile } = require('../../scripts/lib/utils');
const alphaAmendmentsPath = path.join(alphaSkillDir, '.evolution', 'amendments.jsonl');
appendFile(alphaAmendmentsPath, JSON.stringify({
event: 'proposal',
status: 'pending',
created_at: '2026-03-15T07:00:00.000Z',
}) + '\n');
appendJsonl(runsFile, [
{
skill_id: 'alpha',
skill_version: 'v2',
task_description: 'Success task',
outcome: 'success',
failure_reason: null,
tokens_used: 100,
duration_ms: 1000,
user_feedback: 'accepted',
recorded_at: '2026-03-14T10:00:00.000Z',
},
{
skill_id: 'alpha',
skill_version: 'v2',
task_description: 'Failed task',
outcome: 'failure',
failure_reason: 'Regression',
tokens_used: 100,
duration_ms: 1000,
user_feedback: 'rejected',
recorded_at: '2026-03-13T10:00:00.000Z',
},
{
skill_id: 'alpha',
skill_version: 'v1',
task_description: 'Older success',
outcome: 'success',
failure_reason: null,
tokens_used: 100,
duration_ms: 1000,
user_feedback: 'accepted',
recorded_at: '2026-02-20T10:00:00.000Z',
},
{
skill_id: 'beta',
skill_version: 'v1',
task_description: 'Beta success',
outcome: 'success',
failure_reason: null,
tokens_used: 90,
duration_ms: 800,
user_feedback: 'accepted',
recorded_at: '2026-03-15T09:00:00.000Z',
},
{
skill_id: 'beta',
skill_version: 'v1',
task_description: 'Beta failure',
outcome: 'failure',
failure_reason: 'Bad import',
tokens_used: 90,
duration_ms: 800,
user_feedback: 'corrected',
recorded_at: '2026-02-20T09:00:00.000Z',
},
]);
const testRecords = [
{ skill_id: 'alpha', outcome: 'success', failure_reason: null, recorded_at: '2026-03-14T10:00:00.000Z' },
{ skill_id: 'alpha', outcome: 'failure', failure_reason: 'Regression', recorded_at: '2026-03-13T10:00:00.000Z' },
{ skill_id: 'alpha', outcome: 'success', failure_reason: null, recorded_at: '2026-02-20T10:00:00.000Z' },
{ skill_id: 'beta', outcome: 'success', failure_reason: null, recorded_at: '2026-03-15T09:00:00.000Z' },
{ skill_id: 'beta', outcome: 'failure', failure_reason: 'Bad import', recorded_at: '2026-02-20T09:00:00.000Z' },
];
if (test('renderSuccessRatePanel produces one row per skill with sparklines', () => {
const skills = [{ skill_id: 'alpha' }, { skill_id: 'beta' }];
const result = dashboard.renderSuccessRatePanel(testRecords, skills, { now });
assert.ok(result.text.includes('Success Rate'));
assert.ok(result.data.skills.length >= 2);
const alpha = result.data.skills.find(s => s.skill_id === 'alpha');
assert.ok(alpha);
assert.ok(Array.isArray(alpha.daily_rates));
assert.strictEqual(alpha.daily_rates.length, 30);
assert.ok(typeof alpha.sparkline === 'string');
assert.ok(alpha.sparkline.length > 0);
})) passed++; else failed++;
if (test('renderFailureClusterPanel groups failures by reason', () => {
const failureRecords = [
{ skill_id: 'alpha', outcome: 'failure', failure_reason: 'Regression' },
{ skill_id: 'alpha', outcome: 'failure', failure_reason: 'Regression' },
{ skill_id: 'beta', outcome: 'failure', failure_reason: 'Bad import' },
{ skill_id: 'alpha', outcome: 'success', failure_reason: null },
];
const result = dashboard.renderFailureClusterPanel(failureRecords);
assert.ok(result.text.includes('Failure Patterns'));
assert.strictEqual(result.data.clusters.length, 2);
assert.strictEqual(result.data.clusters[0].pattern, 'regression');
assert.strictEqual(result.data.clusters[0].count, 2);
assert.strictEqual(result.data.total_failures, 3);
})) passed++; else failed++;
if (test('renderAmendmentPanel lists pending amendments', () => {
const skillsById = new Map();
skillsById.set('alpha', { skill_id: 'alpha', skill_dir: alphaSkillDir });
const result = dashboard.renderAmendmentPanel(skillsById);
assert.ok(result.text.includes('Pending Amendments'));
assert.ok(result.data.total >= 1);
assert.ok(result.data.amendments.some(a => a.skill_id === 'alpha'));
})) passed++; else failed++;
if (test('renderVersionTimelinePanel shows version history', () => {
const skillsById = new Map();
skillsById.set('alpha', { skill_id: 'alpha', skill_dir: alphaSkillDir });
skillsById.set('beta', { skill_id: 'beta', skill_dir: betaSkillDir });
const result = dashboard.renderVersionTimelinePanel(skillsById);
assert.ok(result.text.includes('Version History'));
assert.ok(result.data.skills.length >= 1);
const alphaVersions = result.data.skills.find(s => s.skill_id === 'alpha');
assert.ok(alphaVersions);
assert.ok(alphaVersions.versions.length >= 2);
})) passed++; else failed++;
console.log('\nFull dashboard:');
if (test('renderDashboard produces all four panels', () => {
const result = dashboard.renderDashboard({
skillsRoot,
learnedRoot,
importedRoot,
homeDir,
runsFilePath: runsFile,
now,
warnThreshold: 0.1,
});
assert.ok(result.text.includes('ECC Skill Health Dashboard'));
assert.ok(result.text.includes('Success Rate'));
assert.ok(result.text.includes('Failure Patterns'));
assert.ok(result.text.includes('Pending Amendments'));
assert.ok(result.text.includes('Version History'));
assert.ok(result.data.generated_at === now);
assert.ok(result.data.summary);
assert.ok(result.data.panels['success-rate']);
assert.ok(result.data.panels['failures']);
assert.ok(result.data.panels['amendments']);
assert.ok(result.data.panels['versions']);
})) passed++; else failed++;
if (test('renderDashboard supports single panel selection', () => {
const result = dashboard.renderDashboard({
skillsRoot,
learnedRoot,
importedRoot,
homeDir,
runsFilePath: runsFile,
now,
panel: 'failures',
});
assert.ok(result.text.includes('Failure Patterns'));
assert.ok(!result.text.includes('Version History'));
assert.ok(result.data.panels['failures']);
assert.ok(!result.data.panels['versions']);
})) passed++; else failed++;
if (test('renderDashboard rejects unknown panel names', () => {
assert.throws(() => {
dashboard.renderDashboard({
skillsRoot,
learnedRoot,
importedRoot,
homeDir,
runsFilePath: runsFile,
now,
panel: 'nonexistent',
});
}, /Unknown panel/);
})) passed++; else failed++;
console.log('\nCLI integration:');
if (test('CLI --dashboard --json returns valid JSON with all panels', () => {
const result = runCli([
'--dashboard',
'--json',
'--skills-root', skillsRoot,
'--learned-root', learnedRoot,
'--imported-root', importedRoot,
'--home', homeDir,
'--runs-file', runsFile,
'--now', now,
]);
assert.strictEqual(result.status, 0, result.stderr);
const payload = JSON.parse(result.stdout.trim());
assert.ok(payload.panels);
assert.ok(payload.panels['success-rate']);
assert.ok(payload.panels['failures']);
assert.ok(payload.summary);
})) passed++; else failed++;
if (test('CLI --panel failures --json returns only the failures panel', () => {
const result = runCli([
'--dashboard',
'--panel', 'failures',
'--json',
'--skills-root', skillsRoot,
'--learned-root', learnedRoot,
'--imported-root', importedRoot,
'--home', homeDir,
'--runs-file', runsFile,
'--now', now,
]);
assert.strictEqual(result.status, 0, result.stderr);
const payload = JSON.parse(result.stdout.trim());
assert.ok(payload.panels['failures']);
assert.ok(!payload.panels['versions']);
})) passed++; else failed++;
if (test('CLI --help mentions --dashboard', () => {
const result = runCli(['--help']);
assert.strictEqual(result.status, 0);
assert.match(result.stdout, /--dashboard/);
assert.match(result.stdout, /--panel/);
})) passed++; else failed++;
console.log('\nEdge cases:');
if (test('dashboard renders gracefully with no execution records', () => {
const emptyRunsFile = path.join(homeDir, '.claude', 'state', 'empty-runs.jsonl');
fs.mkdirSync(path.dirname(emptyRunsFile), { recursive: true });
fs.writeFileSync(emptyRunsFile, '', 'utf8');
const emptySkillsRoot = path.join(repoRoot, 'empty-skills');
fs.mkdirSync(emptySkillsRoot, { recursive: true });
const result = dashboard.renderDashboard({
skillsRoot: emptySkillsRoot,
learnedRoot: path.join(homeDir, '.claude', 'skills', 'empty-learned'),
importedRoot: path.join(homeDir, '.claude', 'skills', 'empty-imported'),
homeDir,
runsFilePath: emptyRunsFile,
now,
});
assert.ok(result.text.includes('ECC Skill Health Dashboard'));
assert.ok(result.text.includes('No failure patterns detected'));
assert.strictEqual(result.data.summary.total_skills, 0);
})) passed++; else failed++;
if (test('failure cluster panel handles all successes', () => {
const successRecords = [
{ skill_id: 'alpha', outcome: 'success', failure_reason: null },
{ skill_id: 'beta', outcome: 'success', failure_reason: null },
];
const result = dashboard.renderFailureClusterPanel(successRecords);
assert.strictEqual(result.data.clusters.length, 0);
assert.strictEqual(result.data.total_failures, 0);
assert.ok(result.text.includes('No failure patterns detected'));
})) passed++; else failed++;
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
} finally {
cleanupTempDir(repoRoot);
cleanupTempDir(homeDir);
}
process.exit(failed > 0 ? 1 : 0);
}
runTests();