Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions lib/context-zone.fixtures.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,20 @@ export default [
{"name":"tokens 99000+999 in 200000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":99000,"total_output_tokens":999,"context_window_size":200000},"cli_version":"2.1.200"},"zones":null,"word":"smart","notices":[]},
{"name":"tokens 99000+1000 in 200000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":99000,"total_output_tokens":1000,"context_window_size":200000},"cli_version":"2.1.200"},"zones":null,"word":"smart","notices":[]},
{"name":"tokens 99000+1001 in 200000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":99000,"total_output_tokens":1001,"context_window_size":200000},"cli_version":"2.1.200"},"zones":null,"word":"acceptable","notices":[]},
{"name":"tokens 150000+9999 in 200000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":150000,"total_output_tokens":9999,"context_window_size":200000},"cli_version":"2.1.200"},"zones":null,"word":"acceptable","notices":[]},
{"name":"tokens 150000+10000 in 200000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":150000,"total_output_tokens":10000,"context_window_size":200000},"cli_version":"2.1.200"},"zones":null,"word":"acceptable","notices":[]},
{"name":"tokens 150000+10001 in 200000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":150000,"total_output_tokens":10001,"context_window_size":200000},"cli_version":"2.1.200"},"zones":null,"word":"dumb","notices":[]},
{"name":"tokens 140000+9999 in 200000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":140000,"total_output_tokens":9999,"context_window_size":200000},"cli_version":"2.1.200"},"zones":null,"word":"acceptable","notices":[]},
{"name":"tokens 140000+10000 in 200000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":140000,"total_output_tokens":10000,"context_window_size":200000},"cli_version":"2.1.200"},"zones":null,"word":"acceptable","notices":[]},
{"name":"tokens 140000+10001 in 200000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":140000,"total_output_tokens":10001,"context_window_size":200000},"cli_version":"2.1.200"},"zones":null,"word":"dumb","notices":[]},
{"name":"tokens 199000+1000 in 200000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":199000,"total_output_tokens":1000,"context_window_size":200000},"cli_version":"2.1.200"},"zones":null,"word":"dumb","notices":[]},
{"name":"tokens 200000+1 in 200000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":200000,"total_output_tokens":1,"context_window_size":200000},"cli_version":"2.1.200"},"zones":null,"word":"unknown","notices":[]},
{"name":"tokens 190000+10000 in 1000000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":190000,"total_output_tokens":10000,"context_window_size":1000000},"cli_version":"2.1.200"},"zones":null,"word":"smart","notices":[]},
{"name":"tokens 200000+1 in 1000000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":200000,"total_output_tokens":1,"context_window_size":1000000},"cli_version":"2.1.200"},"zones":null,"word":"acceptable","notices":[]},
{"name":"tokens 399000+1000 in 1000000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":399000,"total_output_tokens":1000,"context_window_size":1000000},"cli_version":"2.1.200"},"zones":null,"word":"acceptable","notices":[]},
{"name":"tokens 400000+1 in 1000000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":400000,"total_output_tokens":1,"context_window_size":1000000},"cli_version":"2.1.200"},"zones":null,"word":"dumb","notices":[]},
{"name":"tokens 118000+10000 in 1000000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":118000,"total_output_tokens":10000,"context_window_size":1000000},"cli_version":"2.1.200"},"zones":null,"word":"smart","notices":[]},
{"name":"tokens 128000+1 in 1000000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":128000,"total_output_tokens":1,"context_window_size":1000000},"cli_version":"2.1.200"},"zones":null,"word":"acceptable","notices":[]},
{"name":"tokens 249000+1000 in 1000000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":249000,"total_output_tokens":1000,"context_window_size":1000000},"cli_version":"2.1.200"},"zones":null,"word":"acceptable","notices":[]},
{"name":"tokens 250000+1 in 1000000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":250000,"total_output_tokens":1,"context_window_size":1000000},"cli_version":"2.1.200"},"zones":null,"word":"dumb","notices":[]},
{"name":"tokens 999000+1000 in 1000000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":999000,"total_output_tokens":1000,"context_window_size":1000000},"cli_version":"2.1.200"},"zones":null,"word":"dumb","notices":[]},
{"name":"tokens 1000000+1 in 1000000","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":1000000,"total_output_tokens":1,"context_window_size":1000000},"cli_version":"2.1.200"},"zones":null,"word":"unknown","notices":[]},
{"name":"window 500000 takes the 200000 class","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":150000,"total_output_tokens":0,"context_window_size":500000},"cli_version":"2.1.200"},"zones":null,"word":"acceptable","notices":[]},
{"name":"window 500000 takes the 200000 class","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":110000,"total_output_tokens":0,"context_window_size":500000},"cli_version":"2.1.200"},"zones":null,"word":"acceptable","notices":[]},
{"name":"window 999999 takes the 200000 class","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":170000,"total_output_tokens":0,"context_window_size":999999},"cli_version":"2.1.200"},"zones":null,"word":"dumb","notices":[]},
{"name":"window 2000000 takes the 1000000 class","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":300000,"total_output_tokens":0,"context_window_size":2000000},"cli_version":"2.1.200"},"zones":null,"word":"acceptable","notices":[]},
{"name":"window 2000000 takes the 1000000 class","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":200000,"total_output_tokens":0,"context_window_size":2000000},"cli_version":"2.1.200"},"zones":null,"word":"acceptable","notices":[]},
{"name":"window 100000 has no class","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":10000,"total_output_tokens":0,"context_window_size":100000},"cli_version":"2.1.200"},"zones":null,"word":"unknown","notices":[]},
{"name":"window 0 is not computable","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":0,"total_output_tokens":0,"context_window_size":0},"cli_version":"2.1.200"},"zones":null,"word":"unknown","notices":[]},
{"name":"negative input tokens","sid":"s1","snapshot":{"captured_at":"@now-30","session_id":"s1","context_window":{"used_percentage":null,"remaining_percentage":80,"current_usage":{"input_tokens":100},"total_input_tokens":-1,"total_output_tokens":0,"context_window_size":200000},"cli_version":"2.1.200"},"zones":null,"word":"unknown","notices":[]},
Expand Down
12 changes: 6 additions & 6 deletions lib/context-zone.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@
# smart ≤ 50 < acceptable ≤ 75 < dumb over used_percentage, and per
# window-class token bands over occupancy:
#
# window class 200000: smart ≤ 100000 < acceptable ≤ 160000 < dumb
# window class 1000000: smart ≤ 200000 < acceptable ≤ 400000 < dumb
# window class 200000: smart ≤ 100000 < acceptable ≤ 150000 < dumb
# window class 1000000: smart ≤ 128000 < acceptable ≤ 250000 < dumb
#
# All are declared judgment defaults, NOT doc- or benchmark-derived
# constants (anchors and provenance are recorded in the reader contract).
Expand All @@ -63,8 +63,8 @@
# "smart_max_used_percentage": 50,
# "acceptable_max_used_percentage": 75,
# "token_bands": {
# "200000": { "smart_max_tokens": 100000, "acceptable_max_tokens": 160000 },
# "1000000": { "smart_max_tokens": 200000, "acceptable_max_tokens": 400000 }
# "200000": { "smart_max_tokens": 100000, "acceptable_max_tokens": 150000 },
# "1000000": { "smart_max_tokens": 128000, "acceptable_max_tokens": 250000 }
# }
# }
#
Expand Down Expand Up @@ -92,7 +92,7 @@ DEFAULT_ACCEPTABLE_MAX=75
# [class, smart_max, acceptable_max] rows, ascending class order. JSON rather
# than whitespace rows because the bands are handed to the snapshot pass as
# data, so the comparison happens there instead of in a separate awk process.
DEFAULT_TOKEN_BANDS='[[200000,100000,160000],[1000000,200000,400000]]'
DEFAULT_TOKEN_BANDS='[[200000,100000,150000],[1000000,128000,250000]]'

