Skip to content

feat(gmail): add forward command with attachment support#474

Open
brettdavies wants to merge 2 commits intosteipete:mainfrom
brettdavies:feat/gmail-forward
Open

feat(gmail): add forward command with attachment support#474
brettdavies wants to merge 2 commits intosteipete:mainfrom
brettdavies:feat/gmail-forward

Conversation

@brettdavies
Copy link
Copy Markdown

@brettdavies brettdavies commented Mar 24, 2026

Summary

Add gog gmail forward <messageId> command that forwards an email with all original attachments in a single command. Closes #166.

New flags: --to, --cc, --bcc, --subject, --body, --body-file, --from, --attach (repeatable), --no-attachments

Scope

  • New command: gmail forward in the "Write" group (alias: fwd)
  • Forwards original attachments by default; --no-attachments to strip them
  • --attach adds additional local files alongside forwarded attachments
  • Prepends "Fwd: " to subject (case-insensitive skip if already prefixed); --subject to override
  • Optional body preface via --body / --body-file, inserted before forwarded header block
  • --from for send-as alias support via existing resolveComposeFrom()
  • --json / --plain output consistent with gmail send
  • Thread preservation: sets ThreadId on the sent message so the forward stays in the sender's thread, matching Gmail web UI behavior
  • Reuses existing helpers (buildRFC822, resolveComposeFrom, collectAttachments, fetchAttachmentBytes, writeSendResults) — no existing files modified beyond 1-line command registration in gmail.go

Design notes

  • Reviewed PR feat(gmail): add forward command with tests and helpers #167 by @salmonumbrella which informed the design. Key differences from that PR:
    • Calls resolveComposeFrom() directly instead of creating a new resolveSendFrom() — avoids modifying gmail_send.go
    • Adds --attach for local files and --no-attachments to strip originals
    • Preserves thread (sets ThreadId on sent message)
    • Focused scope: no unrelated docs/drive/time changes
    • Recipients via flags (--to) matching gmail send pattern, not positional args

Testing

  • Unit tests (gmail_forward_test.go): 30 subtests covering forwardSubject, forwardHeaderPlain/HTML, buildForwardBodies, plainToHTML, joinForwardSections
  • Integration tests (execute_gmail_forward_test.go): 7 tests via Execute() with httptest mock servers:
    1. Default subject + attachments
    2. HTML-only message (plain text fallback)
    3. Custom subject override
    4. CC/BCC recipients
    5. --no-attachments strips originals
    6. --attach adds local files alongside originals
    7. --no-attachments + --attach replaces originals with locals
  • All existing tests pass (go test ./...)

Known limitations (v1)

  • Inline images (CID-referenced parts) become regular attachments
  • No --body-html for rich-text preface (plain text auto-escaped for HTML variant)

Files

Created:

  • internal/cmd/gmail_forward.go (264 lines) — command + helpers
  • internal/cmd/gmail_forward_test.go (424 lines) — unit tests
  • internal/cmd/execute_gmail_forward_test.go (763 lines) — integration tests

Modified:

  • internal/cmd/gmail.go (+1 line) — command registration

brettdavies and others added 2 commits March 24, 2026 19:54
Add `gog gmail forward <messageId>` command that forwards an email with
all original attachments in a single command. Supports --to, --cc, --bcc,
--subject, --body/--body-file, --from, --attach (local files), and
--no-attachments flags.

Reuses existing helpers (buildRFC822, resolveComposeFrom, collectAttachments,
fetchAttachmentBytes, writeSendResults) without modifying any existing files
beyond command registration in gmail.go.

Closes steipete#166

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Set ThreadId on the sent message to keep the forward in the sender's
thread, matching Gmail web UI behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
brettdavies added a commit to brettdavies/dotfiles that referenced this pull request Mar 24, 2026
Requirements and implementation plan for contributing a gmail forward
command to steipete/gogcli. PR submitted as steipete/gogcli#474.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@brettdavies
Copy link
Copy Markdown
Author

brettdavies commented Mar 30, 2026

@steipete do you mind reviewing this PR when you have the chance? Having the capability to forward will unlock a crucial automation step in a couple of key workflows. Thank you

