/** * Tests for scripts/session-inspect.js */ const assert = require('assert'); const fs = require('fs'); const os = require('os'); const path = require('path'); const { execFileSync } = require('child_process'); const { getFallbackSessionRecordingPath } = require('../../scripts/lib/session-adapters/canonical-session'); const SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'session-inspect.js'); function run(args = [], options = {}) { const envOverrides = { ...(options.env || {}) }; if (typeof envOverrides.HOME === 'string' && !('USERPROFILE' in envOverrides)) { envOverrides.USERPROFILE = envOverrides.HOME; } if (typeof envOverrides.USERPROFILE === 'string' && !('HOME' in envOverrides)) { envOverrides.HOME = envOverrides.USERPROFILE; } try { const stdout = execFileSync('node', [SCRIPT, ...args], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 10000, cwd: options.cwd || process.cwd(), env: { ...process.env, ...envOverrides } }); return { code: 0, stdout, stderr: '' }; } catch (error) { return { code: error.status || 1, stdout: error.stdout || '', stderr: error.stderr || '', }; } } 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 runTests() { console.log('\n=== Testing session-inspect.js ===\n'); let passed = 0; let failed = 0; if (test('shows usage when no target is provided', () => { const result = run(); assert.strictEqual(result.code, 1); assert.ok(result.stdout.includes('Usage:')); })) passed++; else failed++; if (test('lists registered adapters', () => { const result = run(['--list-adapters']); assert.strictEqual(result.code, 0, result.stderr); const payload = JSON.parse(result.stdout); assert.ok(Array.isArray(payload.adapters)); assert.ok(payload.adapters.some(adapter => adapter.id === 'claude-history')); assert.ok(payload.adapters.some(adapter => adapter.id === 'dmux-tmux')); })) passed++; else failed++; if (test('prints canonical JSON for claude history targets', () => { const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-inspect-home-')); const recordingDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-inspect-recordings-')); const sessionsDir = path.join(homeDir, '.claude', 'sessions'); fs.mkdirSync(sessionsDir, { recursive: true }); try { fs.writeFileSync( path.join(sessionsDir, '2026-03-13-a1b2c3d4-session.tmp'), '# Inspect Session\n\n**Branch:** feat/session-inspect\n' ); const result = run(['claude:latest'], { env: { HOME: homeDir, ECC_SESSION_RECORDING_DIR: recordingDir } }); assert.strictEqual(result.code, 0, result.stderr); const payload = JSON.parse(result.stdout); const recordingPath = getFallbackSessionRecordingPath(payload, { recordingDir }); const persisted = JSON.parse(fs.readFileSync(recordingPath, 'utf8')); assert.strictEqual(payload.adapterId, 'claude-history'); assert.strictEqual(payload.session.kind, 'history'); assert.strictEqual(payload.workers[0].branch, 'feat/session-inspect'); assert.strictEqual(persisted.latest.adapterId, 'claude-history'); assert.strictEqual(persisted.history.length, 1); } finally { fs.rmSync(homeDir, { recursive: true, force: true }); fs.rmSync(recordingDir, { recursive: true, force: true }); } })) passed++; else failed++; if (test('supports explicit target types for structured registry routing', () => { const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-inspect-home-')); const sessionsDir = path.join(homeDir, '.claude', 'sessions'); fs.mkdirSync(sessionsDir, { recursive: true }); try { fs.writeFileSync( path.join(sessionsDir, '2026-03-13-a1b2c3d4-session.tmp'), '# Inspect Session\n\n**Branch:** feat/typed-inspect\n' ); const result = run(['latest', '--target-type', 'claude-history'], { env: { HOME: homeDir } }); assert.strictEqual(result.code, 0, result.stderr); const payload = JSON.parse(result.stdout); assert.strictEqual(payload.adapterId, 'claude-history'); assert.strictEqual(payload.session.sourceTarget.type, 'claude-history'); assert.strictEqual(payload.workers[0].branch, 'feat/typed-inspect'); } finally { fs.rmSync(homeDir, { recursive: true, force: true }); } })) passed++; else failed++; if (test('writes snapshot JSON to disk when --write is provided', () => { const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-inspect-home-')); const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-inspect-out-')); const sessionsDir = path.join(homeDir, '.claude', 'sessions'); fs.mkdirSync(sessionsDir, { recursive: true }); const outputPath = path.join(outputDir, 'snapshot.json'); try { fs.writeFileSync( path.join(sessionsDir, '2026-03-13-a1b2c3d4-session.tmp'), '# Inspect Session\n\n**Branch:** feat/session-inspect\n' ); const result = run(['claude:latest', '--write', outputPath], { env: { HOME: homeDir } }); assert.strictEqual(result.code, 0, result.stderr); assert.ok(fs.existsSync(outputPath)); const written = JSON.parse(fs.readFileSync(outputPath, 'utf8')); assert.strictEqual(written.adapterId, 'claude-history'); } finally { fs.rmSync(homeDir, { recursive: true, force: true }); fs.rmSync(outputDir, { recursive: true, force: true }); } })) passed++; else failed++; if (test('inspects skill health from recorded observations', () => { const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-inspect-skills-')); const observationsDir = path.join(projectRoot, '.claude', 'ecc', 'skills'); fs.mkdirSync(observationsDir, { recursive: true }); fs.writeFileSync( path.join(observationsDir, 'observations.jsonl'), [ JSON.stringify({ schemaVersion: 'ecc.skill-observation.v1', observationId: 'obs-1', timestamp: '2026-03-14T12:00:00.000Z', task: 'Review auth middleware', skill: { id: 'security-review', path: 'skills/security-review/SKILL.md' }, outcome: { success: false, status: 'failure', error: 'missing csrf guidance', feedback: 'Need CSRF coverage' }, run: { variant: 'baseline', amendmentId: null, sessionId: 'sess-1' } }), JSON.stringify({ schemaVersion: 'ecc.skill-observation.v1', observationId: 'obs-2', timestamp: '2026-03-14T12:05:00.000Z', task: 'Review auth middleware', skill: { id: 'security-review', path: 'skills/security-review/SKILL.md' }, outcome: { success: false, status: 'failure', error: 'missing csrf guidance', feedback: null }, run: { variant: 'baseline', amendmentId: null, sessionId: 'sess-2' } }) ].join('\n') + '\n' ); try { const result = run(['skills:health'], { cwd: projectRoot }); assert.strictEqual(result.code, 0, result.stderr); const payload = JSON.parse(result.stdout); assert.strictEqual(payload.schemaVersion, 'ecc.skill-health.v1'); assert.ok(payload.skills.some(skill => skill.skill.id === 'security-review')); } finally { fs.rmSync(projectRoot, { recursive: true, force: true }); } })) passed++; else failed++; if (test('proposes skill amendments through session-inspect', () => { const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-inspect-amend-')); const observationsDir = path.join(projectRoot, '.claude', 'ecc', 'skills'); fs.mkdirSync(observationsDir, { recursive: true }); fs.writeFileSync( path.join(observationsDir, 'observations.jsonl'), [ JSON.stringify({ schemaVersion: 'ecc.skill-observation.v1', observationId: 'obs-1', timestamp: '2026-03-14T12:00:00.000Z', task: 'Add rate limiting', skill: { id: 'api-design', path: 'skills/api-design/SKILL.md' }, outcome: { success: false, status: 'failure', error: 'missing rate limiting guidance', feedback: 'Need rate limiting examples' }, run: { variant: 'baseline', amendmentId: null, sessionId: 'sess-1' } }) ].join('\n') + '\n' ); try { const result = run(['skills:amendify', '--skill', 'api-design'], { cwd: projectRoot }); assert.strictEqual(result.code, 0, result.stderr); const payload = JSON.parse(result.stdout); assert.strictEqual(payload.schemaVersion, 'ecc.skill-amendment-proposal.v1'); assert.strictEqual(payload.skill.id, 'api-design'); assert.ok(payload.patch.preview.includes('Failure-Driven Amendments')); } finally { fs.rmSync(projectRoot, { recursive: true, force: true }); } })) passed++; else failed++; if (test('builds skill evaluation scaffolding through session-inspect', () => { const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-session-inspect-eval-')); const observationsDir = path.join(projectRoot, '.claude', 'ecc', 'skills'); fs.mkdirSync(observationsDir, { recursive: true }); fs.writeFileSync( path.join(observationsDir, 'observations.jsonl'), [ JSON.stringify({ schemaVersion: 'ecc.skill-observation.v1', observationId: 'obs-1', timestamp: '2026-03-14T12:00:00.000Z', task: 'Fix flaky login test', skill: { id: 'e2e-testing', path: 'skills/e2e-testing/SKILL.md' }, outcome: { success: false, status: 'failure', error: null, feedback: null }, run: { variant: 'baseline', amendmentId: null, sessionId: 'sess-1' } }), JSON.stringify({ schemaVersion: 'ecc.skill-observation.v1', observationId: 'obs-2', timestamp: '2026-03-14T12:10:00.000Z', task: 'Fix flaky checkout test', skill: { id: 'e2e-testing', path: 'skills/e2e-testing/SKILL.md' }, outcome: { success: true, status: 'success', error: null, feedback: null }, run: { variant: 'baseline', amendmentId: null, sessionId: 'sess-2' } }), JSON.stringify({ schemaVersion: 'ecc.skill-observation.v1', observationId: 'obs-3', timestamp: '2026-03-14T12:20:00.000Z', task: 'Fix flaky login test', skill: { id: 'e2e-testing', path: 'skills/e2e-testing/SKILL.md' }, outcome: { success: true, status: 'success', error: null, feedback: null }, run: { variant: 'amended', amendmentId: 'amend-1', sessionId: 'sess-3' } }), JSON.stringify({ schemaVersion: 'ecc.skill-observation.v1', observationId: 'obs-4', timestamp: '2026-03-14T12:30:00.000Z', task: 'Fix flaky checkout test', skill: { id: 'e2e-testing', path: 'skills/e2e-testing/SKILL.md' }, outcome: { success: true, status: 'success', error: null, feedback: null }, run: { variant: 'amended', amendmentId: 'amend-1', sessionId: 'sess-4' } }) ].join('\n') + '\n' ); try { const result = run(['skills:evaluate', '--skill', 'e2e-testing', '--amendment-id', 'amend-1'], { cwd: projectRoot }); assert.strictEqual(result.code, 0, result.stderr); const payload = JSON.parse(result.stdout); assert.strictEqual(payload.schemaVersion, 'ecc.skill-evaluation.v1'); assert.strictEqual(payload.recommendation, 'promote-amendment'); } finally { fs.rmSync(projectRoot, { recursive: true, force: true }); } })) passed++; else failed++; console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`); process.exit(failed > 0 ? 1 : 0); } runTests();