unknown() {
printf 'unknown\n'
Expand Down Expand Up @@ -197,7 +197,7 @@ if [[ -e "$zones" ]]; then
acceptable_max=${zpct#* }
fi
if [[ "$ztb" == "invalid" ]]; then
printf 'context-guard: zones.json token_bands malformed — using shipped default token bands (200000:100000/160000, 1000000:200000/400000)\n' >&2
printf 'context-guard: zones.json token_bands malformed — using shipped default token bands (200000:100000/150000, 1000000:128000/250000)\n' >&2
elif [[ "$ztb" != "absent" ]]; then
token_bands=$ztb
fi
Expand Down
2 changes: 1 addition & 1 deletion plugins/context-guard/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "context-guard",
"version": "0.12.3",
"version": "0.13.0",
"description": "Per-session context-window observability. A mod writes each session's context-window figures to a snapshot file and tells Claude its context zone (smart, acceptable or dumb, from bands in zones.json) at each crossing, approach and compaction; the person gets a toast and a transcript line. /context-guard prints status and toggles an optional band row. It answers a status tool. Optional blocking mode gates new mutating work in a blocked zone, except handoffs. A PostCompact hook leaves a marker.",
"author": {
"name": "Melodic Software",
Expand Down
6 changes: 6 additions & 0 deletions plugins/context-guard/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to the `context-guard` plugin.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.13.0] - 2026-10-04

### Changed

- **Tighter shipped token bands.** On a 1M-token window a session now leaves `smart` above 128,000 tokens (was 200,000) and reads `dumb` above 250,000 (was 400,000); on a 200k window `dumb` starts above 150,000 (was 160,000). Percent bands stay at 50/75 and the worse of the two still decides. 128K is the last length at which a current Claude model was measured strong on long-context retrieval; the reader contract records the basis. Set `token_bands` in `zones.json` to keep the old edges.

## [0.12.3] - 2026-10-04

### Changed
Expand Down
4 changes: 2 additions & 2 deletions plugins/context-guard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ degraded context **before** quality slips, instead of guessing. Four parts:
word: `smart` / `acceptable` / `dumb` / `unknown`. Two band shapes, combined conservatively (the
worse computable zone wins): percentage bands over `used_percentage` (shipped defaults
smart ≤ 50 < acceptable ≤ 75 < dumb) and window-class token bands over occupancy
(`total_input_tokens + total_output_tokens`; shipped defaults 100k/160k on a 200k window,
200k/400k on a 1M window). Bands come from the machine-scope
(`total_input_tokens + total_output_tokens`; shipped defaults 100k/150k on a 200k window,
128k/250k on a 1M window). Bands come from the machine-scope
`~/.claude/context-guard/zones.json` when present and valid, else from the shipped defaults.
Zones say *where you are*; consumers decide *what to do*.
- **PostCompact marker hook** (`hooks/post-compact-mark.sh`), a settings hook, so it runs where
Expand Down
4 changes: 2 additions & 2 deletions plugins/context-guard/hooks/context-guard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1283,7 +1283,7 @@ test('command: no argument prints the status and details', BLOCKING(7), async ($
'60% of a 200000-token window used (120000 tokens)',
// The token band of the session's window class is shown beside the percent band, since the
// worse of the two decides the zone.
'Bands (the worse decides): smart up to 50% and 100000 tokens, acceptable up to 75% and 160000 tokens',
'Bands (the worse decides): smart up to 50% and 100000 tokens, acceptable up to 75% and 150000 tokens',
'approach margin 4 points',
'gate blocking, 7 grace calls',
'band row off',
Expand Down Expand Up @@ -1379,7 +1379,7 @@ test('pull tool: the latest figures and zone, with no line attached, whatever th
total_input_tokens: 120_000,
total_output_tokens: 900,
context_window_size: 200_000,
bands: { smart_max_used_percentage: 50, acceptable_max_used_percentage: 75, smart_max_tokens: 100_000, acceptable_max_tokens: 160_000 },
bands: { smart_max_used_percentage: 50, acceptable_max_used_percentage: 75, smart_max_tokens: 100_000, acceptable_max_tokens: 150_000 },
approach_margin: 5,
gate: { mode: 'advisory', grace_calls: 20, calls_counted: 0 },
})
Expand Down
4 changes: 2 additions & 2 deletions plugins/context-guard/hooks/zone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export const DEFAULT_BANDS: Bands = {
smart: 50,
acceptable: 75,
tokens: [
[200_000, 100_000, 160_000],
[1_000_000, 200_000, 400_000],
[200_000, 100_000, 150_000],
[1_000_000, 128_000, 250_000],
],
}
export const RANK: Record<Zone, number> = { smart: 0, acceptable: 1, dumb: 2 }
Expand Down
Loading
Loading