brettdavies added a commit to brettdavies/dotfiles that referenced this pull request Apr 1, 2026
* refactor: consolidate repo-local enforcement (hooks, rulesets, config) (#3)

Move git hooks from scripts/git-hooks/ to .githooks/ (de facto
convention), add LFS chaining to post-checkout/post-merge/pre-push
(core.hooksPath overrides .git/hooks/), and add bootstrap setup
script for core.hooksPath automation.

Add GitHub platform config: branch protection rulesets (main requires
PR + squash merge + signed commits, development requires signed
commits), PR template, and ShellCheck CI workflow for shell scripts.

Rewrite CLAUDE.md from stale library-system docs to current
config-store architecture (deployment context, automation requirements,
git signing, hooks, branch workflow, cross-platform considerations).
Update README repository layout and bootstrap guide.

Fix gh-pr-comments alias (broken quoting) by converting to function.
Fix GPG_TTY export to avoid SC2155. Use direct markdownlint-cli2
binary instead of bunx. Fix PR template heading formatting. Add stow
global ignore and markdownlint config symlink.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix(hooks): standardize shebangs, add strict mode, optimize git-crypt check (#5)

- Change post-checkout and post-merge shebangs from #!/bin/bash to
  #!/usr/bin/env bash for portability
- Add set -euo pipefail to catch silent failures
- Replace expensive git-crypt status (200-500ms, scans all files) with
  fast sentinel file check (~5ms) using file command

* feat(stow): add stow-deploy wrapper with conflict resolution (#4)

* docs: add stow adopt workflow and conflict resolution plan

Deepened plan covering GNU Stow's three conflict types (non-stow
symlinks, existing plain files, tree folding) with a scripts/stow-deploy
wrapper script supporting --headless mode for fleet deployment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add pre-flight checks and error classification to stow-deploy plan

SpecFlow analysis identified 18 gaps (4 critical). Key additions:
- Pre-flight checks: local package rejection, git-crypt lock detection,
  dirty working tree guard (fatal in headless, warning in interactive)
- Error classification: non-conflict stow errors fail immediately
  instead of cascading through conflict resolution pipeline
- Headless mode: best-effort per package with failure summary
- .profile resilience: guard ~/.local/bin/env sourcing to prevent
  SSH lockout when local package is not yet deployed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add stow-deploy wrapper with conflict resolution

Create scripts/stow-deploy that handles GNU Stow's three conflict
types automatically: non-stow symlinks (removed + retry), existing
plain files (--adopt + review/auto-restore), and tree folding
(--no-folding always enabled).

Pre-flight checks: rejects local package (dot-Library conflict),
verifies git-crypt is unlocked for encrypted packages, and guards
against dirty stow/ directory in --headless mode to prevent data
loss from git checkout after adopt.

Error classification distinguishes conflict errors from other stow
failures (permissions, 2.3.1 bugs) to prevent confusing cascades.
Headless mode uses best-effort per package with failure summary;
interactive mode fails fast.

Guard ~/.local/bin/env sourcing in .profile to prevent SSH lockout
when local package is not yet deployed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: mark stow-deploy plan as completed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(stow-deploy): eliminate double invocations, deduplicate dirty check, improve portability

- Capture stderr on first stow call instead of discarding and re-running
- Same fix for adopt failure path (was also running stow twice)
- Hoist git status --porcelain above headless conditional (was duplicated)
- Change git-crypt detection from grep "data|encrypted" to ! grep "text"
  for cross-platform reliability
- Use here-strings instead of echo|pipe for ShellCheck SC2001 compliance
- Add scripts/stow-deploy to ShellCheck CI workflow

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* docs: update plan statuses and add stow-deploy solution doc

- Mark refactor-repo-local-enforcement plan as completed
- Mark fix-shell-config-gaps plan as completed
- Add compound solution doc for stow conflict resolution wrapper
  covering three-phase pipeline, key patterns, and gotchas

* fix(ssh,git): standardize SSH auth and enforce SSH-only GitHub access

- Uncomment IdentityFile ~/.ssh/brett_ed25519 in Host github.com block
  (was commented out, causing IdentitiesOnly to reject all keys)
- Add gist.github.com to the Host pattern for GitHub SSH config
- Replace HTTPS credential helpers (gh auth git-credential) with
  url.insteadOf rules rewriting HTTPS to SSH for github.com and
  gist.github.com
- Remove stale per-host IdentityAgent directives now handled by
  global Match exec blocks
- Remove decommissioned host entries (gauntlet_ec2, de-zoomcamp)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: update SSH auth conventions, add plan, remove machine names

- Add Git Authentication section to CLAUDE.md (url.insteadOf,
  brett_ed25519 convention, ordering constraint)
- Update README.md clone URL to SSH, add key naming convention
- Add cross-platform SSH auth plan (2026-02-17)
- Rename deploy plan from machine-specific to generic name
- Replace all machine-specific names with generic terms across
  plan files and solution docs
- Update solution docs for SSH-only GitHub access (url.insteadOf
  replaces credential helpers)
- Update key path references from id_ed25519 to brett_ed25519

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(stow): add post-stow SSH config validation

After stowing the ssh package, stow-deploy now validates that:
- SSH config parses correctly (ssh -G github.com)
- The configured IdentityFile exists on disk

Also tracks deployed packages for validation hooks and fixes
a stale comment referencing a machine name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(stow): replace `file` command with portable binary detection

The git-crypt lock check used `file` to detect encrypted blobs,
but `file` is not installed on minimal Ubuntu servers. Use
`grep -qI` instead, which treats binary files as non-matching
without requiring any additional packages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(stow): require Stow >= 2.4.0, portable binary detection, post-stow SSH validation

- Document Stow >= 2.4.0 requirement in README, CLAUDE.md, and
  solution docs (2.3.x has nested --dotfiles bug, Ubuntu apt only
  ships 2.3.1, install via brew)
- Replace `file` command with `grep -qI` for git-crypt lock
  detection (file not installed on minimal Ubuntu servers)
- Add post-stow SSH config validation after deploying ssh package
- Mark headless server acceptance criteria as verified

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(stow): auto-configure core.hooksPath in stow-deploy

stow-deploy now sets core.hooksPath=.githooks for the dotfiles
repo if not already configured. Eliminates the manual bootstrap
step after cloning.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(hooks): replace `file` command with portable binary detection in hooks

Same fix as stow-deploy: use `grep -qI` instead of `file` for
git-crypt lock detection. The `file` command is not installed on
minimal Ubuntu servers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(shell): don't export DOTFILES_SHELL_DIR sentinel

The sentinel was exported, which leaked into child processes and
prevented them from re-sourcing .profile. This caused non-interactive
zsh subshells to miss secrets when spawned from a parent that had
DOTFILES_SHELL_DIR set but not OP_SERVICE_ACCOUNT_TOKEN.

The sentinel only needs to prevent double-sourcing within the same
shell process. All consumers (.bashrc, .zshrc) run in the same
process as .profile, so a non-exported variable works correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: tmux setup + bigdaddy SSH auto-attach (#11)

* feat: add tmux config + bigdaddy SSH auto-attach

* feat: add tmux-new-session to local bin

Creates a 3-pane dev session (yazi left full-height, Claude Code
top-right, lazygit bottom-right) for any repo.

Usage: tmux-new-session <name> ~/dev/<repo>

* feat(stow-deploy): platform defaults, tree-fold resolution, and local package split (#12)

* feat(gh): add wrapper to block AI merges into main/master

Adds a gh CLI wrapper at ~/.local/bin/gh (via stow gh package) that
intercepts `gh pr merge` commands and blocks them when the PR targets
main or master. All other gh commands and merges to non-protected
branches pass through to the real gh binary.

The wrapper resolves the real gh by walking PATH entries, skipping
itself — works on both macOS (Homebrew) and Linux (Linuxbrew).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(shell): inline ~/.local/bin PATH, fix shellcheck warnings

Replace conditional sourcing of ~/.local/bin/env (from the local stow
package) with an inline case-guard PATH prepend. This removes the
dependency on the local stow package, which requires manual sub-package
stowing and may not be deployed on headless servers.

Positioned after Homebrew init so wrappers in ~/.local/bin (like the gh
merge guard) shadow Homebrew binaries.

Also adds shellcheck directives: shell=bash declaration (suppresses
false POSIX warnings since .profile is only sourced by bash/zsh),
SC2034 for DOTFILES_SHELL_DIR (used externally), and SC1090/SC1091
for dynamic sources.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(stow): add DEFAULT_PACKAGES, args extend defaults

stow-deploy now has a built-in DEFAULT_PACKAGES list (the shared
cross-platform set including gh). Running with no args deploys
defaults; passing extra packages extends the defaults with dedup.

This makes the common cases concise:
  scripts/stow-deploy --headless        # defaults only
  scripts/stow-deploy ghostty cursor    # defaults + desktop extras

Updates README to reference the script as the single source of truth
for the default package list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(claude): add effortLevel setting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(zsh): add Obsidian to PATH in zprofile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(secrets): remove unused X/Twitter API op inject blocks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: update plans and solution doc with current package lists

Update deploy sequences in conflict resolution plan, rename machine
names in SSH auth plan, and update usage examples in stow-deploy
solution doc to reflect current package set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add plans and brainstorm for stow-deploy defaults and infra cleanup

Add the brainstorm and plan for stow-deploy platform defaults, tree-fold
fix, and local package split (this branch's main work). Also add the
P3 infrastructure cleanup plan (separate scope).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ssh): add IdentityFile to all hosts with IdentitiesOnly

Add explicit IdentityFile to arouter, pool, and speedy. These hosts
had IdentitiesOnly yes without a key path, which fails on headless
Linux where the 1Password agent socket may not exist. server_25 and
dnsdhcp were already removed from the config.

Marks the SSH auth plan as completed and compounds the fix into the
signing/auth solution doc with host audit table, key naming convention,
and Tailscale Match originalhost pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(stow): split local package, move LaunchAgent to own package

Move stow/local/dot-Library/ to stow/launchagent/Library/ so the
local package becomes a normal auto-deployable package. The launchagent
package uses Library/ (no dot- prefix) because ~/Library doesn't start
with a dot -- stow handles it correctly without --dotfiles conversion.

After the split, local only contains dot-local/bin/env and
dot-local/bin/op-ssh-sign-wrapper. Updates README package table
and LaunchAgent setup instructions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(stow): add --all flag, tree-fold resolution, platform guards

Phase 2+3 of stow-deploy platform defaults plan:

- Add SHARED_PACKAGES and DESKTOP_PACKAGES as single source of truth
  (secrets-first ordering for git-crypt dependency chain)
- Add --all flag: expands to shared+desktop on macOS, shared on Linux
- Add distinct exit codes (EXIT_USAGE=2 through EXIT_PLATFORM=6)
- Add platform guard: desktop packages warn and skip on non-macOS
- Add tree-fold detection (hardcoded map of 4 known packages) and
  resolution (rename-aside pattern, cp -a, git clean -fd)
- Add headless gating: abort if tree-folds detected in headless mode
- Remove local package rejection (now a normal package after split)
- Update README, CLAUDE.md, and solution docs with --all usage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(stow-deploy): bash 3.2 compat and git clean flags for tree-fold cleanup

- Replace `declare -A` (bash 4+) with loop-based dedup for macOS
  compatibility where /usr/bin/env bash resolves to bash 3.2
- Change `git clean -fd` to `-ffdx` in resolve_tree_fold: -x removes
  gitignored files (.gitignore patterns blocked -fd from cleaning),
  double -f removes nested git repos (e.g. claude plugins)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: update solution doc and mark plan completed

Solution doc (stow-conflict-resolution-wrapper.md):
- Add platform-aware package sets and --all flag documentation
- Add tree-fold detection/resolution section with rename-aside pattern
- Add package split section (local + launchagent)
- Add gotchas: git clean -ffdx flags, bash 3.2 declare -A compat
- Remove outdated local package rejection section
- Update pre-flight checks with exit codes and tree-fold reference

Plan (stow-deploy-platform-defaults-tree-fold-fix):
- Mark status: completed
- Check off all functional and non-functional acceptance criteria
- Check off Phase 4 macOS corrective action items
- Split remaining headless deployment into post-merge section
- Fix git clean flags in code snippet and spec-flow gaps (-fd → -ffdx)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: repair dangling markdownlint-cli2.yaml symlink in repo root

The repo-root symlink pointed to stow/claude/dot-claude/.markdownlint-cli2.yaml
which was a path that only worked when ~/.claude was tree-folded into the repo.
After tree-fold resolution, dot-claude/ no longer contains the file. The correct
target is stow/claude/dot-markdownlint-cli2.yaml (top level of the package).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(stow-deploy): add bats test suite and fix doc inaccuracies

Add tests/ with 21 tests split across three files by domain:
- stow-deploy-args.bats: flag validation, error paths, exit codes
- stow-deploy-packages.bats: package sets, expansion, dedup, tree-fold map
- stow-deploy-integration.bats: full deployment, idempotency, SSH validation

Fix doc inaccuracies found during audit:
- CLAUDE.md: "auto-discovered" → must be added to script arrays
- Brainstorm: "Planned" → "Implemented"
- Solution doc: failure mode exit code "exit 1" → "exit 5"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(pip): cross-reference duplicate cache-dir settings

PIP_CACHE_DIR env var (config/shell/caches.sh) and pip.conf cache-dir
(stow/pip/dot-config/pip/pip.conf) both set ~/.cache/pip. Both are
intentional — env var covers shell sessions, pip.conf covers processes
that don't inherit the shell environment. Added cross-references.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: fix stale references in solution docs

cross-platform-stow-dotfiles-deployment.md:
- Remove ~/.local/bin/env sourcing dependency (PATH is now inline)
- Update deployment order to use stow-deploy --headless --all
- Update prevention checklist to reference stow-deploy

post-deployment-shell-config-fixes.md:
- Fix .profile architecture diagram (inline PATH, not sourced file)
- Fix sourcing order (op inject, not op read)
- Replace stale pool.tailscale/pool-lan entries with current SSH layout

headless-linux-git-signing-and-hook-guards.md:
- Mark gauntlet_ec2 as decommissioned in host audit table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(claude): add jaq preference, fix lint, strengthen PR template instruction

- Add jaq as preferred JSON processor (jq is denied in settings.json)
- Fix MD040: add language specifier to fenced code block
- Strengthen PR template instruction: template is single source of truth,
  do not use hardcoded formats from skills

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci(shellcheck): update actions/checkout v4 to v5 for Node.js 24

GitHub deprecated Node.js 20 actions. actions/checkout@v4 runs on
Node.js 20, triggering CI warnings. v5 uses Node.js 24.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* test: comprehensive bats test suite for release validation (#13)

Add 73 new tests across 6 test files covering all scripts, hooks, and
configs in the repo. All 94 tests (21 existing + 73 new) pass in both
bash and zsh.

New test files:
- gh-wrapper.bats: gh merge-blocking wrapper (6 tests)
- git-hooks.bats: pre-commit, post-checkout, post-merge, pre-push (15 tests)
- op-ssh-sign-wrapper.bats: cross-platform signing wrapper (9 tests)
- shell-config.bats: profile, zshenv, bashrc, zshrc validation (17 tests)
- symlinks.bats: post-stow symlink verification (16 tests)
- tmux-new-session.bats: tmux session script (10 tests)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* perf(shell): optimize zsh startup from ~440ms to ~190ms (#14)

* perf(shell): replace npm config set with env var (~105ms saving)

`npm config set cache` runs on every shell startup and takes ~105ms
because it reads/writes ~/.npmrc. Replace with NPM_CONFIG_CACHE env
var which npm respects natively with zero I/O overhead.

Belt-and-suspenders: ~/.npmrc still has the cache setting from the
previous `npm config set` calls, so npm has two sources agreeing on
the same cache directory.

Interactive zsh startup: ~440ms -> ~220ms (50% reduction).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(shell): add startup performance budget tests for all shell envs

Add 4 startup timing tests measuring non-interactive and interactive
startup for both bash and zsh. Performance budgets:
- Non-interactive: < 200ms (zsh: ~16ms, bash: ~8ms)
- Interactive: < 500ms (zsh: ~236ms, bash: ~18ms)

Uses perl Time::HiRes for millisecond-precision wall-clock measurement.
Test output includes actual timing via fd3 for visibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* perf(zsh): unify compinit, compile dump, eliminate brew --prefix calls

Three optimizations to interactive zsh startup:

1. Unify compinit: set ZSH_COMPDUMP before sourcing oh-my-zsh so it
   uses ~/.zsh/cache/zcompdump instead of its own path. Removes the
   redundant second compinit call (~170ms savings).

2. Add zcompile safety net: if the .zwc compiled dump is missing or
   stale (e.g., oh-my-zsh's lock-dir pattern failed), recompile it.
   Compiled dump loads in ~2ms vs ~170ms for text parsing.

3. Replace `brew --prefix` calls with $HOMEBREW_PREFIX env var
   (already set by brew shellenv in .profile). Two calls at ~16ms
   each = ~32ms savings.

Also removes 50-line platform-branching compinit block (4 near-
identical branches for macOS/Linux x zstat/stat) and 75 lines of
commented-out oh-my-zsh boilerplate.

Interactive zsh startup: ~440ms -> ~190ms (57% reduction).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: compound solution for zsh startup optimization (440ms to 190ms)

Documents profiling methodology, three root causes (npm subprocess,
double compinit, brew --prefix), fixes, and key insights about
compinit compilation, oh-my-zsh stale locks, and fpath ordering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ssh): restore lost first-match-wins ordering and originalhost overrides

Rebase of feat/stow-deploy-platform-defaults dropped three SSH config
commits (3222903, 1dbe51c, 59abcef) due to git-crypt binary conflict
resolution. Restores all changes:

- Move platform 1Password agent Match blocks to top (first-match-wins)
- Use originalhost instead of host in Tailscale Match blocks (fixes
  matching against resolved IP instead of typed alias)
- Add IdentityFile to arouter, pool, speedy (IdentitiesOnly without
  a key path fails on headless Linux)
- Add bigdaddy_wifi Tailscale override (100.91.222.22)
- Remove superseded pool.tailscale and pool-lan aliases
- Remove commented-out IdentityAgent on bigdaddy_wifi
- Add section headers documenting ordering rationale

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(ci): CalVer changelog automation with git-cliff (#16)

* feat(ci): CalVer changelog automation with git-cliff (#15)

Add automated changelog generation and CalVer (YYYY.MM.DD) versioning
triggered on every squash merge to main. Uses git-cliff to generate
keep-a-changelog-compatible CHANGELOG.md from conventional commits,
creates a git tag, and publishes a GitHub Release.

Components:
- cliff.toml: git-cliff config with CalVer tag pattern and
  conventional commit type mapping
- .github/workflows/release.yml: GitHub Action with CalVer collision
  handling (America/Los_Angeles timezone), dual loop prevention
  guards, and PAT-based authentication for pushing to protected main
- CHANGELOG.md: seed file with pre-automation history summary
- .github/rulesets/protect-main.json: admin bypass actor for
  release automation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(claude): add MD013 line-length hint to PostToolUse context

When markdownlint reports MD013 violations, the auto-format hook now
extracts the configured line_length from the active config via yq and
appends it to additionalContext. This tells Claude the exact wrapping
limit instead of relying on memory or guessing a shorter width.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(readme): add release automation section and fix MD013 violations

Add documentation for the CalVer release workflow and RELEASE_TOKEN
secret setup (creation, rotation, 1Password integration).

Fix 10 pre-existing MD013 line-length violations by wrapping at the
120-char limit. Use a reference link for the stow bug URL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* chore: mark CalVer changelog automation plan as completed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): use git-cliff action output instead of bare CLI for release body

The orhun/git-cliff-action installs git-cliff only within its own
context. The "Get release body" step was calling git-cliff as a bare
CLI command, which fails with "command not found" on the runner.

Replace with a second action invocation and pass the output via
steps.release-body.outputs.content.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): force Node.js 24 for GitHub Actions and fix CLAUDE.md lint

Set FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true in all workflows to avoid
the Node.js 20 deprecation (June 2, 2026). Add GitHub Actions section
to CLAUDE.md documenting this requirement and the unsigned bot commit
constraint. Fix 14 pre-existing MD013 line-length violations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(deploy): per-platform git config templates and cross-platform hardening (#21)

* feat(shell): adopt headless server local environment changes

Bring over working-tree modifications from deployed headless
Ubuntu server) to track as configuration:

- bash/zsh: OpenClaw completions (guarded with existence check)
- profile: EDITOR/VISUAL via `command -v micro`, print aliases
  for Lunik printer, ollama-update alias
- tmux: window titles (set-titles), pane dimming (bg colors)
- settings.json: disable adaptive thinking, set MAX_THINKING_TOKENS
- gitconfig: core.editor = micro (will move to per-platform
  template in next commit)
- bash_aliases: remove trailing blank line

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(git): per-platform git config templates via stow-deploy

Add automated deployment of platform-specific git config overrides.
On Linux, stow-deploy now copies config/git/local.linux to
~/.config/git/local (copy-if-absent), providing the signing key
file path and editor override that headless servers need.

- config/git/local.linux: signingkey + core.editor for headless Linux
- stow-deploy: post-stow template deployment with tree-fold guard
- gitconfig: remove [core] editor from shared config (now per-platform)
- tests: integration test for template deploy/skip behavior
- docs: update solution doc and plan (MD013 line-length fixes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(shell): cross-platform guards and lint fixes

- zprofile: gate Obsidian macOS path behind uname check
- zshrc: add Linuxbrew fallback for hbash alias, fix libpq comment,
  quote $ZSH path, add shellcheck disable directive for zsh syntax
- profile: move print aliases to Linux-only platform config, fix
  SC2155 (declare and assign separately for EDITOR)
- platform-linux.sh: new Linux-specific shell config with printer
  aliases (guarded with uname check, sourced via config/shell/*.sh)
- Brewfile: clarify macOS-only sections in comments

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: scrub server hostnames from documentation

Replace machine-specific hostnames with generic labels (host-a,
host-b, etc.) across solution docs and plan files. Functional
configs (SSH, tmux) retain real hostnames as they're needed to
connect. Also fixes pre-existing MD013 line-length violations in
post-deployment-shell-config-fixes.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix(deploy): cross-platform follow-on fixes for shell scripts and configs

Second-wave portability fixes found after the initial cross-platform
hardening (PR #21). Eliminates macOS-only assumptions in helper scripts
and configs that silently fail on headless Ubuntu servers:

- stow-deploy: add tmux to SHARED_PACKAGES
- Brewfile: use `if OS.mac?` for macOS-only formulae
- session-context.sh: cross-platform stat fallback (BSD → GNU → zero)
- statusline.sh: replace bc with shell arithmetic, sed with param expansion
- dot-profile: guard tty call for non-interactive shells
- ssh/config: add socket existence check for Linux 1Password agent Match

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: compound solution for cross-platform shell idiom hardening

Document the second-wave cross-platform portability fixes as a compound
solution in docs/solutions/deployment-issues/. Covers BSD vs GNU tool
incompatibilities (stat, bc, sed, tty), per-platform git config
templates, Brewfile OS guards, and SSH socket existence checks.

- New: cross-platform-shell-idiom-and-config-hardening.md
- Add cross-references from 3 existing deployment-issues solution docs
- Fix pre-existing MD013 line-length violations in touched files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: overhaul README, extract bootstrap guide, update PROJECT.md

- Slim README to concise overview with quick start covering both macOS
  and Linux, reference tables, and links to BOOTSTRAP.md for details
- Extract detailed bootstrap steps into new BOOTSTRAP.md (oh-my-zsh,
  Ghostty, Cursor extensions, iCloud sync, release automation)
- Update PROJECT.md: fix stow package count (15→17), shell fragment
  count (9→11), link to BOOTSTRAP.md, fix line-length lint errors
- Add tree-format repository layout to README
- Add Performance and Documentation sections to README
- Clean up .gitignore organization (group related patterns)
- Remove stale VS Code reference from cross-platform notes
- Add tmux package and .zshenv to stow package documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(claude): dedupe and clean up permission settings

- Deduplicate settings.local.json against global settings.json
  (removed 8 duplicates, 3 accidental entries)
- Consolidate 6 brew subcommand entries into Bash(brew:*)
- Add missing git commands: rev-parse, tag, check-ignore, diff-tree
- Add printf to global allow list
- Move swiftformat from ask to allow (safe formatter)
- Move brew search to global (read-only, useful everywhere)
- Remove accidental done:* entry (shell keyword, not a command)
- Consolidate project-specific bats entries into Bash(bats:*)
- Local settings reduced to 3 project-specific entries: bats,
  shellcheck, stow
- Force-track .claude/settings.local.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve remaining todo backlog

- Add markdownlint-cli2 to Brewfile (bootstrap dependency gap)
- Standardize error messages to UPPERCASE convention (ERROR:,
  WARNING:, FATAL:, NOTE:) across pre-commit hook and gh wrapper
- Document shell script error message convention in CLAUDE.md
- Remove stale acceptance criteria (fuser, .stow-migrate) from
  completed plan file
- Add disk space pre-check (512 MB minimum) before tree-fold
  resolution in stow-deploy
- Update git clean comment to reflect current .gitignore state
- Delete all 5 resolved todo files

Plan: docs/plans/2026-03-13-001-fix-resolve-remaining-todo-backlog-plan.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(claude): allow git rev-list in permissions

Read-only plumbing command, safe to auto-approve.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(gh): add squash merge message CTA when blocking main merges

When an agent attempts gh pr merge into main/master, the wrapper
now prompts it to provide a ready-to-paste squash merge commit
message for the human to use.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add lazygit/yazi to stow packages, document --no-folding

- Add lazygit and yazi to SHARED_PACKAGES in stow-deploy
- Add lazygit and yazi rows to README stow package table
- Document --no-folding tree folding convention in CLAUDE.md
- Remove resolved TODO.md (all items addressed)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(tmux): use warm amber tint for inactive panes

Replace near-black dimming (#0a0a0a) with dark amber (#3c2010) for
inactive panes. The color shift is far more perceptible than a pure
brightness change, especially in bright rooms.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: track lazygit, micro, and yazi stow packages

Add three new stow packages to the repo:
- lazygit: clipboard via OSC 52 over SSH
- micro: editor settings (monokai, soft wrap, 4-space tabs)
- yazi: file manager with smart-enter, glow markdown preview

Add micro to SHARED_PACKAGES in stow-deploy and README table.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(hooks): prevent pre-push failure when git-lfs is not installed

The command -v guard returns exit 1 when git-lfs is absent, which
propagates under errexit. Add || true so the hook succeeds regardless.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(gh): encrypt hosts.yml with git-crypt and add example file

hosts.yml now contains the oauth_token after gh auth login, so it
must be encrypted. Added .example file with just the protocol config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(shell,git,yazi): platform-aware editor via $EDITOR env var

Set $EDITOR in .profile per platform (macOS: code --wait, Linux: micro)
and reference it from git (core.editor = $EDITOR) and yazi opener instead
of hardcoding editors per tool.

- stow/shell/dot-profile: case on uname -s for EDITOR/VISUAL
- stow/git/dot-gitconfig: core.editor = $EDITOR (sh -c expands at runtime)
- config/git/local.linux: remove redundant [core] editor = micro
- stow/yazi: opener uses "$EDITOR %s", install glow + ya pkg install

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add cross-platform editor solution and refresh stale references

Add docs/solutions/configuration-fixes/ entry documenting the $EDITOR
env var pattern for cross-platform editor configuration.

Refresh two existing docs that referenced the now-removed
[core] editor = micro in config/git/local.linux:
- cross-platform-shell-idiom-and-config-hardening.md
- headless-linux-git-signing-and-hook-guards.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(claude): add obsidian plugin and normalize settings.json

- Enable obsidian-skills marketplace plugin
- Normalize env var values to strings
- Reorder keys for consistency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(hooks): resolve SC2015 in pre-push hook

Replace `A && B || C` pattern with proper if-then to satisfy
shellcheck. The old pattern would run `|| true` even when git-lfs
was found but `git lfs pre-push` failed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): pass release token as input and always create tag

Two fixes for the release workflow:
- Pass RELEASE_TOKEN via `with: token` instead of `env: GITHUB_TOKEN`
  to ensure softprops/action-gh-release uses the PAT (fixes 403)
- Always create the tag even when changelog has no changes, so the
  release step doesn't fail on a missing tag

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: pin external actions to commit SHAs

Pin all third-party GitHub Actions to full commit SHAs with version
comments for supply chain security:
- actions/checkout@v5 -> 93cb6ef
- orhun/git-cliff-action@v4 -> c93ef52
- softprops/action-gh-release@v2 -> 153bb8e

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: upgrade actions/checkout to v6.0.2 per canonical pin list

Update actions/checkout SHA from v5 to v6.0.2 to match the canonical
pinned versions in pin-actions.sh.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(claude): add rubocop and actionlint to auto-format hook

Add PostToolUse formatting support for:
- Ruby files (.rb) via rubocop -a
- GitHub Actions workflows (.yml/.yaml) via actionlint

Both guarded by command availability checks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(claude): add shared solutions repo section to CLAUDE.md

Document the docs/solutions/ symlink convention pointing to the
shared ~/dev/solutions-docs repo, including commit-and-push workflow
after compounding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(claude): add markdown prose wrapper to auto-format hook

Add md-wrap.py, a markdown-aware line wrapper that reflows prose
paragraphs and list items to the configured width while preserving
code blocks, tables, headings, frontmatter, and link tokens.

Integrate it into auto-format.sh as a pre-step before markdownlint,
reading the target width from the global markdownlint config. Update
the MD013 hint to clarify that remaining violations are unbreakable
tokens and acceptable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(claude): rewrap CLAUDE.md prose to 120-char line width

Auto-wrapped by md-wrap.py hook. Also updates the auto-format hook
description and adds GitHub CLI auth guidance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(claude): allow git add/commit/push and update gh OAuth token

Move git add, git commit, and git push from ask to allow in global
Claude settings. Update gh hosts.yml OAuth token.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(git): match todos/ directories at any depth in global gitignore

Change pattern from `/todos` (root-only) to `**/todos/` so nested
todos directories are also ignored.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(shell): add trash alias for gio trash on Linux

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(shell): remove --wait from VS Code EDITOR variable

The --wait flag causes blocking behavior that interferes with tools
like yazi and git that invoke $EDITOR. Without it, VS Code opens
the file and returns immediately, which is the expected behavior
for a desktop editor used interactively.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(plans): add deepened plan for claude-sync remote config pull

Researched and enhanced the claude-sync feature plan with findings
from 8 parallel review agents (architecture, security, performance,
simplicity, patterns, spec-flow, learnings, best-practices) plus
4 web research queries.

Key enhancements:
- Batch transfer via rsync --files-from with SSH ControlMaster
- diff3 evaluated and rejected (two-way diff is correct here)
- Security hardening: host alias validation, base-dir sanitization
- Atomic staging with sentinel file for interrupted-transfer safety
- Expanded scope: .claude/commands/ and .claude/agents/ directories
- Exit codes aligned with stow-deploy conventions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude): update commit message template formatting and add security advisory guidance

Fix markdown list numbering and nesting broken by auto-formatter.
Add guidance that security advisory dependency fixes use `fix(deps):`
not `chore(deps):`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(claude): revamp PR template with changelog sections and streamlined checklist

Replace generic "Changes" section with structured changelog categories
(Added, Changed, Fixed, Documentation) for generate-changelog.sh
extraction. Fix heading levels on file lists. Remove redundant
checklist items.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(sync): add Box bisync script for rclone

Bidirectional sync between box:TX-AI and ~/box/TX-AI using rclone
bisync. Supports --resync for initial sync. Configured with conflict
resolution (newer wins, loser gets numeric suffix), resilient mode,
and auto-expiring lock files.

Intended to run on a 1-minute systemd timer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(stow): add rclone package with Box bisync systemd units

Stow package deploys a systemd user service and timer for automatic
Box bidirectional sync. Timer fires every minute; service is oneshot
calling the box-bisync.sh script. Requires network-online.target.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(stow): add rclone to shared packages with Linux platform guard

Add rclone to SHARED_PACKAGES so it deploys automatically. Add a
Linux-only platform guard matching the existing macOS-only pattern
for ghostty/cursor/launchagent — rclone's systemd units are only
relevant on Linux.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(git-crypt): add rclone config to encrypted files

The rclone.conf contains Box OAuth tokens (access_token,
refresh_token) and must be encrypted at rest. Pattern added
before the file itself so git-crypt encrypts on first add.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(stow): add encrypted rclone config to rclone package

Add rclone.conf (Box OAuth tokens) to the rclone stow package.
File is git-crypt encrypted at rest. Also add rclone to the
git-crypt unlock check in stow-deploy to prevent deploying
encrypted blobs.

Includes commented-out upload_cutoff (2Gi) in the config,
a box-filters.txt with *.tmp exclusion, and a commented-out
--filters-from flag in the bisync script for opt-in filtering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(docs): replace docs/solutions/ with symlink to shared repo

Replace the inline docs/solutions/ directory with a symlink to
~/dev/solutions-docs (brettdavies/solutions-docs). All 11 existing
solution docs were migrated to the shared repo first.

This follows the convention in CLAUDE.md where docs/solutions/ in
every repo is a symlink to the shared solutions-docs repo, enabling
cross-repo solution search from any working directory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(yazi): add text-based PDF preview for TTY environments

Default yazi PDF previewer converts to images via pdftoppm, which is
illegible as character art in TTY. Use piper + pdftotext instead for
readable text extraction with layout preservation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(rclone): update Box OAuth token

Refreshed access and refresh tokens from active bisync usage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(zsh): add gogcli wrapper with 1Password keyring password

Wraps gog command to lazily fetch GOG_KEYRING_PASSWORD from 1Password
on first invocation, avoiding plaintext secrets in shell config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(scripts): add gogcli bootstrap script

Pulls OAuth credentials and encrypted keyring tokens from 1Password
to set up gogcli on a new machine. Writes client_secret.json,
credentials.json, config.json, and restores keyring files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(scripts): update 1Password item name in gogcli setup

Rename from "Google Workspace CLI OAuth" to
"Google Workspace CLI OAuth (Streams)" to match the vault entry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(claude): integrate qmd search into Claude Code workflows

Add qmd (local hybrid search engine) to Claude Code's search and
discovery toolchain:

- settings.json: allow Bash(qmd:*) permission
- session-context.sh: show qmd collections at session start
- CLAUDE.md: add qmd to CLI Tool Preferences with escalation ladder
  (BM25 → vector → hybrid)

The qmd skill (SKILL.md + references/search-guide.md) lives in the
skills repo and is committed separately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add qmd Claude Code integration brainstorm and plan

Brainstorm explores CLI vs MCP tradeoffs, collection routing, and
skill patterns. Plan documents the 4-phase implementation with
search command tradeoffs, score optimization guidance, and the
escalation ladder (BM25 → vector → hybrid).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(claude): correct stat fallback chaining in session-context cache

Separate the stat format fallback into independent assignments so the
macOS (-f %m) and Linux (-c %Y) variants each set mtime independently
rather than piping through a single subshell. Fixes cache TTL check
failing silently on Linux, where the macOS-style stat would error and
the chained || would not correctly assign mtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(qmd): add systemd user timers for index update and embedding

Replace cron jobs with source-controlled systemd user timers, matching
the rclone/box-bisync precedent. Logs now go to journald instead of
a custom log file.

- qmd-update.timer: runs `qmd cleanup; qmd update` every 5 min
- qmd-embed.timer: runs `qmd embed` every 5 min (offset by 1 min),
  unloads ollama models first to free VRAM
- stow-deploy: add qmd to SHARED_PACKAGES with Linux-only guard

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rclone): enable bisync filters and exclude Obsidian per-device state

- Uncomment and fix filter flag: --filters-from (wrong) → --filter-from
  (correct rclone global flag)
- Add excludes for .obsidian/workspace.json, workspace-mobile.json, and
  cache/** to prevent bisync conflicts when multiple machines run Obsidian
  on the same vault
- Add catch-all `+ **` include rule (required by rclone filter syntax)
- Resync completed to establish new baseline

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: move Obsidian CLI env vars to shared profile, add Linux Xvfb support

- Add DISPLAY=:99 and XAUTHORITY for headless Linux Obsidian CLI
- Move macOS Obsidian PATH from zprofile to shared dot-profile
- Guard Linux exports on Xauthority file existence
- Both bash and zsh now get Obsidian CLI support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(claude): add Read(/tmp/*) to global allow list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add gogcli gmail forward brainstorm and plan

Requirements and implementation plan for contributing a gmail forward
command to steipete/gogcli. PR submitted as steipete/gogcli#474.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Revert "docs: add gogcli gmail forward brainstorm and plan"

This reverts commit 4872d69d996bda291c373e9e1cd20d8ef3991b23.

* feat(claude): add defuddle PreToolUse hook and WebFetch interception

Add defuddle-webfetch.sh hook that intercepts WebFetch calls and
redirects agents to bunx defuddle for clean markdown extraction.
Prevents JSON injection via jaq-based URL escaping. Uses marker files
for fallback passthrough.

- settings.json: remove WebFetch from allow (Issue #18312), add
  Bash(bunx defuddle:*) and Bash(obsidian:*), register PreToolUse hook
- dot-profile: export OBSIDIAN_VAULT_PATH on Linux (guarded)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(obsidian): add stow package with headless service, config, and CLI wrapper

- systemd service: Xvfb on :99, --disable-gpu, stale SingletonLock
  cleanup via ExecStartPre, Restart=always
- obsidian.json: three vaults (obsidian-vault, streams-vault, transitx-vault)
- CLI wrapper: ~/.local/bin/obsidian sets DISPLAY=:99, delegates to
  /opt/Obsidian/obsidian-cli
- stow-deploy: add obsidian to SHARED_PACKAGES and Linux-only guard

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude): add changelog guidance to CLAUDE.md and improve PR template

- CLAUDE.md: add Changelog section rules (include/exclude criteria, never
  manually edit CHANGELOG.md)
- pull-request.md: rewrite Changelog comment with explicit rules — verb-led
  bullets, 1-5 per PR, delete empty sections, omit for non-user-facing PRs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(git): add .context/ to global gitignore, fix comment spacing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(rclone): refresh Box OAuth token

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(claude): update local permission allowlist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(rust): add nightly rustup-update systemd timer

* feat(rclone): exclude .git directories from Box bisync

Prevents git repositories under TX-AI/ from syncing to Box.
Required before initializing git in streams-control vault.
Resync completed successfully after filter change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(openclaw): add systemd user timers for memory lifecycle

Three timer/service pairs for automated memory lifecycle:
- memory-extract: nightly 11:05pm CT
- morning-briefing: daily 7:05am CT
- memory-distill: weekly Sunday 11:35pm CT (After=memory-extract.service)

Timezone-aware OnCalendar (systemd 255+), Persistent=true for missed-run
recovery, PATH includes ~/.local/bin for claude binary, hardened with
NoNewPrivileges/PrivateTmp/UMask.

Deploy: cd ~/dotfiles/stow && stow --dotfiles -t ~ openclaw

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(shell): add supply-chain safety for uv, npm, pip, and bun

Protect against malicious newly-published packages by enforcing a 7-day
minimum release age across all package managers that support it:

- uv/uvx/uv run: UV_EXCLUDE_NEWER="7 days" (native relative duration)
- npm: npm_config_min_release_age=7 (native relative, v11.10.0+)
- pip: PIP_UPLOADED_PRIOR_TO with cross-platform dynamic date (v26.0+)
- bun: minimumReleaseAge=604800 in bunfig.toml (seconds, config-only)

New bun stow package added to SHARED_PACKAGES in stow-deploy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: sync adopted configs and refresh tokens

Adopted changes from live system:
- obsidian: rename streams-vault to streams-control
- zsh: update gogcli OAuth item title to include (Streams)
- rclone: refresh Box OAuth token
- claude: refine 200-line rule wording, fix changelog indent, add rustfmt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(caam): add CAAM account manager with stow package and shell wrapper

Install CAAM (coding_agent_account_manager) for transparent Claude Code
account rotation. Shell function in config/shell/caam.sh wraps `claude`
via `caam run --precheck` so rate limit switching is automatic. Vault
credentials encrypted via git-crypt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(caam): enable daemon auto-refresh and auto-start on shell init

Enable auth_pool in CAAM config so the daemon proactively refreshes
OAuth tokens for all profiles before expiry. Auto-start the daemon
from config/shell/caam.sh on first shell session after reboot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(caam): drop --precheck from wrapper to avoid launch hang

The --precheck flag queries the usage API on every invocation, which
hangs when the API is slow. The daemon already handles proactive token
refresh, making precheck redundant. The wrapper still provides auto-retry
on rate limit for non-interactive claude -p invocations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(caam): split interactive/non-interactive wrapper, add claude-switch

caam run breaks TTY passthrough for interactive sessions. Now only -p
(non-interactive) invocations go through caam run for auto-retry. Direct
claude invocations bypass caam for full TTY support. Added claude-switch
helper for quick profile rotation when hitting rate limits interactively.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add functions-not-aliases rule for config/shell/*.sh

Agents adding new tool integrations to config/shell/ need to know that
aliases don't work there — POSIX sh doesn't support them, and these
files are sourced by .profile for all shell contexts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(stow): add adopt hooks to recapture config drift from apps

Apps that atomically write config (Claude Code, caam daemon) replace stow
symlinks with plain files. Add adopt triggers so changes flow back to the
stow source automatically:
- Claude: SessionStart hook adopts before each session
- CAAM: shell init adopts after daemon start

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude): sync CLAUDE.md with live gstack+CE workflow updates

Adopted from live system: updated workflow section to reflect gstack +
compound-engineering dual skill set, revised qmd search guidance to
prefer hybrid queries, added Rust pre-push and CI monitoring instructions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(caam): enable daemon auth_pool in config

Adopted from live system: auth_pool.enabled was toggled true by caam
daemon operation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(openclaw): update service paths from OpenClaw to Areas/openclaw

Adopted from live system: vault restructure moved scripts from
~/obsidian-vault/OpenClaw/ to ~/obsidian-vault/Areas/openclaw/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(rclone): refresh Box OAuth token

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(claude): sync accumulated local permissions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude): promote skill routing rules and tool priority to global CLAUDE.md

Moved from streams-control local CLAUDE.md:
- Skill-first invocation directive and routing table
- CLI tool priority order (brew > bunx/uvx > python3/node)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add missing --no-folding to BOOTSTRAP.md restow example

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(gogcli): add stow package with shell wrapper (#27)

* docs: add gogcli stow package implementation plan

Plan for integrating gogcli into dotfiles as a proper stow package:
- Shell wrapper in config/shell/gogcli.sh (not .zshrc.d/)
- Adopt-back pattern for config.json drift
- Register in SHARED_PACKAGES, update bootstrap script

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(gogcli): add stow package with shell wrapper

Move the gog() wrapper function from .zshrc (interactive zsh-only) to
config/shell/gogcli.sh (all shells, all contexts via .profile sourcing).
Create stow/gogcli/ package with config.json so stow-deploy manages the
tool configuration. Update setup_gogcli.sh to remove manual shell profile
instructions now that the wrapper is stow-managed.

The shell wrapper follows the caam.sh pattern: command -v guard, 1Password
keyring password injection, and adopt-back to capture config.json drift
when gogcli writes to it at runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(gogcli): use 1Password skill script for secret retrieval

Replace inline `op item get` call with the 1password skill's
read_field.sh helper, consistent with the project's convention of
centralizing 1Password access through skill scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: mark gogcli stow package plan as completed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update README and BOOTSTRAP with missing stow packages and shell files

Add gogcli, caam, bun, obsidian, qmd, rclone to the stow packages table.
Add caam.sh, gogcli.sh, supply-chain.sh to the shell environment table.
Sync BOOTSTRAP.md manual stow commands with current SHARED_PACKAGES and
DESKTOP_PACKAGES arrays.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* perf(shell): inline brew shellenv to eliminate Ruby subprocess on startup

Replace `eval "$(brew shellenv)"` with static variable assignments.
The output is deterministic per install prefix — no need to fork Ruby
on every shell start (~200ms on macOS, can hang indefinitely on Linux
when brew is unhealthy after a reboot).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(tmuxinator): add stow package for declarative tmux session management (#28)

* feat(tmuxinator): add stow package with 13 session YAML configs

Install tmuxinator via Homebrew and create declarative YAML configs for
all active tmux sessions. Each config defines a single-window session
rooted at the project directory. Added to SHARED_PACKAGES in stow-deploy
(after tmux) and to Brewfile.

Note: dotgithub.yml (not dot-github.yml) avoids stow --dotfiles
converting the dot- prefix to a hidden file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(shell): add mux and mux-all convenience functions for tmuxinator

mux() is a simple passthrough to tmuxinator. mux-all() starts all
configured sessions detached, skipping any that are already running.
Both are POSIX-compatible functions guarded by command -v tmuxinator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add tmuxinator to BOOTSTRAP and README

Add TPM bootstrap section to BOOTSTRAP.md, include tmuxinator in both
manual stow command examples, and add tmuxinator rows to the stow
package and config/shell tables in README.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add tmuxinator stow package implementation plan

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(zsh): add Homebrew site-functions to fpath for completions

Homebrew installs zsh completions (tmuxinator, brew, gh, bat, etc.) to
$HOMEBREW_PREFIX/share/zsh/site-functions/ which was missing from fpath.
Also wires tmuxinator completions to the mux wrapper via compdef.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): rename RELEASE_TOKEN to CI_RELEASE_TOKEN

Sync secret name with main branch (PR #26). Fine-grained PATs are only
for CI/CD, so the CI_ prefix clarifies intent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
brettdavies added a commit to brettdavies/dotfiles that referenced this pull request Apr 2, 2026
* refactor: consolidate repo-local enforcement (hooks, rulesets, config) (#3)

Move git hooks from scripts/git-hooks/ to .githooks/ (de facto
convention), add LFS chaining to post-checkout/post-merge/pre-push
(core.hooksPath overrides .git/hooks/), and add bootstrap setup
script for core.hooksPath automation.

Add GitHub platform config: branch protection rulesets (main requires
PR + squash merge + signed commits, development requires signed
commits), PR template, and ShellCheck CI workflow for shell scripts.

Rewrite CLAUDE.md from stale library-system docs to current
config-store architecture (deployment context, automation requirements,
git signing, hooks, branch workflow, cross-platform considerations).
Update README repository layout and bootstrap guide.

Fix gh-pr-comments alias (broken quoting) by converting to function.
Fix GPG_TTY export to avoid SC2155. Use direct markdownlint-cli2
binary instead of bunx. Fix PR template heading formatting. Add stow
global ignore and markdownlint config symlink.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix(hooks): standardize shebangs, add strict mode, optimize git-crypt check (#5)

- Change post-checkout and post-merge shebangs from #!/bin/bash to
  #!/usr/bin/env bash for portability
- Add set -euo pipefail to catch silent failures
- Replace expensive git-crypt status (200-500ms, scans all files) with
  fast sentinel file check (~5ms) using file command

* feat(stow): add stow-deploy wrapper with conflict resolution (#4)

* docs: add stow adopt workflow and conflict resolution plan

Deepened plan covering GNU Stow's three conflict types (non-stow
symlinks, existing plain files, tree folding) with a scripts/stow-deploy
wrapper script supporting --headless mode for fleet deployment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add pre-flight checks and error classification to stow-deploy plan

SpecFlow analysis identified 18 gaps (4 critical). Key additions:
- Pre-flight checks: local package rejection, git-crypt lock detection,
  dirty working tree guard (fatal in headless, warning in interactive)
- Error classification: non-conflict stow errors fail immediately
  instead of cascading through conflict resolution pipeline
- Headless mode: best-effort per package with failure summary
- .profile resilience: guard ~/.local/bin/env sourcing to prevent
  SSH lockout when local package is not yet deployed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add stow-deploy wrapper with conflict resolution

Create scripts/stow-deploy that handles GNU Stow's three conflict
types automatically: non-stow symlinks (removed + retry), existing
plain files (--adopt + review/auto-restore), and tree folding
(--no-folding always enabled).

Pre-flight checks: rejects local package (dot-Library conflict),
verifies git-crypt is unlocked for encrypted packages, and guards
against dirty stow/ directory in --headless mode to prevent data
loss from git checkout after adopt.

Error classification distinguishes conflict errors from other stow
failures (permissions, 2.3.1 bugs) to prevent confusing cascades.
Headless mode uses best-effort per package with failure summary;
interactive mode fails fast.

Guard ~/.local/bin/env sourcing in .profile to prevent SSH lockout
when local package is not yet deployed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: mark stow-deploy plan as completed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(stow-deploy): eliminate double invocations, deduplicate dirty check, improve portability

- Capture stderr on first stow call instead of discarding and re-running
- Same fix for adopt failure path (was also running stow twice)
- Hoist git status --porcelain above headless conditional (was duplicated)
- Change git-crypt detection from grep "data|encrypted" to ! grep "text"
  for cross-platform reliability
- Use here-strings instead of echo|pipe for ShellCheck SC2001 compliance
- Add scripts/stow-deploy to ShellCheck CI workflow

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* docs: update plan statuses and add stow-deploy solution doc

- Mark refactor-repo-local-enforcement plan as completed
- Mark fix-shell-config-gaps plan as completed
- Add compound solution doc for stow conflict resolution wrapper
  covering three-phase pipeline, key patterns, and gotchas

* fix(ssh,git): standardize SSH auth and enforce SSH-only GitHub access

- Uncomment IdentityFile ~/.ssh/brett_ed25519 in Host github.com block
  (was commented out, causing IdentitiesOnly to reject all keys)
- Add gist.github.com to the Host pattern for GitHub SSH config
- Replace HTTPS credential helpers (gh auth git-credential) with
  url.insteadOf rules rewriting HTTPS to SSH for github.com and
  gist.github.com
- Remove stale per-host IdentityAgent directives now handled by
  global Match exec blocks
- Remove decommissioned host entries (gauntlet_ec2, de-zoomcamp)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: update SSH auth conventions, add plan, remove machine names

- Add Git Authentication section to CLAUDE.md (url.insteadOf,
  brett_ed25519 convention, ordering constraint)
- Update README.md clone URL to SSH, add key naming convention
- Add cross-platform SSH auth plan (2026-02-17)
- Rename deploy plan from machine-specific to generic name
- Replace all machine-specific names with generic terms across
  plan files and solution docs
- Update solution docs for SSH-only GitHub access (url.insteadOf
  replaces credential helpers)
- Update key path references from id_ed25519 to brett_ed25519

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(stow): add post-stow SSH config validation

After stowing the ssh package, stow-deploy now validates that:
- SSH config parses correctly (ssh -G github.com)
- The configured IdentityFile exists on disk

Also tracks deployed packages for validation hooks and fixes
a stale comment referencing a machine name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(stow): replace `file` command with portable binary detection

The git-crypt lock check used `file` to detect encrypted blobs,
but `file` is not installed on minimal Ubuntu servers. Use
`grep -qI` instead, which treats binary files as non-matching
without requiring any additional packages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(stow): require Stow >= 2.4.0, portable binary detection, post-stow SSH validation

- Document Stow >= 2.4.0 requirement in README, CLAUDE.md, and
  solution docs (2.3.x has nested --dotfiles bug, Ubuntu apt only
  ships 2.3.1, install via brew)
- Replace `file` command with `grep -qI` for git-crypt lock
  detection (file not installed on minimal Ubuntu servers)
- Add post-stow SSH config validation after deploying ssh package
- Mark headless server acceptance criteria as verified

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(stow): auto-configure core.hooksPath in stow-deploy

stow-deploy now sets core.hooksPath=.githooks for the dotfiles
repo if not already configured. Eliminates the manual bootstrap
step after cloning.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(hooks): replace `file` command with portable binary detection in hooks

Same fix as stow-deploy: use `grep -qI` instead of `file` for
git-crypt lock detection. The `file` command is not installed on
minimal Ubuntu servers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(shell): don't export DOTFILES_SHELL_DIR sentinel

The sentinel was exported, which leaked into child processes and
prevented them from re-sourcing .profile. This caused non-interactive
zsh subshells to miss secrets when spawned from a parent that had
DOTFILES_SHELL_DIR set but not OP_SERVICE_ACCOUNT_TOKEN.

The sentinel only needs to prevent double-sourcing within the same
shell process. All consumers (.bashrc, .zshrc) run in the same
process as .profile, so a non-exported variable works correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: tmux setup + bigdaddy SSH auto-attach (#11)

* feat: add tmux config + bigdaddy SSH auto-attach

* feat: add tmux-new-session to local bin

Creates a 3-pane dev session (yazi left full-height, Claude Code
top-right, lazygit bottom-right) for any repo.

Usage: tmux-new-session <name> ~/dev/<repo>

* feat(stow-deploy): platform defaults, tree-fold resolution, and local package split (#12)

* feat(gh): add wrapper to block AI merges into main/master

Adds a gh CLI wrapper at ~/.local/bin/gh (via stow gh package) that
intercepts `gh pr merge` commands and blocks them when the PR targets
main or master. All other gh commands and merges to non-protected
branches pass through to the real gh binary.

The wrapper resolves the real gh by walking PATH entries, skipping
itself — works on both macOS (Homebrew) and Linux (Linuxbrew).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(shell): inline ~/.local/bin PATH, fix shellcheck warnings

Replace conditional sourcing of ~/.local/bin/env (from the local stow
package) with an inline case-guard PATH prepend. This removes the
dependency on the local stow package, which requires manual sub-package
stowing and may not be deployed on headless servers.

Positioned after Homebrew init so wrappers in ~/.local/bin (like the gh
merge guard) shadow Homebrew binaries.

Also adds shellcheck directives: shell=bash declaration (suppresses
false POSIX warnings since .profile is only sourced by bash/zsh),
SC2034 for DOTFILES_SHELL_DIR (used externally), and SC1090/SC1091
for dynamic sources.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(stow): add DEFAULT_PACKAGES, args extend defaults

stow-deploy now has a built-in DEFAULT_PACKAGES list (the shared
cross-platform set including gh). Running with no args deploys
defaults; passing extra packages extends the defaults with dedup.

This makes the common cases concise:
  scripts/stow-deploy --headless        # defaults only
  scripts/stow-deploy ghostty cursor    # defaults + desktop extras

Updates README to reference the script as the single source of truth
for the default package list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(claude): add effortLevel setting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(zsh): add Obsidian to PATH in zprofile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(secrets): remove unused X/Twitter API op inject blocks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: update plans and solution doc with current package lists

Update deploy sequences in conflict resolution plan, rename machine
names in SSH auth plan, and update usage examples in stow-deploy
solution doc to reflect current package set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add plans and brainstorm for stow-deploy defaults and infra cleanup

Add the brainstorm and plan for stow-deploy platform defaults, tree-fold
fix, and local package split (this branch's main work). Also add the
P3 infrastructure cleanup plan (separate scope).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ssh): add IdentityFile to all hosts with IdentitiesOnly

Add explicit IdentityFile to arouter, pool, and speedy. These hosts
had IdentitiesOnly yes without a key path, which fails on headless
Linux where the 1Password agent socket may not exist. server_25 and
dnsdhcp were already removed from the config.

Marks the SSH auth plan as completed and compounds the fix into the
signing/auth solution doc with host audit table, key naming convention,
and Tailscale Match originalhost pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(stow): split local package, move LaunchAgent to own package

Move stow/local/dot-Library/ to stow/launchagent/Library/ so the
local package becomes a normal auto-deployable package. The launchagent
package uses Library/ (no dot- prefix) because ~/Library doesn't start
with a dot -- stow handles it correctly without --dotfiles conversion.

After the split, local only contains dot-local/bin/env and
dot-local/bin/op-ssh-sign-wrapper. Updates README package table
and LaunchAgent setup instructions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(stow): add --all flag, tree-fold resolution, platform guards

Phase 2+3 of stow-deploy platform defaults plan:

- Add SHARED_PACKAGES and DESKTOP_PACKAGES as single source of truth
  (secrets-first ordering for git-crypt dependency chain)
- Add --all flag: expands to shared+desktop on macOS, shared on Linux
- Add distinct exit codes (EXIT_USAGE=2 through EXIT_PLATFORM=6)
- Add platform guard: desktop packages warn and skip on non-macOS
- Add tree-fold detection (hardcoded map of 4 known packages) and
  resolution (rename-aside pattern, cp -a, git clean -fd)
- Add headless gating: abort if tree-folds detected in headless mode
- Remove local package rejection (now a normal package after split)
- Update README, CLAUDE.md, and solution docs with --all usage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(stow-deploy): bash 3.2 compat and git clean flags for tree-fold cleanup

- Replace `declare -A` (bash 4+) with loop-based dedup for macOS
  compatibility where /usr/bin/env bash resolves to bash 3.2
- Change `git clean -fd` to `-ffdx` in resolve_tree_fold: -x removes
  gitignored files (.gitignore patterns blocked -fd from cleaning),
  double -f removes nested git repos (e.g. claude plugins)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: update solution doc and mark plan completed

Solution doc (stow-conflict-resolution-wrapper.md):
- Add platform-aware package sets and --all flag documentation
- Add tree-fold detection/resolution section with rename-aside pattern
- Add package split section (local + launchagent)
- Add gotchas: git clean -ffdx flags, bash 3.2 declare -A compat
- Remove outdated local package rejection section
- Update pre-flight checks with exit codes and tree-fold reference

Plan (stow-deploy-platform-defaults-tree-fold-fix):
- Mark status: completed
- Check off all functional and non-functional acceptance criteria
- Check off Phase 4 macOS corrective action items
- Split remaining headless deployment into post-merge section
- Fix git clean flags in code snippet and spec-flow gaps (-fd → -ffdx)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: repair dangling markdownlint-cli2.yaml symlink in repo root

The repo-root symlink pointed to stow/claude/dot-claude/.markdownlint-cli2.yaml
which was a path that only worked when ~/.claude was tree-folded into the repo.
After tree-fold resolution, dot-claude/ no longer contains the file. The correct
target is stow/claude/dot-markdownlint-cli2.yaml (top level of the package).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(stow-deploy): add bats test suite and fix doc inaccuracies

Add tests/ with 21 tests split across three files by domain:
- stow-deploy-args.bats: flag validation, error paths, exit codes
- stow-deploy-packages.bats: package sets, expansion, dedup, tree-fold map
- stow-deploy-integration.bats: full deployment, idempotency, SSH validation

Fix doc inaccuracies found during audit:
- CLAUDE.md: "auto-discovered" → must be added to script arrays
- Brainstorm: "Planned" → "Implemented"
- Solution doc: failure mode exit code "exit 1" → "exit 5"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(pip): cross-reference duplicate cache-dir settings

PIP_CACHE_DIR env var (config/shell/caches.sh) and pip.conf cache-dir
(stow/pip/dot-config/pip/pip.conf) both set ~/.cache/pip. Both are
intentional — env var covers shell sessions, pip.conf covers processes
that don't inherit the shell environment. Added cross-references.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: fix stale references in solution docs

cross-platform-stow-dotfiles-deployment.md:
- Remove ~/.local/bin/env sourcing dependency (PATH is now inline)
- Update deployment order to use stow-deploy --headless --all
- Update prevention checklist to reference stow-deploy

post-deployment-shell-config-fixes.md:
- Fix .profile architecture diagram (inline PATH, not sourced file)
- Fix sourcing order (op inject, not op read)
- Replace stale pool.tailscale/pool-lan entries with current SSH layout

headless-linux-git-signing-and-hook-guards.md:
- Mark gauntlet_ec2 as decommissioned in host audit table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(claude): add jaq preference, fix lint, strengthen PR template instruction

- Add jaq as preferred JSON processor (jq is denied in settings.json)
- Fix MD040: add language specifier to fenced code block
- Strengthen PR template instruction: template is single source of truth,
  do not use hardcoded formats from skills

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci(shellcheck): update actions/checkout v4 to v5 for Node.js 24

GitHub deprecated Node.js 20 actions. actions/checkout@v4 runs on
Node.js 20, triggering CI warnings. v5 uses Node.js 24.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* test: comprehensive bats test suite for release validation (#13)

Add 73 new tests across 6 test files covering all scripts, hooks, and
configs in the repo. All 94 tests (21 existing + 73 new) pass in both
bash and zsh.

New test files:
- gh-wrapper.bats: gh merge-blocking wrapper (6 tests)
- git-hooks.bats: pre-commit, post-checkout, post-merge, pre-push (15 tests)
- op-ssh-sign-wrapper.bats: cross-platform signing wrapper (9 tests)
- shell-config.bats: profile, zshenv, bashrc, zshrc validation (17 tests)
- symlinks.bats: post-stow symlink verification (16 tests)
- tmux-new-session.bats: tmux session script (10 tests)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* perf(shell): optimize zsh startup from ~440ms to ~190ms (#14)

* perf(shell): replace npm config set with env var (~105ms saving)

`npm config set cache` runs on every shell startup and takes ~105ms
because it reads/writes ~/.npmrc. Replace with NPM_CONFIG_CACHE env
var which npm respects natively with zero I/O overhead.

Belt-and-suspenders: ~/.npmrc still has the cache setting from the
previous `npm config set` calls, so npm has two sources agreeing on
the same cache directory.

Interactive zsh startup: ~440ms -> ~220ms (50% reduction).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(shell): add startup performance budget tests for all shell envs

Add 4 startup timing tests measuring non-interactive and interactive
startup for both bash and zsh. Performance budgets:
- Non-interactive: < 200ms (zsh: ~16ms, bash: ~8ms)
- Interactive: < 500ms (zsh: ~236ms, bash: ~18ms)

Uses perl Time::HiRes for millisecond-precision wall-clock measurement.
Test output includes actual timing via fd3 for visibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* perf(zsh): unify compinit, compile dump, eliminate brew --prefix calls

Three optimizations to interactive zsh startup:

1. Unify compinit: set ZSH_COMPDUMP before sourcing oh-my-zsh so it
   uses ~/.zsh/cache/zcompdump instead of its own path. Removes the
   redundant second compinit call (~170ms savings).

2. Add zcompile safety net: if the .zwc compiled dump is missing or
   stale (e.g., oh-my-zsh's lock-dir pattern failed), recompile it.
   Compiled dump loads in ~2ms vs ~170ms for text parsing.

3. Replace `brew --prefix` calls with $HOMEBREW_PREFIX env var
   (already set by brew shellenv in .profile). Two calls at ~16ms
   each = ~32ms savings.

Also removes 50-line platform-branching compinit block (4 near-
identical branches for macOS/Linux x zstat/stat) and 75 lines of
commented-out oh-my-zsh boilerplate.

Interactive zsh startup: ~440ms -> ~190ms (57% reduction).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: compound solution for zsh startup optimization (440ms to 190ms)

Documents profiling methodology, three root causes (npm subprocess,
double compinit, brew --prefix), fixes, and key insights about
compinit compilation, oh-my-zsh stale locks, and fpath ordering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ssh): restore lost first-match-wins ordering and originalhost overrides

Rebase of feat/stow-deploy-platform-defaults dropped three SSH config
commits (3222903, 1dbe51c, 59abcef) due to git-crypt binary conflict
resolution. Restores all changes:

- Move platform 1Password agent Match blocks to top (first-match-wins)
- Use originalhost instead of host in Tailscale Match blocks (fixes
  matching against resolved IP instead of typed alias)
- Add IdentityFile to arouter, pool, speedy (IdentitiesOnly without
  a key path fails on headless Linux)
- Add bigdaddy_wifi Tailscale override (100.91.222.22)
- Remove superseded pool.tailscale and pool-lan aliases
- Remove commented-out IdentityAgent on bigdaddy_wifi
- Add section headers documenting ordering rationale

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(ci): CalVer changelog automation with git-cliff (#16)

* feat(ci): CalVer changelog automation with git-cliff (#15)

Add automated changelog generation and CalVer (YYYY.MM.DD) versioning
triggered on every squash merge to main. Uses git-cliff to generate
keep-a-changelog-compatible CHANGELOG.md from conventional commits,
creates a git tag, and publishes a GitHub Release.

Components:
- cliff.toml: git-cliff config with CalVer tag pattern and
  conventional commit type mapping
- .github/workflows/release.yml: GitHub Action with CalVer collision
  handling (America/Los_Angeles timezone), dual loop prevention
  guards, and PAT-based authentication for pushing to protected main
- CHANGELOG.md: seed file with pre-automation history summary
- .github/rulesets/protect-main.json: admin bypass actor for
  release automation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(claude): add MD013 line-length hint to PostToolUse context

When markdownlint reports MD013 violations, the auto-format hook now
extracts the configured line_length from the active config via yq and
appends it to additionalContext. This tells Claude the exact wrapping
limit instead of relying on memory or guessing a shorter width.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(readme): add release automation section and fix MD013 violations

Add documentation for the CalVer release workflow and RELEASE_TOKEN
secret setup (creation, rotation, 1Password integration).

Fix 10 pre-existing MD013 line-length violations by wrapping at the
120-char limit. Use a reference link for the stow bug URL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* chore: mark CalVer changelog automation plan as completed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): use git-cliff action output instead of bare CLI for release body

The orhun/git-cliff-action installs git-cliff only within its own
context. The "Get release body" step was calling git-cliff as a bare
CLI command, which fails with "command not found" on the runner.

Replace with a second action invocation and pass the output via
steps.release-body.outputs.content.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): force Node.js 24 for GitHub Actions and fix CLAUDE.md lint

Set FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true in all workflows to avoid
the Node.js 20 deprecation (June 2, 2026). Add GitHub Actions section
to CLAUDE.md documenting this requirement and the unsigned bot commit
constraint. Fix 14 pre-existing MD013 line-length violations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(deploy): per-platform git config templates and cross-platform hardening (#21)

* feat(shell): adopt headless server local environment changes

Bring over working-tree modifications from deployed headless
Ubuntu server) to track as configuration:

- bash/zsh: OpenClaw completions (guarded with existence check)
- profile: EDITOR/VISUAL via `command -v micro`, print aliases
  for Lunik printer, ollama-update alias
- tmux: window titles (set-titles), pane dimming (bg colors)
- settings.json: disable adaptive thinking, set MAX_THINKING_TOKENS
- gitconfig: core.editor = micro (will move to per-platform
  template in next commit)
- bash_aliases: remove trailing blank line

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(git): per-platform git config templates via stow-deploy

Add automated deployment of platform-specific git config overrides.
On Linux, stow-deploy now copies config/git/local.linux to
~/.config/git/local (copy-if-absent), providing the signing key
file path and editor override that headless servers need.

- config/git/local.linux: signingkey + core.editor for headless Linux
- stow-deploy: post-stow template deployment with tree-fold guard
- gitconfig: remove [core] editor from shared config (now per-platform)
- tests: integration test for template deploy/skip behavior
- docs: update solution doc and plan (MD013 line-length fixes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(shell): cross-platform guards and lint fixes

- zprofile: gate Obsidian macOS path behind uname check
- zshrc: add Linuxbrew fallback for hbash alias, fix libpq comment,
  quote $ZSH path, add shellcheck disable directive for zsh syntax
- profile: move print aliases to Linux-only platform config, fix
  SC2155 (declare and assign separately for EDITOR)
- platform-linux.sh: new Linux-specific shell config with printer
  aliases (guarded with uname check, sourced via config/shell/*.sh)
- Brewfile: clarify macOS-only sections in comments

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: scrub server hostnames from documentation

Replace machine-specific hostnames with generic labels (host-a,
host-b, etc.) across solution docs and plan files. Functional
configs (SSH, tmux) retain real hostnames as they're needed to
connect. Also fixes pre-existing MD013 line-length violations in
post-deployment-shell-config-fixes.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix(deploy): cross-platform follow-on fixes for shell scripts and configs

Second-wave portability fixes found after the initial cross-platform
hardening (PR #21). Eliminates macOS-only assumptions in helper scripts
and configs that silently fail on headless Ubuntu servers:

- stow-deploy: add tmux to SHARED_PACKAGES
- Brewfile: use `if OS.mac?` for macOS-only formulae
- session-context.sh: cross-platform stat fallback (BSD → GNU → zero)
- statusline.sh: replace bc with shell arithmetic, sed with param expansion
- dot-profile: guard tty call for non-interactive shells
- ssh/config: add socket existence check for Linux 1Password agent Match

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: compound solution for cross-platform shell idiom hardening

Document the second-wave cross-platform portability fixes as a compound
solution in docs/solutions/deployment-issues/. Covers BSD vs GNU tool
incompatibilities (stat, bc, sed, tty), per-platform git config
templates, Brewfile OS guards, and SSH socket existence checks.

- New: cross-platform-shell-idiom-and-config-hardening.md
- Add cross-references from 3 existing deployment-issues solution docs
- Fix pre-existing MD013 line-length violations in touched files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: overhaul README, extract bootstrap guide, update PROJECT.md

- Slim README to concise overview with quick start covering both macOS
  and Linux, reference tables, and links to BOOTSTRAP.md for details
- Extract detailed bootstrap steps into new BOOTSTRAP.md (oh-my-zsh,
  Ghostty, Cursor extensions, iCloud sync, release automation)
- Update PROJECT.md: fix stow package count (15→17), shell fragment
  count (9→11), link to BOOTSTRAP.md, fix line-length lint errors
- Add tree-format repository layout to README
- Add Performance and Documentation sections to README
- Clean up .gitignore organization (group related patterns)
- Remove stale VS Code reference from cross-platform notes
- Add tmux package and .zshenv to stow package documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(claude): dedupe and clean up permission settings

- Deduplicate settings.local.json against global settings.json
  (removed 8 duplicates, 3 accidental entries)
- Consolidate 6 brew subcommand entries into Bash(brew:*)
- Add missing git commands: rev-parse, tag, check-ignore, diff-tree
- Add printf to global allow list
- Move swiftformat from ask to allow (safe formatter)
- Move brew search to global (read-only, useful everywhere)
- Remove accidental done:* entry (shell keyword, not a command)
- Consolidate project-specific bats entries into Bash(bats:*)
- Local settings reduced to 3 project-specific entries: bats,
  shellcheck, stow
- Force-track .claude/settings.local.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve remaining todo backlog

- Add markdownlint-cli2 to Brewfile (bootstrap dependency gap)
- Standardize error messages to UPPERCASE convention (ERROR:,
  WARNING:, FATAL:, NOTE:) across pre-commit hook and gh wrapper
- Document shell script error message convention in CLAUDE.md
- Remove stale acceptance criteria (fuser, .stow-migrate) from
  completed plan file
- Add disk space pre-check (512 MB minimum) before tree-fold
  resolution in stow-deploy
- Update git clean comment to reflect current .gitignore state
- Delete all 5 resolved todo files

Plan: docs/plans/2026-03-13-001-fix-resolve-remaining-todo-backlog-plan.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(claude): allow git rev-list in permissions

Read-only plumbing command, safe to auto-approve.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(gh): add squash merge message CTA when blocking main merges

When an agent attempts gh pr merge into main/master, the wrapper
now prompts it to provide a ready-to-paste squash merge commit
message for the human to use.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add lazygit/yazi to stow packages, document --no-folding

- Add lazygit and yazi to SHARED_PACKAGES in stow-deploy
- Add lazygit and yazi rows to README stow package table
- Document --no-folding tree folding convention in CLAUDE.md
- Remove resolved TODO.md (all items addressed)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(tmux): use warm amber tint for inactive panes

Replace near-black dimming (#0a0a0a) with dark amber (#3c2010) for
inactive panes. The color shift is far more perceptible than a pure
brightness change, especially in bright rooms.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: track lazygit, micro, and yazi stow packages

Add three new stow packages to the repo:
- lazygit: clipboard via OSC 52 over SSH
- micro: editor settings (monokai, soft wrap, 4-space tabs)
- yazi: file manager with smart-enter, glow markdown preview

Add micro to SHARED_PACKAGES in stow-deploy and README table.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(hooks): prevent pre-push failure when git-lfs is not installed

The command -v guard returns exit 1 when git-lfs is absent, which
propagates under errexit. Add || true so the hook succeeds regardless.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(gh): encrypt hosts.yml with git-crypt and add example file

hosts.yml now contains the oauth_token after gh auth login, so it
must be encrypted. Added .example file with just the protocol config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(shell,git,yazi): platform-aware editor via $EDITOR env var

Set $EDITOR in .profile per platform (macOS: code --wait, Linux: micro)
and reference it from git (core.editor = $EDITOR) and yazi opener instead
of hardcoding editors per tool.

- stow/shell/dot-profile: case on uname -s for EDITOR/VISUAL
- stow/git/dot-gitconfig: core.editor = $EDITOR (sh -c expands at runtime)
- config/git/local.linux: remove redundant [core] editor = micro
- stow/yazi: opener uses "$EDITOR %s", install glow + ya pkg install

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add cross-platform editor solution and refresh stale references

Add docs/solutions/configuration-fixes/ entry documenting the $EDITOR
env var pattern for cross-platform editor configuration.

Refresh two existing docs that referenced the now-removed
[core] editor = micro in config/git/local.linux:
- cross-platform-shell-idiom-and-config-hardening.md
- headless-linux-git-signing-and-hook-guards.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(claude): add obsidian plugin and normalize settings.json

- Enable obsidian-skills marketplace plugin
- Normalize env var values to strings
- Reorder keys for consistency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(hooks): resolve SC2015 in pre-push hook

Replace `A && B || C` pattern with proper if-then to satisfy
shellcheck. The old pattern would run `|| true` even when git-lfs
was found but `git lfs pre-push` failed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): pass release token as input and always create tag

Two fixes for the release workflow:
- Pass RELEASE_TOKEN via `with: token` instead of `env: GITHUB_TOKEN`
  to ensure softprops/action-gh-release uses the PAT (fixes 403)
- Always create the tag even when changelog has no changes, so the
  release step doesn't fail on a missing tag

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: pin external actions to commit SHAs

Pin all third-party GitHub Actions to full commit SHAs with version
comments for supply chain security:
- actions/checkout@v5 -> 93cb6ef
- orhun/git-cliff-action@v4 -> c93ef52
- softprops/action-gh-release@v2 -> 153bb8e

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: upgrade actions/checkout to v6.0.2 per canonical pin list

Update actions/checkout SHA from v5 to v6.0.2 to match the canonical
pinned versions in pin-actions.sh.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(claude): add rubocop and actionlint to auto-format hook

Add PostToolUse formatting support for:
- Ruby files (.rb) via rubocop -a
- GitHub Actions workflows (.yml/.yaml) via actionlint

Both guarded by command availability checks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(claude): add shared solutions repo section to CLAUDE.md

Document the docs/solutions/ symlink convention pointing to the
shared ~/dev/solutions-docs repo, including commit-and-push workflow
after compounding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(claude): add markdown prose wrapper to auto-format hook

Add md-wrap.py, a markdown-aware line wrapper that reflows prose
paragraphs and list items to the configured width while preserving
code blocks, tables, headings, frontmatter, and link tokens.

Integrate it into auto-format.sh as a pre-step before markdownlint,
reading the target width from the global markdownlint config. Update
the MD013 hint to clarify that remaining violations are unbreakable
tokens and acceptable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(claude): rewrap CLAUDE.md prose to 120-char line width

Auto-wrapped by md-wrap.py hook. Also updates the auto-format hook
description and adds GitHub CLI auth guidance.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(claude): allow git add/commit/push and update gh OAuth token

Move git add, git commit, and git push from ask to allow in global
Claude settings. Update gh hosts.yml OAuth token.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(git): match todos/ directories at any depth in global gitignore

Change pattern from `/todos` (root-only) to `**/todos/` so nested
todos directories are also ignored.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(shell): add trash alias for gio trash on Linux

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(shell): remove --wait from VS Code EDITOR variable

The --wait flag causes blocking behavior that interferes with tools
like yazi and git that invoke $EDITOR. Without it, VS Code opens
the file and returns immediately, which is the expected behavior
for a desktop editor used interactively.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(plans): add deepened plan for claude-sync remote config pull

Researched and enhanced the claude-sync feature plan with findings
from 8 parallel review agents (architecture, security, performance,
simplicity, patterns, spec-flow, learnings, best-practices) plus
4 web research queries.

Key enhancements:
- Batch transfer via rsync --files-from with SSH ControlMaster
- diff3 evaluated and rejected (two-way diff is correct here)
- Security hardening: host alias validation, base-dir sanitization
- Atomic staging with sentinel file for interrupted-transfer safety
- Expanded scope: .claude/commands/ and .claude/agents/ directories
- Exit codes aligned with stow-deploy conventions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude): update commit message template formatting and add security advisory guidance

Fix markdown list numbering and nesting broken by auto-formatter.
Add guidance that security advisory dependency fixes use `fix(deps):`
not `chore(deps):`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(claude): revamp PR template with changelog sections and streamlined checklist

Replace generic "Changes" section with structured changelog categories
(Added, Changed, Fixed, Documentation) for generate-changelog.sh
extraction. Fix heading levels on file lists. Remove redundant
checklist items.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(sync): add Box bisync script for rclone

Bidirectional sync between box:TX-AI and ~/box/TX-AI using rclone
bisync. Supports --resync for initial sync. Configured with conflict
resolution (newer wins, loser gets numeric suffix), resilient mode,
and auto-expiring lock files.

Intended to run on a 1-minute systemd timer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(stow): add rclone package with Box bisync systemd units

Stow package deploys a systemd user service and timer for automatic
Box bidirectional sync. Timer fires every minute; service is oneshot
calling the box-bisync.sh script. Requires network-online.target.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(stow): add rclone to shared packages with Linux platform guard

Add rclone to SHARED_PACKAGES so it deploys automatically. Add a
Linux-only platform guard matching the existing macOS-only pattern
for ghostty/cursor/launchagent — rclone's systemd units are only
relevant on Linux.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(git-crypt): add rclone config to encrypted files

The rclone.conf contains Box OAuth tokens (access_token,
refresh_token) and must be encrypted at rest. Pattern added
before the file itself so git-crypt encrypts on first add.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(stow): add encrypted rclone config to rclone package

Add rclone.conf (Box OAuth tokens) to the rclone stow package.
File is git-crypt encrypted at rest. Also add rclone to the
git-crypt unlock check in stow-deploy to prevent deploying
encrypted blobs.

Includes commented-out upload_cutoff (2Gi) in the config,
a box-filters.txt with *.tmp exclusion, and a commented-out
--filters-from flag in the bisync script for opt-in filtering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(docs): replace docs/solutions/ with symlink to shared repo

Replace the inline docs/solutions/ directory with a symlink to
~/dev/solutions-docs (brettdavies/solutions-docs). All 11 existing
solution docs were migrated to the shared repo first.

This follows the convention in CLAUDE.md where docs/solutions/ in
every repo is a symlink to the shared solutions-docs repo, enabling
cross-repo solution search from any working directory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(yazi): add text-based PDF preview for TTY environments

Default yazi PDF previewer converts to images via pdftoppm, which is
illegible as character art in TTY. Use piper + pdftotext instead for
readable text extraction with layout preservation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore(rclone): update Box OAuth token

Refreshed access and refresh tokens from active bisync usage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(zsh): add gogcli wrapper with 1Password keyring password

Wraps gog command to lazily fetch GOG_KEYRING_PASSWORD from 1Password
on first invocation, avoiding plaintext secrets in shell config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(scripts): add gogcli bootstrap script

Pulls OAuth credentials and encrypted keyring tokens from 1Password
to set up gogcli on a new machine. Writes client_secret.json,
credentials.json, config.json, and restores keyring files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(scripts): update 1Password item name in gogcli setup

Rename from "Google Workspace CLI OAuth" to
"Google Workspace CLI OAuth (Streams)" to match the vault entry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(claude): integrate qmd search into Claude Code workflows

Add qmd (local hybrid search engine) to Claude Code's search and
discovery toolchain:

- settings.json: allow Bash(qmd:*) permission
- session-context.sh: show qmd collections at session start
- CLAUDE.md: add qmd to CLI Tool Preferences with escalation ladder
  (BM25 → vector → hybrid)

The qmd skill (SKILL.md + references/search-guide.md) lives in the
skills repo and is committed separately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add qmd Claude Code integration brainstorm and plan

Brainstorm explores CLI vs MCP tradeoffs, collection routing, and
skill patterns. Plan documents the 4-phase implementation with
search command tradeoffs, score optimization guidance, and the
escalation ladder (BM25 → vector → hybrid).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(claude): correct stat fallback chaining in session-context cache

Separate the stat format fallback into independent assignments so the
macOS (-f %m) and Linux (-c %Y) variants each set mtime independently
rather than piping through a single subshell. Fixes cache TTL check
failing silently on Linux, where the macOS-style stat would error and
the chained || would not correctly assign mtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(qmd): add systemd user timers for index update and embedding

Replace cron jobs with source-controlled systemd user timers, matching
the rclone/box-bisync precedent. Logs now go to journald instead of
a custom log file.

- qmd-update.timer: runs `qmd cleanup; qmd update` every 5 min
- qmd-embed.timer: runs `qmd embed` every 5 min (offset by 1 min),
  unloads ollama models first to free VRAM
- stow-deploy: add qmd to SHARED_PACKAGES with Linux-only guard

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(rclone): enable bisync filters and exclude Obsidian per-device state

- Uncomment and fix filter flag: --filters-from (wrong) → --filter-from
  (correct rclone global flag)
- Add excludes for .obsidian/workspace.json, workspace-mobile.json, and
  cache/** to prevent bisync conflicts when multiple machines run Obsidian
  on the same vault
- Add catch-all `+ **` include rule (required by rclone filter syntax)
- Resync completed to establish new baseline

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: move Obsidian CLI env vars to shared profile, add Linux Xvfb support

- Add DISPLAY=:99 and XAUTHORITY for headless Linux Obsidian CLI
- Move macOS Obsidian PATH from zprofile to shared dot-profile
- Guard Linux exports on Xauthority file existence
- Both bash and zsh now get Obsidian CLI support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(claude): add Read(/tmp/*) to global allow list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add gogcli gmail forward brainstorm and plan

Requirements and implementation plan for contributing a gmail forward
command to steipete/gogcli. PR submitted as steipete/gogcli#474.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Revert "docs: add gogcli gmail forward brainstorm and plan"

This reverts commit 4872d69d996bda291c373e9e1cd20d8ef3991b23.

* feat(claude): add defuddle PreToolUse hook and WebFetch interception

Add defuddle-webfetch.sh hook that intercepts WebFetch calls and
redirects agents to bunx defuddle for clean markdown extraction.
Prevents JSON injection via jaq-based URL escaping. Uses marker files
for fallback passthrough.

- settings.json: remove WebFetch from allow (Issue #18312), add
  Bash(bunx defuddle:*) and Bash(obsidian:*), register PreToolUse hook
- dot-profile: export OBSIDIAN_VAULT_PATH on Linux (guarded)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(obsidian): add stow package with headless service, config, and CLI wrapper

- systemd service: Xvfb on :99, --disable-gpu, stale SingletonLock
  cleanup via ExecStartPre, Restart=always
- obsidian.json: three vaults (obsidian-vault, streams-vault, transitx-vault)
- CLI wrapper: ~/.local/bin/obsidian sets DISPLAY=:99, delegates to
  /opt/Obsidian/obsidian-cli
- stow-deploy: add obsidian to SHARED_PACKAGES and Linux-only guard

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude): add changelog guidance to CLAUDE.md and improve PR template

- CLAUDE.md: add Changelog section rules (include/exclude criteria, never
  manually edit CHANGELOG.md)
- pull-request.md: rewrite Changelog comment with explicit rules — verb-led
  bullets, 1-5 per PR, delete empty sections, omit for non-user-facing PRs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(git): add .context/ to global gitignore, fix comment spacing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(rclone): refresh Box OAuth token

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(claude): update local permission allowlist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(rust): add nightly rustup-update systemd timer

* feat(rclone): exclude .git directories from Box bisync

Prevents git repositories under TX-AI/ from syncing to Box.
Required before initializing git in streams-control vault.
Resync completed successfully after filter change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(openclaw): add systemd user timers for memory lifecycle

Three timer/service pairs for automated memory lifecycle:
- memory-extract: nightly 11:05pm CT
- morning-briefing: daily 7:05am CT
- memory-distill: weekly Sunday 11:35pm CT (After=memory-extract.service)

Timezone-aware OnCalendar (systemd 255+), Persistent=true for missed-run
recovery, PATH includes ~/.local/bin for claude binary, hardened with
NoNewPrivileges/PrivateTmp/UMask.

Deploy: cd ~/dotfiles/stow && stow --dotfiles -t ~ openclaw

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(shell): add supply-chain safety for uv, npm, pip, and bun

Protect against malicious newly-published packages by enforcing a 7-day
minimum release age across all package managers that support it:

- uv/uvx/uv run: UV_EXCLUDE_NEWER="7 days" (native relative duration)
- npm: npm_config_min_release_age=7 (native relative, v11.10.0+)
- pip: PIP_UPLOADED_PRIOR_TO with cross-platform dynamic date (v26.0+)
- bun: minimumReleaseAge=604800 in bunfig.toml (seconds, config-only)

New bun stow package added to SHARED_PACKAGES in stow-deploy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: sync adopted configs and refresh tokens

Adopted changes from live system:
- obsidian: rename streams-vault to streams-control
- zsh: update gogcli OAuth item title to include (Streams)
- rclone: refresh Box OAuth token
- claude: refine 200-line rule wording, fix changelog indent, add rustfmt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(caam): add CAAM account manager with stow package and shell wrapper

Install CAAM (coding_agent_account_manager) for transparent Claude Code
account rotation. Shell function in config/shell/caam.sh wraps `claude`
via `caam run --precheck` so rate limit switching is automatic. Vault
credentials encrypted via git-crypt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(caam): enable daemon auto-refresh and auto-start on shell init

Enable auth_pool in CAAM config so the daemon proactively refreshes
OAuth tokens for all profiles before expiry. Auto-start the daemon
from config/shell/caam.sh on first shell session after reboot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(caam): drop --precheck from wrapper to avoid launch hang

The --precheck flag queries the usage API on every invocation, which
hangs when the API is slow. The daemon already handles proactive token
refresh, making precheck redundant. The wrapper still provides auto-retry
on rate limit for non-interactive claude -p invocations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(caam): split interactive/non-interactive wrapper, add claude-switch

caam run breaks TTY passthrough for interactive sessions. Now only -p
(non-interactive) invocations go through caam run for auto-retry. Direct
claude invocations bypass caam for full TTY support. Added claude-switch
helper for quick profile rotation when hitting rate limits interactively.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add functions-not-aliases rule for config/shell/*.sh

Agents adding new tool integrations to config/shell/ need to know that
aliases don't work there — POSIX sh doesn't support them, and these
files are sourced by .profile for all shell contexts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(stow): add adopt hooks to recapture config drift from apps

Apps that atomically write config (Claude Code, caam daemon) replace stow
symlinks with plain files. Add adopt triggers so changes flow back to the
stow source automatically:
- Claude: SessionStart hook adopts before each session
- CAAM: shell init adopts after daemon start

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude): sync CLAUDE.md with live gstack+CE workflow updates

Adopted from live system: updated workflow section to reflect gstack +
compound-engineering dual skill set, revised qmd search guidance to
prefer hybrid queries, added Rust pre-push and CI monitoring instructions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(caam): enable daemon auth_pool in config

Adopted from live system: auth_pool.enabled was toggled true by caam
daemon operation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(openclaw): update service paths from OpenClaw to Areas/openclaw

Adopted from live system: vault restructure moved scripts from
~/obsidian-vault/OpenClaw/ to ~/obsidian-vault/Areas/openclaw/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(rclone): refresh Box OAuth token

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(claude): sync accumulated local permissions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude): promote skill routing rules and tool priority to global CLAUDE.md

Moved from streams-control local CLAUDE.md:
- Skill-first invocation directive and routing table
- CLI tool priority order (brew > bunx/uvx > python3/node)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add missing --no-folding to BOOTSTRAP.md restow example

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(gogcli): add stow package with shell wrapper (#27)

* docs: add gogcli stow package implementation plan

Plan for integrating gogcli into dotfiles as a proper stow package:
- Shell wrapper in config/shell/gogcli.sh (not .zshrc.d/)
- Adopt-back pattern for config.json drift
- Register in SHARED_PACKAGES, update bootstrap script

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(gogcli): add stow package with shell wrapper

Move the gog() wrapper function from .zshrc (interactive zsh-only) to
config/shell/gogcli.sh (all shells, all contexts via .profile sourcing).
Create stow/gogcli/ package with config.json so stow-deploy manages the
tool configuration. Update setup_gogcli.sh to remove manual shell profile
instructions now that the wrapper is stow-managed.

The shell wrapper follows the caam.sh pattern: command -v guard, 1Password
keyring password injection, and adopt-back to capture config.json drift
when gogcli writes to it at runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(gogcli): use 1Password skill script for secret retrieval

Replace inline `op item get` call with the 1password skill's
read_field.sh helper, consistent with the project's convention of
centralizing 1Password access through skill scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: mark gogcli stow package plan as completed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update README and BOOTSTRAP with missing stow packages and shell files

Add gogcli, caam, bun, obsidian, qmd, rclone to the stow packages table.
Add caam.sh, gogcli.sh, supply-chain.sh to the shell environment table.
Sync BOOTSTRAP.md manual stow commands with current SHARED_PACKAGES and
DESKTOP_PACKAGES arrays.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* perf(shell): inline brew shellenv to eliminate Ruby subprocess on startup

Replace `eval "$(brew shellenv)"` with static variable assignments.
The output is deterministic per install prefix — no need to fork Ruby
on every shell start (~200ms on macOS, can hang indefinitely on Linux
when brew is unhealthy after a reboot).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(tmuxinator): add stow package for declarative tmux session management (#28)

* feat(tmuxinator): add stow package with 13 session YAML configs

Install tmuxinator via Homebrew and create declarative YAML configs for
all active tmux sessions. Each config defines a single-window session
rooted at the project directory. Added to SHARED_PACKAGES in stow-deploy
(after tmux) and to Brewfile.

Note: dotgithub.yml (not dot-github.yml) avoids stow --dotfiles
converting the dot- prefix to a hidden file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(shell): add mux and mux-all convenience functions for tmuxinator

mux() is a simple passthrough to tmuxinator. mux-all() starts all
configured sessions detached, skipping any that are already running.
Both are POSIX-compatible functions guarded by command -v tmuxinator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add tmuxinator to BOOTSTRAP and README

Add TPM bootstrap section to BOOTSTRAP.md, include tmuxinator in both
manual stow command examples, and add tmuxinator rows to the stow
package and config/shell tables in README.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add tmuxinator stow package implementation plan

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(zsh): add Homebrew site-functions to fpath for completions

Homebrew installs zsh completions (tmuxinator, brew, gh, bat, etc.) to
$HOMEBREW_PREFIX/share/zsh/site-functions/ which was missing from fpath.
Also wires tmuxinator completions to the mux wrapper via compdef.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): rename RELEASE_TOKEN to CI_RELEASE_TOKEN

Sync secret name with main branch (PR #26). Fine-grained PATs are only
for CI/CD, so the CI_ prefix clarifies intent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(openclaw): add gateway systemd security drop-in

Add ProtectHome=read-only, ReadWritePaths for vault and config,
NoNewPrivileges=true to openclaw-gateway.service via security.conf
drop-in. Separate from path-override.conf (which stays live-only).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(ci): adopt changelog-as-committed-artifact release process

Replace CI-generated changelog with locally committed artifact. git-cliff
runs on a release branch where individual commits are visible, CHANGELOG.md
is committed to the PR, and the release workflow extracts notes via awk.

Changes:
- cliff.toml: add [remote.github] for PR links, CalVer tag_pattern,
  changelog skip rule
- release.yml: replace git-cliff action with awk extraction from
  committed CHANGELOG.md, remove bot changelog commits
- RELEASING.md: document CalVer release branch procedure

No more unsigned bot commits on main. No more empty changelogs from
squash merges.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update CHANGELOG.md

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Add email forwarding support

1 participant