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:
co-authored by
Claude Opus 4.8
parent
d9fdd4cbdf
commit
6778b9fadd
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user