fix(doctor): kill 3 false-warns — cargo/RTK, dir-symlink children, token denominator

A doctor that cries false is a doctor you ignore (LRN-047). Three stale
checks fixed:
- cargo "(RTK unavailable)" -> honest optional info: RTK ships prebuilt
  (detect_rtk finds ~/.cargo/bin|~/.local/bin), cargo only builds it from
  source.
- check_symlink passes files reached via dir-level symlinks. hooks/,
  skills/, agents/, lib/, templates/ are directory symlinks, so a child
  like hooks/session-start.sh is a real file under $REPO, not a symlink
  itself. Now: PASS iff the canonical path lands in $REPO; a stray real
  copy still warns as drift.
- gstack check counts the 34 per-skill symlinks into skills-external/gstack/
  instead of a mythical skills/gstack link (link.sh deliberately removes
  that one -> "run link.sh" could never satisfy the old check).
- token budget vs the 200k default context window, not a bogus 11k
  "session budget" -- the old denominator was a category error producing a
  false "92% CRITICAL". Measured footprint ~11.4k post-audit (LRN-088) ->
  ~5% of context.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
This commit is contained in:
Bastien Chanot
2026-07-03 13:57:59 +02:00
co-authored by Claude Opus 4.8
parent d9fdd4cbdf
commit 6778b9fadd
+46 -40
View File
@@ -42,18 +42,24 @@ check_symlink() {
return return
fi fi
if [ -L "$target" ]; then # Broken symlink: points at a target that no longer exists.
# readlink -f is not available on macOS BSD — use -f with fallback if [ -L "$target" ] && [ ! -e "$target" ]; then
local real fail "$HOME/.claude/$name → $(readlink "$target") — BROKEN SYMLINK"
real=$(readlink -f "$target" 2>/dev/null) || real=$(readlink "$target") return
if [ ! -e "$real" ]; then
fail "$HOME/.claude/$name → $real — BROKEN SYMLINK"
else
pass "$HOME/.claude/$name"; _LINK_PASS=$((_LINK_PASS + 1))
fi
else
warn "$HOME/.claude/$name exists but is NOT a symlink (expected symlink to repo)"
fi fi
# Correctly wired iff the canonical path lands inside the repo. This is true
# for a direct symlink (CLAUDE.md, settings.json) AND for a real file reached
# through a symlinked ANCESTOR dir (hooks/, skills/, agents/, lib/, templates/
# are dir-level symlinks — their children are real files under $REPO). A stray
# real copy in ~/.claude resolves to itself (outside $REPO) → still flagged as
# drift. (LRN-047: the dir-symlink layout is legitimate, must not false-warn.)
local real
real=$(readlink -f "$target" 2>/dev/null) || real="$target"
case "$real" in
"$REPO"/*) pass "$HOME/.claude/$name"; _LINK_PASS=$((_LINK_PASS + 1)) ;;
*) warn "$HOME/.claude/$name resolves to $real (outside repo — expected a link into $REPO)" ;;
esac
} }
check_symlink "CLAUDE.md" check_symlink "CLAUDE.md"
@@ -83,24 +89,17 @@ else
warn "GStack submodule missing — run: git submodule update --init" warn "GStack submodule missing — run: git submodule update --init"
fi fi
if [ -L "$HOME/.claude/skills/gstack" ]; then # GStack skills are exposed as PER-SKILL symlinks directly under skills/ (browse,
real=$(readlink -f "$HOME/.claude/skills/gstack" 2>/dev/null || readlink "$HOME/.claude/skills/gstack") # cso, review, …) pointing into skills-external/gstack/ — there is NO single
if [ -d "$real" ]; then # skills/gstack symlink (link.sh deliberately removes it: it duplicated the
pass "Symlink OK → $real" # top-level gstack SKILL.md alongside the per-skill entries). The bin/ +
# Check for skills/ subdirectory (referenced by plugin-advisor PHASE 1). # browse/dist/ helper links under skills/gstack/ are checked in §7 Consistency.
# `|| echo 0` is required because under `set -o pipefail`, a missing # `|| true` guards pipefail if skills/ is unexpectedly absent (checked above).
# gstack/skills/ dir makes find exit non-zero, killing the script. gstack_skill_links=$( { find "$HOME/.claude/skills/" -maxdepth 1 -type l -lname '*skills-external/gstack/*' 2>/dev/null || true; } | wc -l | tr -d ' ')
gstack_skills_count=$( { find "$HOME/.claude/skills/gstack/skills/" -maxdepth 1 -mindepth 1 2>/dev/null || true; } | wc -l | tr -d ' ') if [ "${gstack_skill_links:-0}" -gt 0 ]; then
if [ "${gstack_skills_count:-0}" -gt 0 ]; then pass "GStack: ${gstack_skill_links} skills linked (per-skill symlinks)"
pass "GStack: ${gstack_skills_count} skills available"
else
warn "GStack symlink OK but no skills/ subdirectory found — may need: cd skills-external/gstack && ./setup"
fi
else
fail "Symlink broken → $real"
fi
else else
warn "GStack not symlinked — run: bash link.sh" warn "GStack skills not linked — run: cd skills-external/gstack && ./setup"
fi fi
echo "" echo ""
@@ -136,7 +135,10 @@ fi
if command -v cargo &>/dev/null; then if command -v cargo &>/dev/null; then
pass "Cargo $(cargo --version | awk '{print $2}')" pass "Cargo $(cargo --version | awk '{print $2}')"
else else
warn "Cargo not found (RTK unavailable)" # Cargo does NOT gate RTK: RTK ships as a prebuilt binary and detect_rtk finds
# it via ~/.cargo/bin or ~/.local/bin (RTK status is shown under Plugins).
# Cargo is only the Rust toolchain to BUILD RTK from source → optional, info.
info "Cargo not found (optional — only needed to build RTK from source)"
fi fi
if command -v python3 &>/dev/null; then if command -v python3 &>/dev/null; then
@@ -239,8 +241,12 @@ echo ""
# 6. Token budget estimate # 6. Token budget estimate
# ──────────────────────────────────────────────────────────── # ────────────────────────────────────────────────────────────
echo "── Token budget estimate ──" echo "── Token budget estimate ──"
# Reference: Claude Code Pro plan ~11k tokens/5h session (session budget, not context window). # The passive footprint (CLAUDE.md + skill descriptions + plugin session-injects)
# Seuils: WARNING >15%, CRITICAL >30% of session budget. # loads into the CONTEXT WINDOW every session — it competes with the ~200k default
# context, NOT a per-session token quota (the old "~11k/5h budget" denominator was
# a category error → false "92% CRITICAL", LRN-047). Measured ~11.4k post-audit
# 2026-07-02 (LRN-088); the chars/4 sum below is a coarse proxy of that footprint.
# Thresholds: WARNING >15% of context (~30k), CRITICAL >25% (~50k).
CLAUDE_MD_CHARS=$(wc -c < "$REPO/CLAUDE.md" 2>/dev/null || echo 0) CLAUDE_MD_CHARS=$(wc -c < "$REPO/CLAUDE.md" 2>/dev/null || echo 0)
CLAUDE_MD_TOKENS=$((CLAUDE_MD_CHARS / 4)) CLAUDE_MD_TOKENS=$((CLAUDE_MD_CHARS / 4))
@@ -264,25 +270,25 @@ if detect_context7 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 200));
if detect_graphifyy 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 300)); fi if detect_graphifyy 2>/dev/null; then PLUGIN_TOKENS=$((PLUGIN_TOKENS + 300)); fi
TOTAL_TOKENS=$((CLAUDE_MD_TOKENS + SKILL_DESC_TOKENS + PLUGIN_TOKENS)) TOTAL_TOKENS=$((CLAUDE_MD_TOKENS + SKILL_DESC_TOKENS + PLUGIN_TOKENS))
SESSION_BUDGET=11000 CONTEXT_WINDOW=200000 # Claude Code default context window (conservative; 1M is opt-in)
PCT=$((TOTAL_TOKENS * 100 / SESSION_BUDGET)) PCT=$((TOTAL_TOKENS * 100 / CONTEXT_WINDOW))
echo "" echo ""
echo " CLAUDE.md: ~${CLAUDE_MD_TOKENS}t" echo " CLAUDE.md: ~${CLAUDE_MD_TOKENS}t"
echo " Skill descriptions: ~${SKILL_DESC_TOKENS}t (${SKILL_COUNT} skills)" echo " Skill descriptions: ~${SKILL_DESC_TOKENS}t (${SKILL_COUNT} skills)"
echo " Plugin passive cost: ~${PLUGIN_TOKENS}t (active plugins)" echo " Plugin passive cost: ~${PLUGIN_TOKENS}t (active plugins)"
echo " ─────────────────────────────────────────" echo " ─────────────────────────────────────────"
info " Total: ~${TOTAL_TOKENS}t" info " Total: ~${TOTAL_TOKENS}t (measured ~11.4k post-audit, LRN-088)"
info " Session budget (Pro): ${SESSION_BUDGET}t" info " Context window: ${CONTEXT_WINDOW}t (default; 1M opt-in)"
info " Usage: ~${PCT}%" info " Usage: ~${PCT}% of context"
echo "" echo ""
if [ "$PCT" -gt 30 ]; then if [ "$PCT" -gt 25 ]; then
warn "CRITICAL: ${PCT}% of session budget — /plugin-check to disable unused plugins" warn "CRITICAL: ~${PCT}% of the ${CONTEXT_WINDOW}t context — /plugin-check to disable unused plugins"
elif [ "$PCT" -gt 15 ]; then elif [ "$PCT" -gt 15 ]; then
warn "WARNING: ${PCT}% of session budget — consider disabling unused toggle plugins" warn "WARNING: ~${PCT}% of the ${CONTEXT_WINDOW}t context — consider disabling unused toggle plugins"
else else
pass "Budget: ${PCT}% (comfortable)" pass "Budget: ~${PCT}% of context (comfortable)"
fi fi
# Per-file breakdown (skill bodies — loaded on demand, shown for awareness) # Per-file breakdown (skill bodies — loaded on demand, shown for awareness)