#+TITLE: Session Context — first-session setup + Makefile picker + work/deepsat reorg + skill-budget test #+AUTHOR: Craig Jennings & Claude #+DATE: 2026-05-06 * Summary ** Active Goal *Picking up after a session restart.* The first thing to do is verify the in-flight test: did adding =disable-model-invocation: true= to =~/code/rulesets/.claude/commands/arch-document.md= remove its description from the model's preloaded skill listing? See the *Next Steps* section for the decision tree. ** Decisions - Project type: content/documentation. =.ai/= is committed. - =install-lang= should fzf-pick =LANG== when not set, mirroring how =PROJECT== was already picked. - Move =~/code/deepsat/= → =~/projects/work/deepsat/code/= and rename =~/projects/career/= → =~/projects/work/=. No backwards-compat symlinks. - Test the skill-budget hypothesis on *one* command (=arch-document=) before mass-editing the other 17. - Forward-rewrite forward-looking org files in =work/= to point at the new path. Leave session archives untouched. ** Data Collected / Findings - Per Anthropic's official docs (=code.claude.com/docs/en/skills.md=), "Custom commands have been merged into skills." Moving a file from =~/.claude/skills/= to =~/.claude/commands/= does *not* by itself reduce what gets preloaded into the model's context. The frontmatter field =disable-model-invocation: true= is the documented lever for excluding a command from model-side invocation while keeping =/= routing for the user. Whether it also removes the entry from =SLASH_COMMAND_TOOL_CHAR_BUDGET= accounting is *not stated explicitly* in the docs — that's why we're testing one command first. - The 18 converted commands in =~/code/rulesets/.claude/commands/= had =description:= and some =argument-hint:=, but none had =disable-model-invocation: true=. That's the gap. - =~/.claude/settings.json= is a symlink resolving to =/home/cjennings/code/rulesets/.claude/settings.json=. Edits to "user-scope" Claude Code settings actually land in the rulesets repo as tracked changes. - =~/.gitconfig= resolves through =~/code/archsetup/dotfiles/common/.gitconfig=. Same pattern — edits to the gitconfig land in the archsetup dotfiles repo. - Python bundle ships a single file (=python-testing.md=); elisp ships more (CLAUDE.md, settings.json, hooks, githooks, gitignore-add). The new typescript bundle matches python's minimalism: just =typescript-testing.md=. - Inside =~/projects/work/.gitignore=, the existing =code= rule already covers =deepsat/code/= (no slashes in the rule means it matches any path component named =code=). No new gitignore line needed. - Bug found and fixed in the Makefile change: =$(LANG)= silently inherits =en_US.UTF-8= from the env, bypassing the new picker. Fixed by checking =$(origin LANG)= and blanking when origin is =environment=. The old code had the same latent bug. ** Files Modified *** Rulesets repo (=~/code/rulesets/=) - =Makefile= — added =pick_lang_shell= macro mirroring =pick_project_shell=, wired into =install-lang= and =diff= recipes, blanked =$(LANG)= when origin is environment. - =languages/typescript/claude/rules/typescript-testing.md= — new file. Vitest-canonical TypeScript testing rules with notes for Mocha+Chai, Jest, and Angular Karma legacy idioms. Covers RTL, MSW, =it.each=, async patterns, and TS-specific discipline (no =any=, prefer =satisfies= over =as=, etc.). - =languages/typescript/= directory tree created. - =.claude/commands/arch-document.md= — added =disable-model-invocation: true= to frontmatter as the budget-test target. - =.ai/notes.org= — replaced template starter with project-specific context. Notes that both =elisp= and =python= bundles ship (later corrected to add =typescript=). - =.ai/session-context.org= — this file. *** Other repos / configs - =~/projects/work/= — renamed from =~/projects/career/= (=mv=). Then 14G atomic move of =~/code/deepsat/= → =~/projects/work/deepsat/code/=. =git status= shows 21 modified files (1 pre-existing =M .ai/workflows/wrap-it-up.org= + 20 from forward-rewrites I did). - =~/projects/claude-templates/.ai/workflows/daily-prep.org= — path leak fixed (=~/code/deepsat/...= → =~/projects/work/deepsat/code/...=). - =~/code/archsetup/dotfiles/common/.gitconfig= (resolved from =~/.gitconfig=) — =includeIf gitdir= path updated to =~/projects/work/deepsat/code/=. Verified it fires: =git config --get user.email= inside the moved tree returns =craig.jennings@deepsat.com=. - =~/.emacs.d/modules/dirvish-config.el= line 262 — bookmark renamed from =pcr= to =pwk=, path updated to =~/projects/work/=. - =~/.emacs.d/.ai/notes.org= — two late-April reminders rewritten with new path. - =~/sync/org/drill/deepsat.org= — symlink retargeted to =/home/cjennings/projects/work/deepsat.org=. - =~/projects/career/= no longer exists. =~/code/deepsat/= no longer exists. ** Next Steps *** First thing on restart: verify the skill-budget test The system-reminder Claude Code injects at session start lists every skill / command the model can invoke, with full description text. Before this session ended, =arch-document= was in that list with its full description. I added =disable-model-invocation: true= to its frontmatter. Three possible outcomes on next session: 1. *=arch-document= is absent entirely* from the system-reminder skill list, and the truncation warning's "21 dropped" count drops to 20 (or the warning disappears if it was borderline). → The flag works. Apply the same one-line frontmatter edit to the other 17 converted commands. The list to edit: - =arch-decide.md=, =arch-design.md=, =arch-evaluate.md=, =brainstorm.md=, =c4-analyze.md=, =c4-diagram.md=, =codify.md=, =create-v2mom.md=, =finish-branch.md=, =prompt-engineering.md=, =refactor.md=, =respond-to-cj-comments.md=, =respond-to-review.md=, =review-code.md=, =security-check.md=, =start-work.md=, =update-config.md= - (=arch-document.md= already done.) - Plus =keybindings-help.md= and =simplify.md= and =fewer-permission-prompts.md= and =loop.md= and =schedule.md= and =claude-api.md= if those should also be user-only. 2. *=arch-document= still appears with full description* → flag does *not* exclude from budget. Move on to plan B: either (a) raise =skillListingBudgetFraction= / =SLASH_COMMAND_TOOL_CHAR_BUDGET= to ~5% in =~/code/rulesets/.claude/settings.json= (cost: ~8k tokens/session, but a tracked change in rulesets), or (b) /skills UI to disable specific entries. Re-research the field's exact effect via =claude-code-guide= agent if the docs were unclear. 3. *=arch-document= appears but truncated / different in some way* → partial effect. Investigate before applying broadly. *** Other in-flight work (uncommitted, do at Craig's pace) - =~/projects/claude-templates/= has 1 modified file (=.ai/workflows/daily-prep.org=). Commit + push so other machines pick up the path correction on their next startup sync. - =~/projects/work/= has 21 modified files. The 1 pre-existing =M .ai/workflows/wrap-it-up.org= is Craig's own prior in-flight change — I did not touch it. The other 20 are my forward-rewrites of =~/code/deepsat/...= → =~/projects/work/deepsat/code/...= and =~/projects/career...= → =~/projects/work=. Worth eyeballing the assets/ entries (3 of them: =deepsat/assets/2026-03-02-daily-prep.org=, =2026-03-25-deepsat-vision-analysis.org=, =2026-03-31-se41-analysis-quality-report.org=). They're dated analysis files; if Craig considers those historical archives like sessions, =git checkout deepsat/assets/2026-03-*.org= reverts just those three. - =~/code/rulesets/= has staged/unstaged changes: =.claude/settings.json= (pre-existing M from session start), the Makefile, the new typescript bundle, the =.ai/= files, the arch-document frontmatter. Multiple logical commits possible — Makefile fzf picker, typescript bundle, =.ai/= initialization, skill-budget test. - =~/code/archsetup/= has 1 modified file (=dotfiles/common/.gitconfig=). Track + commit alongside any other dotfile cleanup Craig has in flight there. - =~/.emacs.d/= — dirvish-config.el and =.ai/notes.org= were edited. If =.emacs.d= is a tracked repo, that's two changes to commit there too. *** Things still on the table (mentioned but not actioned) - The skills/commands not yet given =disable-model-invocation: true= treatment that probably *should* stay model-invocable (e.g. =add-tests=, =debug=, =five-whys=, =frontend-design=, =humanizer=, =pairwise-tests=, =playwright-js=, =playwright-py=, =root-cause-trace=). These are the ones Claude needs to know about so it can recommend them. The plan is to leave those alone — only convert the user-triggered =/commands=. - =languages/typescript/= bundle is minimal (just =typescript-testing.md=). If Craig wants more for it later — a =typescript.md= for general code style, hooks, etc. — that's a clean next step. - The =/respond-to-review= and =/respond-to-cj-comments= command files have very long descriptions (the reason they triggered the "exceeds per-entry cap" warning earlier). Either trim those descriptions, or rely on =disable-model-invocation: true= to drop them from the listing entirely once the test confirms the lever works. * Session Log ** First-session workflow Craig kicked off "let's run the first session workflow" after the startup workflow flagged the seed notes.org with the first-session pointer. Step 1 — git/.ai policy. Repo is at =git@cjennings.net:rulesets.git=, single =origin= remote, no GitHub. Craig confirmed it's a content/documentation project, so =.ai/= gets committed normally (not gitignored). Step 2 — project orientation. Craig described the project as a personal Claude Code rules + skills repo. Quick directory walk confirmed the layout from =README.org=: top-level skill dirs, =claude-rules/= for global rules, =languages/= bundles, =.claude/= local config, =Makefile= entry points, =todo.org= at root (~73 KB). Step 3 — current state. Craig flagged the recent skills→commands refactor (commit =aa69245=) as the inflection point worth capturing in notes.org. Step 4 — wrote =.ai/notes.org= with Project-Specific Context, Pending Decisions, and Active Reminders subsections. Removed the "If this is the first session" pointer in the same write. Step 5 — Craig wants to work on something specific that isn't on =todo.org= yet. Waiting for him to describe it before continuing. ** install-lang fzf picker for LANG Craig flagged that =make install-lang= already fzf-picks the *project* directory (via =pick_project_shell=) when =PROJECT== isn't set, but errors out immediately when =LANG== isn't set. Symmetric work was incomplete. Added =pick_lang_shell= as a sibling macro in the Makefile, structured the same way as =pick_project_shell=: bail with a clear error if fzf is missing, otherwise pipe =$(LANGUAGES)= to =fzf --prompt="Language> "= and exit cleanly on Esc / no selection. Wired the picker into =install-lang= and =diff=, dropped their =test -n "$(LANG)"= guards, and adjusted the help strings so =[LANG=]= reads as optional. Caught a real bug during the dry-run: =$(LANG)= silently expanded to =en_US.UTF-8= because =LANG= is also the standard POSIX locale env var and Make inherits env vars into its variable namespace. The old code had this bug too — it just failed less visibly inside =install-lang.sh=. Fixed by checking =$(origin LANG)= and blanking the variable when it came from the environment, so command-line =LANG=elisp= and in-file assignments still work. Verified with =make -n= dry-runs across four invocation patterns (no args, =LANG== only, both set, =install-elisp= sub-make pass-through). Then ran a real install into a temporary git sandbox — copied the four generic rules, the Elisp ruleset, hooks, =CLAUDE.md=, and =.gitignore= entries. All clean. Also updated notes.org to reflect that both =elisp= and =python= bundles ship (earlier first-pass note had said only =elisp= shipped). ** Python install + TypeScript bundle scaffolding Craig asked to install python + typescript bundles into =~/projects/career=. Initial scan found: - No =typescript= bundle existed (only elisp + python). - Career's CLAUDE.md was =D=-deleted in the working tree. - I incorrectly flagged that running install would re-create CLAUDE.md, then corrected myself: python bundle has no CLAUDE.md template, so the install wouldn't touch it. - Craig restored CLAUDE.md from HEAD via =git restore=. Ran =make install-lang LANG=python PROJECT=/home/cjennings/projects/career=. Cleanly added 5 rule files to =.claude/rules/=. =CLAUDE.md= untouched. For typescript: Craig redirected to =~/code/deepsat= (not career) for the install target. Inventoried deepsat's test stacks (Mocha+Chai for backend, Vitest+RTL+Playwright+MSW for the modern frontend, Angular Karma elsewhere). Drafted =languages/typescript/claude/rules/typescript-testing.md= mirroring the python bundle's shape — Vitest-canonical with notes for the legacy idioms, RTL query priorities, MSW for network mocking, =it.each= for parametrize, TS-specific discipline (no =any=, prefer =satisfies=). Lint passed. Installed into =~/code/deepsat= cleanly. Flagged that =~/code/deepsat= itself isn't a git repo (it's a container of inner repos), so the bundle landed at the container level. ** Removed-claude-assets pivot → directory reorg Craig asked to remove all .claude/CLAUDE.md/.ai/AGENTS.md from deepsat subdirs (keeping the container-level one). Scan found 16 untracked items across 4 inner git repos. Before running =rm=, Craig stepped back — asked whether =~/code/deepsat= should live somewhere under =~/projects= instead. Quick layout analysis showed =~/projects/career/deepsat/= was already partly built out (notes, an empty =code/= placeholder, a =doc-staging= symlink). No path-hardcoding in shell, gitconfig, or emacs init beyond the =.gitconfig= includeIf and one =dirvish-config.el= bookmark. Recommendation accepted: move =~/code/deepsat= into =~/projects/career/deepsat/code= AND rename =career= → =work=. No backwards-compat symlinks. Rename the dirvish bookmark. Forward-rewrite emacs auto-memory and forward-looking org files in work/. Pre-flight scan caught: =~/.gitconfig= has =[includeIf "gitdir:~/code/deepsat/"]= pointing at =~/.gitconfig-deepsat=. Inbound symlink =~/sync/org/drill/deepsat.org= pointing at the old career path. Dirvish line 262 with the =pcr= bookmark. Executed in phases: 1. =mv ~/projects/career ~/projects/work=. Symlink retargeted. 2. =rmdir= the empty =work/deepsat/code= placeholder. =mv ~/code/deepsat ~/projects/work/deepsat/code= (14G, atomic on same filesystem). 3. Updated =~/.gitconfig= (via the archsetup symlink target). Verified the includeIf fires inside the moved tree — work identity =craig.jennings@deepsat.com= returns correctly. 4. Renamed dirvish bookmark =pcr= → =pwk=, path → =~/projects/work/=. 5. Forward-rewrites — initial pass on the 10 files I'd pre-flight scanned, then a sweep that caught 14 more I'd missed (the pre-flight regex was too narrow). 6. Found a path leak in =~/projects/claude-templates/.ai/workflows/daily-prep.org= (template source, not just local copy). Updated the template so future startup syncs propagate the fix. Final verification: directories in correct state, bundle survived at new path, includeIf live, dirvish bookmark renamed, inbound symlink resolves, =.gitignore= already covers =deepsat/code= via the existing =code= rule. All clean. ** Skill-budget investigation Craig saw "Skill listing will be truncated" in a session opened in the work directory. Asked whether it should be specific to work. Answer: no — it's a global (per-machine) condition driven by the count and length of skills + commands installed at =~/.claude/= (and via plugins). The work directory has no project-scope skills/commands, so what he sees there is the same thing he'd see anywhere. He then said: "I thought moving the skills to commands would give me the budget back. Can you check on site whether this is correct or not?" I dispatched the =claude-code-guide= agent against the official docs. Findings: - Custom commands have been merged into skills (per =code.claude.com/docs/en/skills.md=). Moving the file from =skills/= to =commands/= doesn't separate them in the loading model. - The frontmatter lever =disable-model-invocation: true= is the documented mechanism for "control who invokes a skill" — model vs user. The docs don't explicitly say this also removes the entry from the =SLASH_COMMAND_TOOL_CHAR_BUDGET= accounting, but it should follow logically. - The 18 converted commands in =~/code/rulesets/.claude/commands/= didn't have that field in their frontmatter — the conversion moved files but didn't add the gating. Craig also gave a feedback rule mid-investigation: "never guess. always check unless you've recently checked (the same session)." Saved as =feedback_never_guess.md= in auto-memory. Plan: add =disable-model-invocation: true= to *one* command (=arch-document=, since it's user-triggered and a clean test target), then restart the session and observe the system-reminder skill list. If =arch-document= is absent and the dropped count went from 21 to 20, the lever works and we'll apply the same edit to the other 17. If it still appears, we'll re-research. Craig asked to write this session-context.org so the post-restart session can pick up cleanly. *That's where we are now.* The =arch-document= edit is in. Awaiting restart.