Skip to content

Latest commit

 

History

History
531 lines (444 loc) · 66.2 KB

WORKLOG.md

File metadata and controls

531 lines (444 loc) · 66.2 KB

WORKLOG

2026-07-29: qmd/search review — fixed staleness + recency, added daily sync, 10x'd --deep

  • What changed
    • search repo: stale-index auto-update now stats the bear-sync manifest (the old index-sqlite mtime signal could never fire); doctor 1.50s → 0.03s.
    • bear-sync stamps mirror file mtimes with note modified times; parseQmd stats them, so qmd hits finally participate in recency decay. Backfilled with one full resync (6,279 notes).
    • tools/qmd/install.sh installs com.kevin.qmd-sync: launchd daily 5:00 AM search update qmd (runs on wake if asleep), log ~/Library/Logs/qmd-sync.log.
    • qmd context summary attached to the bear collection (and in installer).
    • --deep: forced QMD_LLAMA_GPU=metal (auto-detect falls back to CPU on Apple Silicon, 2.5-4x slower) + --no-rerank (fuse() re-ranks across sources anyway; only 5/20 hits differed). 27-58s → ~2-4s cached, 5-30s once per novel query (expansion, cached forever).
    • Metal forced in three contexts: search adapter env, launchd plist, zsh/config.zsh export. Full system documented in ~/.kevin/code/search/docs/architecture.md.
  • What we decided and why
    • Rejected a vsearch middle tier (8.3s vs 11s full query — model load dominates, not worth a third mode) and the qmd MCP daemon (CLI never routes through it; it only serves MCP clients).
    • Kept the >7d search-time auto-update gate as backstop behind the launchd agent; doctor reports true sync age. Three freshness layers.
  • What to revisit next time
    • Headless 5 AM run de-risked: kickstarted the agent with Bear quit — bearcli read the DB fine, embed ran, exit 0. Log worth one glance after the first real unattended run anyway.
    • Consider filing the Apple Silicon GPU auto-detect fallback upstream (tobi/qmd) — QMD_LLAMA_GPU=metal shouldn't be necessary.
    • Plan checklist lives in Bear: "QMD for Bear Notes" note (all items done).

2026-07-29: search install.sh stoa failure — bun compile naming bug

  • What changed
    • stoa-mono 932ba9f: build.ts used compile: true, naming: "stoa", but Bun.build ignores naming for compiled executables (bun 1.3.x names them after the entrypoint → dist/src). Fixed with compile: { outfile: "stoa" }. Pushed.
    • Fast-forwarded this machine's search checkout (~/.kevin/personal-code/search) to 4d90f57; installer now builds search 0.3.0 with all nine sources ok per search doctor.
  • What we decided and why
    • Root cause was in stoa-mono, not install.sh — the installer's cp dist/stoa was correct; fixing the copy path would have papered over the misnamed binary. build.ts also hardcodes its "Built dist/stoa" log line, which is why the build "succeeded".
  • What to revisit next time
    • search doctor here flags bear sync never ran — run search update qmd once on this machine.
    • Work machine still needs a dot run to pick up both fixes; verify search doctor there (first real test of parsePi).

2026-08-02: Email triage via spark CLI — inbox/trash analysis and deletion report

  • Dumped unified inbox (4,175) and recent trash (1,000) with spark emails; aggregated senders in scratchpad
  • Found k@kbr.sh mirrors kridgway@gmail.com — 3,477 of 4,175 inbox msgs are the mirror backlog; 1,800 msgs are from 197 senders Kevin already routinely deletes
  • Wrote triage report (scratchpad/email-triage-report.md) with Spark search strings per delete bucket; all accounts read-only so no actions taken
  • Revisit: grant Spark triage access for agent-driven cleanup; fix kridgway→k@kbr.sh duplication at the source

