mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-02-15 02:43:20 +08:00
fix: consistent periodic interval spacing in suggest-compact, add 10 tests
- suggest-compact.js: count % 25 → (count - threshold) % 25 for consistent spacing regardless of threshold value - Update existing periodic interval test to match corrected behavior - 10 new tests: interval fix regression (non-25-divisible threshold, false suggestion prevention), corrupted counter file, 1M boundary, malformed JSON pass-through, non-TS extension pass-through, empty sessions dir, blank template skip
This commit is contained in:
@@ -66,8 +66,8 @@ async function main() {
|
||||
log(`[StrategicCompact] ${threshold} tool calls reached - consider /compact if transitioning phases`);
|
||||
}
|
||||
|
||||
// Suggest at regular intervals after threshold
|
||||
if (count > threshold && count % 25 === 0) {
|
||||
// Suggest at regular intervals after threshold (every 25 calls from threshold)
|
||||
if (count > threshold && (count - threshold) % 25 === 0) {
|
||||
log(`[StrategicCompact] ${count} tool calls - good checkpoint for /compact if context is stale`);
|
||||
}
|
||||
|
||||
|
||||
@@ -1349,15 +1349,15 @@ async function runTests() {
|
||||
const sessionId = `test-periodic-${Date.now()}`;
|
||||
const counterFile = path.join(os.tmpdir(), `claude-tool-count-${sessionId}`);
|
||||
try {
|
||||
// Pre-seed at 74 so next call = 75 (threshold 5 + 70, 70 % 25 === 20, not a hit)
|
||||
// Actually: count > threshold && count % 25 === 0 → need count = 75
|
||||
fs.writeFileSync(counterFile, '74');
|
||||
// Pre-seed at 29 so next call = 30 (threshold 5 + 25 = 30)
|
||||
// (30 - 5) % 25 === 0 → should trigger periodic suggestion
|
||||
fs.writeFileSync(counterFile, '29');
|
||||
const result = await runScript(path.join(scriptsDir, 'suggest-compact.js'), '', {
|
||||
CLAUDE_SESSION_ID: sessionId,
|
||||
COMPACT_THRESHOLD: '5'
|
||||
});
|
||||
assert.strictEqual(result.code, 0);
|
||||
assert.ok(result.stderr.includes('75 tool calls'), 'Should suggest at multiples of 25');
|
||||
assert.ok(result.stderr.includes('30 tool calls'), 'Should suggest at threshold + 25n intervals');
|
||||
} finally {
|
||||
try { fs.unlinkSync(counterFile); } catch {}
|
||||
}
|
||||
@@ -1865,6 +1865,154 @@ async function runTests() {
|
||||
cleanupTestDir(testDir);
|
||||
})) passed++; else failed++;
|
||||
|
||||
// ─── Round 24: suggest-compact interval fix, fd fallback, session-start maxAge ───
|
||||
console.log('\nRound 24: suggest-compact.js (interval fix & fd fallback):');
|
||||
|
||||
if (await asyncTest('periodic intervals are consistent with non-25-divisible threshold', async () => {
|
||||
// Regression test: with threshold=13, periodic suggestions should fire at 38, 63, 88...
|
||||
// (count - 13) % 25 === 0 → 38-13=25, 63-13=50, etc.
|
||||
const sessionId = `test-interval-fix-${Date.now()}`;
|
||||
const counterFile = path.join(os.tmpdir(), `claude-tool-count-${sessionId}`);
|
||||
try {
|
||||
// Pre-seed at 37 so next call = 38 (13 + 25 = 38)
|
||||
fs.writeFileSync(counterFile, '37');
|
||||
const result = await runScript(path.join(scriptsDir, 'suggest-compact.js'), '', {
|
||||
CLAUDE_SESSION_ID: sessionId,
|
||||
COMPACT_THRESHOLD: '13'
|
||||
});
|
||||
assert.strictEqual(result.code, 0);
|
||||
assert.ok(result.stderr.includes('38 tool calls'), 'Should suggest at threshold(13) + 25 = 38');
|
||||
} finally {
|
||||
try { fs.unlinkSync(counterFile); } catch {}
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (await asyncTest('does not suggest at old-style multiples that skip threshold offset', async () => {
|
||||
// With threshold=13, count=50 should NOT trigger (old behavior would: 50%25===0)
|
||||
// New behavior: (50-13)%25 = 37%25 = 12 → no suggestion
|
||||
const sessionId = `test-no-false-suggest-${Date.now()}`;
|
||||
const counterFile = path.join(os.tmpdir(), `claude-tool-count-${sessionId}`);
|
||||
try {
|
||||
fs.writeFileSync(counterFile, '49');
|
||||
const result = await runScript(path.join(scriptsDir, 'suggest-compact.js'), '', {
|
||||
CLAUDE_SESSION_ID: sessionId,
|
||||
COMPACT_THRESHOLD: '13'
|
||||
});
|
||||
assert.strictEqual(result.code, 0);
|
||||
assert.ok(!result.stderr.includes('checkpoint'), 'Should NOT suggest at count=50 with threshold=13');
|
||||
} finally {
|
||||
try { fs.unlinkSync(counterFile); } catch {}
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (await asyncTest('fd fallback: handles corrupted counter file gracefully', async () => {
|
||||
const sessionId = `test-corrupt-${Date.now()}`;
|
||||
const counterFile = path.join(os.tmpdir(), `claude-tool-count-${sessionId}`);
|
||||
try {
|
||||
// Write non-numeric data to trigger parseInt → NaN → reset to 1
|
||||
fs.writeFileSync(counterFile, 'corrupted data here!!!');
|
||||
const result = await runScript(path.join(scriptsDir, 'suggest-compact.js'), '', {
|
||||
CLAUDE_SESSION_ID: sessionId
|
||||
});
|
||||
assert.strictEqual(result.code, 0);
|
||||
const newCount = parseInt(fs.readFileSync(counterFile, 'utf8').trim(), 10);
|
||||
assert.strictEqual(newCount, 1, 'Should reset to 1 on corrupted file content');
|
||||
} finally {
|
||||
try { fs.unlinkSync(counterFile); } catch {}
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (await asyncTest('handles counter at exact 1000000 boundary', async () => {
|
||||
const sessionId = `test-boundary-${Date.now()}`;
|
||||
const counterFile = path.join(os.tmpdir(), `claude-tool-count-${sessionId}`);
|
||||
try {
|
||||
// 1000000 is the upper clamp boundary — should still increment
|
||||
fs.writeFileSync(counterFile, '1000000');
|
||||
const result = await runScript(path.join(scriptsDir, 'suggest-compact.js'), '', {
|
||||
CLAUDE_SESSION_ID: sessionId
|
||||
});
|
||||
assert.strictEqual(result.code, 0);
|
||||
const newCount = parseInt(fs.readFileSync(counterFile, 'utf8').trim(), 10);
|
||||
assert.strictEqual(newCount, 1000001, 'Should increment from exactly 1000000');
|
||||
} finally {
|
||||
try { fs.unlinkSync(counterFile); } catch {}
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nRound 24: post-edit-format.js (edge cases):');
|
||||
|
||||
if (await asyncTest('passes through malformed JSON unchanged', async () => {
|
||||
const malformedJson = '{"tool_input": {"file_path": "/test.ts"';
|
||||
const result = await runScript(path.join(scriptsDir, 'post-edit-format.js'), malformedJson);
|
||||
assert.strictEqual(result.code, 0);
|
||||
// Should pass through the malformed data (console.log adds \n)
|
||||
assert.ok(result.stdout.includes(malformedJson), 'Should pass through malformed JSON');
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (await asyncTest('passes through data for non-JS/TS file extensions', async () => {
|
||||
const stdinJson = JSON.stringify({ tool_input: { file_path: '/path/to/file.py' } });
|
||||
const result = await runScript(path.join(scriptsDir, 'post-edit-format.js'), stdinJson);
|
||||
assert.strictEqual(result.code, 0);
|
||||
assert.ok(result.stdout.includes('file.py'), 'Should pass through for .py files');
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nRound 24: post-edit-typecheck.js (edge cases):');
|
||||
|
||||
if (await asyncTest('skips typecheck for non-existent file and still passes through', async () => {
|
||||
const stdinJson = JSON.stringify({ tool_input: { file_path: '/nonexistent/deep/file.ts' } });
|
||||
const result = await runScript(path.join(scriptsDir, 'post-edit-typecheck.js'), stdinJson);
|
||||
assert.strictEqual(result.code, 0);
|
||||
assert.ok(result.stdout.includes('file.ts'), 'Should pass through for non-existent .ts file');
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (await asyncTest('passes through for non-TS extensions without running tsc', async () => {
|
||||
const stdinJson = JSON.stringify({ tool_input: { file_path: '/path/to/file.js' } });
|
||||
const result = await runScript(path.join(scriptsDir, 'post-edit-typecheck.js'), stdinJson);
|
||||
assert.strictEqual(result.code, 0);
|
||||
assert.ok(result.stdout.includes('file.js'), 'Should pass through for .js file without running tsc');
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nRound 24: session-start.js (edge cases):');
|
||||
|
||||
if (await asyncTest('exits 0 with empty sessions directory (no recent sessions)', async () => {
|
||||
const isoHome = path.join(os.tmpdir(), `ecc-start-empty-${Date.now()}`);
|
||||
fs.mkdirSync(path.join(isoHome, '.claude', 'sessions'), { recursive: true });
|
||||
fs.mkdirSync(path.join(isoHome, '.claude', 'skills', 'learned'), { recursive: true });
|
||||
try {
|
||||
const result = await runScript(path.join(scriptsDir, 'session-start.js'), '', {
|
||||
HOME: isoHome, USERPROFILE: isoHome
|
||||
});
|
||||
assert.strictEqual(result.code, 0, 'Should exit 0 with no sessions');
|
||||
// Should NOT inject any previous session data (stdout should be empty or minimal)
|
||||
assert.ok(!result.stdout.includes('Previous session summary'), 'Should not inject when no sessions');
|
||||
} finally {
|
||||
fs.rmSync(isoHome, { recursive: true, force: true });
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (await asyncTest('does not inject blank template session into context', async () => {
|
||||
const isoHome = path.join(os.tmpdir(), `ecc-start-blank-${Date.now()}`);
|
||||
const sessionsDir = path.join(isoHome, '.claude', 'sessions');
|
||||
fs.mkdirSync(sessionsDir, { recursive: true });
|
||||
fs.mkdirSync(path.join(isoHome, '.claude', 'skills', 'learned'), { recursive: true });
|
||||
|
||||
// Create a session file with the blank template marker
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const sessionFile = path.join(sessionsDir, `${today}-blank-session.tmp`);
|
||||
fs.writeFileSync(sessionFile, '# Session\n[Session context goes here]\n');
|
||||
|
||||
try {
|
||||
const result = await runScript(path.join(scriptsDir, 'session-start.js'), '', {
|
||||
HOME: isoHome, USERPROFILE: isoHome
|
||||
});
|
||||
assert.strictEqual(result.code, 0);
|
||||
// Should NOT inject blank template
|
||||
assert.ok(!result.stdout.includes('Previous session summary'), 'Should skip blank template sessions');
|
||||
} finally {
|
||||
fs.rmSync(isoHome, { recursive: true, force: true });
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
// Summary
|
||||
console.log('\n=== Test Results ===');
|
||||
console.log(`Passed: ${passed}`);
|
||||
|
||||
Reference in New Issue
Block a user