You are working on save-tokens — the feature that installs Claude Code hooks and commands to warn about token usage, auto-save handoffs before compaction, and support session rotation.
Domain purpose
aspens save-tokens installs three Claude Code hooks (statusline, prompt-guard, precompact) plus /save-handoff, /resume-handoff-latest, and /resume-handoff slash commands. The hooks observe Claude's own token telemetry and inject system messages telling Claude to rotate sessions before context blow-up. Handoff files persist enough state in .aspens/sessions/ to resume seamlessly in a fresh session.
Business rules / invariants
- Claude-only feature. Hooks and statusline only work with Claude Code; Codex has no save-tokens integration. Config lives in
.aspens.jsonundersaveTokens. - Config thresholds default 175k warn / 200k compact.
Number.MAX_SAFE_INTEGERis the disabled sentinel for either threshold;target.js#isValidSaveTokensConfig()validates shape, types, and threshold ordering — invalid config causesreadConfig()to returnnull. writeConfigpreserves feature config —saveTokensis preserved acrosswriteConfigcalls unless explicitly set tonull.- Sessions dir gitignored —
.aspens/sessions/.gitignoreexcludes everything except.gitignoreandREADME.md. - Settings backup — first install creates
.claude/settings.json.bakif settings exist and no backup exists yet. - StatusLine pre-existence guard —
canInstallSaveTokensStatusLine()refuses to overwrite an unrelated customstatusLine.command; when refused,applyStatusLineAvailability()forces both thresholds toMAX_SAFE_INTEGERand disablessessionRotation.
Non-obvious behaviors
- Three hook entry points dispatched by subcommand. Shell wrappers (
*.sh) read stdin, resolve project dir, and callsave-tokens.mjswithstatusline,prompt-guard, orprecompact.ASPENS_PROJECT_DIRenv var overridesCLAUDE_PROJECT_DIR; both fall back tocwd. - Statusline doubles as telemetry recorder.
recordClaudeContextTelemetry()writes.aspens/sessions/claude-context.jsonsumminginput_tokens + cache_creation_input_tokens + cache_read_input_tokens + output_tokensfromcontext_window.current_usage. Telemetry older than 5 minutes is ignored by the prompt guard. - Prompt-guard speaks to Claude, not the user. Above
compactAtTokens: saves a handoff (reasonrotation-thresholdifsessionRotation, elsecompact-threshold) and stdout-prints an "IMPORTANT — you must tell the user" block instructing fresh session +/resume-handoff-latest. AbovewarnAtTokens: suggests/save-handoff. When telemetry is missing, prints a one-time link to the aspens issues page. - Precompact hook only fires when
saveHandoffis enabled andclaude.enabled !== false; reason isprecompact. extractSessionFacts()parses the JSONL transcript to extractoriginalTask(first user message, 500 char cap),recentPrompts(last 3, 200 char cap each),filesModified(Edit/Write tool_usefile_path),gitCommits(regex on Bashgit commit -m "..."), andbranch(fromgitBranchon user records). Transcript path is validated to be insideprojectDir; falls back toinput.promptwhen transcript is missing or outside the project.- Transcript parsing is best-effort — all errors caught, invalid JSON lines silently skipped; returns empty facts on failure.
pruneOldHandoffs()keeps newest 10*-handoff.mdfiles by lexicographic (timestamp) sort; older are unlinked.latestHandoff()uses the same sort.--recommendedinstall is non-interactive — used standalone or invoked bydoc init --recommended(which also installs agents and the doc-sync git hook).--removecleans legacy artifacts too — removes both.shand historic.mjsvariants of each hook, plus the legacysave-tokens-resume.mdcommand; stripsstatusLineif it points at save-tokens and filters any hook entry whose command containssave-tokens-; nullssaveTokensin.aspens.json.
Critical files (purpose, not inventory)
src/commands/save-tokens.js— install/remove orchestration; interactive multiselect (warnings,handoffs),--recommended,--remove. ExportsinstallSaveTokensRecommended()consumed bydoc-init.js.src/lib/save-tokens.js— config + settings + template content builders (DEFAULT_SAVE_TOKENS_CONFIG,buildSaveTokensConfig,buildSaveTokensSettings,buildSaveTokensGitignore,buildSaveTokensReadme,buildSaveTokensRecommendations).src/templates/hooks/save-tokens.mjs— runtime library:runStatusline,runPromptGuard,runPrecompact,saveHandoff,extractSessionFacts, telemetry record/read, handoff pruning,main()subcommand dispatch.src/templates/hooks/save-tokens-{statusline,prompt-guard,precompact}.sh— shell wrappers; resolvePROJECT_DIRviacd "$SCRIPT_DIR/../.." && pwdand exec the.mjswith the matching subcommand.src/templates/commands/{save-handoff,resume-handoff-latest,resume-handoff}.md— slash command bodies installed under.claude/commands/.
Critical Rules
- Settings merge uses
mergeSettings()fromskill-writer.js— save-tokens hooks are treated as aspens-managed; do not hand-edit.claude/settings.jsonto add/remove save-tokens entries. - Hooks write to stdout to inject into Claude's context — comments in
runPromptGuard()flag this; keep those messages addressed to Claude ("Tell the user...") not to a human reader. - Token sum includes cache tokens — do not switch to just
input_tokens + output_tokens; that under-counts Claude's effective context use.
References
- Impact integration:
src/lib/impact.js—evaluateSaveTokensHealth()validates installed state
Last Updated: 2026-05-11


