diff --git a/.beans/dotfiles-7n69--restore-typecheckformat-scripts-dropped-in-devcont.md b/.beans/dotfiles-7n69--restore-typecheckformat-scripts-dropped-in-devcont.md new file mode 100644 index 0000000..55355ea --- /dev/null +++ b/.beans/dotfiles-7n69--restore-typecheckformat-scripts-dropped-in-devcont.md @@ -0,0 +1,22 @@ +--- +# dotfiles-7n69 +title: Restore typecheck/format scripts dropped in devcontainer refactor +status: in-progress +type: bug +priority: normal +created_at: 2026-05-08T16:36:14Z +updated_at: 2026-05-08T16:44:35Z +--- + +CI has been failing since 60c24bc (Nov 25, 2025) — refactor that extracted the devcontainer to pickled-devcontainer over-deleted from package.json: it stripped the entire scripts block (typecheck, format, format:check, lint, test) and dropped typescript from devDependencies, when it only meant to remove the devcontainer:\* scripts. + +The .github/workflows/ci.yml workflow still calls npm run typecheck and npm run format:check, and CLAUDE.md still documents these scripts as the test suite. tsconfig.json + home/.finicky.ts also still exist. + +## Checklist + +- [x] Diagnose root cause (package.json scripts deleted by 60c24bc) +- [x] Restore scripts block to package.json +- [x] Re-add typescript to devDependencies +- [x] Run npm install to regenerate lockfile +- [x] Run npm run lint locally to verify (typecheck passes; format:check fixed 35 drifted files) +- [ ] Push branch, open PR, watch CI go green diff --git a/agents/.skill-lock.json b/agents/.skill-lock.json index 69d28fe..385b405 100644 --- a/agents/.skill-lock.json +++ b/agents/.skill-lock.json @@ -168,4 +168,4 @@ "warp", "claude-code" ] -} \ No newline at end of file +} diff --git a/bin/md2rich b/bin/md2rich index 273292a..24880dd 100755 --- a/bin/md2rich +++ b/bin/md2rich @@ -9,7 +9,7 @@ set -euo pipefail usage() { - cat </dev/null; then - echo "error: pandoc is required but not installed" >&2 - echo " brew install pandoc" >&2 - exit 1 +if ! command -v pandoc &> /dev/null; then + echo "error: pandoc is required but not installed" >&2 + echo " brew install pandoc" >&2 + exit 1 fi # Handle --help @@ -36,25 +36,25 @@ fi # Get input from file or stdin if [[ $# -gt 0 ]]; then - # File argument - if [[ ! -f "$1" ]]; then - echo "error: file not found: $1" >&2 - exit 1 - fi - input=$(cat "$1") + # File argument + if [[ ! -f "$1" ]]; then + echo "error: file not found: $1" >&2 + exit 1 + fi + input=$(cat "$1") else - # Stdin - if [[ -t 0 ]]; then - echo "error: no input provided (pass a file or pipe markdown)" >&2 - usage 1 - fi - input=$(cat) + # Stdin + if [[ -t 0 ]]; then + echo "error: no input provided (pass a file or pipe markdown)" >&2 + usage 1 + fi + input=$(cat) fi # Check for empty input if [[ -z "$input" ]]; then - echo "error: input is empty" >&2 - exit 1 + echo "error: input is empty" >&2 + exit 1 fi # Convert and copy diff --git a/bin/tmux-command-palette b/bin/tmux-command-palette index 03d7ac5..4d38e07 100755 --- a/bin/tmux-command-palette +++ b/bin/tmux-command-palette @@ -4,9 +4,9 @@ # # Colors (catppuccin mocha) -blue='\033[38;2;137;180;250m' # #89b4fa -green='\033[38;2;166;227;161m' # #a6e3a1 -dim='\033[38;2;108;112;134m' # #6c7086 (overlay0) +blue='\033[38;2;137;180;250m' # #89b4fa +green='\033[38;2;166;227;161m' # #a6e3a1 +dim='\033[38;2;108;112;134m' # #6c7086 (overlay0) reset='\033[0m' # Format: colored key on left, description on right, with consistent alignment @@ -26,11 +26,11 @@ formatted=$(tmux list-keys -N | awk -v blue="$blue" -v green="$green" -v dim="$d }') target=$(echo -e "$formatted" | fzf-tmux -p 80%,60% \ - --ansi \ - --no-sort \ - --border-label ' command palette ' \ - --prompt ' ' \ - --bind 'tab:down,btab:up') + --ansi \ + --no-sort \ + --border-label ' command palette ' \ + --prompt ' ' \ + --bind 'tab:down,btab:up') [[ -z "$target" ]] && exit @@ -44,7 +44,7 @@ full=$(tmux list-keys -T prefix | grep -F " ${key} " | head -1) # If not found in prefix table, try other tables if [[ -z "$full" ]]; then - full=$(tmux list-keys | grep -F " ${key} " | head -1) + full=$(tmux list-keys | grep -F " ${key} " | head -1) fi [[ -z "$full" ]] && exit @@ -54,7 +54,7 @@ cmd=$(echo "$full" | awk '{ $1=$2=$3=$4=""; print $0 }' | sed 's/^ *//') # Handle copy-mode bindings if [[ "$cmd" == *"copy-mode"* ]] && [[ "$full" != *"prefix"* ]]; then - tmux copy-mode + tmux copy-mode fi tmux $cmd diff --git a/claude/README.md b/claude/README.md index 67d0164..2018313 100644 --- a/claude/README.md +++ b/claude/README.md @@ -74,16 +74,16 @@ Stack files hold per-topic permissions and sandbox arrays (no scalars): "permissions": { "allow": [], "ask": [], - "deny": [] + "deny": [], }, "sandbox": { "network": { - "allowedHosts": [] + "allowedHosts": [], }, "filesystem": { - "allowWrite": [] - } - } + "allowWrite": [], + }, + }, } ``` @@ -110,19 +110,17 @@ Create `claude/stacks/foo.jsonc`: { // Foo tool "permissions": { - "allow": [ - "Bash(foo:*)" - ] + "allow": ["Bash(foo:*)"], }, // Optional: sandbox config "sandbox": { "network": { - "allowedHosts": ["foo.example.com"] + "allowedHosts": ["foo.example.com"], }, "filesystem": { - "allowWrite": ["~/.foo"] - } - } + "allowWrite": ["~/.foo"], + }, + }, } ``` @@ -176,11 +174,11 @@ claude-permissions cleanup --force ### Permission Lists -| List | Behavior | -| ------- | ------------------------------------------------------------------ | -| `allow` | Always permitted without prompting | +| List | Behavior | +| ------- | ------------------------------------------------------------------- | +| `allow` | Always permitted without prompting | | `ask` | Always prompts for confirmation (useful for destructive operations) | -| `deny` | Always blocked | +| `deny` | Always blocked | ### Permission Format @@ -238,5 +236,5 @@ jq '.sandbox.network.allowedHosts' ~/.claude/settings.json jq '.sandbox.filesystem.allowWrite' ~/.claude/settings.json # Verify claudeconfig.sh output -./claudeconfig.sh # Watch for "Loaded base role", "Merged X stack" messages +./claudeconfig.sh # Watch for "Loaded base role", "Merged X stack" messages ``` diff --git a/claude/stacks/beans.jsonc b/claude/stacks/beans.jsonc index d62b62d..c3e26d7 100644 --- a/claude/stacks/beans.jsonc +++ b/claude/stacks/beans.jsonc @@ -7,11 +7,8 @@ "Bash(beans query:*)", "Bash(beans show:*)", "Bash(beans update:*)", - "Bash(beans:*)" + "Bash(beans:*)", ], - "ask": [ - "Bash(beans archive:*)", - "Bash(beans delete:*)" - ] - } + "ask": ["Bash(beans archive:*)", "Bash(beans delete:*)"], + }, } diff --git a/claude/stacks/colima.jsonc b/claude/stacks/colima.jsonc index a0ae976..40423b8 100644 --- a/claude/stacks/colima.jsonc +++ b/claude/stacks/colima.jsonc @@ -12,16 +12,14 @@ "Bash(colima stop:*)", "Bash(colima template:*)", "Bash(colima version:*)", - "Bash(colima:*)" - ] + "Bash(colima:*)", + ], }, // source: agent-safehouse "sandbox": { "filesystem": { - "allowWrite": [ - "~/.colima" - ] - } - } + "allowWrite": ["~/.colima"], + }, + }, } diff --git a/claude/stacks/datadog.jsonc b/claude/stacks/datadog.jsonc index fd3231a..547c59a 100644 --- a/claude/stacks/datadog.jsonc +++ b/claude/stacks/datadog.jsonc @@ -1,10 +1,7 @@ { "sandbox": { "network": { - "allowedHosts": [ - "app.datadoghq.com", - "api.datadoghq.com" - ] + "allowedHosts": ["app.datadoghq.com", "api.datadoghq.com"], }, - } + }, } diff --git a/claude/stacks/docs.jsonc b/claude/stacks/docs.jsonc index 72931b4..602f28b 100644 --- a/claude/stacks/docs.jsonc +++ b/claude/stacks/docs.jsonc @@ -1,18 +1,12 @@ { // Reference documentation sites "permissions": { - "allow": [ - "WebFetch(domain:karafka.io)", - "WebFetch(domain:lima-vm.io)" - ] + "allow": ["WebFetch(domain:karafka.io)", "WebFetch(domain:lima-vm.io)"], }, // source: agent-safehouse "sandbox": { "network": { - "allowedHosts": [ - "karafka.io", - "lima-vm.io" - ] - } - } + "allowedHosts": ["karafka.io", "lima-vm.io"], + }, + }, } diff --git a/claude/stacks/dotslash.jsonc b/claude/stacks/dotslash.jsonc index b92dc96..bae4da7 100644 --- a/claude/stacks/dotslash.jsonc +++ b/claude/stacks/dotslash.jsonc @@ -1,16 +1,12 @@ { // DotSlash tool launcher (Meta) "permissions": { - "allow": [ - "Bash(dotslash:*)" - ] + "allow": ["Bash(dotslash:*)"], }, "sandbox": { "filesystem": { - "allowWrite": [ - "~/Library/Caches/dotslash" - ] - } - } + "allowWrite": ["~/Library/Caches/dotslash"], + }, + }, } diff --git a/claude/stacks/git.jsonc b/claude/stacks/git.jsonc index be065c5..110bc19 100644 --- a/claude/stacks/git.jsonc +++ b/claude/stacks/git.jsonc @@ -61,7 +61,7 @@ "Bash(git tag:*)", // hk git hooks tool (from old web.json) - "WebFetch(domain:hk.jdx.dev)" + "WebFetch(domain:hk.jdx.dev)", ], // Operations that warrant confirmation @@ -70,7 +70,7 @@ "Bash(git clean -fdx:*)", "Bash(git push --force:*)", "Bash(git push -f:*)", - "Bash(git reset --hard:*)" + "Bash(git reset --hard:*)", ], // Destructive operations that should never be auto-allowed @@ -82,16 +82,14 @@ "Bash(git push --force upstream main:*)", "Bash(git push --force upstream master:*)", "Bash(git push -f upstream main:*)", - "Bash(git push -f upstream master:*)" - ] + "Bash(git push -f upstream master:*)", + ], }, // source: agent-safehouse "sandbox": { "network": { - "allowedHosts": [ - "hk.jdx.dev" - ] - } - } + "allowedHosts": ["hk.jdx.dev"], + }, + }, } diff --git a/claude/stacks/go.jsonc b/claude/stacks/go.jsonc index 2283399..a67a1b3 100644 --- a/claude/stacks/go.jsonc +++ b/claude/stacks/go.jsonc @@ -26,8 +26,8 @@ "Bash(goreleaser build:*)", "Bash(goreleaser check:*)", "Bash(goreleaser release:*)", - "Bash(goreleaser:*)" - ] + "Bash(goreleaser:*)", + ], }, // source: agent-safehouse @@ -40,8 +40,8 @@ "~/.config/go", "~/.cache/golangci-lint", "~/Library/Caches/golangci-lint", - "~/.local/share/go" - ] - } - } + "~/.local/share/go", + ], + }, + }, } diff --git a/claude/stacks/mcp.jsonc b/claude/stacks/mcp.jsonc index d43e9e2..c7ee9cd 100644 --- a/claude/stacks/mcp.jsonc +++ b/claude/stacks/mcp.jsonc @@ -6,10 +6,8 @@ "mcp__MCPProxy__call_tool_write", "mcp__MCPProxy__read_cache", "mcp__MCPProxy__retrieve_tools", - "mcp__MCPProxy__upstream_servers" + "mcp__MCPProxy__upstream_servers", ], - "ask": [ - "mcp__MCPProxy__call_tool_destructive" - ] - } + "ask": ["mcp__MCPProxy__call_tool_destructive"], + }, } diff --git a/claude/stacks/mise.jsonc b/claude/stacks/mise.jsonc index 14eb076..c432ca7 100644 --- a/claude/stacks/mise.jsonc +++ b/claude/stacks/mise.jsonc @@ -19,17 +19,14 @@ "Bash(mise use:*)", "Bash(mise which:*)", // From old web.json - "WebFetch(domain:mise.jdx.dev)" - ] + "WebFetch(domain:mise.jdx.dev)", + ], }, // source: agent-safehouse "sandbox": { "network": { - "allowedHosts": [ - "mise.jdx.dev", - "mise-versions.jdx.dev" - ] + "allowedHosts": ["mise.jdx.dev", "mise-versions.jdx.dev"], }, "filesystem": { "allowWrite": [ @@ -37,8 +34,8 @@ "~/.config/mise", "~/.local/state/mise", "~/.cache/mise", - "~/Library/Caches/mise" - ] - } - } + "~/Library/Caches/mise", + ], + }, + }, } diff --git a/claude/stacks/ruby.jsonc b/claude/stacks/ruby.jsonc index ea51179..e49eb21 100644 --- a/claude/stacks/ruby.jsonc +++ b/claude/stacks/ruby.jsonc @@ -43,8 +43,8 @@ "Bash(rake:*)", "Bash(rspec:*)", "Bash(ruby --version)", - "Bash(ruby:*)" - ] + "Bash(ruby:*)", + ], }, // source: agent-safehouse @@ -57,8 +57,8 @@ "~/.cache/rubocop_cache", "~/.cache/rubygems", "~/Library/Caches/bundle", - "~/.rbenv" - ] - } - } + "~/.rbenv", + ], + }, + }, } diff --git a/claude/stacks/shell.jsonc b/claude/stacks/shell.jsonc index 1b465d2..3f0ebff 100644 --- a/claude/stacks/shell.jsonc +++ b/claude/stacks/shell.jsonc @@ -79,20 +79,18 @@ "Bash(xz:*)", "Bash(yamllint:*)", "Bash(yes:*)", - "Bash(zip:*)" + "Bash(zip:*)", ], "ask": [ "Bash(claude-permissions cleanup --force)", - "Bash(claude-permissions cleanup -f)" - ] + "Bash(claude-permissions cleanup -f)", + ], }, // source: agent-safehouse "sandbox": { "network": { - "allowedHosts": [ - "formulae.brew.sh" - ] - } - } + "allowedHosts": ["formulae.brew.sh"], + }, + }, } diff --git a/config/fitout/profiles/browsing.toml b/config/fitout/profiles/browsing.toml index 64cc05e..f655948 100644 --- a/config/fitout/profiles/browsing.toml +++ b/config/fitout/profiles/browsing.toml @@ -1,6 +1,4 @@ # Browsing profile - opt-in for browser automation # Use when you need Chrome-based fetching/testing -plugins = [ - "superpowers-chrome@superpowers-marketplace", -] +plugins = ["superpowers-chrome@superpowers-marketplace"] diff --git a/config/fitout/profiles/python.toml b/config/fitout/profiles/python.toml index fcb0ef7..6b4c4fa 100644 --- a/config/fitout/profiles/python.toml +++ b/config/fitout/profiles/python.toml @@ -1,6 +1,4 @@ # Python profile - opt-in for Python LSP intelligence # Type checking, diagnostics via Pyright -plugins = [ - "pyright-lsp@claude-plugins-official", -] +plugins = ["pyright-lsp@claude-plugins-official"] diff --git a/config/fitout/profiles/rust.toml b/config/fitout/profiles/rust.toml index 6383e3d..9254a64 100644 --- a/config/fitout/profiles/rust.toml +++ b/config/fitout/profiles/rust.toml @@ -1,6 +1,4 @@ # Rust profile - opt-in for Rust LSP intelligence # Type checking, diagnostics via rust-analyzer -plugins = [ - "rust-analyzer-lsp@claude-plugins-official", -] +plugins = ["rust-analyzer-lsp@claude-plugins-official"] diff --git a/config/fitout/profiles/superpowers-lab.toml b/config/fitout/profiles/superpowers-lab.toml index e719c89..f875fd0 100644 --- a/config/fitout/profiles/superpowers-lab.toml +++ b/config/fitout/profiles/superpowers-lab.toml @@ -1,6 +1,4 @@ # Lab profile - opt-in for experimental features # Contains experimental skills and tools -plugins = [ - "superpowers-lab@superpowers-marketplace", -] +plugins = ["superpowers-lab@superpowers-marketplace"] diff --git a/config/fitout/profiles/superpowers.toml b/config/fitout/profiles/superpowers.toml index d8b74b2..3ec5c9c 100644 --- a/config/fitout/profiles/superpowers.toml +++ b/config/fitout/profiles/superpowers.toml @@ -1,6 +1,4 @@ # Superpowers profile - opt-in for workflow discipline # Brainstorming, TDD, systematic debugging, plan execution -plugins = [ - "superpowers@superpowers-marketplace", -] +plugins = ["superpowers@superpowers-marketplace"] diff --git a/config/fitout/profiles/typescript.toml b/config/fitout/profiles/typescript.toml index 1ceed1b..757e564 100644 --- a/config/fitout/profiles/typescript.toml +++ b/config/fitout/profiles/typescript.toml @@ -1,6 +1,4 @@ # TypeScript profile - opt-in for TypeScript LSP intelligence # Type checking, diagnostics, go-to-definition via tsserver -plugins = [ - "typescript-lsp@claude-plugins-official", -] +plugins = ["typescript-lsp@claude-plugins-official"] diff --git a/config/gh/config.yml b/config/gh/config.yml index daba8c5..4920808 100644 --- a/config/gh/config.yml +++ b/config/gh/config.yml @@ -8,8 +8,8 @@ prompt: enabled pager: # Aliases allow you to create nicknames for gh commands aliases: - co: pr checkout - default-branch: '!gh api repos/:owner/:repo | jq --raw-output .default_branch' - sup: pr checks - sw: pr checkout + co: pr checkout + default-branch: '!gh api repos/:owner/:repo | jq --raw-output .default_branch' + sup: pr checks + sw: pr checkout version: '1' diff --git a/doc/adr/0027-tmux-command-palette.md b/doc/adr/0027-tmux-command-palette.md index 59acb5f..7391659 100644 --- a/doc/adr/0027-tmux-command-palette.md +++ b/doc/adr/0027-tmux-command-palette.md @@ -38,11 +38,13 @@ Using forks (pinned to `add-key-binding-notes` branch) until upstream merges. ### Alternatives Considered 1. **tmux-which-key** (alexwforsythe/tmux-which-key) + - Manually curated menu system, not auto-discovered from actual bindings - Requires maintaining a separate `config.yaml` in sync with real bindings - Rejected: doesn't solve the discovery problem 2. **tmux-menus** (jaclu/tmux-menus) + - Rich built-in menu system using tmux's native `display-menu` - No fuzzy finding, static navigation only - Rejected: not a command palette diff --git a/doc/adr/0028-safe-symlink-function.md b/doc/adr/0028-safe-symlink-function.md index c8df0b2..5950add 100644 --- a/doc/adr/0028-safe-symlink-function.md +++ b/doc/adr/0028-safe-symlink-function.md @@ -14,7 +14,7 @@ The original implementation had two problems: 1. **Silent data loss.** If the target was a real file or directory (not a symlink), `link()` used `ln -Ff -s` to silently replace it. Tools like ccstatusline create their own config directories at runtime with user customizations. Running `symlinks.sh` would nuke those without warning. -2. **Nested symlink bug.** If the target was an existing directory and `-F` didn't behave as expected, `ln -s source target/` would create a symlink *inside* the directory (e.g. `~/.config/fish/fish`) instead of replacing it. This is a well-known `ln` footgun. +2. **Nested symlink bug.** If the target was an existing directory and `-F` didn't behave as expected, `ln -s source target/` would create a symlink _inside_ the directory (e.g. `~/.config/fish/fish`) instead of replacing it. This is a well-known `ln` footgun. The function also had no way to run non-interactively, making it unsuitable for CI or scripted installs. @@ -23,16 +23,19 @@ The function also had no way to run non-interactively, making it unsuitable for Rewrite `link()` with four explicit cases and two new helpers: **Cases:** + - Target doesn't exist: create symlink (plain `ln -s`, no flags) - Target is a correct symlink: no-op - Target is a wrong symlink: prompt to repoint (or auto-repoint with `--yes`) - Target is a real file or directory: prompt to backup and replace (or auto with `--yes`) **Helpers:** + - `confirm()` centralizes prompt logic. Returns yes immediately in auto-yes mode, prompts interactively otherwise. - `backup_path()` generates timestamped backup names (`target.backup.20260325-143022`) with collision counter. **Interactivity modes:** + - Interactive (default): prompts for anything that isn't already correct - Auto-yes (`--yes`/`-y`): auto-answers yes to all prompts, safe for scripts - Non-interactive without `--yes`: script exits with error immediately @@ -42,6 +45,7 @@ The nested symlink bug is prevented structurally: real files/dirs are `mv`'d out ### Alternatives Considered 1. **Warn and skip (like claudeconfig.sh does)** + - Simpler, no backup logic needed - Rejected: requires manual cleanup, annoying on repeated runs diff --git a/doc/adr/0029-tmux-local-config-overrides.md b/doc/adr/0029-tmux-local-config-overrides.md index b7078bb..cf1b87e 100644 --- a/doc/adr/0029-tmux-local-config-overrides.md +++ b/doc/adr/0029-tmux-local-config-overrides.md @@ -31,10 +31,12 @@ Catppuccin's tmux plugin reads `@catppuccin_flavor` at plugin init time, but `so ### Alternatives Considered 1. **Conditional logic in .tmux.conf based on hostname** + - `if-shell '[ "$(hostname)" = "myserver" ]' 'set ...'` - Rejected: puts machine-specific knowledge in the committed config. Grows into a mess. 2. **Separate .tmux.conf per machine** + - Rejected: duplication. The configs are 95% identical. 3. **Environment variable toggles** diff --git a/doc/plans/2026-03-25-claudeconfig-stacks-refactor.md b/doc/plans/2026-03-25-claudeconfig-stacks-refactor.md index e1ef510..4f79ca6 100644 --- a/doc/plans/2026-03-25-claudeconfig-stacks-refactor.md +++ b/doc/plans/2026-03-25-claudeconfig-stacks-refactor.md @@ -145,18 +145,22 @@ All config below was captured from the live `~/.claude/settings.json` (source: a ### roles/base.jsonc Settings: + - `statusLine`, `includeCoAuthoredBy` Sandbox scalars: + - `enabled: true` - `autoAllowBashIfSandboxed: true` - `enableWeakerNetworkIsolation: true` Sandbox network: + - `api.anthropic.com` (claude infrastructure) - `code.claude.com` (claude docs) Permissions (from current `permissions.json`): + - All base safety `allow`/`ask`/`deny` rules - NEW: `Read(~/.claude/plugins/cache/**)` - From old `web.json`: `WebFetch(domain:code.claude.com)` @@ -164,12 +168,15 @@ Permissions (from current `permissions.json`): ### roles/work.jsonc Settings: + - `awsAuthRefresh`, `env` (AWS/Bedrock config) Permissions (from current `permissions.work.json`): + - `Bash(npx bktide:*)`, `Bash(bundle install)`, etc. Sandbox network: + - `portal.sso.us-west-2.amazonaws.com` (AWS SSO) ### stacks/mise.jsonc @@ -193,6 +200,7 @@ WebFetch permissions (from old `web.json`): `WebFetch(domain:docs.github.com)`, Sandbox network: `registry.npmjs.org` Sandbox filesystem: + - npm: `~/.npm`, `~/.config/npm`, `~/.cache/npm`, `~/.cache/node`, `~/.node-gyp`, `~/.cache/node-gyp`, `~/.config/configstore`, `~/Library/Caches/npm` - pnpm: `~/.config/pnpm`, `~/.pnpm-state`, `~/.pnpm-store`, `~/.local/share/pnpm`, `~/.local/state/pnpm`, `~/Library/pnpm`, `~/Library/Caches/pnpm`, `~/Library/Preferences/pnpm` - yarn: `~/.yarn`, `~/.yarnrc`, `~/.yarnrc.yml`, `~/.config/yarn`, `~/.cache/yarn`, `~/Library/Caches/Yarn` diff --git a/doc/plans/2026-03-26-claudeconfig-stacks-implementation.md b/doc/plans/2026-03-26-claudeconfig-stacks-implementation.md index 0f79ad0..dd8c9c9 100644 --- a/doc/plans/2026-03-26-claudeconfig-stacks-implementation.md +++ b/doc/plans/2026-03-26-claudeconfig-stacks-implementation.md @@ -74,11 +74,13 @@ Before changing anything, review the live `~/.claude/settings.json` to catch any drift since the spec was written. New permissions, hosts, write paths, or settings may have been added by Claude Code sessions or manual edits. These need to be accounted for in the role/stack files. **Files:** + - Read: `~/.claude/settings.json` - [ ] **Step 1: Review live settings.json for drift** Compare the live file against what the spec expects. Check for: + - New permissions in `allow`/`ask`/`deny` not in any source `permissions.*.json` file - New network hosts in `sandbox.network.allowedHosts` beyond the 15 in the spec - New filesystem write paths in `sandbox.filesystem.allowWrite` beyond the 64 in the spec @@ -90,12 +92,13 @@ Compare the live file against what the spec expects. Check for: echo "Allow: $(jq '.permissions.allow | length' ~/.claude/settings.json)" echo "Ask: $(jq '.permissions.ask | length' ~/.claude/settings.json)" echo "Deny: $(jq '.permissions.deny | length' ~/.claude/settings.json)" -echo "Hosts: $(jq '.sandbox.network.allowedHosts | length' ~/.claude/settings.json 2>/dev/null || echo 'none')" -echo "Write paths: $(jq '.sandbox.filesystem.allowWrite | length' ~/.claude/settings.json 2>/dev/null || echo 'none')" +echo "Hosts: $(jq '.sandbox.network.allowedHosts | length' ~/.claude/settings.json 2> /dev/null || echo 'none')" +echo "Write paths: $(jq '.sandbox.filesystem.allowWrite | length' ~/.claude/settings.json 2> /dev/null || echo 'none')" echo "Top-level keys: $(jq 'keys[]' ~/.claude/settings.json)" ``` If any drift is found, decide for each item: + - Which role or stack file should it go in? - Is it a local_key that should be preserved but not managed? - Is it stale and should be dropped? @@ -128,6 +131,7 @@ These counts become the baseline for Task 6 verification. ### Task 2: Create role files **Files:** + - Create: `claude/roles/base.jsonc` - Create: `claude/roles/personal.jsonc` - Create: `claude/roles/work.jsonc` @@ -156,7 +160,7 @@ The file should have this structure (abbreviated, full content from source files "statusLine": { "type": "command", "command": "npx -y ccstatusline@latest", - "padding": 0 + "padding": 0, }, "includeCoAuthoredBy": true, @@ -167,14 +171,14 @@ The file should have this structure (abbreviated, full content from source files // NEW: plugin cache read access "Read(~/.claude/plugins/cache/**)", // From old web.json - "WebFetch(domain:code.claude.com)" + "WebFetch(domain:code.claude.com)", ], "ask": [ // ... all entries from permissions.json .ask ... ], "deny": [ // ... all entries from permissions.json .deny ... - ] + ], }, // Sandbox: base config (source: agent-safehouse) @@ -184,11 +188,11 @@ The file should have this structure (abbreviated, full content from source files "enableWeakerNetworkIsolation": true, "network": { "allowedHosts": [ - "api.anthropic.com", // claude infrastructure - "code.claude.com" // claude docs - ] - } - } + "api.anthropic.com", // claude infrastructure + "code.claude.com", // claude docs + ], + }, + }, } ``` @@ -225,18 +229,18 @@ Merge `settings.work.json` + `permissions.work.json`. Add SSO network host. Use "Bash(bundle install)", "Bash(npx bktide build:*)", "Bash(npx bktide)", - "Bash(npx bktide:*)" - ] + "Bash(npx bktide:*)", + ], }, // Work-specific sandbox (source: agent-safehouse) "sandbox": { "network": { "allowedHosts": [ - "portal.sso.us-west-2.amazonaws.com" // AWS SSO - ] - } - } + "portal.sso.us-west-2.amazonaws.com", // AWS SSO + ], + }, + }, } ``` @@ -260,6 +264,7 @@ Adds Read(~/.claude/plugins/cache/**) to base allow list." These stacks carry forward existing permissions unchanged, just wrapped in the new schema. No sandbox config. **Files:** + - Create: `claude/stacks/beans.jsonc` - Create: `claude/stacks/mcp.jsonc` - Create: `claude/stacks/skills.jsonc` @@ -284,13 +289,10 @@ Read `permissions.beans.json`, wrap in `{"permissions": {...}}`: "Bash(beans query:*)", "Bash(beans show:*)", "Bash(beans update:*)", - "Bash(beans:*)" + "Bash(beans:*)", ], - "ask": [ - "Bash(beans archive:*)", - "Bash(beans delete:*)" - ] - } + "ask": ["Bash(beans archive:*)", "Bash(beans delete:*)"], + }, } ``` @@ -307,12 +309,10 @@ Read `permissions.mcp.json`, wrap in `{"permissions": {...}}`: "mcp__MCPProxy__call_tool_write", "mcp__MCPProxy__read_cache", "mcp__MCPProxy__retrieve_tools", - "mcp__MCPProxy__upstream_servers" + "mcp__MCPProxy__upstream_servers", ], - "ask": [ - "mcp__MCPProxy__call_tool_destructive" - ] - } + "ask": ["mcp__MCPProxy__call_tool_destructive"], + }, } ``` @@ -334,6 +334,7 @@ git commit -m "feat: create permissions-only stack files (beans, mcp, skills)" These stacks carry forward existing permissions AND add new sandbox config from agent-safehouse. **Files:** + - Create: `claude/stacks/buildkite.jsonc` (NEW) - Create: `claude/stacks/colima.jsonc` - Create: `claude/stacks/docker.jsonc` @@ -358,12 +359,12 @@ For each stack: read the source permissions file, wrap in the new schema with `{ // Buildkite CI (source: agent-safehouse) "sandbox": { "network": { - "allowedHosts": ["buildkite.com"] + "allowedHosts": ["buildkite.com"], }, "filesystem": { - "allowWrite": ["~/.local/state/bktide"] - } - } + "allowWrite": ["~/.local/state/bktide"], + }, + }, } ``` @@ -373,20 +374,14 @@ For each stack: read the source permissions file, wrap in the new schema with `{ { // Reference documentation sites "permissions": { - "allow": [ - "WebFetch(domain:karafka.io)", - "WebFetch(domain:lima-vm.io)" - ] + "allow": ["WebFetch(domain:karafka.io)", "WebFetch(domain:lima-vm.io)"], }, // source: agent-safehouse "sandbox": { "network": { - "allowedHosts": [ - "karafka.io", - "lima-vm.io" - ] - } - } + "allowedHosts": ["karafka.io", "lima-vm.io"], + }, + }, } ``` @@ -435,6 +430,7 @@ Distributes web.json WebFetch permissions to their topic stacks." ### Task 5: Rewrite claudeconfig.sh merge logic **Files:** + - Modify: `claudeconfig.sh` (the `generate_settings()` function, lines 70-182) - [ ] **Step 1: Read current `claudeconfig.sh` fully** @@ -476,6 +472,7 @@ extraKnownMarketplaces." ### Task 6: Verify output matches **Files:** + - Read: `~/.claude/settings.json` (generated output) - Read: `/tmp/claude-settings-before-refactor.json` (saved in Task 1) @@ -495,6 +492,7 @@ diff <(jq -S . /tmp/claude-settings-before-refactor.json) <(jq -S . ~/.claude/se ``` Expected diff: + - The new `Read(~/.claude/plugins/cache/**)` permission (added to base role) - Network `allowedHosts` array now populated (these are NEW in the generated output; previously they were either absent or came from Claude Code's own sandbox enforcement, not user settings) - Ordering differences (dedup + sort) @@ -521,6 +519,7 @@ jq '{ ``` Expected: + - `allow_count`: baseline + 1 (the new `Read(~/.claude/plugins/cache/**)`) - `ask_count`: same as baseline - `deny_count`: same as baseline @@ -562,6 +561,7 @@ Expected: No output (all source permissions present in generated output). Only do this after Task 6 verification passes. **Files:** + - Delete: All `claude/permissions.*.json`, `claude/permissions.*.jsonc`, `claude/settings.*.json` - [ ] **Step 1: Remove old permission files** @@ -594,6 +594,7 @@ These are replaced by claude/roles/ and claude/stacks/ directories." ### Task 8: Update README.md **Files:** + - Modify: `claude/README.md` - [ ] **Step 1: Read current README** @@ -603,6 +604,7 @@ Read `claude/README.md` to understand structure and what to preserve. - [ ] **Step 2: Rewrite README for new structure** Update to cover: + - New `roles/` + `stacks/` architecture with tree diagram - Schema: `permissions` + `sandbox` shape for both roles and stacks - All files use JSONC, comments encouraged for provenance (`// source: agent-safehouse`) @@ -624,6 +626,7 @@ git commit -m "docs: update claude/README.md for roles/stacks structure" ### Task 9: Fix spec and mark implemented **Files:** + - Modify: `docs/plans/2026-03-25-claudeconfig-stacks-refactor.md` - [ ] **Step 1: Update spec status to "Implemented"** diff --git a/doc/superpowers/plans/2026-03-25-safe-link-function.md b/doc/superpowers/plans/2026-03-25-safe-link-function.md index 66ff0b6..1f2e1f1 100644 --- a/doc/superpowers/plans/2026-03-25-safe-link-function.md +++ b/doc/superpowers/plans/2026-03-25-safe-link-function.md @@ -14,18 +14,19 @@ ## File Map -| File | Action | Responsibility | -|------|--------|----------------| -| `functions.sh` | Modify lines 87-106 | `confirm()`, `backup_path()`, rewritten `link()` | -| `symlinks.sh` | Modify lines 1-14 | `--yes`/`-y` flag parsing, interactivity guard | -| `install.sh` | Modify lines 1-6 | `--yes`/`-y` flag parsing, interactivity guard | -| `scripts/test-link.sh` | Create | Manual verification script | +| File | Action | Responsibility | +| ---------------------- | ------------------- | ------------------------------------------------ | +| `functions.sh` | Modify lines 87-106 | `confirm()`, `backup_path()`, rewritten `link()` | +| `symlinks.sh` | Modify lines 1-14 | `--yes`/`-y` flag parsing, interactivity guard | +| `install.sh` | Modify lines 1-6 | `--yes`/`-y` flag parsing, interactivity guard | +| `scripts/test-link.sh` | Create | Manual verification script | --- ### Task 1: Add `backup_path()` helper to `functions.sh` **Files:** + - Modify: `functions.sh:86` (insert before `link()`) - [ ] **Step 1: Add `backup_path()` function** @@ -64,6 +65,7 @@ git commit -m "feat(link): add backup_path() helper for timestamped backups" ### Task 2: Add `confirm()` helper to `functions.sh` **Files:** + - Modify: `functions.sh:86` (insert before `backup_path()`) - [ ] **Step 1: Add `confirm()` function** @@ -102,6 +104,7 @@ git commit -m "feat(link): add confirm() helper for interactive/auto-yes prompts ### Task 3: Rewrite `link()` in `functions.sh` **Files:** + - Modify: `functions.sh:87-106` (the current `link()` function, line numbers will have shifted from Tasks 1-2) - [ ] **Step 1: Replace the `link()` function** @@ -171,6 +174,7 @@ nested symlink creation." ### Task 4: Add `--yes`/`-y` flag and interactivity guard to `symlinks.sh` **Files:** + - Modify: `symlinks.sh:1-14` - [ ] **Step 1: Add flag parsing and guard after the shebang block** @@ -188,7 +192,7 @@ export DIR # Parse flags for arg in "$@"; do case "$arg" in - --yes|-y) export DOTPICKLES_YES=1 ;; + --yes | -y) export DOTPICKLES_YES=1 ;; esac done @@ -225,6 +229,7 @@ git commit -m "feat(symlinks): add --yes/-y flag and interactivity guard" ### Task 5: Add `--yes`/`-y` flag and interactivity guard to `install.sh` **Files:** + - Modify: `install.sh:1-6` - [ ] **Step 1: Add flag parsing after the shebang block** @@ -235,7 +240,7 @@ Insert after line 4 (`set -eo pipefail`) and before line 6 (`DIR=...`): # Parse flags for arg in "$@"; do case "$arg" in - --yes|-y) export DOTPICKLES_YES=1 ;; + --yes | -y) export DOTPICKLES_YES=1 ;; esac done ``` @@ -267,6 +272,7 @@ git commit -m "feat(install): add --yes/-y flag and interactivity guard" ### Task 6: Write verification script and manually test **Files:** + - Create: `scripts/test-link.sh` - [ ] **Step 1: Create the verification script** @@ -287,7 +293,7 @@ export DIR for arg in "$@"; do case "$arg" in - --yes|-y) export DOTPICKLES_YES=1 ;; + --yes | -y) export DOTPICKLES_YES=1 ;; esac done @@ -335,7 +341,7 @@ echo "precious data" > "$TEST_DIR/result4/config.json" link "source/thing" "$TEST_DIR/result4" if [ -L "$TEST_DIR/result4" ]; then echo "PASS: directory replaced with symlink" - backup=$(ls -d "$TEST_DIR"/result4.backup.* 2>/dev/null | head -1) + backup=$(ls -d "$TEST_DIR"/result4.backup.* 2> /dev/null | head -1) if [ -n "$backup" ] && [ -f "$backup/config.json" ]; then echo "PASS: backup exists with original content" else @@ -352,7 +358,7 @@ echo "important stuff" > "$TEST_DIR/result5" link "source/thing" "$TEST_DIR/result5" if [ -L "$TEST_DIR/result5" ]; then echo "PASS: file replaced with symlink" - backup=$(ls "$TEST_DIR"/result5.backup.* 2>/dev/null | head -1) + backup=$(ls "$TEST_DIR"/result5.backup.* 2> /dev/null | head -1) if [ -n "$backup" ]; then echo "PASS: backup exists" else diff --git a/home/.bash_profile b/home/.bash_profile index adf5d09..bd3111f 100644 --- a/home/.bash_profile +++ b/home/.bash_profile @@ -94,4 +94,3 @@ fi # Added by LM Studio CLI (lms) export PATH="$PATH:/Users/josh.nichols/.lmstudio/bin" # End of LM Studio CLI section - diff --git a/home/.bashrc b/home/.bashrc index 49685f8..2b17722 100644 --- a/home/.bashrc +++ b/home/.bashrc @@ -123,4 +123,3 @@ fi # Added by LM Studio CLI (lms) export PATH="$PATH:/Users/josh.nichols/.lmstudio/bin" # End of LM Studio CLI section - diff --git a/home/.zshrc b/home/.zshrc index ff302ab..b41fa73 100644 --- a/home/.zshrc +++ b/home/.zshrc @@ -108,4 +108,3 @@ fi # Added by LM Studio CLI (lms) export PATH="$PATH:/Users/josh.nichols/.lmstudio/bin" # End of LM Studio CLI section - diff --git a/package-lock.json b/package-lock.json index 1eb20ac..e7f3463 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "dotfiles", + "name": "fix-ci-restore-scripts", "lockfileVersion": 3, "requires": true, "packages": { @@ -8,9 +8,9 @@ "adr-tools": "^2.0.4", "prettier": "^3.4.1", "prettier-plugin-ignored": "^1.0.0", - "prettier-plugin-ini": "^1.3.0", "prettier-plugin-sh": "^0.14.0", - "prettier-plugin-toml": "^2.0.1" + "prettier-plugin-toml": "^2.0.1", + "typescript": "^5.9.3" } }, "node_modules/@taplo/core": { @@ -1039,16 +1039,6 @@ "prettier": "3.x" } }, - "node_modules/prettier-plugin-ini": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-ini/-/prettier-plugin-ini-1.3.0.tgz", - "integrity": "sha512-kX1KnPwITCTtYV0MTBZQKuFLxcNcbHOS9r7Ta1jHLLvRvZRTzHHqX0MEZMrCkedjLuxpQSgzr4aC5IRd+R3FPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "prettier": ">=3.0.0-alpha.3" - } - }, "node_modules/prettier-plugin-sh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/prettier-plugin-sh/-/prettier-plugin-sh-0.14.0.tgz", @@ -1517,6 +1507,20 @@ "dev": true, "license": "MIT" }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index dd4f604..9ae5258 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,17 @@ { + "scripts": { + "typecheck": "tsc --noEmit", + "format": "bin/prettier --write .", + "format:check": "bin/prettier --check .", + "lint": "npm run typecheck && npm run format:check", + "test": "npm run lint" + }, "devDependencies": { "adr-tools": "^2.0.4", "prettier": "^3.4.1", "prettier-plugin-ignored": "^1.0.0", "prettier-plugin-sh": "^0.14.0", - "prettier-plugin-toml": "^2.0.1" + "prettier-plugin-toml": "^2.0.1", + "typescript": "^5.9.3" } } diff --git a/scripts/test-link.sh b/scripts/test-link.sh index 5fa2b58..f344866 100755 --- a/scripts/test-link.sh +++ b/scripts/test-link.sh @@ -12,7 +12,7 @@ export DIR for arg in "$@"; do case "$arg" in - --yes|-y) export DOTPICKLES_YES=1 ;; + --yes | -y) export DOTPICKLES_YES=1 ;; esac done @@ -60,7 +60,7 @@ echo "precious data" > "$TEST_DIR/result4/config.json" link "source/thing" "$TEST_DIR/result4" if [ -L "$TEST_DIR/result4" ]; then echo "PASS: directory replaced with symlink" - backup=$(ls -d "$TEST_DIR"/result4.backup.* 2>/dev/null | head -1) + backup=$(ls -d "$TEST_DIR"/result4.backup.* 2> /dev/null | head -1) if [ -n "$backup" ] && [ -f "$backup/config.json" ]; then echo "PASS: backup exists with original content" else @@ -77,7 +77,7 @@ echo "important stuff" > "$TEST_DIR/result5" link "source/thing" "$TEST_DIR/result5" if [ -L "$TEST_DIR/result5" ]; then echo "PASS: file replaced with symlink" - backup=$(ls "$TEST_DIR"/result5.backup.* 2>/dev/null | head -1) + backup=$(ls "$TEST_DIR"/result5.backup.* 2> /dev/null | head -1) if [ -n "$backup" ]; then echo "PASS: backup exists" else diff --git a/scripts/update-symlinks.sh b/scripts/update-symlinks.sh index 0103c73..a068efc 100755 --- a/scripts/update-symlinks.sh +++ b/scripts/update-symlinks.sh @@ -8,46 +8,46 @@ NEW_BASE="$HOME/gt/repos/dotfiles/worktrees/main" DRY_RUN=false if [[ "$1" == "--dry-run" ]]; then - DRY_RUN=true - echo "=== DRY RUN MODE ===" + DRY_RUN=true + echo "=== DRY RUN MODE ===" fi # Find symlinks pointing to old location (excluding backup directories) find_symlinks() { - find "$HOME" -maxdepth 2 -type l 2>/dev/null | while read link; do - # Skip backup directories - if echo "$link" | grep -q "\.backup\." ; then - continue - fi + find "$HOME" -maxdepth 2 -type l 2> /dev/null | while read link; do + # Skip backup directories + if echo "$link" | grep -q "\.backup\."; then + continue + fi - target=$(readlink "$link" 2>/dev/null) - if echo "$target" | grep -q "workspace/dotfiles"; then - echo "$link" - fi - done + target=$(readlink "$link" 2> /dev/null) + if echo "$target" | grep -q "workspace/dotfiles"; then + echo "$link" + fi + done } update_symlink() { - local link="$1" - local old_target=$(readlink "$link") - local new_target=$(echo "$old_target" | sed 's|workspace/dotfiles|gt/repos/dotfiles/worktrees/main|') + local link="$1" + local old_target=$(readlink "$link") + local new_target=$(echo "$old_target" | sed 's|workspace/dotfiles|gt/repos/dotfiles/worktrees/main|') - # Verify new target exists - if [[ ! -e "$new_target" ]]; then - echo "WARNING: Target does not exist: $new_target" - echo " Skipping: $link" - return 1 - fi + # Verify new target exists + if [[ ! -e "$new_target" ]]; then + echo "WARNING: Target does not exist: $new_target" + echo " Skipping: $link" + return 1 + fi - if $DRY_RUN; then - echo "Would update: $link" - echo " From: $old_target" - echo " To: $new_target" - else - rm "$link" - ln -s "$new_target" "$link" - echo "Updated: $link -> $new_target" - fi + if $DRY_RUN; then + echo "Would update: $link" + echo " From: $old_target" + echo " To: $new_target" + else + rm "$link" + ln -s "$new_target" "$link" + echo "Updated: $link -> $new_target" + fi } echo "Finding symlinks pointing to $OLD_BASE..." @@ -60,17 +60,17 @@ echo "Found $count symlinks to update" echo "" if [[ "$count" -eq 0 ]]; then - echo "Nothing to do!" - exit 0 + echo "Nothing to do!" + exit 0 fi for link in $symlinks; do - update_symlink "$link" || true + update_symlink "$link" || true done echo "" if $DRY_RUN; then - echo "Run without --dry-run to apply changes" + echo "Run without --dry-run to apply changes" else - echo "Done! Symlinks updated." + echo "Done! Symlinks updated." fi diff --git a/skills.sh b/skills.sh index 4a454b8..1823ef4 100755 --- a/skills.sh +++ b/skills.sh @@ -12,7 +12,7 @@ echo "🧩 configuring agent skills" mkdir -p "$HOME/.agents" link "agents/.skill-lock.json" "$HOME/.agents/.skill-lock.json" -if ! command -v npx &>/dev/null; then +if ! command -v npx &> /dev/null; then echo " ⚠ npx not found, skipping skill restoration" exit 0 fi