From 8cacf0f6a618a6caadd107d3d3390e0896bb924c Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Fri, 13 Feb 2026 12:11:26 -0800 Subject: [PATCH] fix: use nullish coalescing for confidence default + add 3 tests (round 85) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix confidence=0 showing 80% instead of 0% in patterns() (|| → ??). Test evaluate-session.js config parse error catch, getSessionIdShort fallback at root CWD, and precise confidence=0 assertion. --- scripts/skill-create-output.js | 2 +- tests/hooks/evaluate-session.test.js | 49 +++++++++++++++++++++++ tests/lib/utils.test.js | 29 ++++++++++++++ tests/scripts/skill-create-output.test.js | 16 ++++++++ 4 files changed, 95 insertions(+), 1 deletion(-) diff --git a/scripts/skill-create-output.js b/scripts/skill-create-output.js index 0c7df3d0..2d0aed7e 100644 --- a/scripts/skill-create-output.js +++ b/scripts/skill-create-output.js @@ -125,7 +125,7 @@ ${chalk.bold('Files Tracked:')} ${chalk.green(data.files)} console.log(chalk.gray('─'.repeat(50))); patterns.forEach((pattern, i) => { - const confidence = pattern.confidence || 0.8; + const confidence = pattern.confidence ?? 0.8; const confidenceBar = progressBar(Math.round(confidence * 100), 15); console.log(` ${chalk.bold(chalk.yellow(`${i + 1}.`))} ${chalk.bold(pattern.name)} diff --git a/tests/hooks/evaluate-session.test.js b/tests/hooks/evaluate-session.test.js index bf01b9fd..41fdc4c8 100644 --- a/tests/hooks/evaluate-session.test.js +++ b/tests/hooks/evaluate-session.test.js @@ -311,6 +311,55 @@ function runTests() { cleanupTestDir(testDir); })) passed++; else failed++; + // ── Round 85: config file parse error (corrupt JSON) ── + console.log('\nRound 85: config parse error catch block:'); + + if (test('falls back to defaults when config file contains invalid JSON', () => { + // The evaluate-session.js script reads config from: + // path.join(__dirname, '..', '..', 'skills', 'continuous-learning', 'config.json') + // where __dirname = scripts/hooks/ → config = repo_root/skills/continuous-learning/config.json + const configPath = path.join(__dirname, '..', '..', 'skills', 'continuous-learning', 'config.json'); + let originalContent = null; + try { + originalContent = fs.readFileSync(configPath, 'utf8'); + } catch { + // Config file may not exist — that's fine + } + + try { + // Write corrupt JSON to the config file + fs.writeFileSync(configPath, 'NOT VALID JSON {{{ corrupt data !!!', 'utf8'); + + // Create a transcript with 12 user messages (above default threshold of 10) + const testDir = createTestDir(); + const transcript = createTranscript(testDir, 12); + const result = runEvaluate({ transcript_path: transcript }); + + assert.strictEqual(result.code, 0, 'Should exit 0 despite corrupt config'); + // With corrupt config, defaults apply: min_session_length = 10 + // 12 >= 10 → should evaluate (not "too short") + assert.ok(!result.stderr.includes('too short'), + `Should NOT say too short — corrupt config falls back to default min=10. Got: ${result.stderr}`); + assert.ok( + result.stderr.includes('12 messages') || result.stderr.includes('evaluate'), + `Should evaluate with 12 messages using default threshold. Got: ${result.stderr}` + ); + // The catch block logs "Failed to parse config" — verify that log message + assert.ok(result.stderr.includes('Failed to parse config'), + `Should log config parse error. Got: ${result.stderr}`); + + cleanupTestDir(testDir); + } finally { + // Restore original config file + if (originalContent !== null) { + fs.writeFileSync(configPath, originalContent, 'utf8'); + } else { + // Config didn't exist before — remove the corrupt one we created + try { fs.unlinkSync(configPath); } catch { /* best-effort */ } + } + } + })) passed++; else failed++; + // Summary console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`); process.exit(failed > 0 ? 1 : 0); diff --git a/tests/lib/utils.test.js b/tests/lib/utils.test.js index 85209f12..6029f2fa 100644 --- a/tests/lib/utils.test.js +++ b/tests/lib/utils.test.js @@ -1165,6 +1165,35 @@ function runTests() { } })) passed++; else failed++; + // ── Round 85: getSessionIdShort fallback parameter ── + console.log('\ngetSessionIdShort fallback (Round 85):'); + + if (test('getSessionIdShort uses fallback when getProjectName returns null (CWD at root)', () => { + if (process.platform === 'win32') { + console.log(' (skipped — root CWD differs on Windows)'); + return; + } + // Spawn a subprocess at CWD=/ with CLAUDE_SESSION_ID empty. + // At /, git rev-parse --show-toplevel fails → getGitRepoName() = null. + // path.basename('/') = '' → '' || null = null → getProjectName() = null. + // So getSessionIdShort('my-custom-fallback') = null || 'my-custom-fallback'. + const utilsPath = path.join(__dirname, '..', '..', 'scripts', 'lib', 'utils.js'); + const script = ` + const utils = require('${utilsPath.replace(/'/g, "\\'")}'); + process.stdout.write(utils.getSessionIdShort('my-custom-fallback')); + `; + const { spawnSync } = require('child_process'); + const result = spawnSync('node', ['-e', script], { + encoding: 'utf8', + cwd: '/', + env: { ...process.env, CLAUDE_SESSION_ID: '' }, + timeout: 10000 + }); + assert.strictEqual(result.status, 0, `Should exit 0, got status ${result.status}. stderr: ${result.stderr}`); + assert.strictEqual(result.stdout, 'my-custom-fallback', + `At CWD=/ with no session ID, should use the fallback parameter. Got: "${result.stdout}"`); + })) passed++; else failed++; + // Summary console.log('\n=== Test Results ==='); console.log(`Passed: ${passed}`); diff --git a/tests/scripts/skill-create-output.test.js b/tests/scripts/skill-create-output.test.js index e2895679..1675b4b0 100644 --- a/tests/scripts/skill-create-output.test.js +++ b/tests/scripts/skill-create-output.test.js @@ -481,6 +481,22 @@ function runTests() { assert.strictEqual(typeof mod.SkillCreateOutput, 'function', 'SkillCreateOutput should be a constructor'); })) passed++; else failed++; + // ── Round 85: patterns() confidence=0 uses ?? (not ||) ── + console.log('\nRound 85: patterns() confidence=0 nullish coalescing:'); + + if (test('patterns() with confidence=0 shows 0%, not 80% (nullish coalescing fix)', () => { + const output = new SkillCreateOutput('repo'); + const logs = captureLog(() => output.patterns([ + { name: 'Zero Confidence', trigger: 'never', confidence: 0, evidence: 'none' }, + ])); + const combined = stripAnsi(logs.join('\n')); + // With ?? operator: 0 ?? 0.8 = 0 → Math.round(0 * 100) = 0 → shows "0%" + // With || operator (bug): 0 || 0.8 = 0.8 → shows "80%" + assert.ok(combined.includes('0%'), 'Should show 0% for zero confidence'); + assert.ok(!combined.includes('80%'), + 'Should NOT show 80% — confidence=0 is explicitly provided, not missing'); + })) passed++; else failed++; + // Summary console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`); process.exit(failed > 0 ? 1 : 0);