fix: fold session manager blockers into one candidate

This commit is contained in:
Affaan Mustafa
2026-03-24 23:08:27 -04:00
parent 7726c25e46
commit 1d0aa5ac2a
30 changed files with 1126 additions and 288 deletions

View File

@@ -4,8 +4,9 @@
* Covers the ECC root resolution fallback chain:
* 1. CLAUDE_PLUGIN_ROOT env var
* 2. Standard install (~/.claude/)
* 3. Plugin cache auto-detection
* 4. Fallback to ~/.claude/
* 3. Exact legacy plugin roots under ~/.claude/plugins/
* 4. Plugin cache auto-detection
* 5. Fallback to ~/.claude/
*/
const assert = require('assert');
@@ -39,6 +40,13 @@ function setupStandardInstall(homeDir) {
return claudeDir;
}
function setupLegacyPluginInstall(homeDir, segments) {
const legacyDir = path.join(homeDir, '.claude', 'plugins', ...segments);
const scriptDir = path.join(legacyDir, 'scripts', 'lib');
fs.mkdirSync(scriptDir, { recursive: true });
fs.writeFileSync(path.join(scriptDir, 'utils.js'), '// stub');
return legacyDir;
}
function setupPluginCache(homeDir, orgName, version) {
const cacheDir = path.join(
homeDir, '.claude', 'plugins', 'cache',
@@ -103,6 +111,50 @@ function runTests() {
}
})) passed++; else failed++;
if (test('finds exact legacy plugin install at ~/.claude/plugins/everything-claude-code', () => {
const homeDir = createTempDir();
try {
const expected = setupLegacyPluginInstall(homeDir, ['everything-claude-code']);
const result = resolveEccRoot({ envRoot: '', homeDir });
assert.strictEqual(result, expected);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('finds exact legacy plugin install at ~/.claude/plugins/everything-claude-code@everything-claude-code', () => {
const homeDir = createTempDir();
try {
const expected = setupLegacyPluginInstall(homeDir, ['everything-claude-code@everything-claude-code']);
const result = resolveEccRoot({ envRoot: '', homeDir });
assert.strictEqual(result, expected);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('finds marketplace legacy plugin install at ~/.claude/plugins/marketplace/everything-claude-code', () => {
const homeDir = createTempDir();
try {
const expected = setupLegacyPluginInstall(homeDir, ['marketplace', 'everything-claude-code']);
const result = resolveEccRoot({ envRoot: '', homeDir });
assert.strictEqual(result, expected);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('prefers exact legacy plugin install over plugin cache', () => {
const homeDir = createTempDir();
try {
const expected = setupLegacyPluginInstall(homeDir, ['marketplace', 'everything-claude-code']);
setupPluginCache(homeDir, 'everything-claude-code', '1.8.0');
const result = resolveEccRoot({ envRoot: '', homeDir });
assert.strictEqual(result, expected);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
})) passed++; else failed++;
// ─── Plugin Cache Auto-Detection ───
if (test('discovers plugin root from cache directory', () => {
@@ -207,6 +259,22 @@ function runTests() {
assert.strictEqual(result, '/inline/test/root');
})) passed++; else failed++;
if (test('INLINE_RESOLVE discovers exact legacy plugin root when env var is unset', () => {
const homeDir = createTempDir();
try {
const expected = setupLegacyPluginInstall(homeDir, ['marketplace', 'everything-claude-code']);
const { execFileSync } = require('child_process');
const result = execFileSync('node', [
'-e', `console.log(${INLINE_RESOLVE})`,
], {
env: { PATH: process.env.PATH, HOME: homeDir, USERPROFILE: homeDir },
encoding: 'utf8',
}).trim();
assert.strictEqual(result, expected);
} finally {
fs.rmSync(homeDir, { recursive: true, force: true });
}
})) passed++; else failed++;
if (test('INLINE_RESOLVE discovers plugin cache when env var is unset', () => {
const homeDir = createTempDir();
try {

View File

@@ -990,7 +990,7 @@ src/main.ts
assert.ok(result.endsWith(filename), `Path should end with filename, got: ${result}`);
// Since HOME is overridden, sessions dir should be under tmpHome
assert.ok(result.includes('.claude'), 'Path should include .claude directory');
assert.ok(result.includes('sessions'), 'Path should include sessions directory');
assert.ok(result.includes('session-data'), 'Path should use canonical session-data directory');
})) passed++; else failed++;
// ── Round 66: getSessionById noIdMatch path (date-only string for old format) ──
@@ -1629,18 +1629,13 @@ src/main.ts
// best-effort
}
// ── Round 98: parseSessionFilename with null input throws TypeError ──
console.log('\nRound 98: parseSessionFilename (null input — crashes at line 30):');
// ── Round 98: parseSessionFilename with null input returns null ──
console.log('\nRound 98: parseSessionFilename (null input is safely rejected):');
if (test('parseSessionFilename(null) throws TypeError because null has no .match()', () => {
// session-manager.js line 30: `filename.match(SESSION_FILENAME_REGEX)`
// When filename is null, null.match() throws TypeError.
// Function lacks a type guard like `if (!filename || typeof filename !== 'string')`.
assert.throws(
() => sessionManager.parseSessionFilename(null),
{ name: 'TypeError' },
'null.match() should throw TypeError (no type guard on filename parameter)'
);
if (test('parseSessionFilename(null) returns null instead of throwing', () => {
assert.strictEqual(sessionManager.parseSessionFilename(null), null);
assert.strictEqual(sessionManager.parseSessionFilename(undefined), null);
assert.strictEqual(sessionManager.parseSessionFilename(123), null);
})) passed++; else failed++;
// ── Round 99: writeSessionContent with null path returns false (error caught) ──

View File

@@ -7,6 +7,7 @@
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const { spawnSync } = require('child_process');
// Import the module
const utils = require('../../scripts/lib/utils');
@@ -68,7 +69,13 @@ function runTests() {
const sessionsDir = utils.getSessionsDir();
const claudeDir = utils.getClaudeDir();
assert.ok(sessionsDir.startsWith(claudeDir), 'Sessions should be under Claude dir');
assert.ok(sessionsDir.includes('sessions'), 'Should contain sessions');
assert.ok(sessionsDir.endsWith(path.join('.claude', 'session-data')) || sessionsDir.endsWith('/.claude/session-data'), 'Should use canonical session-data directory');
})) passed++; else failed++;
if (test('getSessionSearchDirs includes canonical and legacy paths', () => {
const searchDirs = utils.getSessionSearchDirs();
assert.ok(searchDirs.includes(utils.getSessionsDir()), 'Should include canonical session dir');
assert.ok(searchDirs.includes(utils.getLegacySessionsDir()), 'Should include legacy session dir');
})) passed++; else failed++;
if (test('getTempDir returns valid temp directory', () => {
@@ -118,17 +125,77 @@ function runTests() {
assert.ok(name && name.length > 0);
})) passed++; else failed++;
// sanitizeSessionId tests
console.log('\nsanitizeSessionId:');
if (test('sanitizeSessionId strips leading dots', () => {
assert.strictEqual(utils.sanitizeSessionId('.claude'), 'claude');
})) passed++; else failed++;
if (test('sanitizeSessionId replaces dots and spaces', () => {
assert.strictEqual(utils.sanitizeSessionId('my.project'), 'my-project');
assert.strictEqual(utils.sanitizeSessionId('my project'), 'my-project');
})) passed++; else failed++;
if (test('sanitizeSessionId replaces special chars and collapses runs', () => {
assert.strictEqual(utils.sanitizeSessionId('project@v2'), 'project-v2');
assert.strictEqual(utils.sanitizeSessionId('a...b'), 'a-b');
})) passed++; else failed++;
if (test('sanitizeSessionId preserves valid chars', () => {
assert.strictEqual(utils.sanitizeSessionId('my-project_123'), 'my-project_123');
})) passed++; else failed++;
if (test('sanitizeSessionId returns null for empty or punctuation-only values', () => {
assert.strictEqual(utils.sanitizeSessionId(''), null);
assert.strictEqual(utils.sanitizeSessionId(null), null);
assert.strictEqual(utils.sanitizeSessionId(undefined), null);
assert.strictEqual(utils.sanitizeSessionId('...'), null);
assert.strictEqual(utils.sanitizeSessionId('…'), null);
})) passed++; else failed++;
if (test('sanitizeSessionId returns stable hashes for non-ASCII values', () => {
const chinese = utils.sanitizeSessionId('我的项目');
const cyrillic = utils.sanitizeSessionId('проект');
const emoji = utils.sanitizeSessionId('🚀🎉');
assert.ok(/^[a-f0-9]{8}$/.test(chinese), `Expected 8-char hash, got: ${chinese}`);
assert.ok(/^[a-f0-9]{8}$/.test(cyrillic), `Expected 8-char hash, got: ${cyrillic}`);
assert.ok(/^[a-f0-9]{8}$/.test(emoji), `Expected 8-char hash, got: ${emoji}`);
assert.notStrictEqual(chinese, cyrillic);
assert.notStrictEqual(chinese, emoji);
assert.strictEqual(utils.sanitizeSessionId('日本語プロジェクト'), utils.sanitizeSessionId('日本語プロジェクト'));
})) passed++; else failed++;
if (test('sanitizeSessionId disambiguates mixed-script names from pure ASCII', () => {
const mixed = utils.sanitizeSessionId('我的app');
const mixedTwo = utils.sanitizeSessionId('他的app');
const pure = utils.sanitizeSessionId('app');
assert.strictEqual(pure, 'app');
assert.ok(mixed.startsWith('app-'), `Expected mixed-script prefix, got: ${mixed}`);
assert.notStrictEqual(mixed, pure);
assert.notStrictEqual(mixed, mixedTwo);
})) passed++; else failed++;
if (test('sanitizeSessionId is idempotent', () => {
for (const input of ['.claude', 'my.project', 'project@v2', 'a...b', 'my-project_123']) {
const once = utils.sanitizeSessionId(input);
const twice = utils.sanitizeSessionId(once);
assert.strictEqual(once, twice, `Expected idempotent result for ${input}`);
}
})) passed++; else failed++;
// Session ID tests
console.log('\nSession ID Functions:');
if (test('getSessionIdShort falls back to project name', () => {
if (test('getSessionIdShort falls back to sanitized project name', () => {
const original = process.env.CLAUDE_SESSION_ID;
delete process.env.CLAUDE_SESSION_ID;
try {
const shortId = utils.getSessionIdShort();
assert.strictEqual(shortId, utils.getProjectName());
assert.strictEqual(shortId, utils.sanitizeSessionId(utils.getProjectName()));
} finally {
if (original) process.env.CLAUDE_SESSION_ID = original;
if (original !== undefined) process.env.CLAUDE_SESSION_ID = original;
else delete process.env.CLAUDE_SESSION_ID;
}
})) passed++; else failed++;
@@ -154,6 +221,28 @@ function runTests() {
}
})) passed++; else failed++;
if (test('getSessionIdShort sanitizes explicit fallback parameter', () => {
if (process.platform === 'win32') {
console.log(' (skipped — root CWD differs on Windows)');
return true;
}
const utilsPath = path.join(__dirname, '..', '..', 'scripts', 'lib', 'utils.js');
const script = `
const utils = require('${utilsPath.replace(/'/g, "\\'")}');
process.stdout.write(utils.getSessionIdShort('my.fallback'));
`;
const result = spawnSync('node', ['-e', script], {
encoding: 'utf8',
cwd: '/',
env: { ...process.env, CLAUDE_SESSION_ID: '' },
timeout: 10000
});
assert.strictEqual(result.status, 0, `Expected exit 0, got ${result.status}. stderr: ${result.stderr}`);
assert.strictEqual(result.stdout, 'my-fallback');
})) passed++; else failed++;
// File operations tests
console.log('\nFile Operations:');
@@ -1415,25 +1504,26 @@ function runTests() {
// ── Round 97: getSessionIdShort with whitespace-only CLAUDE_SESSION_ID ──
console.log('\nRound 97: getSessionIdShort (whitespace-only session ID):');
if (test('getSessionIdShort returns whitespace when CLAUDE_SESSION_ID is all spaces', () => {
// utils.js line 116: if (sessionId && sessionId.length > 0) — ' ' is truthy
// and has length > 0, so it passes the check instead of falling back.
const original = process.env.CLAUDE_SESSION_ID;
try {
process.env.CLAUDE_SESSION_ID = ' '; // 10 spaces
const result = utils.getSessionIdShort('fallback');
// slice(-8) on 10 spaces returns 8 spaces — not the expected fallback
assert.strictEqual(result, ' ',
'Whitespace-only ID should return 8 trailing spaces (no trim check)');
assert.strictEqual(result.trim().length, 0,
'Result should be entirely whitespace (demonstrating the missing trim)');
} finally {
if (original !== undefined) {
process.env.CLAUDE_SESSION_ID = original;
} else {
delete process.env.CLAUDE_SESSION_ID;
}
if (test('getSessionIdShort sanitizes whitespace-only CLAUDE_SESSION_ID to fallback', () => {
if (process.platform === 'win32') {
console.log(' (skipped — root CWD differs on Windows)');
return true;
}
const utilsPath = path.join(__dirname, '..', '..', 'scripts', 'lib', 'utils.js');
const script = `
const utils = require('${utilsPath.replace(/'/g, "\\'")}');
process.stdout.write(utils.getSessionIdShort('fallback'));
`;
const result = spawnSync('node', ['-e', script], {
encoding: 'utf8',
cwd: '/',
env: { ...process.env, CLAUDE_SESSION_ID: ' ' },
timeout: 10000
});
assert.strictEqual(result.status, 0, `Expected exit 0, got ${result.status}. stderr: ${result.stderr}`);
assert.strictEqual(result.stdout, 'fallback');
})) passed++; else failed++;
// ── Round 97: countInFile with same RegExp object called twice (lastIndex reuse) ──