Show the per-model usage limit, and size the status line by measurement
/usage reports a weekly limit scoped to a single model (Fable) that the status line did not surface. It is absent from both the status line's stdin JSON and the legacy seven_day_opus/seven_day_sonnet fields, which are always null now; it lives in the usage API's .limits[] under kind "weekly_scoped". Render it labelled by scope.model.display_name so it follows whichever model the limit applies to, inside the 7d segment since both are weekly limits sharing one reset time. Fitting it exposed a problem with sizing by width tier. Tiers only know the terminal width, so content that varies with session state — branch name, cwd, model display name — could push a line past the edge and be clipped by the renderer. Measure the assembled line instead (vis_len) and emit the richest of four rate-limit variants that fits, on one line or two: with reset times, with extra-usage credits, bars only, or bars without the per-model segment. Every branch is fit-checked, including with wrapping disabled. Two lines render correctly in the status line. Cap the cwd basename as well: nothing else shortened line one, so a long project directory could overflow it on its own. Correct the usable width. Claude Code exports COLUMNS but applies the `padding` setting on top of it rather than deducting it first, so a line sized to COLUMNS is clipped; deduct padding on both sides plus a column of margin. Sanitise payload data before rendering. printf %b interprets backslash escapes, which is how the colour variables work, so a cwd or model name containing a literal \n — or a real newline, which jq decodes from the JSON — would split the line and break both the width measurement and the two-line guarantee. Parse the usage payload in one jq pass rather than ten. The status line runs on every redraw and per-field parsing had become the dominant cost; this brings a render back to roughly what it was before (~155ms vs ~115ms parent), the remainder being the reset times the tiered version did not show at this width. Fields are read one per line rather than via @tsv: tab is IFS whitespace, so `read` collapses runs of it and an empty field — no scoped limit, which is the common case — silently shifted every later field along by one. Tests cover the API payload shapes including malformed and hostile input, the width invariants (never exceed the usable width, never more than two lines, never wrap unnecessarily), and a sweep over widths, branch lengths, model names, cwd lengths and extra-usage. They restore the live usage cache on exit, and remove the fixture outright when there was no cache to restore — otherwise a test run would leave fabricated usage figures live for an hour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
80683b82c1
commit
697ad5055f
5 changed files with 629 additions and 49 deletions
24
claude/tests/run.sh
Executable file
24
claude/tests/run.sh
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
#!/bin/bash
|
||||
# Run every test for claude/statusline.isaacaudet.sh.
|
||||
#
|
||||
# Usage: claude/tests/run.sh
|
||||
#
|
||||
# The tests render the status line directly, so they need the same tools it
|
||||
# does (bash, jq, git, python3). They briefly replace the cached usage-API
|
||||
# response in /tmp/claude with fixtures and restore it on exit.
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1
|
||||
|
||||
status=0
|
||||
for t in statusline.isaacaudet.payload_test.sh \
|
||||
statusline.isaacaudet.width_test.sh; do
|
||||
echo "== $t"
|
||||
bash "$t" || status=1
|
||||
echo
|
||||
done
|
||||
|
||||
echo "== statusline.isaacaudet.overflow_test.py"
|
||||
python3 statusline.isaacaudet.overflow_test.py || status=1
|
||||
|
||||
echo
|
||||
[ "$status" -eq 0 ] && echo "ALL TESTS PASSED" || echo "SOME TESTS FAILED"
|
||||
exit "$status"
|
||||
110
claude/tests/statusline.isaacaudet.overflow_test.py
Executable file
110
claude/tests/statusline.isaacaudet.overflow_test.py
Executable file
|
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Exhaustive overflow sweep for claude/statusline.isaacaudet.sh.
|
||||
|
||||
Covers the Isaac Audet-inspired status line (the variant symlinked from
|
||||
~/.claude/statusline.sh); the sibling statusline.burnrate.sh and
|
||||
statusline.original.sh are not covered.
|
||||
|
||||
Asserts the two hard invariants across a grid of terminal widths, branch-name
|
||||
lengths and model-name lengths:
|
||||
1. no rendered line exceeds the usable width
|
||||
2. never more than two lines
|
||||
|
||||
Catches the case tier-based sizing missed: content whose width depends on
|
||||
session state (branch, cwd, model display name) rather than on the terminal.
|
||||
"""
|
||||
import subprocess, re, unicodedata, sys, os, itertools, tempfile, shutil
|
||||
|
||||
SCRIPT = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||
"statusline.isaacaudet.sh")
|
||||
BRANCHES = ["main", "feature/some-longer-branch-name",
|
||||
"feature/an-extremely-long-branch-name-that-keeps-going-and-going"]
|
||||
MODELS = ["Opus 5", "Opus 5 (1M context)"]
|
||||
WIDTHS = range(64, 245, 10)
|
||||
# The cwd basename is rendered at the widest layouts and is capped by
|
||||
# CWD_MAX_LEN; vary it, since a long project directory was one of the ways
|
||||
# line one used to overflow.
|
||||
CWDS = ["proj", "a-really-quite-long-project-directory-name-that-someone-might-have"]
|
||||
# Extra-usage credits add ~30 columns and used to overflow line two on their own.
|
||||
EXTRA = {
|
||||
"off": '"extra_usage":{"is_enabled":false}',
|
||||
"on": '"extra_usage":{"is_enabled":true,"monthly_limit":200000,'
|
||||
'"used_credits":123456,"utilization":62.0}',
|
||||
}
|
||||
CACHE = "/tmp/claude/statusline-usage-cache.json"
|
||||
FIXTURE = ('{"five_hour":{"utilization":5.0,"resets_at":"2026-08-27T15:40:00+00:00"},'
|
||||
'"seven_day":{"utilization":7.0,"resets_at":"2026-08-28T02:00:00+00:00"},%s,'
|
||||
'"limits":[{"kind":"weekly_scoped","percent":10,'
|
||||
'"scope":{"model":{"display_name":"Fable"}},"is_active":true}]}')
|
||||
|
||||
def width_of(s):
|
||||
return sum(2 if unicodedata.east_asian_width(c) in "WF" else 1
|
||||
for c in re.sub(r"\033\[[0-9;]*m", "", s))
|
||||
|
||||
def main():
|
||||
pad = int(subprocess.run(
|
||||
["jq", "-r", ".statusLine.padding // 0", os.path.expanduser("~/.claude/settings.json")],
|
||||
capture_output=True, text=True).stdout.strip() or 0)
|
||||
root = tempfile.mkdtemp()
|
||||
# Back up the live usage cache. If there wasn't one, remove the fixture at
|
||||
# the end rather than leaving it: the status line would treat it as a fresh
|
||||
# response for up to an hour and report fabricated usage.
|
||||
had_cache = os.path.exists(CACHE)
|
||||
saved = open(CACHE, "rb").read() if had_cache else None
|
||||
|
||||
fails, checked = [], 0
|
||||
try:
|
||||
for cwd_name, extra_name in itertools.product(CWDS, EXTRA):
|
||||
with open(CACHE, "w") as fh:
|
||||
fh.write(FIXTURE % EXTRA[extra_name])
|
||||
repo = os.path.join(root, cwd_name)
|
||||
os.makedirs(repo, exist_ok=True)
|
||||
subprocess.run(["git", "-C", repo, "init", "-q"], check=True)
|
||||
subprocess.run(["git", "-C", repo, "-c", "user.email=t@t", "-c", "user.name=t",
|
||||
"commit", "-q", "--allow-empty", "-m", "i"], check=True)
|
||||
for br in BRANCHES:
|
||||
subprocess.run(["git", "-C", repo, "checkout", "-q", "-B", br], capture_output=True)
|
||||
for f in os.listdir("/tmp/claude"):
|
||||
if f.startswith("git-"):
|
||||
os.remove("/tmp/claude/" + f)
|
||||
for model, cols in itertools.product(MODELS, WIDTHS):
|
||||
stdin = ('{"model":{"display_name":"%s"},"cwd":"%s",'
|
||||
'"cost":{"total_cost_usd":8.31},"context_window":'
|
||||
'{"context_window_size":1000000,"current_usage":'
|
||||
'{"input_tokens":137000,"cache_read_input_tokens":34000}}}' % (model, repo))
|
||||
out = subprocess.run(["bash", SCRIPT], input=stdin, capture_output=True,
|
||||
text=True, env=dict(os.environ, TERM_WIDTH=str(cols))).stdout
|
||||
usable = max(20, cols - 2 * pad - 1)
|
||||
lines = out.rstrip("\n").split("\n")
|
||||
checked += 1
|
||||
tag = (br, model, cols, cwd_name, extra_name)
|
||||
if len(lines) > 2:
|
||||
fails.append(tag + ("more than two lines",))
|
||||
continue
|
||||
if not any(l.strip() for l in lines):
|
||||
fails.append(tag + ("rendered nothing",))
|
||||
continue
|
||||
mx = max(width_of(l) for l in lines)
|
||||
if mx > usable:
|
||||
fails.append(tag + (f"max {mx} > usable {usable}",))
|
||||
finally:
|
||||
shutil.rmtree(root, ignore_errors=True)
|
||||
if saved is not None:
|
||||
with open(CACHE, "wb") as fh:
|
||||
fh.write(saved)
|
||||
elif os.path.exists(CACHE):
|
||||
os.remove(CACHE)
|
||||
|
||||
print(f"checked {checked} combinations "
|
||||
f"(widths {WIDTHS.start}-{WIDTHS.stop - 1}, {len(BRANCHES)} branches, "
|
||||
f"{len(MODELS)} models, {len(CWDS)} cwds, extra-usage on and off)")
|
||||
if fails:
|
||||
print(f"FAIL: {len(fails)} overflow(s)")
|
||||
for f in fails[:10]:
|
||||
print(" ", f)
|
||||
return 1
|
||||
print("PASS: no line exceeded the usable width")
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
165
claude/tests/statusline.isaacaudet.payload_test.sh
Executable file
165
claude/tests/statusline.isaacaudet.payload_test.sh
Executable file
|
|
@ -0,0 +1,165 @@
|
|||
#!/bin/bash
|
||||
# Tests for claude/statusline.isaacaudet.sh — the Isaac Audet-inspired Claude
|
||||
# Code status line (the variant symlinked from ~/.claude/statusline.sh).
|
||||
#
|
||||
# Sibling variants in claude/ (statusline.burnrate.sh, statusline.original.sh)
|
||||
# are NOT covered by these tests.
|
||||
#
|
||||
# Payload shapes: how the usage API's rate-limit JSON is rendered, in
|
||||
# particular the per-model ("weekly_scoped") limit such as Fable, and how the
|
||||
# renderer degrades when the group will not fit.
|
||||
SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/statusline.isaacaudet.sh"
|
||||
CACHE="/tmp/claude/statusline-usage-cache.json"
|
||||
BACKUP="$(mktemp)"
|
||||
REPO="$(mktemp -d)"
|
||||
[ -f "$CACHE" ] && cp "$CACHE" "$BACKUP"
|
||||
|
||||
# Restore the real cache. If there was no cache to begin with, REMOVE the
|
||||
# fixture rather than leaving it: the status line treats a fixture as a fresh
|
||||
# response for api_cache_max (1h) and would report fabricated usage.
|
||||
cleanup() {
|
||||
if [ -s "$BACKUP" ]; then cp "$BACKUP" "$CACHE"; else rm -f "$CACHE"; fi
|
||||
rm -f "$BACKUP"; rm -rf "$REPO"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
git -C "$REPO" init -q 2>/dev/null
|
||||
git -C "$REPO" -c user.email=t@t -c user.name=t commit -q --allow-empty -m init 2>/dev/null
|
||||
|
||||
pass=0; fail=0
|
||||
ok() { echo " PASS $1"; pass=$((pass+1)); }
|
||||
bad() { echo " FAIL $1"; [ -n "$2" ] && printf '%s\n' "$2" | sed 's/^/ /'; fail=$((fail+1)); }
|
||||
|
||||
# Render, with ANSI stripped. $1 = TERM_WIDTH, $2 = cwd, $3 = model name.
|
||||
render() {
|
||||
local w="$1" cwd="${2:-$REPO}" model="${3:-Opus 5}"
|
||||
stdin_json "$cwd" "$model" \
|
||||
| TERM_WIDTH="$w" bash "$SCRIPT" 2>&1 | sed $'s/\033\[[0-9;]*m//g'
|
||||
}
|
||||
|
||||
# Build the payload in python: a backslash written into shell-constructed JSON
|
||||
# is a JSON escape, so "pro\nj" would reach the script as a REAL newline rather
|
||||
# than the literal characters the hostile-string tests mean to exercise.
|
||||
stdin_json() {
|
||||
CWD="$1" MODEL="$2" python3 -c '
|
||||
import json, os
|
||||
print(json.dumps({"model": {"display_name": os.environ["MODEL"]},
|
||||
"cwd": os.environ["CWD"],
|
||||
"cost": {"total_cost_usd": 0.5},
|
||||
"context_window": {"context_window_size": 200000,
|
||||
"current_usage": {"input_tokens": 1000}}}))'
|
||||
}
|
||||
nth() { printf '%s\n' "$1" | sed -n "${2}p"; }
|
||||
count() { printf '%s\n' "$1" | wc -l | tr -d ' '; }
|
||||
vis() { printf '%s' "$1" | python3 -c "
|
||||
import sys,unicodedata
|
||||
s=sys.stdin.read(); print(sum(2 if unicodedata.east_asian_width(c) in 'WF' else 1 for c in s))"; }
|
||||
|
||||
fixture() { cat > "$CACHE"; }
|
||||
|
||||
BASE='"five_hour":{"utilization":5.0,"resets_at":"2026-08-27T15:40:00+00:00"},
|
||||
"seven_day":{"utilization":7.0,"resets_at":"2026-08-28T02:00:00+00:00"}'
|
||||
|
||||
# ---------------------------------------------------------------- scoped limit
|
||||
echo "Per-model (weekly_scoped) limit:"
|
||||
fixture <<JSON
|
||||
{$BASE,"extra_usage":{"is_enabled":false},
|
||||
"limits":[{"kind":"weekly_scoped","percent":10,"scope":{"model":{"display_name":"Fable"}},"is_active":true}]}
|
||||
JSON
|
||||
out=$(render 250)
|
||||
# Assert the label AND its percentage together, so a bar rendered with the
|
||||
# wrong number cannot pass.
|
||||
case "$out" in *"Fable"*"10%"*) ok "wide: renders 'Fable' with its percentage" ;;
|
||||
*) bad "wide: renders 'Fable' with its percentage" "$out" ;; esac
|
||||
|
||||
# Wrapped: assert Fable is on line TWO specifically, not merely present.
|
||||
out=$(render 90)
|
||||
[ "$(count "$out")" = "2" ] && ok "narrow: wraps to two lines" || bad "narrow: wraps to two lines" "$out"
|
||||
case "$(nth "$out" 2)" in *"Fable"*"10%"*) ok "narrow: Fable is on line two" ;;
|
||||
*) bad "narrow: Fable is on line two" "$out" ;; esac
|
||||
case "$(nth "$out" 1)" in *"Fable"*) bad "narrow: Fable not duplicated on line one" "$out" ;;
|
||||
*) ok "narrow: Fable not duplicated on line one" ;; esac
|
||||
|
||||
# The label must come from the payload, not be hardcoded.
|
||||
fixture <<JSON
|
||||
{$BASE,"extra_usage":{"is_enabled":false},
|
||||
"limits":[{"kind":"weekly_scoped","percent":42,"scope":{"model":{"display_name":"Nimbus"}},"is_active":false}]}
|
||||
JSON
|
||||
out=$(render 250)
|
||||
case "$out" in *"Nimbus"*"42%"*) ok "label comes from scope.model.display_name" ;;
|
||||
*) bad "label comes from scope.model.display_name" "$out" ;; esac
|
||||
case "$out" in *Fable*) bad "no hardcoded 'Fable'" "$out" ;; *) ok "no hardcoded 'Fable'" ;; esac
|
||||
|
||||
# ------------------------------------------------------------- malformed input
|
||||
echo "Malformed / legacy payloads still render the 5h and 7d bars:"
|
||||
for desc in "no limits key:{$BASE,\"extra_usage\":{\"is_enabled\":false}}" \
|
||||
"limits is a string:{$BASE,\"extra_usage\":{\"is_enabled\":false},\"limits\":\"nope\"}" \
|
||||
"limits has no scoped:{$BASE,\"extra_usage\":{\"is_enabled\":false},\"limits\":[{\"kind\":\"weekly_all\",\"percent\":7}]}" \
|
||||
"percent as string:{$BASE,\"extra_usage\":{\"is_enabled\":false},\"limits\":[{\"kind\":\"weekly_scoped\",\"percent\":\"42.7\",\"scope\":{\"model\":{\"display_name\":\"Str\"}},\"is_active\":true}]}"; do
|
||||
name="${desc%%:*}"; json="${desc#*:}"
|
||||
printf '%s' "$json" | fixture
|
||||
out=$(render 250)
|
||||
if [ "$(count "$out")" = "1" ] && case "$out" in *"5h"*"7d"*) true;; *) false;; esac \
|
||||
&& case "$out" in *error*|*null*|*"jq:"*|*"line "*) false;; *) true;; esac; then
|
||||
ok "$name"
|
||||
else bad "$name" "$out"; fi
|
||||
done
|
||||
# a string percentage should round, not become 0
|
||||
case "$out" in *"Str"*"43%"*) ok "percent given as a string rounds to 43%" ;;
|
||||
*) bad "percent given as a string rounds to 43%" "$out" ;; esac
|
||||
|
||||
# ------------------------------------------------- extra usage (regression §1)
|
||||
echo "Extra-usage credits must not overflow line two:"
|
||||
fixture <<JSON
|
||||
{$BASE,
|
||||
"extra_usage":{"is_enabled":true,"monthly_limit":200000,"used_credits":123456,"utilization":62.0},
|
||||
"limits":[{"kind":"weekly_scoped","percent":10,"scope":{"model":{"display_name":"Fable"}},"is_active":true}]}
|
||||
JSON
|
||||
out=$(render 250)
|
||||
case "$out" in *"extra"*) ok "wide: extra-usage segment is shown" ;;
|
||||
*) bad "wide: extra-usage segment is shown" "$out" ;; esac
|
||||
for w in 73 80 95; do
|
||||
out=$(render "$w"); u=$(( w - 5 )); worst=0
|
||||
while IFS= read -r l; do c=$(vis "$l"); [ "$c" -gt "$worst" ] && worst=$c; done <<< "$out"
|
||||
[ "$worst" -le "$u" ] \
|
||||
&& ok "width $w: no line exceeds usable $u (worst $worst)" \
|
||||
|| bad "width $w: no line exceeds usable $u (worst $worst)" "$out"
|
||||
done
|
||||
|
||||
# ------------------------------------------ hostile strings (regression §9)
|
||||
echo "Control characters and backslashes in payload data:"
|
||||
fixture <<JSON
|
||||
{$BASE,"extra_usage":{"is_enabled":false},
|
||||
"limits":[{"kind":"weekly_scoped","percent":10,"scope":{"model":{"display_name":"A\nB"}},"is_active":true}]}
|
||||
JSON
|
||||
out=$(render 250)
|
||||
[ "$(count "$out")" = "1" ] && ok "newline in display_name does not split the line" \
|
||||
|| bad "newline in display_name does not split the line" "$out"
|
||||
fixture <<JSON
|
||||
{$BASE,"extra_usage":{"is_enabled":false},"limits":[]}
|
||||
JSON
|
||||
mkdir -p "$REPO/sub"
|
||||
out=$(render 250 "$REPO/pro\\nj")
|
||||
[ "$(count "$out")" = "1" ] && ok "literal backslash-n in cwd does not split the line" \
|
||||
|| bad "literal backslash-n in cwd does not split the line" "$out"
|
||||
out=$(render 250 "$REPO" 'Opus\n5')
|
||||
[ "$(count "$out")" = "1" ] && ok "literal backslash-n in model name does not split the line" \
|
||||
|| bad "literal backslash-n in model name does not split the line" "$out"
|
||||
|
||||
# --------------------------------------------- WRAP_NARROW=false (regression §2)
|
||||
echo "With WRAP_NARROW=false the single line must still be fit-checked:"
|
||||
NOWRAP=$(mktemp); sed 's/^WRAP_NARROW=true/WRAP_NARROW=false/' "$SCRIPT" > "$NOWRAP"
|
||||
fixture <<JSON
|
||||
{$BASE,"extra_usage":{"is_enabled":false},
|
||||
"limits":[{"kind":"weekly_scoped","percent":10,"scope":{"model":{"display_name":"Fable"}},"is_active":true}]}
|
||||
JSON
|
||||
for w in 81 105 130; do
|
||||
out=$(stdin_json "$REPO" "Opus 5" | TERM_WIDTH=$w bash "$NOWRAP" 2>&1 | sed $'s/\033\[[0-9;]*m//g')
|
||||
u=$(( w - 5 )); c=$(vis "$out")
|
||||
[ "$(count "$out")" = "1" ] && [ "$c" -le "$u" ] \
|
||||
&& ok "width $w: one line, $c <= usable $u" \
|
||||
|| bad "width $w: one line within usable $u (got $(count "$out") line/s, $c cols)" "$out"
|
||||
done
|
||||
rm -f "$NOWRAP"
|
||||
|
||||
echo; echo "pass=$pass fail=$fail"; [ "$fail" -eq 0 ]
|
||||
93
claude/tests/statusline.isaacaudet.width_test.sh
Executable file
93
claude/tests/statusline.isaacaudet.width_test.sh
Executable file
|
|
@ -0,0 +1,93 @@
|
|||
#!/bin/bash
|
||||
# Tests for claude/statusline.isaacaudet.sh — the Isaac Audet-inspired Claude
|
||||
# Code status line (the variant symlinked from ~/.claude/statusline.sh).
|
||||
#
|
||||
# Sibling variants in claude/ (statusline.burnrate.sh, statusline.original.sh)
|
||||
# are NOT covered by these tests.
|
||||
#
|
||||
# Width invariants.
|
||||
#
|
||||
# Expressed in USABLE width (what the renderer actually has: COLUMNS minus
|
||||
# padding on both sides minus a margin), since that is what the script wraps on.
|
||||
# Asserting invariants rather than fixed line counts keeps these from rotting
|
||||
# every time a segment's content changes.
|
||||
SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/statusline.isaacaudet.sh"
|
||||
CACHE="/tmp/claude/statusline-usage-cache.json"
|
||||
PAD=$(jq -r '.statusLine.padding // 0' "$HOME/.claude/settings.json" 2>/dev/null || echo 0)
|
||||
OVERHEAD=$(( 2 * PAD + 1 ))
|
||||
BACKUP=$(mktemp); [ -f "$CACHE" ] && cp "$CACHE" "$BACKUP"
|
||||
REPO=$(mktemp -d)
|
||||
# If there was no cache to begin with, REMOVE the fixture rather than leaving
|
||||
# it: the status line would treat it as a fresh response for up to an hour.
|
||||
cleanup() {
|
||||
if [ -s "$BACKUP" ]; then cp "$BACKUP" "$CACHE"; else rm -f "$CACHE"; fi
|
||||
rm -f "$BACKUP"; rm -rf "$REPO"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
cat > "$CACHE" <<'JSON'
|
||||
{"five_hour":{"utilization":5.0,"resets_at":"2026-08-27T15:40:00+00:00"},
|
||||
"seven_day":{"utilization":7.0,"resets_at":"2026-08-28T02:00:00+00:00"},
|
||||
"extra_usage":{"is_enabled":false},
|
||||
"limits":[{"kind":"weekly_scoped","group":"weekly","percent":10,"resets_at":"2026-08-28T02:00:00+00:00","scope":{"model":{"display_name":"Fable"}},"is_active":true}]}
|
||||
JSON
|
||||
git -C "$REPO" init -q 2>/dev/null
|
||||
git -C "$REPO" -c user.email=t@t -c user.name=t commit -q --allow-empty -m init 2>/dev/null
|
||||
|
||||
widths() { python3 -c "
|
||||
import sys,re,unicodedata
|
||||
for ln in sys.stdin.read().rstrip('\n').split('\n'):
|
||||
p=re.sub(r'\033\[[0-9;]*m','',ln)
|
||||
print(sum(2 if unicodedata.east_asian_width(c) in 'WF' else 1 for c in p))"; }
|
||||
|
||||
pass=0; fail=0
|
||||
check() { # usable, branch, model, cost, [xfail-reason]
|
||||
local u="$1" branch="$2" model="$3" cost="$4" xfail="${5:-}"
|
||||
local w=$(( u + OVERHEAD ))
|
||||
git -C "$REPO" checkout -q -B "$branch" 2>/dev/null; rm -f /tmp/claude/git-*
|
||||
local stdin="{\"model\":{\"display_name\":\"$model\"},\"cwd\":\"$REPO\",\"cost\":{\"total_cost_usd\":$cost},\"context_window\":{\"context_window_size\":200000,\"current_usage\":{\"input_tokens\":45000,\"cache_read_input_tokens\":30000}}}"
|
||||
local out ws n mx=0 over=0
|
||||
out=$(TERM_WIDTH=$w bash "$SCRIPT" <<<"$stdin")
|
||||
ws=$(printf '%s' "$out" | widths)
|
||||
n=$(printf '%s\n' "$ws" | wc -l | tr -d ' ')
|
||||
while read -r c; do [ "$c" -gt "$u" ] && over=1; [ "$c" -gt "$mx" ] && mx=$c; done <<< "$ws"
|
||||
|
||||
local why=""
|
||||
# INVARIANT 1: never exceed the usable width
|
||||
[ "$over" = "1" ] && why="line exceeds usable width"
|
||||
# INVARIANT 2: at most two lines
|
||||
[ "$n" -gt 2 ] && why="${why:-more than two lines}"
|
||||
# INVARIANT 3: only wrap when it was actually necessary
|
||||
if [ -z "$why" ] && [ "$n" = "2" ]; then
|
||||
local l1 l2; l1=$(printf '%s\n' "$ws" | sed -n 1p); l2=$(printf '%s\n' "$ws" | sed -n 2p)
|
||||
[ $(( l1 + 3 + l2 )) -le "$u" ] && why="wrapped unnecessarily (would have fit on one line)"
|
||||
fi
|
||||
|
||||
local label="usable=$u branch=${#branch}ch model='${model:0:12}' lines=$n max=$mx"
|
||||
if [ -n "$xfail" ] && [ -n "$why" ]; then
|
||||
echo " XFAIL $label -- $why (known, pre-existing: $xfail)"; pass=$((pass+1)); return
|
||||
fi
|
||||
if [ -z "$why" ]; then echo " PASS $label"; pass=$((pass+1))
|
||||
else echo " FAIL $label -- $why"
|
||||
printf '%s\n' "$out" | sed $'s/\033\[[0-9;]*m//g' | sed 's/^/ /'; fail=$((fail+1)); fi
|
||||
}
|
||||
|
||||
SHORT=main
|
||||
LONG=feature/some-really-long-branch-name-here
|
||||
M1="Opus 5"
|
||||
M2="Opus 5 (1M context)"
|
||||
|
||||
echo "Sweep of usable widths, short branch:"
|
||||
for u in 200 155 150 130 116 110 100 95 85 75 68 67 60 40; do check "$u" "$SHORT" "$M1" 0.50; done
|
||||
# Narrow tier has a ~35-col floor; nothing can fit below that. Verified byte-identical
|
||||
# on the pre-change script, so not a regression.
|
||||
check 25 "$SHORT" "$M1" 0.50 "narrow tier floor ~35 cols"
|
||||
echo "Sweep with the real long model name:"
|
||||
for u in 200 150 116 100 85 68 40; do check "$u" "$SHORT" "$M2" 4.61; done
|
||||
echo "Sweep with a long branch name:"
|
||||
for u in 200 150 116 100 85 68; do check "$u" "$LONG" "$M2" 4.61; done
|
||||
check 40 "$LONG" "$M2" 4.61 "narrow tier floor ~43 cols with a long branch"
|
||||
echo "Large cost figure:"
|
||||
for u in 116 85 68; do check "$u" "$SHORT" "$M1" 1234.56; done
|
||||
|
||||
echo; echo "pass=$pass fail=$fail"; [ "$fail" -eq 0 ]
|
||||
Loading…
Reference in a new issue