mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
Compare commits
1 Commits
fix/2300-l
...
feat/2318-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53078d3f85 |
@@ -124,9 +124,15 @@ function runStatusline() {
|
||||
const remaining = data.context_window?.remaining_percentage;
|
||||
|
||||
// Context window display (shows USED percentage scaled to usable context)
|
||||
// Claude Code reserves ~16.5% for autocompact buffer, so usable context
|
||||
// is 83.5% of the total window. We normalize to show 100% at that point.
|
||||
const AUTO_COMPACT_BUFFER_PCT = 16.5;
|
||||
// Claude Code reserves a buffer for autocompact. By default this is ~16.5%
|
||||
// of the total window, but users can override it via CLAUDE_CODE_AUTO_COMPACT_WINDOW
|
||||
// (a token count). When the env var is set, compute the buffer % dynamically so
|
||||
// the meter correctly reflects early-compaction configurations (#2219).
|
||||
const totalCtx = data.context_window?.total_tokens || 1_000_000;
|
||||
const acw = parseInt(process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW || '0', 10);
|
||||
const AUTO_COMPACT_BUFFER_PCT = acw > 0
|
||||
? Math.min(100, (acw / totalCtx) * 100)
|
||||
: 16.5;
|
||||
let ctx = '';
|
||||
if (remaining != null) {
|
||||
// Normalize: subtract buffer from remaining, scale to usable range
|
||||
|
||||
@@ -247,3 +247,78 @@ describe('readGsdState', () => {
|
||||
assert.deepEqual(s, {});
|
||||
});
|
||||
});
|
||||
|
||||
// ─── CLAUDE_CODE_AUTO_COMPACT_WINDOW context meter (#2219) ──────────────────
|
||||
|
||||
describe('context meter respects CLAUDE_CODE_AUTO_COMPACT_WINDOW (#2219)', () => {
|
||||
const { execFileSync } = require('node:child_process');
|
||||
const hookPath = path.join(__dirname, '..', 'hooks', 'gsd-statusline.js');
|
||||
|
||||
/**
|
||||
* Run the statusline hook with a synthetic context_window payload and
|
||||
* return the used_pct written to the bridge file.
|
||||
*/
|
||||
function runHook(remainingPct, totalTokens, acwEnv) {
|
||||
const sessionId = `test-2219-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
const payload = JSON.stringify({
|
||||
model: { display_name: 'Claude' },
|
||||
workspace: { current_dir: os.tmpdir() },
|
||||
session_id: sessionId,
|
||||
context_window: {
|
||||
remaining_percentage: remainingPct,
|
||||
total_tokens: totalTokens,
|
||||
},
|
||||
});
|
||||
|
||||
const env = { ...process.env };
|
||||
if (acwEnv != null) {
|
||||
env.CLAUDE_CODE_AUTO_COMPACT_WINDOW = String(acwEnv);
|
||||
} else {
|
||||
delete env.CLAUDE_CODE_AUTO_COMPACT_WINDOW;
|
||||
}
|
||||
|
||||
try {
|
||||
execFileSync(process.execPath, [hookPath], {
|
||||
input: payload,
|
||||
env,
|
||||
timeout: 4000,
|
||||
});
|
||||
} catch (e) {
|
||||
// Non-zero exit is fine — hook exits 0 on success, but we're reading
|
||||
// the bridge file, not the exit code. Ignore exit failures here.
|
||||
}
|
||||
|
||||
const bridgePath = path.join(os.tmpdir(), `claude-ctx-${sessionId}.json`);
|
||||
const bridge = JSON.parse(fs.readFileSync(bridgePath, 'utf8'));
|
||||
fs.unlinkSync(bridgePath);
|
||||
return bridge.used_pct;
|
||||
}
|
||||
|
||||
test('default buffer (no env var): 50% remaining → ~60% used', () => {
|
||||
// Default 16.5% buffer: usableRemaining = (50 - 16.5) / (100 - 16.5) * 100 ≈ 40.12%
|
||||
// used ≈ 100 - 40.12 = 59.88 → rounded 60
|
||||
const used = runHook(50, 1_000_000, null);
|
||||
assert.strictEqual(used, 60);
|
||||
});
|
||||
|
||||
test('CLAUDE_CODE_AUTO_COMPACT_WINDOW=400000: 50% remaining → ~83% used', () => {
|
||||
// With 1M total, 400k window → buffer = 40%. usableRemaining = (50 - 40) / (100 - 40) * 100 ≈ 16.67%
|
||||
// used ≈ 100 - 16.67 = 83.33 → rounded 83
|
||||
const used = runHook(50, 1_000_000, 400_000);
|
||||
assert.strictEqual(used, 83);
|
||||
});
|
||||
|
||||
test('CLAUDE_CODE_AUTO_COMPACT_WINDOW=0 falls back to default buffer', () => {
|
||||
// Explicit "0" means unset — should behave like no env var (16.5% buffer)
|
||||
const used = runHook(50, 1_000_000, 0);
|
||||
assert.strictEqual(used, 60);
|
||||
});
|
||||
|
||||
test('buffer capped at 100% when ACW exceeds total context', () => {
|
||||
// Pathological: ACW > totalCtx → buffer = 100%. With no usable range left,
|
||||
// usableRemaining = max(0, (50-100)/(100-100)*100) = max(0, -Inf) = 0,
|
||||
// so used = 100 (context reported as completely full).
|
||||
const used = runHook(50, 1_000_000, 2_000_000);
|
||||
assert.strictEqual(used, 100);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user