2026-08-04: search rule updated for reminders source, doctor upgrades, web UI

  • What changed
    • agents/claude/rules/search.md now covers: reminders source (open todos only, --completed opts in; same Full Disk Access grant as imessage), reminders in the --source list, --list, streaming picker with session-resume on enter, doctor's FDA probes + stale-binary rebuild, and a one-liner on the web PWA (make serve, port 3847; make install-launchdaemon for the login server — renamed mid-session from install-agent).
    • Verified every claim against search 0.5.0 (--source imessage,reminders works despite the help text) and GREEN-tested with a fresh-agent retrieval quiz on the updated file.
    • Fixed the upstream stale help too: cli.ts USAGE --source list now derives from DEFAULT_CONFIG.sources (search repo 4f9feea, not pushed).
    • After Kevin added search sources, the rule delegates the source roster to search sources --json instead of hardcoding 11 names — same single-source-of-truth fix as cli.ts.
  • What we decided and why
    • Kept the web UI to one line marked "for Kevin's use, not agents" — the rule loads into every session, so lines cost permanent context.
    • Rule keeps source concepts in prose (quirks like --completed, session-source semantics) but not the name list, which changes with every new source and goes stale.
  • What to revisit next time
    • Push search repo 4f9feea (and Kevin's sources/Makefile work) when ready.

2026-08-10: OOO meeting catch-up — Supernormal summaries into a Bear note

  • Confirmed OOO window Jul 30–Aug 9 (last recording + last commit Jul 29; back Aug 10).
  • Found NO Supernormal recordings exist for that window (own capture off, no teammate captures visible; Supernormal's own Aug 6 brief confirms the transcription gap).
  • Reconstructed gists from daily personal brief emails (Spark IDs 5122–5192) and calendar recaps; wrote Bear note 71297200-58CD-4D11-ACEC-A51758B5A596 ("OOO Catch-up: Jul 30 – Aug 9, 2026", #supernormal/_today).
  • Learned: Supernormal MCP list meetings only returns own captures; meeting URLs are app.supernormal.com/meetings/; brief emails wrap all links in SendGrid trackers (underlying URLs unrecoverable without following redirects).

2026-08-11: Tweet notes enrichment run (/notes-organize-tweets)

  • Enriched 22 Bear tweet notes: 18 fresh saves got structured bodies (12 tweet-text, 6 link-only), 18 images (7 embedded photos + 11 Playwright tweet-card screenshots), 18 inbox tags; 12 notes got topical tags from the existing taxonomy.
  • 4 thread-check notes (shadcn, lucasmeijer, jamonholmgren, kappaemme1926) stamped thread:auth-needed — no X cookies at ~/.config/notes-organize-tweets/x-cookies.json. Run refresh-x-cookies.sh then re-run the workflow to backfill threads.
  • System Python lacks playwright; ran x-screenshot-fetcher.py via uv run --with playwright and installed the Chromium headless shell (~/Library/Caches/ms-playwright). Consider baking uv invocation into the workflow.

2026-08-11: iCloud conflict cleanup + sync guard for /notes-organize-tweets

  • Today's run collided with iCloud sync: the 4 thread-check notes had already been thread-enriched on the other machine, but that version hadn't synced down. Local auth-needed stamps + late-arriving cloud versions made CloudKit duplicate all 4 (red fork icon in Bear). Kept the enriched "(thread: N tweets)" copies, trashed the 4 stale originals (tags/attachments were identical across each pair).
  • bearcli has no sync command (verified: app subgroup is only open/get-selection), so added Step 0 to commands/notes-organize-tweets.md: ensure Bear is running (open -g -a Bear) and wait 60s cold / 15s warm before the audit.
  • Uncommitted: notes-organize-tweets.md edit + earlier WORKLOG entries.

2026-08-11: Cleared persistent Bear conflict icons by recreating notes

  • Bear's fork icon is driven by ZSFNOTE.ZCONFLICTUNIQUEIDENTIFIER, stamped on the conflict-created copy; bearcli trash/overwrite never clears it (verified in DB). Bear's FAQ says deleting one version or editing the note resolves it — that logic evidently lives in Bear's UI layer, and CLI writes bypass it.
  • Fix: recreated all 6 flagged notes (4 tweet threads + Learn Voice + Learn Piano) under fresh IDs via bearcli (content + attachments + tags verified identical), trashed the flagged originals. DB now has zero conflict stamps.
  • Caveat: recreated notes have new IDs and today's created date; wiki links are title-based so they survive.

2026-08-11: Baked sync/conflict knowledge into bear-notes skill + tweet workflow

  • bear-notes SKILL.md: new "iCloud Sync Conflicts" section — conflict-stamp mechanics (ZCONFLICTUNIQUEIDENTIFIER), why bearcli can't clear it, read-only detection query, recreate-under-fresh-ID recipe, double-save vs conflict discriminator, prevention. Softened the "no sync" bullet that implied sync couldn't bite.
  • notes-organize-tweets.md: Step 0 now points at the skill section; pre-check gained a duplicate guard (groups notes by tweet status id, excludes pairs from the run); Step A3 now invokes the screenshot fetcher via uv run --with playwright; added a post-run read-only conflict check before the final report.
  • Validation run of the new pre-check surfaced 6 pre-existing DUPLICATE pairs (12 notes) — double-saves (created days apart, no conflict stamps), not sync conflicts: shadcn, Dan Koe, Machina, Dwarkesh, Thariq, Jerry Liu. Two copies (Thariq 045E9892, Jerry Liu 3D4D3DD9) have a leading-blank-line bug → empty Bear titles. Pending user decision on dedupe.

2026-08-11: Deduped 6 double-save tweet note pairs

  • Kept the richer/intact copy per pair (shadcn 48187426, Dan Koe 1950EEB2, Machina 13FF51C0, Dwarkesh DB48F2F2, Thariq 9B937759, Jerry Liu 76CB9816), merged missing topical tags into keepers, trashed the 6 others. Guarded against losing My-note blocks; only diff found was a redundant parent-tag line (Dwarkesh).
  • Post-dedupe audit: zero duplicate pairs. 4 brand-new saves (businessbarista, unslothai, shannholmberg, aakashgupta) + 12 thread_checks await the next /notes-organize-tweets run (thread backfill still blocked on X cookies).

2026-08-18: Herdr setup — integrations, skill, and dotfiles adoption

  • Read https://herdr.dev/agent-guide.md; Herdr 0.8.0 was already installed (Homebrew, Brewfile) and this session runs inside a Herdr pane (HERDR_ENV=1), so setup was verification + gap-filling.
  • Installed integrations for claude/codex/pi (herdr integration install <agent>) — adds native session restore; state detection stays screen-based. The claude installer wrote a SessionStart hook through the settings.json symlink, so agents/claude/home/settings.json is modified in the repo.
  • Installed the herdr skill via npx skills add herdrdev/herdr --skill herdr -g. The CLI's relative symlink broke through the ~/.claude/skills dotfiles symlink; repointed it absolute to ~/.agents/skills/herdr. Saved as a memory (skills-cli-relative-symlink).
  • Added herdr/ (config.toml + install.sh): symlinks only config.toml into ~/.config/herdr (runtime state stays machine-local), then runs the integration installs idempotently and reloads the server config. Uses install.sh so script/install auto-discovers it; the install.zsh files (starship, mise, warp) are the older manually-run generation. Needed because the committed SessionStart hook references ~/.claude/hooks/herdr-agent-state.sh, which only the integration install creates on a fresh machine.
  • The adopt step caught live-config drift (agent_panel_sort = "spaces" appeared after the initial copy) — reconciled. ~/.config/herdr/config.toml.pre-dotfiles is a redundant backup, safe to delete.
  • Revisit: commit Brewfile, settings.json, skills/herdr symlink, and herdr/ together; decide whether other machines want the same integrations.

2026-08-18: Adopted QuickMD as the live markdown viewer for agent-written files

  • Added b451c/quickmd tap + cask to Brewfile, installed it (needed new brew trust), and verified live reload against appends, wipes, and atomic temp-file renames.
  • Wrote agents/claude/home/skills/quickmd/SKILL.md via the writing-skills TDD loop: baseline agent misread "watch the markdown file" as a polling watcher; with the skill it opens QuickMD instead. Committed as 221c6eb.
  • Revisit: brew flagged 10 pre-existing untrusted taps (heroku, stripe, anthropics/tap, illegalstudio, noahgorstein…) it now ignores — trust the ones in use or brew bundle will skip them.

2026-08-19: pi moved to @earendil-works scope; enabledModels provider ids corrected

  • npm deprecated @mariozechner/pi-coding-agent in favor of @earendil-works/pi-coding-agent. A scope rename is a new package to pnpm, so pnpm add -g the new one would have left both installed, each declaring a pi bin and racing for the same symlink. agents/pi/install.sh now removes the deprecated package first, guarded by a pnpm ls -g grep so a fresh machine doesn't trip pnpm remove -g under set -euo pipefail. Verified: 0.73.1 → 0.84.2, deprecation warning gone, ~/.kevin/bin/pi relinked, second install.sh run is a no-op. Commit 45adafe.
  • enabledModels in agents/pi/home/settings.json had three dead entries, and they were dead for two different reasons — worth separating because the symptom is identical:
    • anthropic/claude-sonnet-4-5 → stale id, replaced with anthropic/claude-opus-5 (correct Opus 5 id; takes no date suffix).
    • openai/gpt-5.4 → wrong provider name. pi's provider is openai-codex, not openai; pi auth check --provider openai-codex was ready the whole time while the entry warned on every startup. Now openai-codex/gpt-5.5.
    • google/gemini-2.5-pro → removed. Provider id google is real (it's in the package), but Google has no credentials here and pi's model catalog is auth-filtered, so the entry could only ever warn. Commit 753be88; both pushed.
  • pi's model catalog is auth-filtered: before credentials, pi --list-models anthropic returned nothing, so an unauthenticated provider is indistinguishable from a nonexistent one. Also pi auth check returns not_ready for any string including nonsense, so it can't confirm a provider name — grep the installed package under $(pnpm root -g) for provider ids instead.
  • Ollama pull failed 4× with an opaque Error: EOF. Not network, not the server: attempt 1 stalled mid-transfer and left 2 of 16 per-chunk resume journals (blobs/sha256-<layer>-partial-0 and -partial-4) truncated to 0 bytes. Every retry then read a journal claiming a byte range was complete when it wasn't and died instantly at manifest. Deleting the 17 *-partial* files fixed it first try. A 46MB probe model pulling fine while the 4.7GB one failed is what isolated it to the cached blobs.
  • Gotcha for scripting pi: it reads stdin when stdin isn't a TTY, so from a non-interactive shell it blocks waiting for EOF — pass < /dev/null. Warnings go to stderr, the answer to stdout, so pi -p ... 2>/dev/null stays parseable.
  • Revisit: no Google credentials on this machine — if Gemini is ever set up, re-add a verified id rather than the old 2.5-pro string. pnpm approve-builds -g is still pending for @google/genai and protobufjs.

2026-08-24: Researched + verified codebase-memory-mcp (DeusData); Bear note written

  • Cloned, built from source, and tested the C-based code-graph MCP server: 15 tools / 158 grammars / 43 client surfaces all check out; indexed ~/.dotfiles (2,730 nodes) correctly; MCP calls 13–47ms warm; zero network sockets at runtime.
  • Full security sweep (subagent): clean — no telemetry, download code compiled out of release builds, credential-path indexing denylist, fail-open hooks, reversible config writes. Nits: install appends PATH to shell rc without uninstall cleanup; curl|bash configures all detected agents unless --skip-config.
  • Marketing vs paper gap: README says 120x fewer tokens/ms indexing; their own arXiv (2603.27277) says 10x tokens with 83% vs 92% answer quality vs plain file exploration. Dejan's "Claude ignores MCP tools" claim: Claude half supported (repo issue #69, shipped Grep/Glob hooks), ChatGPT half unverified.
  • Bear note "codebase-memory-mcp — verified research note" (29DDF584); test artifacts in /tmp/codebase-memory-mcp + /tmp/cbm-test-cache (disposable).
  • Revisit: if adopting, install with --skip-config and hand-add only the Claude Code entry; watch issue #1654 (large-repo OOM) before pointing it at supermono.

2026-08-25: Tweet-enrichment skill — closed the untagged bare-link hole, enriched 94 notes

  • Verified the reported hole: 47 untagged notes held tweet URLs invisible to the classifier, which only recognized title-starts-with-URL or existing inbox tag. Four miss shapes: annotation-first saves ("Make agents.md! "), share-sheet markdown-link saves ("Name (@handle) 107 likes"), enriched bodies whose literal #inbox/saved-tweets line Bear never promoted to a real tag (external-pipeline writes), and twitter.com-domain URLs.
  • Rewrote the pre-check recognition in agents/claude/commands/notes-organize-tweets.md as four signals (title prefix, inbox tag, enriched-body footprint, bare-link residual ≤300 chars); genuine project notes that merely reference a tweet go to a printed manual-review list instead of being rewritten. Residual text becomes the My note annotation so user words survive body rewrites.
  • Also fixed: URL regex swallowing ) and <!-- after query strings; /statuses/ legacy paths; reference_existing_attachments now percent-encodes filenames (Bear rejects writes whose attachment refs contain raw spaces — 7 notes failed on Screenshot ... PM.png before the fix); Step C now falls back to reading tweet text from the enriched body, since extra_tags-only notes are never in the current run's syndication batch.
  • Ran the full pipeline: 46 bodies built (39 single + 4 tombstone + 3 link-only), 37 thread upgrades via Tier 2 cookies, 35 head images + 22 Playwright card screenshots + 19 thread photos, 73 inbox tags, 37 topical tags. Zero iCloud conflicts.
  • Left for Kevin: 2 manual-review notes ("Agents in Stoa" 7DB6A7F7, Dwarkesh podcast note C3C6928A) and 6 tweets skipped in tagging for lack of a taxonomy fit (tferriss patience letter, Dostoevsky letter, Logitech alternative, SahilBloom video, thekitze paperclip, maxedapps thanks).
  • Skill edits are uncommitted in ~/.dotfiles (agents/claude/commands/notes-organize-tweets.md).

2026-08-26: Tweet-enrichment re-run — idempotence confirmed, stale-marker cleanup

  • Re-ran the full workflow: zero new enrichment work needed — yesterday's pass fully converged (no new tweet saves, no iCloud conflicts).
  • Found and fixed a marker-hygiene bug: Step B's settle path stripped only trailing thread markers, but an attachment ref appended after <!-- thread:unchecked --> pushes it mid-body, where it survived. 63 notes carried a stale unchecked marker alongside their settled one — all cleaned; both settle regexes in the skill now strip markers anywhere in the body.
  • Settled 3 of the 6 tag-skipped notes: tferriss/Coach Sommer + Dostoevsky letters → writing/quips; maxedapps (links dmmulroy/anti-slop) → learn/ai.
  • Permanently skipped (no taxonomy fit, will keep listing under extra_tags): sahilbloom motivational video EA4A9989, almonk openlogi.org rec 4FFE26C4, thekitze paperclip 134E3490. Tag manually or add a taxonomy branch if they grate.
  • Manual-review pair unchanged: "Agents in Stoa" 7DB6A7F7, Dwarkesh podcast note C3C6928A.
  • Skill edits still uncommitted in ~/.dotfiles.

2026-08-26: Tweet notes enrichment run — clean corpus, settled the 3 tag-skipped notes

  • Ran /notes-organize-tweets. Audit found zero notes needing body/image/thread work — Steps A/A2/A3/B all no-ops. No duplicate pairs, post-run conflict query clean.
  • Tagged the 3 notes the 2026-08-25 session had left as "no taxonomy fit": sahilbloom EA4A9989 → personal/motivational; almonk 4FFE26C4 → personal/technology; thekitze 134E3490 → projects/developer-productivity. Judged these existing tags close enough — remove/retag if they grate. They no longer surface under extra_tags.
  • Manual-review pair unchanged: "Agents in Stoa" 7DB6A7F7, Dwarkesh podcast note C3C6928A.

2026-08-26: Updated impeccable skill v3.5.0 → v4.1.2

  • Ran npx impeccable update --yes --no-hooks; it writes through the ~/.claude/skills symlink straight into the vendored copy at agents/claude/home/skills/impeccable. First pass landed 4.1.1 because impeccable.style's bundle lagged the skill-v4.1.2 GitHub release (tagged this morning); re-ran with IMPECCABLE_BUNDLE_PATH pointed at the release's universal.zip to get 4.1.2 exactly.
  • The v3.6.0 npm CLI's update path silently skipped installing the four shipped subagents (asset-producer, documenter, finish-reviewer, manual-edit-applier) into ~/.claude/agents — a bug the v4.1.2 notes confirm ("Claude subagents install correctly again"). Copied them from the release bundle by hand; they now live in agents/claude/agents/.
  • Deliberately skipped the design hook install (--no-hooks) — the skill works without it; revisit if post-edit detector reminders sound useful (/impeccable hooks on).
  • Push required a rebase over 4 remote commits (pi scope rename etc.); autostash conflicted in WORKLOG.md, resolved by keeping both sides chronologically. Commit 4a68c68, pushed.
  • Added durable-skip convention to the Step C tagging pass in notes-organize-tweets.md: when no taxonomy tag fits, apply catch-all learn/misc instead of leaving the note untagged — any topical tag settles it so it stops resurfacing under extra_tags every run. Uncommitted alongside the existing skill edits.

2026-08-27: herdr-ticket-campaign skill reviewed, hardened, committed

  • Code-reviewed the untracked skill (10 findings, 2 confirmed by executing the monitor loop): false agent-"gone" events, comm fed numeric sort, phantom slot-free on failed owner.json reads, silent gh blindness, invisible no-PR completions, naming contradiction, unquoted recap interpolation, unchained spawn block, /tmp brief purge risk, missing phase hooks for repo standing orders. All fixed; the rewritten monitor was re-verified against mocked failure scenarios before committing.
  • Work privacy: dotfiles is PUBLIC — the work repo's reference file (repo slug, issue-tracker conventions, tracker page id, shared-environment behavior, resource-lock protocol) is now gitignored via references/*-repo.md and stays machine-local. SKILL.md/brief-template genericized to <repo>-repo.md. Standing rule going forward: no work repo names, ticket numbers, or real ticket subjects in WORKLOG or any committed file, even as examples.
  • Briefs relocated /tmp/port-briefs → ~/.herdr/briefs; all 25 existing briefs copied over (originals left in /tmp for any in-flight agent).
  • Committed (skill + .gitignore only) and pushed. The first push carried a real ticket id in a naming example; per the new privacy rule the tip two commits were squashed into one clean commit (9d4047e) and main force-pushed (guard-destructive.sh blocks agent force-pushes, so Kevin ran the push himself). Old SHAs may linger in GitHub's cache until GC — contact GitHub support if that ever matters.
  • Revisit: other machines running campaigns need their *-repo.md reference file copied by hand; consider a private sync channel for those files if that gets old.

2026-08-27: Tweet enrichment run + extracted workflow scripts from command md

  • Ran /notes-organize-tweets: 12 fresh saves enriched (11 bodies + 1 link-only, 12 images — 3 embedded photos, 9 Playwright screenshots, 12 inbox tags). Second convergence pass settled threads: 2 upgraded to real thread bodies (@mattshumer_, @patrick_oshag), 9 settled count=1, 11 topically tagged. Corpus fully settled (pre-check now reports 0).
  • Resolved a double-save (no conflict stamp): trashed the bare Aug-26 re-save of the Matt Ronge tweet; the enriched April copy survives.
  • Token-efficiency refactor (Kevin's ask): the command md embedded ~36KB of Python that got loaded into context AND re-typed to /tmp every run. Extracted all 9 scripts verbatim into agents/claude/tools/notes-organize-tweets/ and slimmed the command md 44KB → 11KB, keeping all prose knowledge (four-signal recognition, marker semantics, conflict playbook). step_c_apply.py now reads /tmp/tweet_tag_assignments.json instead of an inline dict. Added an explicit "convergence pass" section so one session fully settles fresh saves.
  • Next time: manual-review notes 7DB6A7F7 ("Agents in Stoa") and C3C6928A (Dwarkesh episode) still untagged — tag by hand or decide they're fine. Changes uncommitted — review and commit when ready.

2026-09-04: Retired the claude() shell-function updater; moving to the native installer

  • Root-caused the intermittent zsh: permission denied: on launch. The wrapper called mise which 'npm:@anthropic-ai/claude-code', but mise which takes a bin name, not a tool spec, so it always errored. The first call had a whence -p claude fallback; the post-upgrade re-assignment did not, leaving claude_bin="". zsh execing an empty string returns exactly permission denied: (exit 126) — so every time mise found an update, the launch died instead of starting Claude.
  • Claude Code self-updates now (native and npm-global installs check on startup and periodically; only brew/winget/apt/dnf/apk don't). The whole update block was redundant, and worse: npm config get prefix pointed at mise's node install, so Claude's own updater wrote there while our mise upgrade wrote to the npm-backend dir. Two updaters, two 200MB copies, and the PATH winner wasn't the one mise managed. Plus a stale 2.1.252 npm global under /opt/homebrew.
  • Decided (with Kevin): standardize on the native installer — the npm package now ships the same native binary and no longer runs on Node, which was the entire reason for the 2026-06-10 mise-npm migration. Wrapper keeps only the Warp tab rename; no update logic, accepting a one-launch lag.
  • Changed: system/env.zsh adds ~/.local/bin to PATH (lands at position 4, ahead of mise and brew); zsh/aliases.zsh claude() slimmed to title-rename and now propagates the exit code via rc (not status — that's a special zsh param aliased to $?); new migrations/2026-09-04-claude-code-native-installer.zsh.
  • Found while writing it: bin/dot runs migrations under set -e, and the 2026-06-10 migration's verification step fails today (the leftover /opt/homebrew/bin/claude is an npm global, not the cask it uninstalls) — so dot has been aborting before kit index. Neutered that migration to an early exit 0; the new one skips (exit 0, not 1) when $CLAUDECODE is set and no-ops once already native.
  • Not run yet: the migration refuses to run inside a Claude session because the installer rewrites ~/.claude.json. Run ./migrations/2026-09-04-claude-code-native-installer.zsh from a plain shell, then reload!.
  • Follow-up same day: the migration broke Kevin's already-open terminal with claude:41: permission denied:. Cause: that shell still held the OLD claude() function in memory (functions are snapshots taken at source time), and the migration had just deleted every binary that function knew how to find — so mise which returned empty (as always) AND the whence -p claude fallback returned empty too. claude:41 is function-relative offset 41 from the opening brace = "$claude_bin" "$@", confirmed against git show HEAD:zsh/aliases.zsh. reload! was the whole fix; the re-run of the official installer was a no-op. Migration now ends with a loud reload warning.
  • Kevin's echo 'export PATH=...' >> ~/.zshrc landed inside the repo (~/.zshrc is the symlink to zsh/zshrc.symlink), duplicating system/env.zsh:9 — removed. A clean shell has ~/.local/bin exactly once, at position 5.
  • Audited the fresh-machine path afterwards and found three more stale spots, none of which the on-this-machine verification would have caught: node/install.zsh:10 still did mise use --global 'npm:@anthropic-ai/claude-code'; agents/shell.zsh ccu() upgraded a mise tool that no longer exists; and nothing in script/install installed the CLI at all (agents/claude/install.sh only symlinks config). Note script/install runs find . -name install.sh — the install.zsh files are the older manually-run generation and never execute, so node/install.zsh had been dead code for a while.
  • Fixed: CLI install now lives in agents/claude/install.sh (idempotent — installs only when ~/.local/bin/claude is missing, since Claude self-updates after that), node/install.zsh comments why Claude isn't there, ccu() calls command claude update. Verified both branches of the install block and ccu against the live install.
  • Revisit: the install.zsh vs install.sh split is a trap — three files that look like installers never run. Worth collapsing to one convention.

2026-09-25: Re-assessed the uncommitted native-installer work; slimmed ccu; committed

  • Verified the 2026-09-04 migration took: claude -> ~/.local/bin/claude (2.1.281, installMethod native), and it self-updated from 2.1.260 on 09-23 without help.
  • Fixed a wrapper bug: claude() emitted OSC title escapes even when stdout was a pipe, corrupting claude -p ... | jq output. Both printfs are now gated on [[ -t 1 ]].
  • Replaced the 50-line ccu() with alias ccu="claude update" — it had become a wrapper around a built-in. Release notes: /release-notes in-session.
  • Split the pile into three commits: native-installer switch, notes-organize-tweets script extraction, settings + tldraw-offline node helpers.
  • Revisit: a leftover npm global (2.1.260) lives in mise node 22.23.2, created 26 min after the native install (likely an old session's npm auto-updater). Still second on PATH. Removal was blocked for the agent; run npm uninstall -g --prefix ~/.local/share/mise/installs/node/22.23.2 @anthropic-ai/claude-code. The migration's early "already migrated" exit skips its leftover check, so it won't catch this.
  • Revisit: ~/.claude.json still says autoUpdates: false; native installs appear to ignore it (binary has autoUpdatesProtectedForNative), but confirm with claude doctor. Also confirm autoCompactEnabled: false in settings.json is intended.
  • Follow-up: Kevin ran the npm uninstall for the leftover 2.1.260 copy; whence -ap claude now returns only ~/.local/bin/claude and no mise node install holds a claude binary.
  • Correction to 2026-08-27: the squash to a clean single commit was never force-pushed. origin/main still carries the original commit plus a follow-up that genericized the naming example, so the real ticket id remains in public history. Pushed today by rebasing onto origin/main and dropping the local squash (identical tree). Scrubbing it would need a force-push of main, which Kevin must do himself if he wants it.

2026-09-27: /doctor health check and cleanup

  • Disabled 22 unused plugins (posthog, compound-engineering, cloudflare, slack, datadog, duplicate review/commit/frontend plugins, 3 LSPs, etc.) and 30 unused user skills via enabledPlugins/skillOverrides in agents/claude/home/settings.json — the skill listing was over budget and truncating entries to names only.
  • Fixed broken hooks: fast-code-reviewer PostToolUse path moved to ~/.kevin/code/fast-code-reviewer; removed tldraw-offline SubagentStart hook (script no longer exists). Removed dangling skills/herdr symlink.
  • Removed leftover Claude copies (Homebrew-node npm global 2.1.220, mise npm tool 2.1.283); claude install set installMethod=native.
  • Revisit: something outside dotfiles re-added npm:@anthropic-ai/claude-code to ~/.config/mise/config.toml today at 12:15 — find the culprit if it reappears. monarchmoney/msgvault MCP servers fail to connect and are unused; run /mcp disable or fix them.

2026-09-27: Audit Claude symlinks against install.sh

  • All install.sh symlinks resolve correctly (~/.claude/{CLAUDE.md,settings.json,commands,statusline.sh,rules,agents,skills,SOFTWARE_ENGINEERING.md}, ~/.dotfiles/.claude, ~/.dotfiles/SOFTWARE_ENGINEERING.md); no broken links inside linked dirs.
  • Removed stale vercel-vercel-plugin marketplace (pointed at missing ~/.cache path) and uninstalled the plugin; dropped duplicate statusLine from project settings.local.json; gitignored .playwright-mcp/; deleted Feb/Jul ~/.claude backups.
  • Note: /auto-mode-setup wrote an autoMode.environment block into agents/claude/home/settings.json — this repo is public, so review it before committing.
  • ~/.claude/hooks/herdr-agent-state.sh is intentionally outside dotfiles (created by herdr/install.sh).

2026-09-28: notes-organize-tweets runs (09-25 and 09-28)

  • 09-25: enriched 23 tweet notes (15 new bodies, 10 thread upgrades, 15 images, 23 tagged). Step A3 crashed because Playwright updated and needed a new Chromium; installed it with uv run --with playwright playwright install chromium.
  • Gap found: precheck.py:144 flags image only when a note has no body, so a failed A3 is never retried once Step B writes the body. Backfilled the 12 affected notes by hand-adding image to /tmp/tweet_todo.json. Script not changed yet.
  • 09-28: trashed the bare copy from each of two duplicate pairs (double-saves, not sync conflicts), then enriched the surviving copy (2-tweet thread, screenshot, tags).
  • Revisit: postrun_conflict_check.sh fails with "authorization denied" on Bear's Group Container DB (macOS TCC, not the sandbox). Grant Full Disk Access or switch the check to bearcli. Fix the image-retry gap in precheck.py.

2026-09-28: Fix fast-code-reviewer hook path regression

  • The 09-27 /doctor "fix" pointed the PostToolUse hook at ~/.kevin/code/fast-code-reviewer, which doesn't exist; every Edit/Write raised a blocking hook error. Restored the original path ~/code/tools/fast-code-reviewer (verified the script runs, exit 0).
  • Lesson: check that a "moved" path exists before repointing config to it.
  • Scrub abandoned: origin/main had the unscrubbed history again (another clone pushed yesterday's prune commit on top of it). Kevin chose to rebase onto origin/main rather than force-push, so the 08-27 ticket id stays in public history. Dropped the local scrub note and the duplicate prune commit during the rebase.
  • Correction: scrub re-done and completed. Found a second leak, a team-prefixed ticket id in the /workday skill (still in the current tree), plus the 08-27 one. Ran git filter-repo --replace-text in a fresh mirror clone, swapping each id for fictional placeholders on main and claude/plan-port-management-2g6XE; that emptied the old genericizing commit, so it was dropped. The only tip change is the example id in workday/SKILL.md. Kevin force-pushed both branches with pinned leases, reset this checkout, and deleted the three local backup branches.
  • Revisit: the other clone must fetch and hard-reset to origin/main before it pushes, or the old history comes back. stash@{0} is still built on the pre-scrub history (local only). GitHub may still serve the old commit hashes until it garbage-collects them; contact GitHub Support if that matters.

2026-09-28: Make fast-code-reviewer hook path machine-independent

  • 09-27 and 09-28 "fixes" fought each other: the reviewer lives at ~/.kevin/code/fast-code-reviewer on one machine and ~/code/tools/fast-code-reviewer on the other, so any hardcoded path breaks one of them. The PostToolUse hook now tries both and exits 0 silently if neither exists (verified: resolves on this machine; HOME=/nonexistent exits 0).
  • Added "tui": "fullscreen" (set via /config).
  • Revisit: settings.json mcpServers.msgvault is likely dead config — the live msgvault server is defined in ~/.claude.json. Confirm against the settings schema before deleting.

2026-09-28: Make pi a daily driver that shares Claude Code's rules and skills

  • Default model is now openai-codex/gpt-5.5 (flat subscription) instead of local qwen2.5-coder:7b, which couldn't follow a simple instruction in testing. Opus 5.5 added to enabledModels; per-model thinking levels set.
  • New extensions in agents/pi/home/extensions (loaded via settings extensions): shared-rules.ts injects claude/home/CLAUDE.md, the philosophy, and claude/rules/*.md (minus tool-use.md) as a system prompt section; claude-skills.ts adds only the ~/.claude/skills pi doesn't already discover (the 23 overlaps with ~/.agents/skills are identical copies; synced/ is skipped as Claude-only); confirm-irreversible.ts gates push/reset --hard/branch -D/clean -f/rm/sudo and blocks them without a UI.
  • Roles via vendored preset.ts + presets.json (explore/plan/work/critic/promote). Changed preset.ts to use a prompt section instead of replacing the whole system prompt, which had silently dropped the shared rules.
  • ~/.claude/commands added as pi prompts, so /notes-organize-tweets works in pi.
  • Anthropic in pi is always billed per token (subscription auth draws extra usage), so Opus-heavy work belongs in Claude Code; in pi, hand off between roles via /new + the plans/ file so Opus doesn't re-read the worker's transcript.
  • Revisit: promote preset -> claude-sonnet-5-5 and showCacheMissNotices; gemma4:e2b is in enabledModels but not pulled; rm -f /tmp/... in notes-organize-tweets triggers the gate each run.

2026-09-29: Routine notes-organize-tweets run

  • Enriched 1 new save (karpathy on the Sutton pod): body, tweet-card screenshot, inbox tag; convergence pass upgraded it to a 3-tweet thread; tagged learn/ai/research + learn/ai/llm.
  • No conflict pairs pre- or post-run; no script changes.
  • Revisit: 2 untagged notes reference tweets but look like project notes (manual-review list) — they resurface every run until tagged by hand.

2026-09-29: notes-organize-tweets captures long-form tweets, quotes, and links at full fidelity

  • A long-form "note tweet" was saved truncated at ~280 chars: syndication returns only a note_tweet id, and Tier 2's searchTweets also returns legacy full_text. Only getTweet (TweetDetail) carries the full text and the quoted tweet.
  • Step A records is_note_tweet/quoted_id; A2 routes those through Tier 2 even with no replies; the fetcher re-fetches every chain tweet via getTweet (failure = transient, note stays unsettled). Switched to ErrorRateLimitStrategy: the default silently sleeps up to 15 min on a rate limit (caused a hung probe).
  • Step B renders a Quoting @handle block, expands t.co links (drops media/quote self-links), adds body_full_text for settled single tweets, carries My note through rebuilds (the enrich path previously dropped it), skips re-attaching existing thread photos (made tweet_3 2.png), and re-adds only leaf tags.
  • Pre-check now keys on the **@handle** · [View on X] footer: the quote link higher in the body made a re-run rebuild the note as the quoted author's thread (caught in verification, note restored).
  • Revisit: the Tier 3 embed screenshot still stops at "Show more" for long-form tweets (the body text is now complete, so the screenshot is just the visual card). Fixing that would mean screenshotting x.com with cookies. Only 1 note on this machine has a thread marker, so there's no local backfill; run FORCE_THREAD_RECHECK=1 on the other machine if its corpus differs.

2026-10-01: dotfiles main conflict check and pi version bump

  • What changed
    • Fetched origin/main and verified main had no outstanding merge conflicts; local main is ahead only.
    • Committed the pending Pi lastChangelogVersion bump to 0.99.2.
  • What we decided and why
    • Kept the Pi settings diff minimal by preserving existing JSON formatting and adding only the version change.
  • What to revisit next time
    • Push the two local commits to origin/main after confirmation.

2026-10-12: ast-grep-codemods skill, built and tested with baseline/green runs

  • What changed
    • New skill agents/claude/home/skills/ast-grep-codemods/SKILL.md: when to reach for ast-grep (many-file mechanical rewrites, repo-wide structural CI rules), global-only install, a verified migration codemod and CI lint rule for 0.50, gotchas, and the rationalizations agents used to avoid it.
    • Installed ast-grep 0.50 globally via brew (sg alias is deprecated; use ast-grep).
  • What we decided and why
    • Without the skill, fresh agents on a hard fixture (multi-line calls, nested args, same-named foreign method, comment/string decoys) got correct results but by hand-rolling a 148-line tokenizer or adding acorn plus a 60-77 line walker; none installed ast-grep even when they named it as missing. With the skill, an unprompted agent discovered it, installed globally, and finished with a 3-rule YAML file matching the reference exactly.
    • ast-grep is never added to a repo's package.json or lockfile; CI installs it in its own pinned step. Rule files may live in the repo. The skill asks before a global install rather than running it.
    • The first fixture was too uniform (line-anchored regex was actually correct), so the baseline was redone on a harder one before writing the skill.
  • What to revisit next time
    • Watch whether agents rewrite semantically-unsafe shapes (variable log level) instead of leaving them; the skill now requires listing those sites either way.
    • Skill is 1k words; trim if it starts loading too often.

2026-10-08: Fix neovim treesitter errors after a plugin update; move maestro PATH to zshrc.local

  • What changed
    • Moved the installer-appended ~/.maestro/bin PATH line from zsh/zshrc.symlink to ~/.zshrc.local (machine-specific).
    • Committed Claude "model": "opus" and pi changelog bumps (1.0.4, 1.1.0) as minimal diffs; pi keeps reformatting its arrays and dropping the trailing newline on every update.
    • neovim 0.11.3 -> 0.12.5 (brew). nvim-treesitter main needs 0.12; on 0.11 plugins failed to load and lazy rewrote lazy-lock.json without ~15 plugins. Restored the committed lock + Lazy! restore.
    • Installed tree-sitter-cli (now in Brewfile) and rebuilt stale Oct-2025 parsers — the noice Invalid node type "tab" error was the old vim.so against new queries.
  • What we decided and why
    • Never commit a lazy-lock.json that drops plugins while lazyvim.json extras are unchanged — it means the machine failed to load them.
  • What to revisit next time
    • jsonc parser no longer exists upstream; LazyVim json extra still requests it (harmless warning).
    • Other machine: confirm neovim >= 0.12 and tree-sitter-cli installed (brew bundle).

2026-10-15: Researched landing-page skills; drafted landing skill; dogfooded on Side Projects

  • Added agents/claude/home/skills/landing/ (SKILL.md + references/conversion.md, references/deploy.md) on branch feat/landing-page-skill (worktree ~/.dotfiles-worktrees/landing-page-skill, outside the repo because .worktrees isn't gitignored and .claude is a symlink that blocks native worktrees).
  • Decided to build a thin orchestrator instead of adopting one: no existing skill is monorepo-, impeccable-, and Vercel-CLI-aware. Borrowed rules from coreyhaines31/marketingskills, Paldom/landing-page-builder-skills, Pieter Levels' formula; design is delegated to impeccable.
  • Dogfood: 3 variants in sideprojects worktree (~/.kevin/code/sideprojects-worktrees/landing, branch feat/landing-page, marketing/), preview-deployed to Vercel project sideprojects-marketing. Nothing committed in either repo.
  • Revisit: pick a variant, wire WAITLIST_WEBHOOK_URL (logs-only storage now), enable Web Analytics, real OG image, then commit both branches.
  • Later same day: shipped. Kevin picked variant B + A's bullets; live at https://getsideprojects.app (Namecheap → Vercel nameservers, www 308 → apex). Waitlist → Resend segment "Side Projects waitlist" via a dedicated full-access key stored only as a Vercel prod secret. Web Analytics on (plain-HTML /_vercel/insights/script.js).
  • Found and fixed: re-creating a Resend contact resets unsubscribed, so the function now adds existing contacts to the segment and only creates new ones (verified live). Skill references updated with this, the deploy-then-attach-domain order, and analytics path.
  • Revisit: real OG image; Resend sending domain for getsideprojects.app (records via vercel dns add); whether custom events record on Hobby; delete or keep /a /b /c variants.

2026-10-15: Per-machine Brewfile selection in bootstrap

  • Added homebrew/select-brews: sk multi-select over every brew/cask/mas line, everything preselected; deselected entries are saved to ~/.config/dotfiles/brew-skip. homebrew/install.zsh runs it on the first interactive bootstrap (no skip file yet, stdin is a TTY), installs sk first if missing, and pipes the filtered Brewfile to brew bundle --file=-. dot --select-brews reopens the picker.
  • Decided to persist skips rather than picks so new Brewfile entries install everywhere by default. Kept the skip file under ~/.config so it survives a re-clone and never hits git.
  • First draft was named select.zsh, which zshrc.symlink's */*.zsh glob sourced on every shell start, launching the picker. Renamed to an extensionless script like bin/dot. Anything under a topic dir ending in .zsh that is not meant to be sourced must be added to the filter on zsh/zshrc.symlink:23 or renamed.
  • Revisit: verified the picker logic with a stubbed sk and the real sk's UI under a piped stdout, but could not drive keypresses through expect reliably; worth one real run on the next fresh machine. kbr.sh/dotfiles still redirects to the GitHub page, not a script, so curl | sh bootstrap would need a new route.
  • Later: new-Mac bootstrap stalled at the picker. Homebrew installs skim 5.7.4 now, which draws its UI on stderr (0.20.5 used /dev/tty), and bootstrap merges stderr into the pipe it reads line by line, so the picker ran invisibly. Fixed by running sk with 2>/dev/tty; reproduced and verified against both versions with the 5.7.4 bottle extracted into a scratch dir. 5.7.4 also dropped toggle-all, so ctrl-a is select-all.
  • Also fixed in the same pass: bootstrap's printf helpers used $1 as the format (any % in dot output errored), the Homebrew installer's "Next steps" block is filtered since the script already does those steps, and the .zprofile append now runs once instead of every dot.
  • Later: new-Mac bootstrap spewed nine "Could not write domain .../Containers/com.apple.Safari" errors. Safari's prefs are sandboxed and need Full Disk Access for the terminal. set-defaults.zsh now probes with the first Safari write and, if denied, skips the block with one line saying what to grant; dot applies them on a later run.
  • Later: Brewfile cleanup after bootstrap errors on the new Mac. Dropped the deprecated homebrew/bundle and homebrew/cask-fonts taps (bundle is built in, fonts live in homebrew/cask), renamed docker to docker-desktop and font-source-sans-pro to font-source-sans-3. Picker: ctrl-a select-all was a no-op on an all-selected list, so ctrl-d now clears; a Python pty driver (scratchpad) was the only harness that delivered keys to sk, expect never did.
  • Later: more new-Mac bundle failures. 1password-cli and fantastical are casks, not formulae. Homebrew 6 refuses third-party taps until brew trusted and a fresh machine trusts none, so install.zsh now trusts every tap the Brewfile declares or references before bundling. herdr's start_service: true made bundle mark the whole entry failed when the service start errored; herdr/install.sh now starts it via brew services only if the server isn't already running. keeper-cli is a private repo cloned over SSH, so its installer probes GitHub SSH access non-interactively and skips with a hint when there is no key yet.
  • Later: illegalstudio/tap/lazyagent moved from a formula to a cask in its tap; Brewfile entry is now cask. This machine already has it as the cask.
  • Later: "node not found" from the QMD installer traced to script/install only running files named install.sh, so mise/install.zsh, node/install.zsh and starship/install.zsh never ran on a fresh Mac. Renamed them to install.sh, made script/install run installers sorted (mise before tools/) with mise shims on PATH, and turned QMD's prerequisite errors into skips. Left macos/install.zsh (App Store + softwareupdate) and warp/install.zsh (non-idempotent ln -s) dormant on purpose.
  • Picker: dot --select-brews now forces the picker (the new Mac had a skip file from the blind run) and continues into the normal dot flow, so selections get installed. After saving, select-brews lists deselected entries that are installed and asks [y/N] on /dev/tty before brew uninstalling them. Verified under the pty driver with stubbed brew/sk.
  • Later: Pi installer failed with "pnpm not found" because installers ran alphabetically and agents/ came before mise/. script/install now runs mise then node explicitly before the sorted rest.
  • Later: Pi installer hit ERR_PNPM_GLOBAL_BIN_DIR_NOT_IN_PATH on the new Mac: pnpm refuses add -g unless ~/Library/pnpm is on PATH, which only the interactive zshrc does. The installer now exports PNPM_HOME and prepends it itself.
  • Later: Warp theme didn't come through on the new Mac. Warp Settings Sync only carries the theme selection; custom theme YAML lives locally in ~/.warp/themes and never syncs, and warp/install.zsh never ran (script/install only runs install.sh). Replaced it with an idempotent warp/install.sh that symlinks each warp/themes/*.yaml into ~/.warp/themes. Ran it twice here; both Synthgreen files linked.
  • Revisit: confirm Warp picks up Synthgreen after a restart; if the synced selection still shows the default, pick it once in Settings > Appearance.

2026-10-15: Add Tailscale cask to Brewfile

  • Added cask 'tailscale-app' to the Brewfile. brew info --cask tailscale now resolves to tailscale-app, so the Brewfile uses the current name.
  • Revisit: not installed on this machine yet; dot or brew bundle will pick it up.

2026-10-15: Point gwt and fresh at $PROJECTS

  • gwt (functions/functions.zsh) and bin/fresh now use $PROJECTS instead of hardcoded ~/code, which doesn't exist on this machine.
  • Fallback when $PROJECTS is unset is ~/.kevin/code, matching zsh/zshrc.symlink, so non-interactive shells behave the same.
  • Revisit: installers and agents/claude/home/settings.json still hardcode ~/.kevin/code / ~/.kevin/personal-code; the work machine could set PROJECTS in ~/.localrc.

2026-10-15: Create ~/.kevin dirs on dot

  • script/custom_dirs.zsh was never called by anything. bin/dot now runs it first, and it also creates ~/.kevin/code so $PROJECTS exists on a fresh machine.
  • It runs before macos/set-defaults.zsh, which points screenshots at ~/.kevin/screenshots.

2026-10-15: Make dot survive a fresh machine

  • herdr/install.sh: herdr status server exits 0 even when stopped, so reload-config failed and set -e killed every installer after herdr. It now checks --json for "running":true, and a failed brew services start only warns.
  • script/install runs every installer, collects the failures, and exits 1 at the end with a list. bin/dot still runs migrations and kit index, then exits with the install status.
  • bin/dot: removed the second brew update (homebrew/install.zsh already ran one) and added brew shellenv so a fresh machine has brew on PATH.
  • Brewfile: added bun (search, keeper-cli, qmd) and rust (livediff, bh). bearcli, livediff and search now skip with a note when a prerequisite is missing, the same way keeper-cli and qmd already did.
  • node/install.sh: --allow-scripts=dev-browser silences the npm install-scripts warning.
  • Revisit: Pi's pnpm "Ignored build scripts" (@google/genai, protobufjs) warning; git-all.config.local still has to be created by hand per machine; Safari defaults still need Full Disk Access.

2026-10-15: App Store apps via Brewfile mas lines

  • Moved Amphetamine, Bear, Flow and Plash out of macos/install.zsh (which dot never runs) into the Brewfile as mas lines. brew bundle skips apps already installed, and select-brews already offers mas lines.
  • Spark: the old ID 1176895641 is Spark Classic. Switched to Spark Mail (6445813049), the Spark Desktop app that provides the spark CLI search uses.
  • Left Xcode out on purpose (10+ GB). macos/install.zsh keeps only softwareupdate, xcode-select and brew upgrade, and still isn't run by dot.
  • Revisit: confirm the mas installs work on the next fresh machine; they need App Store sign-in first.

2026-10-15: Second fresh-run pass on dot

  • script/install now reads the installer list on fd 3. keeper-cli's ssh -T read stdin and swallowed every installer after it (kit, starship, bearcli, livediff, qmd, search, warp silently never ran).
  • herdr: the formula ships no service and there is no herdr server start, so brew services start can never work. It now prints a note; the server starts on the first herdr.
  • LazyVim migration was not idempotent: [ -d ~/.config/nvim ] is true for its own symlink, so a re-run would move config and ~/.local/share/nvim plugins to .bak. It now exits early when the link is in place, and no longer clones the starter just to delete it.
  • Claude skills migration marked superseded (agents/claude/install.sh owns ~/.claude/skills; its glob over the removed .dotfiles/.claude/skills aborted dot).
  • bin/dot: a failing migration is reported and dot keeps going; exit status reflects it.
  • Revisit: migrations have no "already ran" record, so each one must guard itself. Consider a stamp file per migration if more get added.

2026-10-15: livediff link failure was a half-updated Command Line Tools

  • cargo install livediff failed with tapi error: malformed file ... unknown architecture arm64e.x1-macos. The MacOSX27.0 SDK had been installed (xcrun picks the newest) while ld was still ld-1267 from CLT 26.6, so every native link failed, even a C hello-world.
  • Fixed on the machine, not in the dotfiles: sudo softwareupdate -i "Command Line Tools for Xcode 27.0-27.0" (ld is now ld-27037.1). Needs a real terminal; ! in Claude Code has no TTY for sudo. Avoid -i -a, which also pulls the macOS update.
  • Revisit: if this recurs on another machine, check ld -v against xcrun --show-sdk-version before blaming dot.

2026-10-15: Picked up the interrupted Side Projects launch session

  • Confirmed both repos' main branches are merged and pushed (sideprojects 3f90f28, dotfiles ce20da4); kept the unrelated agents/pi/home/settings.json change untouched. The OG card is live (200), old variants return 404, and analytics script returns 200 when routed to Vercel's current IP.
  • Resend DNS records are publicly visible; domain verification was pending at the end of the prior session. The local macOS DNS cache still resolves the apex to Namecheap's old 162.255.119.119, making ordinary curl time out, while public resolvers and Vercel authoritative DNS return current Vercel IPs. No launch email sent.
  • Revisit: check Resend domain status with permission, resolve local DNS cache, and clarify intended launch email audience/content before sending.

2026-10-19: Fixed qmd missing from PATH (search fell back to stale index)

  • ~/.kevin/bin/qmd was a dangling symlink into mise's node 24.14.1, pruned ~Oct 12; the qmd index in ~/.cache/qmd was empty too. Reinstalled @tobilu/qmd 2.8.3 with Homebrew npm and rebuilt the index via search update qmd.
  • tools/qmd/install.sh now installs with Homebrew's npm and writes ~/.kevin/bin/qmd as a wrapper that execs /opt/homebrew/bin/node. Reason: qmd's #!/usr/bin/env node resolves to mise shims first under launchd PATH (dead shim, or the wrong ABI for native modules).
  • The first rebuild failed on a full disk (ENOSPC); Kevin freed 15 GB.
  • Revisit: ~/.config/mise/config.toml wants node 24 but 24.22.0 isn't installed (mise install node@24, or drop it). A brew node major upgrade may need npm rebuild -g @tobilu/qmd.

2026-10-19: Cleared the per-prompt mise WARN missing: node@24.22.0

  • ~/.config/mise/config.toml (generated by mise/install.sh, untracked) pins node = "24", which resolves to 24.22.0, and nothing satisfied it after 24.14.1 was removed. Ran mise install; a fresh zsh -i is quiet and node resolves to mise's 24.22.0.
  • No repo change needed: mise/install.sh already runs mise use --global node@24, which installs it on a fresh machine.
  • Revisit: mise activate zsh runs twice (mise/path.zsh and zsh/zshrc.symlink:93). It's harmless today; drop one if shell startup time matters.
  • Follow-up: removed the duplicate mise activate zsh at zsh/zshrc.symlink:93 (added 2026-03-26, before mise/path.zsh existed). Verified node resolution and mise's position in PATH are the same with only mise/path.zsh activating.

2026-10-19: Added /make-check-repo skill

  • New skill agents/claude/home/skills/make-check-repo/SKILL.md: surveys a repo's CI config and tooling, builds a non-mutating make check that mirrors CI (format, lint, typecheck, tests, build, etc.), runs it until green, and writes a Verification section into the repo's AGENTS.md (plus @AGENTS.md in CLAUDE.md).
  • New rule agents/claude/rules/make-check.md: suggest the skill right after a repo is created, or in repos with CI but no make check; run make check before calling work done.
  • Inspired by Cory House's "strict check script" post (tsc, cspell, test, knip, e2e build, lint, format:check).
  • First run: triage (no hosted CI). make check went from tsc-only to typecheck (now incl. test/, scripts/), bash -n, swiftc -typecheck, bun test, and a demo-data server smoke test; green in ~2s, pushed as triage ace82a5. The run showed that Claude Code only auto-loads CLAUDE.md, so the skill now creates a @AGENTS.md CLAUDE.md when none exists.
  • Revisit: triage has no formatter/linter (proposed biome, not added). Codex/pi don't get the skill or rule yet. Overlaps with /local-ci: make-check-repo builds make check; local-ci runs the GitHub Actions workflows.

2026-10-19: Added /local-ci skill

  • New skill agents/claude/home/skills/local-ci/SKILL.md: wires Local CI (redwoodjs/local-ci, formerly agent-ci; npm run-local-ci, pinned exact) into a repo, adds a make ci-local target next to make check, gitignores .env.local-ci, runs it until green, and writes a Local CI section into AGENTS.md.
  • Decided: name the check workflows with --workflow and don't use --all, because --all also selects deploy workflows, and secrets fall back to same-named shell env vars, so a local run could really deploy. Our skill replaces the upstream npx skills add skill.
  • Revisit: not yet run against a real repo, because Docker Desktop wasn't running. Codex/pi don't get it yet.

2026-10-19: Swapped Docker Desktop for OrbStack

  • Brewfile: replaced cask 'docker-desktop' with cask 'orbstack', and installed OrbStack 2.2.4. docker now uses the orbstack context (docker info reports OrbStack 29.8.2).
  • New docker/env.zsh exports LOCAL_CI_DOCKER_HOST=unix://$HOME/.orbstack/run/docker.sock when that socket exists. Local CI ignores docker contexts and defaults to /var/run/docker.sock, which OrbStack only creates in admin mode. Chose the env var over enabling admin mode, so no sudo is needed.
  • Verified: a one-job workflow passed with npx -y run-local-ci@0.18.1 on OrbStack. The skill now covers orb start, the socket, and the first-run image pull.
  • Revisit: Docker Desktop.app is still installed and owns /usr/local/bin/docker. Uninstall it (brew uninstall --cask docker-desktop), then open OrbStack.app once so it links its own docker CLI.
  • Follow-up: uninstalled Docker Desktop. The uninstall also removed /usr/local/bin/docker, and OrbStack didn't link its CLI. New docker/path.zsh appends /Applications/OrbStack.app/Contents/MacOS/xbin (docker, docker-compose, buildx, kubectl) to PATH, which needs no sudo. Also symlinked ~/.docker/cli-plugins/{docker-compose,docker-buildx} to the xbin copies; that's machine state outside the repo. Verified docker run hello-world and a Local CI run from a fresh shell.

2026-10-19: Added mole to Brewfile

  • Brewfile: added brew 'mole' (tw93/Mole, Mac deep-clean/optimize CLI, in homebrew-core). Committed and pushed as ccdb2b9.
  • Installed mole 1.61.0; it provides both mo and mole.

2026-10-19: Switched dotfiles project instructions to AGENTS.md

  • Claude Code 2.1.277 (Sep 2026) reads a repo's AGENTS.md natively when there's no CLAUDE.md/CLAUDE.local.md on the path (default "Project instructions" = claude-md-or-agents-md). User-level instructions are still ~/.claude/CLAUDE.md only, so that stays.
  • New committed root AGENTS.md (layout, conventions, verify, worklog). Deliberately no root CLAUDE.md. Verified with claude -p from the root and from zsh/ (2.1.304).
  • The old root AGENTS.md was a gitignored symlink to agents/codex/project/AGENTS.md, which was never committed. The bare AGENTS.md line in .gitignore matched every AGENTS.md in the repo. Removed that line, and removed the root link from agents/codex/install.sh.
  • Moved the "Dotfiles" section out of the global CLAUDE.md (pi also injects that file globally). Skills make-check-repo/local-ci no longer create a @AGENTS.md CLAUDE.md, and project-init scaffolds AGENTS.md.
  • New migration 2026-10-19-agents-md-project-instructions.zsh: replaces a leftover symlink with the tracked file, warns about shadowing CLAUDE files, and runs claude update if older than 2.1.277. Tested in scratch clones; git pull over the old ignored symlink also works on its own.
  • Revisit: ~/.codex/AGENTS.md and ~/.pi/agent/AGENTS.md are still dangling (codex/home and pi/home AGENTS.md were never committed, for the same gitignore reason). Codex gets no global instructions today. guard-destructive.sh prints its block message to stdout, so Claude only sees "No stderr output".
  • Follow-up: fixed the revisit items. agents/codex/install.sh now generates ~/.codex/AGENTS.md (marker line on top) from global CLAUDE.md + philosophy + rules minus tool-use.md, the same sources pi's shared-rules extension injects. Codex has no @ imports, so this is a concatenated copy, refreshed on every dot. A hand-written file there gets backed up first. Verified with codex exec (0.163.0): it sees both the global rules and the project AGENTS.md.
  • pi/install.sh no longer links ~/.pi/agent/AGENTS.md and removes the stale link. guard-destructive.sh writes its BLOCKED message to stderr, so Claude sees why a command was blocked.
  • Revisit: edits to the rules reach Codex only after the next dot or Codex installer run.
  • Follow-up: moved the Codex generator into agents/codex/generate-agents-md.sh. The installer and the 2026-10-19 migration both call it, and it can be run by hand after editing rules. It recognizes the old install.sh marker, so existing copies regenerate without a backup. Documented loading per agent (global vs project, why Codex gets a generated copy, the upgrade path, how to check) in agents/README.md under "How Instructions Load". Root AGENTS.md tells agents to regenerate after editing rules.

2026-10-20: Added Oil to LazyVim

  • Added config/lua/plugins/oil.lua with default options, mini.icons dependency, and lazy = false; the existing plugins import discovers it automatically.
  • Validated the spec with headless Neovim and ran git diff --check. Left the pre-existing lazy-lock.json changes untouched.
  • Revisit: open Neovim to install Oil, then use :Oil. Consider /make-check-repo to add the repo's missing make check.

2026-10-20: Added Oil keyboard shortcut

  • Mapped - in normal mode to :Oil through the plugin spec, opening the parent directory.
  • Validated the Lua spec with headless Neovim and checked whitespace with git diff --check.
  • Revisit: restart Neovim to use the mapping.

2026-10-20: Warp config managed by dotfiles

  • Warp's quake window (Ctrl+) and theme had to be set up by hand on the new Mac. Both live in ~/.warp/settings.toml ([global_hotkey.dedicated_window], [appearance.themes]`), which nothing in the repo managed.
  • Expanded warp/install.sh (the 2026-10-15 version only linked each theme file). It now symlinks settings.toml, themes/ and tab_configs/ from warp/, and adopts local copies on first run. A differing local file is backed up to *.pre-dotfiles.<timestamp> before linking, never overwritten. On the new Mac, ~/.warp/themes is a real dir of per-file links to the same YAML, so diff -r matches and it gets swapped for the dir link with no backup.
  • launch_configurations/ stays machine-local because it can name work projects and this repo is public.
  • Verified on Warp 0.2026.10.14: a Settings save (Cmd+= zoom) wrote through the symlink into warp/settings.toml with the link left in place. The reverse doesn't work: editing the repo copy (zoom back to 100) wasn't picked up by a running Warp, probably because macOS reports the change under ~/.dotfiles/warp/ and not ~/.warp/, where Warp watches.
  • Revisit: whether touch -h ~/.warp/settings.toml triggers a reload; whether a Settings-panel save writes Warp's in-memory values back over a pulled change; whether the new Mac picks up the linked file after install.

2026-10-22: Pulled upstream, repaired agents symlink, adopted tldraw updates

  • Stash / pull --ff-only / pop was clean (impeccable 4.7.0, nvim lazy-lock, oil.lua). Committed the local leftovers as 2f5b48e: tldraw-offline SKILL.md and agent file (the tldraw installer writes through the skills symlink), a tldraw SubagentStart hook and a new plugin in settings.json, the heroku credential helper in gitconfig.
  • Decided: model in settings.json stays opus. The working tree had claude-fable-5-1[1m] from a /model switch; reverted that one line, kept the rest.
  • ~/.claude/agents was a real dir again (tldraw installer replaced the symlink, same failure as the memory note), so only tldraw-offline was visible and the other 7 agents were not. Its tldraw-offline.md was newer than the repo's, so copied it in first, then relinked. The installer's adopt_and_link would have kept the older repo copy.
  • ~/.codex/AGENTS.md was still the old symlink to the never-committed agents/codex/home/AGENTS.md; ran generate-agents-md.sh, which replaced it with the generated file. Deleted agents/codex/home, agents/codex/project and agents/pi/home/AGENTS.md (obsoleted 2026-10-19), and the skills CLI .trash dir (now gitignored).
  • Revisit: the tldraw installer will break the agents symlink again on its next update. Worth a migration or a check in agents/claude/install.sh that warns when ~/.claude/agents is a real dir. Not pushed yet.
  • Follow-up: fixed the revisit item. adopt_and_link in agents/claude/install.sh no longer drops a file that exists in both the real dir and dotfiles but differs; it saves the local copy as <name>.local beside the dotfiles file, prints a [conflict] line per file and a [warn] summary. Tested the function in isolation on a scratch dir.
  • Follow-up: ran dot to verify. Claude installer reports [ok] on every link. herdr/install.sh was the one failure: a stale standalone herdr 0.8.0 in ~/.local/bin shadowed brew 0.9.5, and that old client also rewrote the SessionStart hook in settings.json (matcher *, reordered). Moved it to ~/.local/bin/herdr.0.8.0.stale (delete it). With 0.9.5 the integration step leaves settings.json alone and the script is idempotent. The remaining error was the long-running herdr server (since Sep 8) on an older protocol; the installer now prints a [note] for protocol_mismatch instead of failing, since herdr server stop exits every pane and must be run by hand from a plain shell. Also noted: tools/qmd/install.sh runs qmd update and qmd embed on every dot, which is most of the 5+ minute runtime.