diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index d561c78..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: Bug report -about: Report something that is broken -title: "" -labels: bug -assignees: "" ---- - -## Summary - -## Steps To Reproduce - -1. - -## Expected Behavior - -## Actual Behavior - -## Context - -- Area: -- Command or workflow: -- Local Superpowers spec or plan: diff --git a/.github/ISSUE_TEMPLATE/task.yml b/.github/ISSUE_TEMPLATE/task.yml deleted file mode 100644 index f6d7ff9..0000000 --- a/.github/ISSUE_TEMPLATE/task.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Task -description: Track a feature, enhancement, cleanup, or research item -title: "" -labels: [enhancement] -body: - - type: textarea - id: outcome - attributes: - label: Outcome - description: What should be true when this is done? - validations: - required: true - - type: textarea - id: scope - attributes: - label: Scope - description: What is included, and what is intentionally out of scope? - validations: - required: true - - type: dropdown - id: area - attributes: - label: Area - options: - - APM - - Chezmoi - - CI - - Docs - - Agents - - Editor - validations: - required: true - - type: dropdown - id: priority - attributes: - label: Priority - description: How important is this task for the repo right now? - options: - - High - - Normal - - Low - validations: - required: true - - type: textarea - id: acceptance-checks - attributes: - label: Acceptance Checks - description: List the checks that prove this task is complete. - placeholder: "- [ ] mise lint passes" - validations: - required: true - - type: textarea - id: superpowers-docs - attributes: - label: Superpowers Docs - description: Link the spec or plan under docs/superpowers/ before implementation starts. - placeholder: "Spec: docs/superpowers/specs/YYYY-MM-DD-topic-design.md\nPlan: docs/superpowers/plans/YYYY-MM-DD-topic.md" - validations: - required: true diff --git a/.github/workflows/apm-update.yaml b/.github/workflows/apm-update.yaml deleted file mode 100644 index 468e8f5..0000000 --- a/.github/workflows/apm-update.yaml +++ /dev/null @@ -1,47 +0,0 @@ -name: Update APM Locks - -on: - schedule: - - cron: "0 0 * * 5" # Every Friday at midnight UTC, matching CI schedule - workflow_dispatch: # Allow manual trigger from the Actions tab - -concurrency: - group: ${{ github.workflow }} - cancel-in-progress: true - -permissions: - contents: write - pull-requests: write - -jobs: - refresh_locks: - name: Refresh APM lockfiles - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 - - name: Refresh APM lockfiles - run: mise exec -- bash scripts/refresh-apm-locks.sh - # Gate the refreshed lockfiles here: a PR opened with the default - # GITHUB_TOKEN does not trigger the on: pull_request CI run, so the - # frozen-install check must pass before the PR is opened. - - name: Validate refreshed lockfiles - run: mise exec -- bash scripts/validate-apm.sh all - - name: Open PR if lockfiles changed - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 - with: - commit-message: "chore: refresh APM lockfiles" - branch: chore/apm-lock-refresh - delete-branch: true - title: "Refresh APM lockfiles" - body: | - Automated refresh of committed APM lockfiles. - - Re-resolves each channel ref to its latest SHA and rewrites - `home/.chezmoitemplates/apm/apm.lock.*.yaml`. - - The frozen-install gate ran on these lockfiles before this PR was - opened, since an auto-PR does not trigger the on: pull_request CI run. - - Refs DOT-31 - labels: dependencies diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bf5f7cc..008079a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -25,23 +25,6 @@ jobs: - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 - run: mise lint - validate_apm: - name: Validate APM config (${{ matrix.context }}) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - context: [personal, work] - steps: - - uses: actions/checkout@v6 - - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 - - name: Stub chezmoi config (${{ matrix.context }} context) - uses: ./.github/actions/write-chezmoi-config - with: - context: ${{ matrix.context }} - - name: Frozen install + audit (${{ matrix.context }}) - run: mise exec -- bash scripts/validate-apm.sh ${{ matrix.context }} - actions_lint: name: Lint GitHub Actions workflows runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index af06f02..3930148 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -# APM dependencies -apm_modules/ - .DS_Store .claude .worktrees/ diff --git a/docs/cheatsheets/lazyvim-tmux.md b/docs/cheatsheets/lazyvim-tmux.md deleted file mode 100644 index e044757..0000000 --- a/docs/cheatsheets/lazyvim-tmux.md +++ /dev/null @@ -1,131 +0,0 @@ -# LazyVim and tmux Cheatsheet - -This is a daily reference for this dotfiles setup. In LazyVim, `` is the space key. In tmux, the default prefix is ``. - -## Daily Flow - -1. Start a named tmux session: `tmux new -s dotfiles` -2. Open the repo in Neovim: `nvim .` -3. Split tmux to the right: `%` -4. Run OpenCode in the right pane: `opencode --port` -5. In Neovim, send the current buffer or visual selection to OpenCode: `oa` - -## Find Keys - -| Action | Keys | -| ------------------------- | -------------- | -| Show buffer-local keymaps | `?` | -| Search all keymaps | `sk` | -| Open Lazy plugin UI | `:Lazy` | -| Open health report | `:checkhealth` | -| Cancel a menu or mode | `` | - -## Files And Search - -| Action | Keys | -| ---------------------- | --------------------------------- | -| Find files | `` or `ff` | -| Find config files | `fc` | -| Recent files | `fr` | -| Search text in project | `/` | -| Search help | `sh` | -| Search diagnostics | `sd` | -| Git status | `gs` | - -## Hidden Dotfiles - -LazyVim's picker does not use the shell `FZF_DEFAULT_COMMAND`. Dotfiles and ignored files are toggled inside the picker or explorer. - -| Place | Hidden files | Gitignored files | -| ---------------- | ------------ | ---------------- | -| File picker | `Alt-h` | `Alt-i` | -| Explorer sidebar | `H` | `I` | - -Examples in this repo include `home/.chezmoiscripts/`, `home/.chezmoiignore`, `home/.chezmoiexternal.yaml.tmpl`, and `home/.chezmoi.yaml.tmpl`. Chezmoi source names like `home/dot_zshenv` become dotfiles only after chezmoi applies them. - -## Buffers And Windows - -| Action | Keys | -| --------------------- | -------------------------------------- | -| Pick open buffers | `,` or `fb` | -| Split below | `-` | -| Split right | `\|` | -| Move between windows | `h`, `j`, `k`, `l` | -| Delete current window | `wd` | -| Zoom current window | `wm` | -| Window command helper | `` | - -## Copy From Neovim To OpenCode - -`vim.opt.clipboard = "unnamedplus"` sends Neovim yanks to the macOS clipboard. - -1. In Neovim visual mode, select text. -2. Press `y`. -3. Move to the OpenCode tmux pane. -4. Paste with `Cmd-v`. - -## OpenCode From Neovim - -These keymaps come from `home/dot_config/nvim/lua/plugins/opencode.lua`. - -| Action | Keys | -| ---------------------------------------------- | ------------ | -| Ask OpenCode about current buffer or selection | `oa` | -| Select OpenCode action | `os` | -| Select OpenCode server | `oS` | -| Start a new OpenCode session | `on` | - -## tmux Sessions - -| Action | Command or keys | -| -------------------- | ------------------------- | -| New named session | `tmux new -s dotfiles` | -| Attach named session | `tmux attach -t dotfiles` | -| List sessions | `tmux ls` | -| Detach from session | `d` | -| Rename session | `$` | - -## tmux Panes - -| Action | Keys | -| ------------------ | -------------- | -| Split right | `%` | -| Split below | `"` | -| Move between panes | `` | -| Show pane numbers | `q` | -| Move to next pane | `o` | -| Zoom current pane | `z` | -| Kill current pane | `x` | - -## tmux Windows - -| Action | Keys | -| --------------------- | ------------------------- | -| New window | `c` | -| Next window | `n` | -| Previous window | `p` | -| Jump to window number | `0` through `9` | -| Rename current window | `,` | -| Kill current window | `&` | - -## Copy From tmux To OpenCode - -The tmux config uses vi copy mode and copies selections to the macOS clipboard with `pbcopy`. - -| Action | Keys | -| --------------------------------- | ------------------ | -| Enter copy mode | `[` | -| Move in copy mode | `h`, `j`, `k`, `l` | -| Start selection | `` | -| Copy selection to macOS clipboard | `y` or `` | -| Paste into OpenCode | `Cmd-v` | - -Mouse selection in tmux copy mode also copies to the macOS clipboard when the drag ends. - -## Reload After Applying Dotfiles - -| Config | Command | -| --------------------- | ---------------------------------------------------- | -| Reload tmux config | `:` then `source-file ~/.config/tmux/tmux.conf` | -| Reload zsh config | `source ~/.config/zsh/.zshrc` | -| Restart Neovim config | close and reopen `nvim` | diff --git a/docs/superpowers/plans/2026-06-30-apm-migration-phase-1-skills-install.md b/docs/superpowers/plans/2026-06-30-apm-migration-phase-1-skills-install.md new file mode 100644 index 0000000..cff2e64 --- /dev/null +++ b/docs/superpowers/plans/2026-06-30-apm-migration-phase-1-skills-install.md @@ -0,0 +1,410 @@ +# AI Skills Install (Phase 1) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Install cross-agent skills declared in `home/.chezmoidata/ai.yaml` into the active machine's agent targets using the `skills` CLI, driven by a chezmoi run script and a sourceable, tested install library. + +**Architecture:** Mirror the existing `graphify-skills` pattern. A sourceable bash library (`lib/install/ai-skills.sh`) runs `npx skills add` per skill. A `run_onchange` chezmoi script resolves the active targets and active skills from `ai.yaml` via the existing `active-group-values` template, renders them into bash arrays, and sources the library. Bats tests stub `npx` and assert the captured arguments. + +**Tech Stack:** chezmoi templates, bash, the `skills` CLI (`npx skills`), bats. + +**Spec:** `docs/superpowers/specs/2026-06-30-apm-to-claude-marketplace-migration-design.md` (sections: Group and Target Data Model, Skills Design). + +**Scope note:** This is Phase 1 of 7. It delivers skills install on its own. The local domain skills (`typescript`, `react`, `testing`) already exist under `home/.chezmoitemplates/skills/`, authored ahead of this phase, so `source: local` skills install normally. The library still skips a `source: local` skill whose directory is absent as a safety net. + +--- + +## File Structure + +- Create: `home/.chezmoitemplates/lib/install/ai-skills.sh` — sourceable install library; one responsibility: run `npx skills add` for each selected skill against the target agents. +- Create: `home/.chezmoiscripts/darwin/run_onchange_07_install-ai-skills.sh.tmpl` — chezmoi run script; resolves active targets and skills from `ai.yaml`, renders bash arrays, sources the library. +- Create: `tests/unit/lib/install/ai-skills.bats` — behavior tests for the library, stubbing `npx`. +- Exists (no change): `home/.chezmoidata/ai.yaml` — the manifest. + +## Data Contract + +The run script passes the library three inputs: + +- `AI_SKILL_TARGETS` — bash array of agent ids, e.g. `("claude-code")`. Sourced from `active-group-values` of `.ai.targets`. +- `AI_SKILLS` — bash array of `"|"` strings, e.g. `("mattpocock/skills|grill-me")`. Sourced from `active-group-values` of `.ai.skills`. +- `AI_LOCAL_SKILLS_DIR` — absolute path to the authored local skills, `{{ .chezmoi.sourceDir }}/.chezmoitemplates/skills`. + +The `|` delimiter is safe because skill sources (`owner/repo`, `local`) and skill names contain no `|`. + +--- + +### Task 1: Install library + +**Files:** + +- Create: `home/.chezmoitemplates/lib/install/ai-skills.sh` +- Test: `tests/unit/lib/install/ai-skills.bats` + +- [ ] **Step 1: Write the failing test file** + +Create `tests/unit/lib/install/ai-skills.bats`: + +```bash +#!/usr/bin/env bats +# @file tests/unit/lib/install/ai-skills.bats +# @brief Behavior tests for home/.chezmoitemplates/lib/install/ai-skills.sh. + +load '../../../test_helpers/load.bash' + +LOG_LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/common/log.sh" +PRELUDE="$DOTFILES_ROOT/home/.chezmoitemplates/lib/common/install-prelude.sh" +LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/install/ai-skills.sh" + +setup() { + export NPX_ARGS_FILE="$BATS_TEST_TMPDIR/npx-args" + mkdir -p "$BATS_TEST_TMPDIR/bin" + + cat >"$BATS_TEST_TMPDIR/bin/npx" <<'NPX' +#!/usr/bin/env bash +printf '%s\n' "$*" >>"$NPX_ARGS_FILE" +exit "${NPX_EXIT_CODE:-0}" +NPX + chmod +x "$BATS_TEST_TMPDIR/bin/npx" + export PATH="$BATS_TEST_TMPDIR/bin:/usr/bin:/bin" + + export AI_LOCAL_SKILLS_DIR="$BATS_TEST_TMPDIR/skills" + mkdir -p "$AI_LOCAL_SKILLS_DIR" +} + +_run_main() { + run bash -c "source '$LOG_LIB' && source '$PRELUDE' && source '$LIB' && $1 && main" +} + +@test "main: installs each skill for a single target" { + _run_main "AI_SKILL_TARGETS=('claude-code') && AI_SKILLS=('mattpocock/skills|grill-me' 'blader/humanizer|humanizer')" + + assert_success + assert_line "[ai-skills] Installing cross-agent skills..." + assert_line "[ai-skills] Cross-agent skills installed." + assert_line "--yes skills add mattpocock/skills --skill grill-me -a claude-code --global --copy --yes" + assert_line "--yes skills add blader/humanizer --skill humanizer -a claude-code --global --copy --yes" +} + +@test "main: passes one -a flag per target" { + _run_main "AI_SKILL_TARGETS=('claude-code' 'github-copilot') && AI_SKILLS=('mattpocock/skills|grill-me')" + + assert_success + assert_line "--yes skills add mattpocock/skills --skill grill-me -a claude-code -a github-copilot --global --copy --yes" +} + +@test "main: skips empty skill entries" { + _run_main "AI_SKILL_TARGETS=('claude-code') && AI_SKILLS=('mattpocock/skills|grill-me' '' 'blader/humanizer|humanizer')" + + assert_success + [ "$(wc -l <"$NPX_ARGS_FILE")" -eq 2 ] +} + +@test "main: installs a local skill when its directory exists" { + mkdir -p "$AI_LOCAL_SKILLS_DIR/typescript" + _run_main "AI_SKILL_TARGETS=('claude-code') && AI_SKILLS=('local|typescript')" + + assert_success + assert_line "--yes skills add $AI_LOCAL_SKILLS_DIR/typescript --skill typescript -a claude-code --global --copy --yes" +} + +@test "main: skips a local skill when its directory is missing" { + _run_main "AI_SKILL_TARGETS=('claude-code') && AI_SKILLS=('local|react')" + + assert_success + assert_line "warn: [ai-skills] local skill 'react' not found at $AI_LOCAL_SKILLS_DIR/react; skipping." + [ ! -s "$NPX_ARGS_FILE" ] +} + +@test "main: exits cleanly with no skills" { + _run_main "AI_SKILL_TARGETS=('claude-code') && AI_SKILLS=()" + + assert_success + assert_line "[ai-skills] No skills to install." + [ ! -s "$NPX_ARGS_FILE" ] +} + +@test "main: exits cleanly with no targets" { + _run_main "AI_SKILL_TARGETS=() && AI_SKILLS=('mattpocock/skills|grill-me')" + + assert_success + assert_line "[ai-skills] No agent targets; nothing to install." + [ ! -s "$NPX_ARGS_FILE" ] +} + +@test "main: warns but succeeds when a skill fails" { + export NPX_EXIT_CODE=1 + _run_main "AI_SKILL_TARGETS=('claude-code') && AI_SKILLS=('mattpocock/skills|grill-me')" + + assert_success + assert_line "warn: [ai-skills] 1 skill(s) failed to install:" + assert_line " - grill-me" + assert_line "[ai-skills] Cross-agent skills installed." +} + +@test "main: fails when npx is missing" { + rm -f "$BATS_TEST_TMPDIR/bin/npx" + _run_main "AI_SKILL_TARGETS=('claude-code') && AI_SKILLS=('mattpocock/skills|grill-me')" + + assert_failure 1 + assert_line "error: [ai-skills] npx not found. Ensure Node.js is installed (run_onchange_03_install-mise-tools)." +} +``` + +- [ ] **Step 2: Run the tests to verify they fail** + +Run: `bats tests/unit/lib/install/ai-skills.bats` +Expected: FAIL — the library file does not exist, so sourcing `$LIB` errors. + +- [ ] **Step 3: Write the install library** + +Create `home/.chezmoitemplates/lib/install/ai-skills.sh`: + +```bash +#!/usr/bin/env bash +# @file lib/install/ai-skills.sh +# @brief Install cross-agent skills with the skills CLI. +# @description +# Runs `npx skills add` for each selected skill against the active agent +# targets. Local skill sources resolve under AI_LOCAL_SKILLS_DIR and are +# skipped when their directory is absent. Sourceable from bats tests and +# injected into chezmoi run scripts via chezmoi template rendering. + +set -Eeuo pipefail + +# +# @description Install each selected skill for the active agent targets. +# @exitcode 0 Skills installed, or nothing to do. +# @exitcode 1 npx is not available. +# +function ai_skills_install_main() { + local has_skill=0 + local entry + for entry in "${AI_SKILLS[@]-}"; do + [[ -z "${entry}" ]] && continue + has_skill=1 + break + done + + if ((has_skill == 0)); then + log_info "[ai-skills] No skills to install." + return 0 + fi + + local -a target_flags=() + local target + for target in "${AI_SKILL_TARGETS[@]-}"; do + [[ -z "${target}" ]] && continue + target_flags+=("-a" "${target}") + done + + if ((${#target_flags[@]} == 0)); then + log_info "[ai-skills] No agent targets; nothing to install." + return 0 + fi + + require_command npx "Ensure Node.js is installed (run_onchange_03_install-mise-tools)." || return 1 + + log_info "[ai-skills] Installing cross-agent skills..." + + local source skill add_source + local failed=() + for entry in "${AI_SKILLS[@]-}"; do + [[ -z "${entry}" ]] && continue + source="${entry%%|*}" + skill="${entry##*|}" + + if [[ "${source}" == "local" ]]; then + add_source="${AI_LOCAL_SKILLS_DIR:?AI_LOCAL_SKILLS_DIR must be set}/${skill}" + if [[ ! -d "${add_source}" ]]; then + log_warn "[ai-skills] local skill '${skill}' not found at ${add_source}; skipping." + continue + fi + else + add_source="${source}" + fi + + if ! npx --yes skills add "${add_source}" --skill "${skill}" "${target_flags[@]}" --global --copy --yes; then + failed+=("${skill}") + fi + done + + if ((${#failed[@]} > 0)); then + log_warn "[ai-skills] ${#failed[@]} skill(s) failed to install:" + printf ' - %s\n' "${failed[@]}" >&2 + fi + + log_info "[ai-skills] Cross-agent skills installed." +} + +# +# @description Run the AI skills install flow. +# +function main() { + ai_skills_install_main +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + # When run directly, pull in the shared libraries that chezmoi otherwise + # concatenates ahead of this file. + # shellcheck source=/dev/null + command -v log_info >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../common/log.sh" + # shellcheck source=/dev/null + command -v require_command >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../common/install-prelude.sh" + main +fi +``` + +- [ ] **Step 4: Run the tests to verify they pass** + +Run: `bats tests/unit/lib/install/ai-skills.bats` +Expected: PASS — all nine tests green. + +- [ ] **Step 5: Lint the library** + +Run: `shellcheck home/.chezmoitemplates/lib/install/ai-skills.sh` +Expected: no output (clean). The `${AI_SKILLS[@]-}` guarded expansion avoids unbound-variable errors under `set -u`. + +- [ ] **Step 6: Commit** + +```bash +git add home/.chezmoitemplates/lib/install/ai-skills.sh tests/unit/lib/install/ai-skills.bats +git commit -m "feat: DOT-43 add ai-skills install library" +``` + +--- + +### Task 2: chezmoi run script + +**Files:** + +- Create: `home/.chezmoiscripts/darwin/run_onchange_07_install-ai-skills.sh.tmpl` + +- [ ] **Step 1: Write the run script** + +Create `home/.chezmoiscripts/darwin/run_onchange_07_install-ai-skills.sh.tmpl`: + +```bash +#!/usr/bin/env bash +# @file run_onchange_07_install-ai-skills.sh +# @brief Install cross-agent AI skills. +# @description AI data hash: {{ include ".chezmoidata/ai.yaml" | sha256sum }} +{{- $activeTargets := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.targets) | fromJson -}} +{{- $activeSkills := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.skills) | fromJson -}} + +{{ if and (eq .chezmoi.os "darwin") (gt (len $activeTargets) 0) (gt (len $activeSkills) 0) }} +{{ template "lib/common/log.sh" . }} +{{ template "lib/common/install-prelude.sh" . }} +export AI_LOCAL_SKILLS_DIR="{{ .chezmoi.sourceDir }}/.chezmoitemplates/skills" +AI_SKILL_TARGETS=( +{{ range $activeTargets -}} + "{{ . }}" +{{ end -}} +) +AI_SKILLS=( +{{ range $activeSkills -}} + "{{ .source }}|{{ .skill }}" +{{ end -}} +) +{{ template "lib/install/ai-skills.sh" . }} +{{ end }} +``` + +- [ ] **Step 2: Verify the template renders for a personal machine** + +Run: `chezmoi execute-template --init --promptString 'groups=personal' < home/.chezmoiscripts/darwin/run_onchange_07_install-ai-skills.sh.tmpl` (adjust the prompt to match this repo's group-selection variable if different; confirm from `home/.chezmoitemplates/lib/chezmoi/active-groups.json.tmpl`). +Expected: rendered bash with `AI_SKILL_TARGETS=("claude-code")` and `AI_SKILLS` containing the shared plus personal entries (`mattpocock/skills|grill-me`, ..., `schpet/linear-cli|linear-cli`, `upstash/context7|context7-cli`), and the `lib/install/ai-skills.sh` body inlined. + +- [ ] **Step 3: Dry-run apply to confirm no template errors** + +Run: `chezmoi apply --dry-run --verbose 2>&1 | rg -A2 'run_onchange_07_install-ai-skills'` +Expected: chezmoi shows the script would run; no template parse errors. + +- [ ] **Step 4: Commit** + +```bash +git add home/.chezmoiscripts/darwin/run_onchange_07_install-ai-skills.sh.tmpl +git commit -m "feat: DOT-43 add ai-skills run_onchange script" +``` + +--- + +### Task 3: Template render test + +**Files:** + +- Modify: `tests/template/darwin-install-scripts.bats` (add cases; follow the existing structure in that file) + +- [ ] **Step 1: Inspect the existing template test structure** + +Run: `bats --count tests/template/darwin-install-scripts.bats` and open the file to copy its rendering helper and assertion style (it renders `.tmpl` scripts with a group context and asserts on output). + +- [ ] **Step 2: Write failing render tests** + +Add two tests mirroring the file's existing pattern. Personal render includes shared plus personal skills against `claude-code`; work render excludes personal-only skills. Use the file's existing template-render helper (do not invent a new one). Example assertions: + +```bash +@test "run_onchange_07: personal render targets claude-code and includes shared+personal skills" { + run render_darwin_script "run_onchange_07_install-ai-skills.sh.tmpl" "personal" + assert_success + assert_output --partial 'AI_SKILL_TARGETS=(' + assert_output --partial '"claude-code"' + assert_output --partial '"mattpocock/skills|grill-me"' + assert_output --partial '"schpet/linear-cli|linear-cli"' +} + +@test "run_onchange_07: work render targets github-copilot and excludes personal skills" { + run render_darwin_script "run_onchange_07_install-ai-skills.sh.tmpl" "work" + assert_success + assert_output --partial '"github-copilot"' + refute_output --partial 'schpet/linear-cli|linear-cli' +} +``` + +- [ ] **Step 3: Run to verify they fail, then pass** + +Run: `bats tests/template/darwin-install-scripts.bats` +Expected: the two new tests fail until the helper name matches the file's actual helper; adjust the helper call to the real one, then they pass. + +- [ ] **Step 4: Run the full unit and template suites** + +Run: `bats tests/unit/lib/install/ai-skills.bats tests/template/darwin-install-scripts.bats` +Expected: all green. + +- [ ] **Step 5: Commit** + +```bash +git add tests/template/darwin-install-scripts.bats +git commit -m "test: DOT-43 add ai-skills template render tests" +``` + +--- + +## Self-Review + +- **Spec coverage (Skills Design):** Library runs `npx skills add --skill -a --global --copy --yes` (Task 1); targets and skills resolve from `ai.yaml` via `active-group-values` (Task 2); local sources skip when absent, deferring Phase 5 (Task 1 test 5). Covered. +- **Placeholder scan:** No TBD/TODO; every step has full code or an exact command with expected output. The one adjustable point (the template test helper name in Task 3) is called out explicitly because it depends on the existing file, and the step says to match the real helper. +- **Type consistency:** `AI_SKILLS`, `AI_SKILL_TARGETS`, `AI_LOCAL_SKILLS_DIR`, `ai_skills_install_main`, and the `|` contract are identical across the library, the run script, and the tests. +- **Out of Phase 1 scope:** plugins install, MCP delivery, instructions, and apm teardown are later phases. Domain-skill authoring and Copilot commit wiring are already done ahead of this plan. + +## Execution Deltas + +Corrections applied during implementation, each verified against the codebase before coding: + +- **npx check is inline, not `require_command`.** `require_command npx "..."` emits `error: [npx] not found. ...`, but the `[ai-skills]` component-tag convention and Task 1's own test need `error: [ai-skills] npx not found. ...`. The library uses `command -v npx || { log_error "[ai-skills] npx not found. ..."; return 1; }`. +- **The render test helper is `render_chezmoi_template "$ABS_TEMPLATE" "$JSON_DATA"`**, not `render_darwin_script "basename" "personal"`; group is selected with the `$DARWIN_DATA` / `$WORK_DATA` payload constants. +- **The npx test stub tees to both stdout and the args file** (`printf '%s\n' "$*" | tee -a "$NPX_ARGS_FILE"`) so `assert_line` sees the invocation while the count checks still read the file. +- **The command carries a leading `npx --yes`** (npx's own auto-confirm) to keep non-interactive apply from hanging. +- **The trigger comment hashes the local skill tree** in addition to `ai.yaml`, so an in-place `SKILL.md` edit re-runs the `--copy` install. +- **Coverage beyond the plan's two render tests:** added local+remote mix, partial-failure, cartesian target-by-skill, empty-entry identity, and export-line assertions; the existing darwin glob tests already cover shebang and `bash -n` validity of the new script. + +Known limitations, shared with the graphify sibling and out of scope here: a missing npx hard-fails `chezmoi apply` on first boot before mise shims load, and removing a skill from `ai.yaml` does not prune the previously installed copy. + +## Remaining Phases (separate plans) + +1. This plan — skills install. +2. Plugins install (`run_onchange_06`, caveman + superpowers dual-path). +3. MCP delivery (`claude mcp add` for Claude; templated `mcp.json`/`mcp-config.json` for Copilot). +4. Instructions (`AGENTS.md` canonical → `CLAUDE.md` + Copilot) and commit-message wiring. +5. Author local domain skills (`typescript`, `react`, `testing`) from `home/.chezmoidata/instructions/`. +6. Organize work and Copilot files. +7. Remove APM and update tests. diff --git a/docs/superpowers/plans/2026-06-30-apm-migration-phase-2-plugins-install.md b/docs/superpowers/plans/2026-06-30-apm-migration-phase-2-plugins-install.md new file mode 100644 index 0000000..00f1d31 --- /dev/null +++ b/docs/superpowers/plans/2026-06-30-apm-migration-phase-2-plugins-install.md @@ -0,0 +1,322 @@ +# AI Plugins Install (Phase 2) Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Install the hook-bearing plugins declared in `home/.chezmoidata/ai.yaml` (`caveman`, `superpowers`) for the active machine's agent target, replacing the retired apm install path. + +**Architecture:** Mirror the `ai-skills` Phase-1 pattern (sourceable bash library + `run_onchange` chezmoi script + bats). The library is decomposed into small single-responsibility functions (Kaizen): a predicate, per-plugin installers, per-target loops, a dispatcher, and a thin orchestrator. Install branches on the active target: `claude-code` installs through the Claude marketplace CLI (`claude plugin marketplace add ` then `claude plugin install @`); `github-copilot` installs the plugin source as a skill (`npx --yes skills add -a github-copilot --global --copy --yes`), because Copilot has no plugin or hook runtime. Marketplaces and enabled plugins are also declared in the plain `home/dot_claude/settings.json`. + +**Tech Stack:** chezmoi templates, bash, the `claude` CLI (`claude plugin ...`), the `skills` CLI (`npx skills`), bats. + +**Spec:** `docs/superpowers/specs/2026-06-30-apm-to-claude-marketplace-migration-design.md` (sections: Plugins Design, Group and Target Data Model). + +**Naming:** `ai-plugins` (not `claude-plugins`) — agnostic, matching the `ai.plugins` data namespace and the `ai-skills` sibling, because plugins install to both agents. + +**Scope note:** Phase 2 of 7. Delivers plugins install on its own. The `06` run-script slot is free (the apm script was removed by prior cleanup). Phase 2 does not touch MCP, instructions, or apm teardown. + +--- + +## Verification findings (resolved) + +Confirmed live against the `claude plugin` CLI (read-only): + +- **Marketplace list format:** `❯ ` with a `Source: GitHub (owner/repo)` line under it. +- **Installed-plugin format:** `❯ @` with `Status: ✔ enabled|✘ disabled`; skills-directory plugins list separately as `@skills-dir`. +- **`caveman` already exists as `caveman@skills-dir`** (`~/.claude/skills/caveman`) from a prior run. Installing `caveman@caveman` (marketplace) adds a second copy. **Validate on real apply**; may warrant pruning the skills-dir copy in a later phase. +- **`claude plugin install` has no non-interactive flag** (`--config`, `--scope` only). A first-time trust prompt could block `chezmoi apply`. **Validate on real apply.** +- **Idempotency of `marketplace add` / re-`install` is untested** (testing mutates the machine). Decision: no idempotency guards — match `ai-skills`, collect per-plugin failures and warn (graceful, non-fatal). Add guards later only if re-run noise is observed (Kaizen JIT; respects the no-defensive-programming rule). + +## File Structure + +- Create: `home/.chezmoitemplates/lib/install/ai-plugins.sh` — sourceable install library; small functions, branches per target. +- Create: `home/.chezmoiscripts/darwin/run_onchange_06_install-ai-plugins.sh.tmpl` — chezmoi run script; resolves active targets and plugins, renders bash arrays, sources the library. +- Modify: `home/dot_claude/settings.json` — declare `caveman` + `superpowers-dev` marketplaces and enable `caveman@caveman` + `superpowers@superpowers-dev` (SuperWhisper stays `true`). +- Create: `tests/unit/lib/install/ai-plugins.bats` — behavior tests stubbing `claude` and `npx`. +- Modify: `tests/template/darwin-install-scripts.bats` — render tests for `run_onchange_06`. + +## Data Contract + +The run script passes the library two arrays: + +- `AI_PLUGIN_TARGETS` — agent ids, e.g. `("claude-code")`. From `active-group-values` of `.ai.targets`. +- `AI_PLUGINS` — `"||"` strings, e.g. `("caveman|caveman|JuliusBrussee/caveman" "superpowers|superpowers-dev|obra/superpowers")`. From `active-group-values` of `.ai.plugins`. + +The `|` delimiter is safe: names, marketplace names, and `owner/repo` sources contain no `|`. + +--- + +### Task 1: Install library (decomposed) + +**Files:** Create `home/.chezmoitemplates/lib/install/ai-plugins.sh`; Test `tests/unit/lib/install/ai-plugins.bats`. + +- [ ] **Step 1: Write the failing test file** — mirror `ai-skills.bats`. `setup()` stubs both `claude` and `npx` into `$BATS_TEST_TMPDIR/bin` (each `printf '%s\n' "$*" | tee -a` its own args file, then `exit "${_EXIT_CODE:-0}"`), exports `PATH="$BATS_TEST_TMPDIR/bin:/usr/bin:/bin"`. Helper: `_run_main() { run bash -c "source '$LOG_LIB' && source '$LIB' && $1 && main"; }`. Cases (fill bodies in the `ai-skills.bats` style — exact `assert_line`, `refute_line`, count asserts): + +```bash +@test "claude-code target adds marketplace and installs each plugin" { + _run_main "AI_PLUGIN_TARGETS=('claude-code') && AI_PLUGINS=('caveman|caveman|JuliusBrussee/caveman' 'superpowers|superpowers-dev|obra/superpowers')" + assert_success + assert_line "plugin marketplace add JuliusBrussee/caveman" + assert_line "plugin install caveman@caveman" + assert_line "plugin marketplace add obra/superpowers" + assert_line "plugin install superpowers@superpowers-dev" + [ ! -s "$NPX_ARGS_FILE" ] +} + +@test "github-copilot target installs plugin source as a skill" { + _run_main "AI_PLUGIN_TARGETS=('github-copilot') && AI_PLUGINS=('caveman|caveman|JuliusBrussee/caveman')" + assert_success + assert_line "--yes skills add JuliusBrussee/caveman -a github-copilot --global --copy --yes" + [ ! -s "$CLAUDE_ARGS_FILE" ] +} + +@test "both targets install every plugin to each" { ... claude add+install for each AND npx skills add for each ... } +@test "exits cleanly with no plugins" { ... assert_line "[ai-plugins] No plugins to install." ; } +@test "exits cleanly with no targets" { ... assert_line "[ai-plugins] No agent targets; nothing to install." ; } +@test "skips empty plugin entries" { ... expected install count ; } +@test "skips empty target entries" { ... expected install count ; } +@test "warns but succeeds when a plugin fails" { CLAUDE_EXIT_CODE=1 ... assert_success ; assert_line "warn: [ai-plugins] 1 plugin(s) failed to install:" ; assert_line " - caveman" ; } +@test "unknown target warns and skips" { ... assert_line "warn: [ai-plugins] unknown target 'grok'; skipping." ; } +@test "fails when claude is missing for a claude-code target" { rm claude stub; PATH=".../bin:/bin"; assert_failure 1; assert_line "error: [ai-plugins] claude CLI not found. Ensure Claude Code is installed." ; } +@test "fails when npx is missing for a github-copilot target" { rm npx stub; PATH=".../bin:/bin"; assert_failure 1; assert_line "error: [ai-plugins] npx not found. Ensure Node.js is installed (run_onchange_03_install-mise-tools)." ; } +``` + +- [ ] **Step 2: Run the tests to verify they fail** (`bats tests/unit/lib/install/ai-plugins.bats` — lib missing). + +- [ ] **Step 3: Write the library** — small functions, each one responsibility. `set -Eeuo pipefail`; all array expansions use the `[@]-` default or a count guard (bash 3.2 safe); the per-plugin installers are always called in a `|| failed+=(...)` context so `set -e` is suppressed inside them. + +```bash +#!/usr/bin/env bash +# @file lib/install/ai-plugins.sh +# @brief Install cross-agent AI plugins for the active agent targets. +# @description +# Installs each plugin in AI_PLUGINS for every agent in AI_PLUGIN_TARGETS. +# Claude Code plugins install through the marketplace CLI; GitHub Copilot has +# no plugin runtime, so the plugin source installs as a skill instead. +# Sourceable from bats tests and injected into chezmoi run scripts. + +set -Eeuo pipefail + +# @description True when AI_PLUGINS holds at least one non-empty entry. +function _ai_plugins_have_any() { + local entry + for entry in "${AI_PLUGINS[@]-}"; do + [[ -z "${entry}" ]] && continue + return 0 + done + return 1 +} + +# @description Warn about the plugins that failed to install. +# @arg $@ string Names of the plugins that failed. +function _ai_plugins_report_failures() { + log_warn "[ai-plugins] ${#} plugin(s) failed to install:" + printf ' - %s\n' "$@" >&2 +} + +# @description Add one plugin's marketplace and install it into Claude Code. +# @arg $1 string Plugin name. @arg $2 string Marketplace. @arg $3 string Source repo. +function _ai_plugins_add_to_claude() { + local name="$1" marketplace="$2" src="$3" + claude plugin marketplace add "${src}" && + claude plugin install "${name}@${marketplace}" +} + +# @description Install one plugin source into GitHub Copilot as a skill. +# @arg $1 string Source repo. +function _ai_plugins_add_to_copilot() { + npx --yes skills add "$1" -a github-copilot --global --copy --yes +} + +# @description Install every plugin for the Claude Code target. +# @exitcode 1 The claude CLI is missing. +function _ai_plugins_for_claude_code() { + if ! command -v claude >/dev/null 2>&1; then + log_error "[ai-plugins] claude CLI not found. Ensure Claude Code is installed." + return 1 + fi + local -a failed=() + local entry name marketplace src rest + for entry in "${AI_PLUGINS[@]-}"; do + [[ -z "${entry}" ]] && continue + name="${entry%%|*}" + rest="${entry#*|}" + marketplace="${rest%%|*}" + src="${rest##*|}" + _ai_plugins_add_to_claude "${name}" "${marketplace}" "${src}" || failed+=("${name}") + done + if ((${#failed[@]} > 0)); then + _ai_plugins_report_failures "${failed[@]}" + fi +} + +# @description Install every plugin for the GitHub Copilot target. +# @exitcode 1 npx is missing. +function _ai_plugins_for_copilot() { + if ! command -v npx >/dev/null 2>&1; then + log_error "[ai-plugins] npx not found. Ensure Node.js is installed (run_onchange_03_install-mise-tools)." + return 1 + fi + local -a failed=() + local entry name src + for entry in "${AI_PLUGINS[@]-}"; do + [[ -z "${entry}" ]] && continue + name="${entry%%|*}" + src="${entry##*|}" + _ai_plugins_add_to_copilot "${src}" || failed+=("${name}") + done + if ((${#failed[@]} > 0)); then + _ai_plugins_report_failures "${failed[@]}" + fi +} + +# @description Route one agent target to its installer. +# @arg $1 string Agent target id. +function _ai_plugins_for_target() { + case "$1" in + claude-code) _ai_plugins_for_claude_code ;; + github-copilot) _ai_plugins_for_copilot ;; + *) + log_warn "[ai-plugins] unknown target '$1'; skipping." + return 0 + ;; + esac +} + +# @description Install the active plugins for each active agent target. +# @exitcode 0 Installed, or nothing to do. +# @exitcode 1 A required CLI is missing for a requested target. +function ai_plugins_install_main() { + if ! _ai_plugins_have_any; then + log_info "[ai-plugins] No plugins to install." + return 0 + fi + local -a targets=() + local target + for target in "${AI_PLUGIN_TARGETS[@]-}"; do + [[ -z "${target}" ]] && continue + targets+=("${target}") + done + if ((${#targets[@]} == 0)); then + log_info "[ai-plugins] No agent targets; nothing to install." + return 0 + fi + log_info "[ai-plugins] Installing AI plugins..." + for target in "${targets[@]}"; do + _ai_plugins_for_target "${target}" || return 1 + done + log_info "[ai-plugins] AI plugins installed." +} + +function main() { ai_plugins_install_main; } + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + # shellcheck source=/dev/null + command -v log_info >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../common/log.sh" + main +fi +``` + +- [ ] **Step 4: Run the tests to verify they pass.** +- [ ] **Step 5: `shellcheck home/.chezmoitemplates/lib/install/ai-plugins.sh`.** +- [ ] **Step 6: Commit** `feat: add ai-plugins install library` (DOT-43). + +--- + +### Task 2: run script + settings.json declaration + +**Files:** Create `home/.chezmoiscripts/darwin/run_onchange_06_install-ai-plugins.sh.tmpl`; Modify `home/dot_claude/settings.json`. + +- [ ] **Step 1: Write the run script** (mirror `run_onchange_07`; no local-content hash — plugins come from external sources): + +``` +#!/usr/bin/env bash +# @file run_onchange_06_install-ai-plugins.sh +# @brief Install cross-agent AI plugins. +# @description AI data hash: {{ include ".chezmoidata/ai.yaml" | sha256sum }} +{{- $activeTargets := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.targets) | fromJson -}} +{{- $activePlugins := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.plugins) | fromJson -}} + +{{ if and (eq .chezmoi.os "darwin") (gt (len $activeTargets) 0) (gt (len $activePlugins) 0) }} +{{ template "lib/common/log.sh" . }} +{{ template "lib/common/install-prelude.sh" . }} +AI_PLUGIN_TARGETS=( +{{ range $activeTargets -}} + "{{ . }}" +{{ end -}} +) +AI_PLUGINS=( +{{ range $activePlugins -}} + "{{ .name }}|{{ .marketplace }}|{{ .source }}" +{{ end -}} +) +{{ template "lib/install/ai-plugins.sh" . }} +{{ end }} +``` + +- [ ] **Step 2: Verify the render** for personal (`claude-code`, both plugins) and work (`github-copilot`, both plugins) via `chezmoi execute-template --source home --override-data ...`. + +- [ ] **Step 3: Declare marketplaces and enabled plugins in `home/dot_claude/settings.json`** (plain JSON): + +```json +"enabledPlugins": { + "superwhisper@superwhisper": true, + "caveman@caveman": true, + "superpowers@superpowers-dev": true +}, +"extraKnownMarketplaces": { + "superwhisper": { "source": { "source": "github", "repo": "superultrainc/superwhisper-claude-code" } }, + "caveman": { "source": { "source": "github", "repo": "JuliusBrussee/caveman" } }, + "superpowers-dev": { "source": { "source": "github", "repo": "obra/superpowers" } } +} +``` + +- [ ] **Step 4: Commit** `feat: add ai-plugins run_onchange script and settings` (DOT-43). + +--- + +### Task 3: Template render tests + +**Files:** Modify `tests/template/darwin-install-scripts.bats`. + +- [ ] **Step 1:** Add `PLUGINS_TEMPLATE="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_06_install-ai-plugins.sh.tmpl"`, an inject-lib assertion (`{{ template "lib/install/ai-plugins.sh" . }}`), a prelude-injection assertion, and render tests: + +```bash +@test "personal plugins template targets claude-code and includes both plugins" { + run render_chezmoi_template "$PLUGINS_TEMPLATE" "$DARWIN_DATA" + assert_success + assert_line 'AI_PLUGIN_TARGETS=(' + assert_line --partial '"claude-code"' + assert_line --partial '"caveman|caveman|JuliusBrussee/caveman"' + assert_line --partial '"superpowers|superpowers-dev|obra/superpowers"' + assert_line --partial 'ai_plugins_install_main' +} + +@test "work plugins template targets github-copilot" { + run render_chezmoi_template "$PLUGINS_TEMPLATE" "$WORK_DATA" + assert_success + assert_line --partial '"github-copilot"' + refute_line --partial '"claude-code"' +} + +@test "empty AI target groups render no plugin commands" { + run render_chezmoi_template "$PLUGINS_TEMPLATE" "$EMPTY_AI_DATA" + assert_success + refute_line 'AI_PLUGIN_TARGETS=(' + refute_line --partial 'ai_plugins_install_main' +} +``` + +The existing glob tests (bash shebang, `bash -n`) auto-cover the new script. + +- [ ] **Step 2:** Run `bats tests/unit/lib/install/ai-plugins.bats tests/template/darwin-install-scripts.bats`; then `mise run check`. +- [ ] **Step 3: Commit** `test: add ai-plugins template render tests` (DOT-43). + +--- + +## Self-Review + +- **Spec coverage (Plugins Design):** dual path per target (Task 1), `run_onchange_06` replaces the apm script and gates darwin (Task 2), settings.json declares marketplaces + enabled plugins (Task 2). Covered. +- **Naming/type consistency:** `ai-plugins`, `AI_PLUGIN_TARGETS`, `AI_PLUGINS`, `||`, `ai_plugins_install_main` identical across library, run script, and tests. +- **Kaizen decomposition:** predicate, per-plugin installers, per-target loops, dispatcher, orchestrator — each one responsibility, `set -Eeuo` and bash-3.2 safe. +- **Known risks for real-apply validation:** `claude plugin install` interactivity/hang; `caveman@skills-dir` duplicate; `marketplace add` re-run idempotency. All degrade to a warned failure, not data loss. +- **Out of Phase 2 scope:** MCP delivery, instructions wiring, apm teardown. diff --git a/docs/superpowers/plans/2026-06-30-repo-refine-cleanup.md b/docs/superpowers/plans/2026-06-30-repo-refine-cleanup.md new file mode 100644 index 0000000..de89889 --- /dev/null +++ b/docs/superpowers/plans/2026-06-30-repo-refine-cleanup.md @@ -0,0 +1,137 @@ +# Repo Refine and Cleanup Plan + +> **For agentic workers:** execute task-by-task. Each task lists exact files, the change, and how to verify it. Steps use checkbox (`- [ ]`) syntax. + +**Goal:** Refine, clean up, and simplify the chezmoi dotfiles repo without changing applied behavior, aligned with the in-flight apm to ai migration (DOT-43). + +**Architecture:** Two adversarially-verified reviews (structure-research and kaizen-review) plus a direct `mise.toml` analysis produced this task set. Most of the structure is already sound and is deliberately kept. Changes are small, mostly single-file, and each is verified rendering-equivalent before it lands. + +**Tech Stack:** chezmoi (`.chezmoiroot=home`), Go templates, POSIX/bash install libs, mise tasks, treefmt/shfmt/shellcheck/prettier/taplo, bats. + +--- + +## Verified: kept as-is (do NOT touch) + +These were evaluated and deliberately left alone. Reasons recorded so they are not re-litigated. + +- **`booleans -> active-groups -> active-group-values` layering.** Correct primitive (3+ consumers, single source of truth for the personal/work mapping); the two-template split maps to two real data shapes. Self-describing names. No simpler equivalent preserves the scoping. +- **`.chezmoitemplates/lib/` split** (`lib/chezmoi` template helpers vs `lib/{common,darwin,install}` shell libs). chezmoi imposes no internal convention and there is none in the community; the split already separates the two kinds. Renaming is churn. +- **`active-group-values.json.tmpl`.** Not dead code: one live consumer today, nil-safe, bats-tested, gains consumers in the migration. +- **`lib/darwin/defaults.sh` dock layout duplication.** Ordered config list where explicit per-layout order aids readability; extracting the shared middle would obscure dock order. +- **`chezmoiignore.d/darwin` empty fragment.** Deliberate extension point mirroring the populated `chezmoiexternal.d/`; cheap ceremony. +- **Data-hash `# hash` comments** on scripts that inline their data. Redundant but harmless; removing risks silent onchange breakage on a future refactor. +- **Empty-skip guards** (`$hasTools`, `len platforms > 0`) present only on installers whose active set can legitimately be empty. Correct as-is; new migration installers with possibly-empty groups should copy the guarded pattern. + +--- + +## Group A: structure cleanups (migration-independent, safe now) + +### Task A1: Route the vscode installer through the tested helper + +**Files:** + +- Modify: `home/.chezmoiscripts/darwin/run_onchange_04_install-vscode-extensions.sh.tmpl` + +The inline `range .vscode.shared` + `if .personal` + `if .work` fan-out is the one place the group-selection idiom is hand-rolled a third way. `.vscode` has the identical scalar-list-per-group shape as `.ai.targets`, which `run_onchange_08` already flattens via the helper. + +- [ ] Replace the inline fan-out with: + `{{- $ext := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .vscode) | fromJson -}}` + then render `VSCODE_EXTENSIONS=( {{ range $ext }}"{{ . }}" {{ end }} )`. +- [ ] Verify rendering-equivalent: `chezmoi execute-template < ` before/after produce the same `VSCODE_EXTENSIONS` array (order shared -> personal -> work preserved; only cosmetic per-group `# shared` comments are lost). +- [ ] The onchange hash is over `extensions.yaml` (unchanged), so no spurious re-run. + +### Task A2: Generalize the `AGENTS.md` template to be provider-neutral (keep the name) + +**Files:** + +- Modify: `home/.chezmoitemplates/AGENTS.md` (H1 + intro line only) + +Decision: KEEP the filename `AGENTS.md` — it is the cross-provider standard, so the same guidance can feed Claude (`~/.claude/CLAUDE.md`), Copilot (`AGENTS.md`), and any future agent. No rename. The name "collision" with the repo-root `AGENTS.md` is human-readability only; `{{ template "AGENTS.md" }}` resolves unambiguously to the `.chezmoitemplates/` file, so it costs nothing. Fix the content instead: the body (AI Guidance, GitHub CLI, Git/PR workflow, Graphify) is already provider-neutral; only the top two lines name Claude. + +- [ ] Line 1: `# Claude Code Settings` -> `# Agent Guidance` (or `# AI Agent Guidance`). +- [ ] Line 3: `Guidance for Claude Code and other AI tools.` -> `Guidance for AI coding agents (Claude Code, GitHub Copilot, and others).` +- [ ] Leave the rest of the body unchanged; it is already agnostic. +- [ ] Verify: `chezmoi execute-template < home/dot_claude/CLAUDE.md.tmpl` still renders the full guidance body (the `{{ template "AGENTS.md" . }}` reference is untouched). +- [ ] Cross-agent wiring (feeding this same template to Copilot's `AGENTS.md`) is handled in the DOT-43 migration's instructions design, not here. + +### Task A3: Rename `extensions.yaml` -> `vscode.yaml` for filename==key uniformity + +**Files:** + +- Rename: `home/.chezmoidata/extensions.yaml` -> `home/.chezmoidata/vscode.yaml` +- Modify: `home/.chezmoiscripts/darwin/run_onchange_04_install-vscode-extensions.sh.tmpl` (hash-comment path only) + +Every other data file matches filename to its single top-level key (`packages`, `ai`, `mise`, `uv`); `extensions.yaml` is the lone outlier (key is `vscode:`). Renaming the file (not the key) keeps the `.vscode` accessor untouched. Included here because Group A is already touching this script and this completes the convention; skipped otherwise. + +- [ ] `git mv` the data file. +- [ ] Update the hash comment `include ".chezmoidata/extensions.yaml"` -> `".chezmoidata/vscode.yaml"`. +- [ ] Verify: `chezmoi execute-template` on run_onchange_04 renders without error; one harmless `--force` re-run occurs because the hash-comment string changed. + +--- + +## Group B: mise.toml and tooling simplification + +### Task B1: Drop redundant `mise exec --` inside tasks + +**Files:** + +- Modify: `mise.toml` (all task `run` bodies) + +Inside `mise run `, the `[tools]` are already on `PATH`; `mise exec --` is redundant noise on every line. + +- [ ] Confirm empirically first: `mise run diff` (or a trivial task) with `chezmoi` called bare works. +- [ ] Remove `mise exec -- ` prefixes from `diff`, `update`, `reset`, `reset-config`, `format`, `lint`, `test-*` task bodies. +- [ ] Verify: `mise lint` and `mise test` still run (leave CI's raw `run:` steps alone — those are outside a task env and keep `mise exec --`). + +### Task B2: Adopt treefmt for format orchestration (DECIDED) + +**Files:** + +- Create: `treefmt.toml` +- Modify: `mise.toml` (`[tools]` add treefmt; rewrite `format`, shrink `lint`) + +Replaces the two duplicated ~15-line shell-discovery bash blocks and the manual per-tool orchestration with one declarative config. `treefmt` walks the tree, respects each formatter's own ignore config, and dispatches shfmt/prettier/taplo. `shellcheck` stays a standalone lint step (treefmt is format-only). + +- [ ] Add `treefmt = "2.5.0"` to `[tools]` via the aqua/ubi backend (`"aqua:numtide/treefmt" = "2.5.0"`). +- [ ] Create `treefmt.toml` with formatters: `shfmt` (`*.sh`, `*.zsh`, `.zshrc`; exclude `*.tmpl`), `prettier` (`*.md`, `*.yaml`, `*.yml`, `*.json`), `taplo fmt` (`*.toml`; exclude `*.tmpl`). +- [ ] `format` task -> `treefmt`. +- [ ] `lint` task -> `treefmt --ci` (fail on change) + the retained `shellcheck` block for `*.sh`/`*.zsh` and the `bats` shellcheck block. +- [ ] Verify: `mise format` then `git diff` shows no unexpected reformatting of `*.tmpl` files; `mise lint` passes on a clean tree. + +### Task B3: Remove the retired apm dependency-management layer + +**Files:** + +- Modify: `mise.toml` (delete `[tasks.validate-apm]`, `[tasks.refresh-apm-locks]`) +- Delete: `tests/template/install-apm-script.bats` +- Modify: `.gitignore` (drop `# APM dependencies` + `apm_modules/`) + +Keep the apm CLI (`home/.chezmoidata/mise.yaml` `github:microsoft/apm` + `Bash(apm:*)` permission). Only the dependency-management tasks/tests/artifacts go; their `scripts/*.sh` already do not exist. + +- [ ] Delete the two apm tasks. +- [ ] Delete `install-apm-script.bats` (renders the already-removed apm script; would fail). +- [ ] Remove the `apm_modules/` gitignore lines. +- [ ] Verify: `mise test` passes (no apm bats failure); `command -v apm` still resolves. + +--- + +## Group C: deferred INTO the DOT-43 migration + +These are correct but must land inside the migration that is already restructuring `ai/` and reassigning script numbers, to avoid conflicting churn. + +- **Rename `.chezmoitemplates/ai/skills/` -> `ai/instructions/`.** The six `*.instructions.md` files are GitHub Copilot custom-instructions (description+applyTo frontmatter), not skills; `skills` is currently overloaded three ways (ai.yaml `skills:` key, this dir, `dot_copilot/skills`). Zero references today, pure `git mv`. Settle the final name (`ai/instructions` vs `ai/copilot-instructions`) in the migration. +- **Give uv-tools a unique script number above mise-tools.** `uv-tools.sh` needs `mise` first (`require_command uv`); today the order holds only by `mise` < `uv` alphabetical accident within the shared `03` prefix. Assign a free number during the migration renumber (note: `04` is taken by vscode). +- **Holistic run-script renumbering.** After the migration adds plugins/skills/MCP scripts, renumber to be gapless and dependency-encoding. + +--- + +## Execution approach + +- Run as a team of subagents, one per task (or per group), each in an **isolated git worktree** so edits land on a reviewable branch, never the live working tree. +- Precondition: the working tree must be clean first — the in-flight `run_onchange_02` (taps) break and any uncommitted WIP resolved/committed — so worktrees branch from a coherent base and `chezmoi apply` / `mise lint` / `mise test` can verify cleanly. +- Verification gate per task: `chezmoi execute-template` on touched templates (rendering-equivalent), then `mise lint` and `mise test` green before the task is kept. + +## Decisions (locked) + +- **Tooling (Task B2): adopt treefmt.** One declarative `treefmt.toml` dispatches shfmt/prettier/taplo; `format = treefmt`, `lint = treefmt --ci` plus standalone `shellcheck`. Deletes both duplicated shell-discovery blocks. Accepts +1 mise tool and +1 config file. +- **Execution: clean tree first, then spin the team.** User resolves the `run_onchange_02` (taps) break and commits/stashes WIP; on confirmation, the worktree-isolated team executes Groups A + B against a coherent base with per-task verify gates. Group C lands inside the DOT-43 migration. diff --git a/docs/superpowers/plans/2026-07-01-apm-migration-phase-3-mcp-delivery.md b/docs/superpowers/plans/2026-07-01-apm-migration-phase-3-mcp-delivery.md new file mode 100644 index 0000000..f9845a8 --- /dev/null +++ b/docs/superpowers/plans/2026-07-01-apm-migration-phase-3-mcp-delivery.md @@ -0,0 +1,599 @@ +# APM Migration Phase 3: MCP Delivery Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Deliver the MCP servers declared in `ai.yaml` to Claude Code (imperatively via `claude mcp add`) and to GitHub Copilot (declaratively via rendered config files), driven by the same group and target data as skills and plugins. + +**Architecture:** One `ai.mcp` data source, two delivery idioms. Claude Code owns mutable state in `~/.claude.json`, so a `run_onchange` shell script registers servers with `claude mcp add` and stays idempotent via `claude mcp get`. Copilot config files are standalone, so chezmoi renders them whole. Each surface gates on its active target, so the machine's active MCP set (shared plus personal on a Claude machine, shared plus work on a Copilot machine) routes to the correct surface for free. + +**Tech Stack:** chezmoi templates, bash 3.2 (macOS), bats, `claude` CLI, sprig template functions (`dict`, `set`, `has`, `toPrettyJson`). + +--- + +## Background + +`ai.yaml` already declares MCP servers, grouped like every other install list: + +```yaml +mcp: + shared: + - { name: grep, transport: http, url: https://mcp.grep.app } + personal: + - { name: tavily, transport: http, url: https://mcp.tavily.com/mcp/ } + work: + - { name: figma, transport: http, url: https://mcp.figma.com/mcp } + - { name: jira, transport: http, url: https://mcp.atlassian.com/v1/mcp } +``` + +`lib/chezmoi/active-group-values.json.tmpl` flattens the active groups (always `shared`; plus `personal` and/or `work`) into one list. On a personal/`claude-code` machine that yields `grep, tavily`; on a work/`github-copilot` machine it yields `grep, figma, jira`. The spec's routing (tavily to Claude only, figma and jira to Copilot only, grep to both) is the natural result of the group-to-target mapping. + +Confirmed target schemas: + +- Claude Code: `claude mcp add --scope user --transport http `. Idempotent guard: `claude mcp get `. +- Copilot CLI (`~/.copilot/mcp-config.json`): `{ "mcpServers": { "": { "type": "http", "url": "", "tools": ["*"] } } }`. +- VS Code Copilot (`~/Library/Application Support/Code/User/mcp.json`): `{ "servers": { "": { "type": "http", "url": "" } } }`. + +## File Structure + +**Create:** + +- `home/.chezmoitemplates/lib/install/ai-mcp.sh` — Claude-only MCP registration library, decomposed into small functions, mirroring `ai-skills.sh`. +- `tests/unit/lib/install/ai-mcp.bats` — behavior tests for the library, mirroring `ai-plugins.bats`. +- `home/.chezmoiscripts/darwin/run_onchange_08_install-ai-mcp.sh.tmpl` — renders the active Claude MCP list and injects the library. +- `home/dot_copilot/mcp-config.json.tmpl` — Copilot CLI MCP config, rendered from `ai.mcp`. +- `home/Library/Application Support/Code/User/mcp.json.tmpl` — VS Code Copilot MCP config, rendered from `ai.mcp`. +- `tests/template/mcp-config.bats` — render tests for the two declarative Copilot configs. + +**Rename (honor "graphify at the end"):** + +- `home/.chezmoiscripts/darwin/run_onchange_08_install-graphify-skills.sh.tmpl` -> `run_onchange_09_install-graphify-skills.sh.tmpl` (and its `@file` comment). + +**Modify:** + +- `tests/template/darwin-install-scripts.bats` — repoint `GRAPHIFY_TEMPLATE` to `_09_`; add the MCP run-script constant and tests. +- Delete: `home/dot_copilot/mcp-config.json` and `home/Library/Application Support/Code/User/mcp.json` (replaced by their `.tmpl` versions). + +## Design Notes + +- The Claude run script injects `log.sh` and `install-prelude.sh` before the library, matching every sibling install script (standardized scaffold). `ai-mcp.sh` itself only calls `log_*`, so its self-exec guard sources `log.sh` only, like `ai-plugins.sh`. +- The MCP run script gates on `has "claude-code" $activeTargets`, not merely `len > 0`, because Copilot MCP is delivered by the declarative templates, not this script. +- The Copilot templates always apply; when `github-copilot` is not an active target they render an empty server map, matching today's static `{}` files. + +--- + +### Task 1: Rename the Graphify script to slot 09 + +**Files:** + +- Rename: `home/.chezmoiscripts/darwin/run_onchange_08_install-graphify-skills.sh.tmpl` -> `home/.chezmoiscripts/darwin/run_onchange_09_install-graphify-skills.sh.tmpl` +- Modify: the renamed file's `@file` line +- Modify: `tests/template/darwin-install-scripts.bats:11` + +- [ ] **Step 1: Rename the script file** + +```bash +git mv "home/.chezmoiscripts/darwin/run_onchange_08_install-graphify-skills.sh.tmpl" \ + "home/.chezmoiscripts/darwin/run_onchange_09_install-graphify-skills.sh.tmpl" +``` + +- [ ] **Step 2: Update the `@file` comment inside the renamed file** + +Change line 2 from: + +```bash +# @file run_onchange_08_install-graphify-skills.sh +``` + +to: + +```bash +# @file run_onchange_09_install-graphify-skills.sh +``` + +- [ ] **Step 3: Repoint the test constant** + +In `tests/template/darwin-install-scripts.bats` line 11, change: + +```bash +GRAPHIFY_TEMPLATE="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_08_install-graphify-skills.sh.tmpl" +``` + +to: + +```bash +GRAPHIFY_TEMPLATE="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_09_install-graphify-skills.sh.tmpl" +``` + +- [ ] **Step 4: Run the template tests to verify the rename is clean** + +Run: `mise exec -- bats tests/template/darwin-install-scripts.bats` +Expected: all tests PASS (Graphify inject, render, and empty tests still green). + +- [ ] **Step 5: Commit** + +```bash +git add home/.chezmoiscripts/darwin/run_onchange_09_install-graphify-skills.sh.tmpl tests/template/darwin-install-scripts.bats +git commit -m "refactor: renumber graphify install script to 09" +``` + +--- + +### Task 2: MCP registration library with unit tests (TDD) + +**Files:** + +- Test: `tests/unit/lib/install/ai-mcp.bats` +- Create: `home/.chezmoitemplates/lib/install/ai-mcp.sh` + +- [ ] **Step 1: Write the failing unit tests** + +Create `tests/unit/lib/install/ai-mcp.bats`: + +```bash +#!/usr/bin/env bats +# @file tests/unit/lib/install/ai-mcp.bats +# @brief Behavior tests for home/.chezmoitemplates/lib/install/ai-mcp.sh. + +load '../../../test_helpers/load.bash' + +LOG_LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/common/log.sh" +LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/install/ai-mcp.sh" + +setup() { + export CLAUDE_ARGS_FILE="$BATS_TEST_TMPDIR/claude-args" + mkdir -p "$BATS_TEST_TMPDIR/bin" + + # Default stub: `mcp get` reports "not found" (exit 1) so `mcp add` runs. + cat >"$BATS_TEST_TMPDIR/bin/claude" <<'CLAUDE' +#!/usr/bin/env bash +printf '%s\n' "$*" | tee -a "$CLAUDE_ARGS_FILE" +[[ "$1 $2" == "mcp get" ]] && exit 1 +exit "${CLAUDE_EXIT_CODE:-0}" +CLAUDE + chmod +x "$BATS_TEST_TMPDIR/bin/claude" + + export PATH="$BATS_TEST_TMPDIR/bin:/usr/bin:/bin" +} + +_run_main() { + run bash -c "source '$LOG_LIB' && source '$LIB' && $1 && main" +} + +@test "main: registers each server with user scope and transport" { + _run_main "AI_MCP=('grep|http|https://mcp.grep.app' 'tavily|http|https://mcp.tavily.com/mcp/')" + + assert_success + assert_line "[ai-mcp] Registering MCP servers..." + assert_line "[ai-mcp] MCP servers registered." + assert_line "mcp add --scope user --transport http grep https://mcp.grep.app" + assert_line "mcp add --scope user --transport http tavily https://mcp.tavily.com/mcp/" +} + +@test "main: skips a server that is already registered" { + cat >"$BATS_TEST_TMPDIR/bin/claude" <<'CLAUDE' +#!/usr/bin/env bash +printf '%s\n' "$*" | tee -a "$CLAUDE_ARGS_FILE" +# grep already exists; tavily does not. +[[ "$1 $2" == "mcp get" && "$3" == "grep" ]] && exit 0 +[[ "$1 $2" == "mcp get" ]] && exit 1 +exit 0 +CLAUDE + chmod +x "$BATS_TEST_TMPDIR/bin/claude" + + _run_main "AI_MCP=('grep|http|https://mcp.grep.app' 'tavily|http|https://mcp.tavily.com/mcp/')" + + assert_success + assert_line "[ai-mcp] grep already registered; skipping." + refute_line "mcp add --scope user --transport http grep https://mcp.grep.app" + assert_line "mcp add --scope user --transport http tavily https://mcp.tavily.com/mcp/" +} + +@test "main: exits cleanly with no servers" { + _run_main "AI_MCP=()" + + assert_success + assert_line "[ai-mcp] No MCP servers to register." + [ ! -s "$CLAUDE_ARGS_FILE" ] +} + +@test "main: skips empty server entries" { + _run_main "AI_MCP=('grep|http:x' '' 'jira|http|https://mcp.atlassian.com/v1/mcp')" + + assert_success + assert_line "mcp add --scope user --transport http jira https://mcp.atlassian.com/v1/mcp" +} + +@test "main: warns but succeeds when a server fails to register" { + cat >"$BATS_TEST_TMPDIR/bin/claude" <<'CLAUDE' +#!/usr/bin/env bash +printf '%s\n' "$*" | tee -a "$CLAUDE_ARGS_FILE" +[[ "$1 $2" == "mcp get" ]] && exit 1 +[[ "$1 $2" == "mcp add" ]] && exit 1 +exit 0 +CLAUDE + chmod +x "$BATS_TEST_TMPDIR/bin/claude" + + _run_main "AI_MCP=('grep|http|https://mcp.grep.app')" + + assert_success + assert_line "warn: [ai-mcp] 1 server(s) failed to register:" + assert_line " - grep" + assert_line "[ai-mcp] MCP servers registered." +} + +@test "main: fails when claude is missing" { + rm -f "$BATS_TEST_TMPDIR/bin/claude" + # Drop /usr/bin so a host-installed claude cannot mask the missing path; /bin still provides bash. + export PATH="$BATS_TEST_TMPDIR/bin:/bin" + _run_main "AI_MCP=('grep|http|https://mcp.grep.app')" + + assert_failure 1 + assert_line "error: [ai-mcp] claude CLI not found. Ensure Claude Code is installed." +} +``` + +- [ ] **Step 2: Run the tests to verify they fail** + +Run: `mise exec -- bats tests/unit/lib/install/ai-mcp.bats` +Expected: FAIL (the library file does not exist yet). + +- [ ] **Step 3: Write the library** + +Create `home/.chezmoitemplates/lib/install/ai-mcp.sh`: + +```bash +#!/usr/bin/env bash +# @file lib/install/ai-mcp.sh +# @brief Register cross-agent MCP servers on Claude Code. +# @description +# Runs `claude mcp add` for each server in AI_MCP, scoped to the user. A +# `claude mcp get` check keeps the flow idempotent by skipping a server that +# is already registered. Copilot MCP arrives through rendered config files, +# so this library targets Claude Code only. Sourceable from bats tests and +# injected into chezmoi run scripts via chezmoi template rendering. + +set -Eeuo pipefail + +# +# @description True when AI_MCP holds at least one non-empty entry. +# @exitcode 0 A server is present. +# @exitcode 1 No servers. +# +function _ai_mcp_have_any() { + local entry + for entry in "${AI_MCP[@]-}"; do + [[ -z "${entry}" ]] && continue + return 0 + done + return 1 +} + +# +# @description Warn about the MCP servers that failed to register. +# @arg $@ string Names of the servers that failed. +# +function _ai_mcp_report_failures() { + log_warn "[ai-mcp] ${#} server(s) failed to register:" + printf ' - %s\n' "$@" >&2 +} + +# +# @description Register each MCP server on Claude Code, skipping existing ones. +# @exitcode 0 Registered, or nothing to do. +# @exitcode 1 The claude CLI is missing. +# +function ai_mcp_install_main() { + if ! _ai_mcp_have_any; then + log_info "[ai-mcp] No MCP servers to register." + return 0 + fi + + if ! command -v claude >/dev/null 2>&1; then + log_error "[ai-mcp] claude CLI not found. Ensure Claude Code is installed." + return 1 + fi + + log_info "[ai-mcp] Registering MCP servers..." + + local -a failed=() + local entry name transport url rest + for entry in "${AI_MCP[@]-}"; do + [[ -z "${entry}" ]] && continue + name="${entry%%|*}" + rest="${entry#*|}" + transport="${rest%%|*}" + url="${rest##*|}" + + if claude mcp get "${name}" >/dev/null 2>&1; then + log_info "[ai-mcp] ${name} already registered; skipping." + continue + fi + + claude mcp add --scope user --transport "${transport}" "${name}" "${url}" || failed+=("${name}") + done + + if ((${#failed[@]} > 0)); then + _ai_mcp_report_failures "${failed[@]}" + fi + + log_info "[ai-mcp] MCP servers registered." +} + +# +# @description Run the AI MCP registration flow. +# +function main() { + ai_mcp_install_main +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + # When run directly, pull in the shared log library that chezmoi otherwise + # concatenates ahead of this file. + # shellcheck source=/dev/null + command -v log_info >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../common/log.sh" + main +fi +``` + +- [ ] **Step 4: Run the tests to verify they pass** + +Run: `mise exec -- bats tests/unit/lib/install/ai-mcp.bats` +Expected: all PASS. + +- [ ] **Step 5: Commit** + +```bash +git add home/.chezmoitemplates/lib/install/ai-mcp.sh tests/unit/lib/install/ai-mcp.bats +git commit -m "feat: add MCP registration library for claude code" +``` + +--- + +### Task 3: Claude MCP run script with template tests + +**Files:** + +- Create: `home/.chezmoiscripts/darwin/run_onchange_08_install-ai-mcp.sh.tmpl` +- Modify: `tests/template/darwin-install-scripts.bats` + +- [ ] **Step 1: Add the template test constant and tests** + +In `tests/template/darwin-install-scripts.bats`, add near the other template constants (after the AI plugins constant): + +```bash +AI_MCP_TEMPLATE="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_08_install-ai-mcp.sh.tmpl" +``` + +Add these tests (mirroring the AI plugins tests, using the file's existing `render_chezmoi_template`, `DARWIN_DATA`, `WORK_DATA`, and `EMPTY_AI_DATA` helpers): + +```bash +@test "darwin install script templates inject AI MCP shell library" { + assert_file_contains "$AI_MCP_TEMPLATE" '{{ template "lib/install/ai-mcp.sh" . }}' +} + +@test "darwin install script templates inject the prelude before AI MCP" { + local prelude + prelude='{{ template "lib/common/install-prelude.sh" . }}' + assert_file_contains "$AI_MCP_TEMPLATE" "$prelude" +} + +@test "personal AI MCP template registers shared and personal servers" { + run render_chezmoi_template "$AI_MCP_TEMPLATE" "$DARWIN_DATA" + assert_success + assert_line --partial '"grep|http|https://mcp.grep.app"' + assert_line --partial '"tavily|http|https://mcp.tavily.com/mcp/"' +} + +@test "work AI MCP template registers no Claude servers" { + run render_chezmoi_template "$AI_MCP_TEMPLATE" "$WORK_DATA" + assert_success + refute_line --partial 'lib/install/ai-mcp.sh' +} + +@test "empty AI target groups render no AI MCP commands" { + run render_chezmoi_template "$AI_MCP_TEMPLATE" "$EMPTY_AI_DATA" + assert_success + refute_line --partial 'lib/install/ai-mcp.sh' +} +``` + +Note: the "work" render produces no Claude MCP block because the gate requires `claude-code` in the active targets; a work machine targets `github-copilot`, so the `if` is false and the library is not injected. + +- [ ] **Step 2: Run the new tests to verify they fail** + +Run: `mise exec -- bats tests/template/darwin-install-scripts.bats` +Expected: the five new tests FAIL (template file does not exist yet). + +- [ ] **Step 3: Create the run script template** + +Create `home/.chezmoiscripts/darwin/run_onchange_08_install-ai-mcp.sh.tmpl`: + +```bash +#!/usr/bin/env bash +# @file run_onchange_08_install-ai-mcp.sh +# @brief Register cross-agent MCP servers. +# @description AI data hash: {{ include ".chezmoidata/ai.yaml" | sha256sum }} +{{- $activeTargets := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.targets) | fromJson -}} +{{- $activeMcp := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.mcp) | fromJson -}} + +{{ if and (eq .chezmoi.os "darwin") (has "claude-code" $activeTargets) (gt (len $activeMcp) 0) }} +{{ template "lib/common/log.sh" . }} +{{ template "lib/common/install-prelude.sh" . }} +AI_MCP=( +{{ range $activeMcp -}} + "{{ .name }}|{{ .transport }}|{{ .url }}" +{{ end -}} +) +{{ template "lib/install/ai-mcp.sh" . }} +{{ end }} +``` + +- [ ] **Step 4: Run the tests to verify they pass** + +Run: `mise exec -- bats tests/template/darwin-install-scripts.bats` +Expected: all PASS. + +- [ ] **Step 5: Commit** + +```bash +git add home/.chezmoiscripts/darwin/run_onchange_08_install-ai-mcp.sh.tmpl tests/template/darwin-install-scripts.bats +git commit -m "feat: add claude code MCP install run script" +``` + +--- + +### Task 4: Copilot MCP config templates with render tests + +**Files:** + +- Create: `home/dot_copilot/mcp-config.json.tmpl` +- Create: `home/Library/Application Support/Code/User/mcp.json.tmpl` +- Delete: `home/dot_copilot/mcp-config.json`, `home/Library/Application Support/Code/User/mcp.json` +- Create: `tests/template/mcp-config.bats` + +- [ ] **Step 1: Write the failing render tests** + +Create `tests/template/mcp-config.bats`: + +```bash +#!/usr/bin/env bats +# @file tests/template/mcp-config.bats +# @brief Render tests for the Copilot MCP config templates. + +load '../test_helpers/load.bash' +load '../test_helpers/templates.bash' + +COPILOT_MCP="$DOTFILES_ROOT/home/dot_copilot/mcp-config.json.tmpl" +VSCODE_MCP="$DOTFILES_ROOT/home/Library/Application Support/Code/User/mcp.json.tmpl" + +# WORK_DATA and PERSONAL_DATA come from templates.bash; the real ai.yaml supplies +# .ai (targets and mcp) because chezmoi merges --override-data over .chezmoidata. +# A work machine targets github-copilot and gets shared + work MCP servers; a +# personal machine has no github-copilot target, so the maps render empty. + +@test "work Copilot CLI MCP config renders shared and work servers" { + run render_chezmoi_template "$COPILOT_MCP" "$WORK_DATA" + assert_success + assert_line --partial '"mcpServers"' + assert_line --partial '"grep"' + assert_line --partial '"figma"' + assert_line --partial '"jira"' + assert_line --partial 'https://mcp.figma.com/mcp' + assert_line --partial '"tools"' +} + +@test "personal Copilot CLI MCP config renders an empty server map" { + run render_chezmoi_template "$COPILOT_MCP" "$PERSONAL_DATA" + assert_success + assert_line --partial '"mcpServers": {}' +} + +@test "work VS Code MCP config renders shared and work servers" { + run render_chezmoi_template "$VSCODE_MCP" "$WORK_DATA" + assert_success + assert_line --partial '"servers"' + assert_line --partial '"grep"' + assert_line --partial '"jira"' + assert_line --partial 'https://mcp.atlassian.com/v1/mcp' +} + +@test "personal VS Code MCP config renders an empty server map" { + run render_chezmoi_template "$VSCODE_MCP" "$PERSONAL_DATA" + assert_success + assert_line --partial '"servers": {}' +} +``` + +- [ ] **Step 2: Run the tests to verify they fail** + +Run: `mise exec -- bats tests/template/mcp-config.bats` +Expected: FAIL (template files do not exist yet). + +- [ ] **Step 3: Create the Copilot CLI template and remove the static file** + +```bash +git rm home/dot_copilot/mcp-config.json +``` + +Create `home/dot_copilot/mcp-config.json.tmpl`: + +``` +{{- $activeTargets := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.targets) | fromJson -}} +{{- $activeMcp := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.mcp) | fromJson -}} +{{- $servers := dict -}} +{{- if has "github-copilot" $activeTargets -}} +{{- range $activeMcp -}} +{{- $_ := set $servers .name (dict "type" .transport "url" .url "tools" (list "*")) -}} +{{- end -}} +{{- end -}} +{{ dict "mcpServers" $servers | toPrettyJson }} +``` + +- [ ] **Step 4: Create the VS Code template and remove the static file** + +```bash +git rm "home/Library/Application Support/Code/User/mcp.json" +``` + +Create `home/Library/Application Support/Code/User/mcp.json.tmpl`: + +``` +{{- $activeTargets := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.targets) | fromJson -}} +{{- $activeMcp := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.mcp) | fromJson -}} +{{- $servers := dict -}} +{{- if has "github-copilot" $activeTargets -}} +{{- range $activeMcp -}} +{{- $_ := set $servers .name (dict "type" .transport "url" .url) -}} +{{- end -}} +{{- end -}} +{{ dict "servers" $servers | toPrettyJson }} +``` + +- [ ] **Step 5: Run the tests to verify they pass** + +Run: `mise exec -- bats tests/template/mcp-config.bats` +Expected: all PASS. + +- [ ] **Step 6: Commit** + +```bash +git add home/dot_copilot/mcp-config.json.tmpl "home/Library/Application Support/Code/User/mcp.json.tmpl" tests/template/mcp-config.bats +git add -u home/dot_copilot "home/Library/Application Support/Code/User" +git commit -m "feat: render copilot MCP config from ai.yaml" +``` + +--- + +### Task 5: Full verification and apply + +**Files:** none (verification only) + +- [ ] **Step 1: Run the full check suite** + +Run: `mise run check` +Expected: treefmt clean and all bats tests PASS. + +- [ ] **Step 2: Preview the apply on this (personal) machine** + +Run: `chezmoi diff --source home | rg -A3 -i 'mcp'` +Expected: `~/.copilot/mcp-config.json` shows `"mcpServers": {}` (unchanged), `~/Library/.../Code/User/mcp.json` shows `"servers": {}`, and the new `run_onchange_08_install-ai-mcp` script is queued. + +- [ ] **Step 3: Apply** + +Run: `mise update` +Expected: the run script registers `grep` and `tavily` on Claude Code (or skips them if already present). Confirm with `claude mcp list`. + +- [ ] **Step 4: Confirm idempotency** + +Run: `mise update` a second time. +Expected: the MCP script logs `already registered; skipping.` for each server and adds nothing. + +- [ ] **Step 5: Verify the Linear issue and open the PR** + +Ensure a Linear `DOT-*` issue tracks Phase 3, move it to `In Review`, and open the PR from the `DOT-*` branch with `Refs DOT-XXX` in the body. + +## Self-Review + +- **Spec coverage:** Claude `claude mcp add` script (spec "MCP Design"), Copilot CLI `mcp-config.json` and VS Code `mcp.json` templates (spec lines 143 to 148), `tavily` personal to Claude only and `grep` to both (spec line 212) — all covered by the group-to-target gating. +- **Type consistency:** the `name|transport|url` field order is identical in the run-script render (`"{{ .name }}|{{ .transport }}|{{ .url }}"`) and the library parse (`%%|`, `#*|`, `##*|`). The library function names (`_ai_mcp_have_any`, `_ai_mcp_report_failures`, `ai_mcp_install_main`, `main`) match between the library and its tests. +- **Placeholder scan:** every step contains the actual file content or command; no TBDs. diff --git a/docs/superpowers/specs/2026-06-30-apm-to-claude-marketplace-migration-design.md b/docs/superpowers/specs/2026-06-30-apm-to-claude-marketplace-migration-design.md new file mode 100644 index 0000000..6356b87 --- /dev/null +++ b/docs/superpowers/specs/2026-06-30-apm-to-claude-marketplace-migration-design.md @@ -0,0 +1,235 @@ +# APM to Cross-Agent AI Tooling Migration Design + +**Status:** Approved for implementation. + +**Issue:** [DOT-43](https://linear.app/edwinhern/issue/DOT-43/migrate-personal-ai-setup-from-apm-to-claude-plugin-marketplaces) + +## Goal + +Make the dotfiles repo the single source of truth for AI tooling across Claude Code and GitHub Copilot. Remove APM. Deliver skills through the cross-agent `skills` CLI (skills.sh), reserve Claude plugins for hook-bearing bundles, and apply MCP servers to every agent. Drive all of it from group and agent scoped data, the way APM targets worked. + +## Background + +APM did three jobs: fetch skill and plugin source, pin versions, and wire runtime into `~/.claude`. Its partial caveman hook install caused broken hook paths. APM was also chosen to keep instructions agnostic across agents. The `skills` CLI now installs one skill into multiple agents from a single command, native `AGENTS.md` covers cross-agent instructions, and chezmoi already renders per group. Personal use is Claude Code; work use is Copilot in VS Code. + +## Delivery Model + +The deciding question for each artifact is: does it need a hook, statusline, or slash command on Claude? + +| Artifact | Definition | Delivery | Cross-agent | +| ---------- | ---------------------------------------------------------------------------------------- | -------------------- | ----------------------------------------- | +| Skill | Instructions or knowledge, no runtime | `npx skills add` | One command, both agents | +| Plugin | Bundle with hooks, statusline, slash commands, or packaged agents that only Claude loads | `claude plugin` | Claude only; Copilot uses skills or rules | +| MCP server | A tool or data backend spoken over MCP | Per-agent MCP config | Write each agent's config | + +Plugins are reserved for hook-bearing bundles. Everything that is only a skill goes through the `skills` CLI to the targeted agents. MCP servers are written into each targeted agent's config. + +## Group and Target Data Model + +`home/.chezmoidata/ai.yaml` declares what to install, replacing `home/.chezmoidata/apm.yaml`. It carries a `targets` map that binds each group to the AI providers it targets, mirroring APM targets. The active group per machine comes from the existing `active-groups` template, and the OS gate stays `darwin`, matching the other install scripts. + +Group membership implies the agent through `targets`, so no per-item agent field is needed: `personal` targets `claude-code`, `work` targets `github-copilot`, and `shared` items install for whatever the active machine targets. + +```yaml +ai: + targets: + shared: [] + personal: + - claude-code + work: + - github-copilot + skills: + shared: + - source: mattpocock/skills + skill: grill-me + - source: juliusbrussee/skills + skill: junior-to-senior + - source: blader/humanizer + skill: humanizer + - source: imadAttar/kaizen + skill: kaizen + # local domain skills authored in this repo + - source: local + skill: typescript + - source: local + skill: react + - source: local + skill: testing + personal: + - source: schpet/linear-cli + skill: linear-cli + - source: juliusbrussee/skills + skill: interface-kit + - source: upstash/context7 + skill: context7-cli + work: [] + plugins: + shared: + - name: caveman + marketplace: caveman + source: JuliusBrussee/caveman + - name: superpowers + marketplace: superpowers-dev + source: obra/superpowers + mcp: + shared: + - name: grep + transport: http + url: https://mcp.grep.app + personal: + - name: tavily + transport: http + url: https://mcp.tavily.com/mcp/ + work: + - name: figma + transport: http + url: https://mcp.figma.com/mcp + - name: jira + transport: http + url: https://mcp.atlassian.com/v1/mcp +``` + +Personal machines install shared plus personal entries against `claude-code`; work machines install shared plus work entries against `github-copilot`. `linear-cli`, `interface-kit`, and `context7-cli` sit in `personal`, so they reach Claude only until Copilot is added to the personal target. `skill-creator` is dropped. `tavily` MCP is personal, so Claude only. `deep-analysis` is deferred to the work Copilot setup and is not listed until that work starts. + +## Skills Design + +Skills install with the cross-agent CLI. For each entry the install script runs: + +```bash +npx --yes skills add --skill -a [-a ] --global --copy --yes +``` + +The `npx --yes` prefix auto-confirms npx's own install prompt so the non-interactive `chezmoi apply` never hangs. + +`--global` installs into `~/.claude/skills/` and `~/.copilot/skills/`. `--copy` writes real files rather than symlinks because Claude Code ignores symlinked skill directories. The `-a` flags come from the active group's `target` in `ai.yaml`, so a skill in `shared` installs for the machine's target, and a skill in `personal` installs for `claude-code`. + +Local domain skills `typescript`, `react`, and `testing` (React Testing Library guidance lives in the `testing` skill) are authored under `home/.chezmoitemplates/skills/` from the former instruction files, and install with `source: local` pointing at that path. The previously vendored `grill-me` and `junior-to-senior` copies under that directory have been removed; both install from their upstream sources. + +The `skills` CLI has no manifest file, so `home/.chezmoidata/ai.yaml` is the manifest. A `run_onchange_07_install-ai-skills.sh.tmpl` script gates on `darwin`, reads the active group and its target, iterates the matching skill entries, and runs the CLI. Its trigger comment hashes both `ai.yaml` and the local skill tree under `home/.chezmoitemplates/skills/`, so it re-runs when the manifest changes or when a local `SKILL.md` is edited in place (local skills install with `--copy`, so a stale copy would otherwise persist). + +## Plugins Design + +Plugins are hook-bearing bundles: `caveman` and `superpowers`. They live in `shared`, so each installs by the path that matches the active machine's `target`. + +- `claude-code` target: through the marketplace. + + ```bash + claude plugin marketplace add + claude plugin install @ + ``` + +- `github-copilot` target: through the cross-agent skills CLI, since Copilot has no plugin or hook system. + + ```bash + npx skills add -a github-copilot --global --copy --yes + ``` + +`run_onchange_06_install-claude-plugins.sh.tmpl` gates on `darwin`, reads the active target, and runs the matching path for each plugin entry. This replaces `run_onchange_06_install-apm.sh.tmpl`. Marketplaces and enabled plugins are also declared in `home/dot_claude/settings.json`. + +Caveman installs as a plugin on Claude, so Claude Code loads its hooks from the plugin directory. The manual caveman and superpowers hook entries stay removed from `settings.json`. On the Copilot side the plugins install as skills only, because Copilot has no hooks; the Copilot path runs only on a machine where Copilot is present. + +## MCP Design + +MCP delivery avoids templating any main `settings.json`, so JSON schema IntelliSense and hover help stay intact. Each agent uses its own dedicated MCP surface, and both are driven by the `ai.yaml` `mcp` block scoped by the active group's target. + +Claude Code MCP is applied with the official CLI at user scope, which merges into the stateful `~/.claude.json` without overwriting other state: + +```bash +claude mcp add --scope user --transport http grep https://mcp.grep.app +``` + +A `run_onchange` script gates on `darwin`, reads the active target, iterates the matching MCP entries, and runs `claude mcp add` for each. It is idempotent: it checks `claude mcp get ` and skips or re-adds as needed. The stateful `~/.claude.json` is never templated. + +Copilot MCP uses dedicated MCP config files, which are safe to render as chezmoi templates because they are separate from the main VS Code `settings.json`: + +- VS Code: `home/Library/Application Support/Code/User/mcp.json`. +- Copilot CLI: `home/dot_copilot/mcp-config.json`. + +Each template reads the `ai.yaml` `mcp` entries for the active target and renders them into that surface's schema. `tavily` is personal, so Claude only. Work MCP servers use the HTTP transport with the vendor's MCP URL, including Jira at `https://mcp.atlassian.com/v1/mcp`, and authenticate over OAuth at connect time rather than a stored token. Any remaining secret values use `${VAR}` placeholders kept out of source control, sourced from `~/.secrets.local` on the work machine. + +## Instructions Design + +One canonical `home/.chezmoitemplates/AGENTS.md` holds universal behavioral guidance only: AI guidance, git and pull request workflow, commit message rules, and the `gh` CLI standard. It does not assert a frontend persona. Frontend specifics live in the `typescript`, `react`, and `testing` skills. + +`AGENTS.md` renders to `~/.claude/CLAUDE.md` for Claude Code and to the Copilot instructions path for VS Code Copilot. GitHub MCP references and the `context7` tool mention are removed from the meta rules content; GitHub operations use `gh`. The `coding-standards` foundations fold into the local domain skills, and the TypeScript specifics into the `typescript` skill. + +## Commit Message Instructions + +VS Code Source Control commit generation reads `github.copilot.chat.commitMessageGeneration.instructions` in `home/Library/Application Support/Code/User/settings.json`. That file stays plain managed JSON, not a chezmoi template, to keep JSON schema IntelliSense and hover help. + +The two generic entries are replaced with the `commit-guide` rules as inline `{ "text": ... }` entries: conventional commit format, imperative mood, subject under 50 characters, optional one-line body, no test plans. The workspace-relative `{ "file": ... }` form is not used because it has no global equivalent. The small duplication with the `AGENTS.md` git section is accepted to preserve IntelliSense. This shapes only the generated message, which is reviewed in Source Control. It does not automate committing or pushing. + +## Work and Copilot Design + +Work Copilot tooling is organized now and wired later. + +``` +home/Library/Application Support/Code/User/mcp.json # work MCP, from ai.yaml +home/dot_copilot/skills/ # Copilot skills, from skills CLI +home/dot_copilot/agents/ # code-reviewer, ts-reviewer, research +home/dot_copilot/mcp-config.json # Copilot CLI MCP, from ai.yaml +``` + +The custom agents move to the Copilot side because they are frontend and work focused. Personal Claude keeps no custom agents and relies on plugin-provided agents such as the caveman reviewer. `deep-analysis` joins the work Copilot skills when that setup starts. + +## APM Teardown + +The apm CLI stays installed for global use. Only apm's dependency-management layer is retired. + +Keep: + +- The apm CLI entry in `home/.chezmoidata/mise.yaml` (`github:microsoft/apm`, shared group). +- The `Bash(apm:*)` permission in `home/dot_claude/settings.json`. + +Remove the dependency-management layer: + +- `mise.toml` `[tasks.validate-apm]` and `[tasks.refresh-apm-locks]` (their `scripts/*.sh` no longer exist). +- `tests/template/install-apm-script.bats` (renders the already-removed apm script; would fail). +- `.gitignore` `apm_modules/` entry (optional; apm dependency artifact). +- Runtime `~/.apm` dependency modules and stale `~/.claude/apm-hooks.json`. + +Already removed by prior cleanup: `home/.chezmoidata/apm.yaml`, `home/dot_apm/`, `home/.chezmoitemplates/apm/`, `home/.chezmoitemplates/lib/install/apm.sh`, `home/.chezmoiscripts/darwin/run_onchange_06_install-apm.sh.tmpl`, and the apm entry from `mise.toml [tools]`. The caveman hook and reassert scripts were removed earlier on this branch. + +## Graphify Script Repoint + +`run_onchange_08_install-graphify-skills.sh.tmpl` still reads retired apm data and currently breaks `chezmoi apply` on render: + +- Line 4 `include ".chezmoidata/apm.yaml"` references the deleted data file, which hard-errors. +- Line 5 reads `.apm.targets`. + +Repoint it off apm data onto the new `ai` data: hash `.chezmoidata/ai.yaml` in the trigger comment, resolve targets from `.ai.targets`, and update the platform mapping so the graphify platforms are derived from the `ai` target ids (`claude-code`, `github-copilot`) rather than the old apm values (`agent-skills`, `claude`). This repoint is a prerequisite for a clean `chezmoi apply` and belongs to this migration. + +## Components Kept As Is + +The manual caveman and superpowers hook entries stay removed from `settings.json`, because the caveman plugin supplies its hooks. The `.chezmoitemplates/lib/` layout stays as-is: chezmoi imposes no internal structure on `.chezmoitemplates/` and there is no community convention, and the directory already separates templating helpers (`lib/chezmoi/`) from shell libraries (`lib/{common,darwin,install}/`). + +## Testing and Verification + +- `chezmoi apply` on a clean state succeeds. +- Template tests verify personal rendering includes shared plus personal entries and excludes work entries, and work rendering includes shared plus work entries. +- After apply, `~/.claude/skills` and `~/.copilot/skills` contain the group-targeted skills; Claude-only skills like `linear-cli` are absent from `~/.copilot/skills`. +- `claude plugin list` shows caveman and superpowers. +- `grep` MCP is registered for both Claude and Copilot; `tavily` MCP is registered for Claude only. +- The VS Code commit button produces a message in the configured style. +- A new session `/doctor` reports no hook or settings errors. +- `command -v apm` still resolves (the CLI is kept), while the apm dependency modules under `~/.apm` are absent. +- The bats suite passes, including tests for the new install scripts. + +## Implementation Phases + +0. Repoint `run_onchange_08_install-graphify-skills.sh.tmpl` off retired apm data onto `ai` data, restoring a clean `chezmoi apply`. +1. Add `home/.chezmoidata/ai.yaml` and the skills install script, so Claude and Copilot get skills. +2. Add the plugins install script for caveman and superpowers. +3. Add the MCP install script and apply shared MCP to both agents. +4. Author `AGENTS.md` universal instructions and wire the Copilot commit instructions. +5. Author the local domain skills from the instruction files. +6. Organize the work and Copilot files. +7. Retire the apm dependency-management layer (dead mise tasks, apm bats test) and update tests. + +## Out of Scope + +- Wiring work Copilot MCP authentication or enabling the work skill at runtime. +- Installing caveman and superpowers into Copilot beyond the deferred setup. +- Migrating work to Claude. +- Adding a secret backend. +- Verifying live Figma or Jira MCP authentication. diff --git a/home/.chezmoi.yaml.tmpl b/home/.chezmoi.yaml.tmpl index 5fe60da..4f5ef63 100644 --- a/home/.chezmoi.yaml.tmpl +++ b/home/.chezmoi.yaml.tmpl @@ -22,6 +22,8 @@ {{- $personal = true -}} {{- end -}} +sourceDir: {{ .chezmoi.sourceDir | quote }} + data: hostname: {{ $hostname | quote }} personal: {{ $personal }} diff --git a/home/.chezmoidata/ai.yaml b/home/.chezmoidata/ai.yaml new file mode 100644 index 0000000..716a85b --- /dev/null +++ b/home/.chezmoidata/ai.yaml @@ -0,0 +1,56 @@ +ai: + targets: + shared: [] + personal: + - claude-code + work: + - github-copilot + skills: + shared: + - source: mattpocock/skills + skill: grill-me + - source: juliusbrussee/skills + skill: junior-to-senior + - source: blader/humanizer + skill: humanizer + - source: neolabhq/context-engineering-kit + skill: kaizen + # local domain skills authored in this repo + - source: local + skill: typescript + - source: local + skill: react + - source: local + skill: testing + personal: + - source: schpet/linear-cli + skill: linear-cli + - source: juliusbrussee/skills + skill: interface-kit + - source: upstash/context7 + skill: context7-cli + work: [] + plugins: + shared: + - name: caveman + marketplace: caveman + source: JuliusBrussee/caveman + - name: superpowers + marketplace: superpowers-dev + source: obra/superpowers + mcp: + shared: + - name: grep + transport: http + url: https://mcp.grep.app + personal: + - name: tavily + transport: http + url: https://mcp.tavily.com/mcp/ + work: + - name: figma + transport: http + url: https://mcp.figma.com/mcp + - name: jira + transport: http + url: https://mcp.atlassian.com/v1/mcp diff --git a/home/.chezmoidata/apm.yaml b/home/.chezmoidata/apm.yaml deleted file mode 100644 index 247efd5..0000000 --- a/home/.chezmoidata/apm.yaml +++ /dev/null @@ -1,47 +0,0 @@ -apm: - targets: - shared: [] - personal: - - claude - - agent-skills - work: - - copilot - dependencies: - shared: - - obra/superpowers#v6.0.3 - - JuliusBrussee/caveman#v1.9.0 - - anthropics/claude-plugins-official/plugins/skill-creator#main - - personal: - - schpet/linear-cli#v2.0.0 - - tavily-ai/skills#main - - git: JuliusBrussee/skills#main - skills: - - grill-me - - junior-to-senior - - work: [] - - mcp: - shared: - - name: grep - registry: false - transport: http - url: https://mcp.grep.app - - personal: - - name: tavily - registry: false - transport: http - url: https://mcp.tavily.com/mcp/ - - work: - - name: figma - registry: false - transport: http - url: https://mcp.figma.com/mcp - - - name: jira - registry: false - transport: http - url: https://mcp.atlassian.com/v1/mcp diff --git a/home/.chezmoidata/packages.yaml b/home/.chezmoidata/packages.yaml index 5fe78ab..6220861 100644 --- a/home/.chezmoidata/packages.yaml +++ b/home/.chezmoidata/packages.yaml @@ -2,7 +2,7 @@ # Single source of truth for all package manager declarations. # Referenced by run_onchange_ scripts via Chezmoi template data (.packages.*). # -# Each group under darwin.homebrew has the same shape: {formulas, casks, mas}. +# Each group under darwin.homebrew has the same shape: {taps, formulas, casks, mas}. # `shared` always applies; `personal` or `work` is added based on host context. packages: @@ -23,13 +23,6 @@ packages: - zoxide - fd - fzf - - lazygit - - neovim - - tree-sitter-cli - - imagemagick - - ghostscript - - tectonic - - mermaid-cli casks: # fonts - font-monaspace @@ -44,17 +37,15 @@ packages: mas: [] personal: - taps: - - anomalyco/tap # For Opencode - formulas: - - anomalyco/tap/opencode + taps: [] + formulas: [] casks: - - aldente - claude-code - cleanshot - discord - microsoft-excel - synology-drive + - tailscale-app mas: - name: Klack id: 6446206067 diff --git a/home/.chezmoidata/uv.yaml b/home/.chezmoidata/uv.yaml index d02f961..437f0c5 100644 --- a/home/.chezmoidata/uv.yaml +++ b/home/.chezmoidata/uv.yaml @@ -1,7 +1,7 @@ uv: tools: - shared: [] - personal: + shared: - graphifyy + personal: - tavily-cli work: [] diff --git a/home/.chezmoidata/extensions.yaml b/home/.chezmoidata/vscode.yaml similarity index 94% rename from home/.chezmoidata/extensions.yaml rename to home/.chezmoidata/vscode.yaml index 4e6109e..4629260 100644 --- a/home/.chezmoidata/extensions.yaml +++ b/home/.chezmoidata/vscode.yaml @@ -6,7 +6,6 @@ vscode: - esbenp.prettier-vscode - usernamehw.errorlens # ui / dx - - illixion.vscode-vibrancy-continued - adpyke.codesnap - edwinhuish.better-comments-next - fosshaas.fontsize-shortcuts diff --git a/home/.chezmoiignore b/home/.chezmoiignore index d54afed..6c4d4db 100644 --- a/home/.chezmoiignore +++ b/home/.chezmoiignore @@ -1,3 +1,4 @@ -{{ if eq .chezmoi.os "darwin" -}} -{{ template "chezmoiignore.d/darwin" . }} +{{- /* GitHub Copilot config is a work-only target (see .chezmoidata/ai.yaml). */ -}} +{{- if not .work }} +.copilot {{- end }} diff --git a/home/.chezmoiscripts/darwin/run_onchange_04_install-vscode-extensions.sh.tmpl b/home/.chezmoiscripts/darwin/run_onchange_04_install-vscode-extensions.sh.tmpl index 92778b9..03873f6 100644 --- a/home/.chezmoiscripts/darwin/run_onchange_04_install-vscode-extensions.sh.tmpl +++ b/home/.chezmoiscripts/darwin/run_onchange_04_install-vscode-extensions.sh.tmpl @@ -1,26 +1,14 @@ #!/usr/bin/env bash -# extensions.yaml hash: {{ include ".chezmoidata/extensions.yaml" | sha256sum }} +# vscode.yaml hash: {{ include ".chezmoidata/vscode.yaml" | sha256sum }} +{{- $extensions := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .vscode) | fromJson -}} {{ if eq .chezmoi.os "darwin" }} {{ template "lib/common/log.sh" . }} {{ template "lib/common/install-prelude.sh" . }} VSCODE_EXTENSIONS=( - # shared - {{ range .vscode.shared -}} +{{ range $extensions -}} "{{ . }}" - {{ end }} - {{ if .personal -}} - # personal - {{ range .vscode.personal -}} - "{{ . }}" - {{ end -}} - {{- end }} - {{ if .work -}} - # work - {{ range .vscode.work -}} - "{{ . }}" - {{ end -}} - {{- end }} +{{ end -}} ) {{ template "lib/install/vscode.sh" . }} {{ end }} diff --git a/home/.chezmoiscripts/darwin/run_onchange_06_install-ai-plugins.sh.tmpl b/home/.chezmoiscripts/darwin/run_onchange_06_install-ai-plugins.sh.tmpl new file mode 100644 index 0000000..8469a73 --- /dev/null +++ b/home/.chezmoiscripts/darwin/run_onchange_06_install-ai-plugins.sh.tmpl @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# @file run_onchange_06_install-ai-plugins.sh +# @brief Install cross-agent AI plugins. +# @description AI data hash: {{ include ".chezmoidata/ai.yaml" | sha256sum }} +{{- $activeTargets := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.targets) | fromJson -}} +{{- $activePlugins := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.plugins) | fromJson -}} + +{{ if and (eq .chezmoi.os "darwin") (gt (len $activeTargets) 0) (gt (len $activePlugins) 0) }} +{{ template "lib/common/log.sh" . }} +{{ template "lib/common/install-prelude.sh" . }} +AI_PLUGIN_TARGETS=( +{{ range $activeTargets -}} + "{{ . }}" +{{ end -}} +) +AI_PLUGINS=( +{{ range $activePlugins -}} + "{{ .name }}|{{ .marketplace }}|{{ .source }}" +{{ end -}} +) +{{ template "lib/install/ai-plugins.sh" . }} +{{ end }} diff --git a/home/.chezmoiscripts/darwin/run_onchange_06_install-apm.sh.tmpl b/home/.chezmoiscripts/darwin/run_onchange_06_install-apm.sh.tmpl deleted file mode 100644 index 6e6035c..0000000 --- a/home/.chezmoiscripts/darwin/run_onchange_06_install-apm.sh.tmpl +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -{{ $src := .chezmoi.sourceDir }} -# apm.yml: {{ include "dot_apm/apm.yml.tmpl" | sha256sum }} -# apm data: {{ include ".chezmoidata/apm.yaml" | sha256sum }} -# apm mcp template: {{ include ".chezmoitemplates/apm/mcp-server.yml.tmpl" | sha256sum }} -# apm lock (personal): {{ include ".chezmoitemplates/apm/apm.lock.personal.yaml" | sha256sum }} -# apm lock (work): {{ include ".chezmoitemplates/apm/apm.lock.work.yaml" | sha256sum }} -# agents: {{ range (glob (printf "%s/dot_apm/dot_apm/agents/*" $src)) }}{{ include (trimPrefix (printf "%s/" $src) .) | sha256sum }} {{ end }} -# instructions: {{ range (glob (printf "%s/dot_apm/dot_apm/instructions/*" $src)) }}{{ include (trimPrefix (printf "%s/" $src) .) | sha256sum }} {{ end }} -# skills: {{ range (glob (printf "%s/dot_apm/dot_apm/skills/**/*.md" $src)) }}{{ include (trimPrefix (printf "%s/" $src) .) | sha256sum }} {{ end }} - -{{ if eq .chezmoi.os "darwin" }} -{{ template "lib/common/log.sh" . }} -{{ template "lib/common/install-prelude.sh" . }} -{{ template "lib/install/apm.sh" . }} -{{ end }} diff --git a/home/.chezmoiscripts/darwin/run_onchange_07_fix-opencode-agent-tools.sh.tmpl b/home/.chezmoiscripts/darwin/run_onchange_07_fix-opencode-agent-tools.sh.tmpl deleted file mode 100644 index 8b7ee22..0000000 --- a/home/.chezmoiscripts/darwin/run_onchange_07_fix-opencode-agent-tools.sh.tmpl +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -# apm.yml: {{ include "dot_apm/apm.yml.tmpl" | sha256sum }} - -{{ if eq .chezmoi.os "darwin" }} -{{ template "lib/common/log.sh" . }} -{{ template "lib/common/install-prelude.sh" . }} -{{ template "lib/install/opencode-agent-tools.sh" . }} -{{ end }} diff --git a/home/.chezmoiscripts/darwin/run_onchange_07_install-ai-skills.sh.tmpl b/home/.chezmoiscripts/darwin/run_onchange_07_install-ai-skills.sh.tmpl new file mode 100644 index 0000000..67c13ad --- /dev/null +++ b/home/.chezmoiscripts/darwin/run_onchange_07_install-ai-skills.sh.tmpl @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# @file run_onchange_07_install-ai-skills.sh +# @brief Install cross-agent AI skills. +# @description AI data hash: {{ include ".chezmoidata/ai.yaml" | sha256sum }}; local skills hash: {{ $h := "" }}{{ range (glob (joinPath .chezmoi.sourceDir ".chezmoitemplates" "skills" "**" "SKILL.md")) }}{{ $h = print $h (include .) }}{{ end }}{{ $h | sha256sum }} +{{- $activeTargets := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.targets) | fromJson -}} +{{- $activeSkills := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.skills) | fromJson -}} + +{{ if and (eq .chezmoi.os "darwin") (gt (len $activeTargets) 0) (gt (len $activeSkills) 0) }} +{{ template "lib/common/log.sh" . }} +{{ template "lib/common/install-prelude.sh" . }} +export AI_LOCAL_SKILLS_DIR="{{ .chezmoi.sourceDir }}/.chezmoitemplates/skills" +AI_SKILL_TARGETS=( +{{ range $activeTargets -}} + "{{ . }}" +{{ end -}} +) +AI_SKILLS=( +{{ range $activeSkills -}} + "{{ .source }}|{{ .skill }}" +{{ end -}} +) +{{ template "lib/install/ai-skills.sh" . }} +{{ end }} diff --git a/home/.chezmoiscripts/darwin/run_onchange_08_install-ai-mcp.sh.tmpl b/home/.chezmoiscripts/darwin/run_onchange_08_install-ai-mcp.sh.tmpl new file mode 100644 index 0000000..c5a47b7 --- /dev/null +++ b/home/.chezmoiscripts/darwin/run_onchange_08_install-ai-mcp.sh.tmpl @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# @file run_onchange_08_install-ai-mcp.sh +# @brief Register cross-agent MCP servers. +# @description AI data hash: {{ include ".chezmoidata/ai.yaml" | sha256sum }} +{{- $activeTargets := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.targets) | fromJson -}} +{{- $activeMcp := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.mcp) | fromJson -}} + +{{ if and (eq .chezmoi.os "darwin") (has "claude-code" $activeTargets) (gt (len $activeMcp) 0) }} +{{ template "lib/common/log.sh" . }} +{{ template "lib/common/install-prelude.sh" . }} +AI_MCP=( +{{ range $activeMcp -}} + "{{ .name }}|{{ .transport }}|{{ .url }}" +{{ end -}} +) +{{ template "lib/install/ai-mcp.sh" . }} +{{ end }} diff --git a/home/.chezmoiscripts/darwin/run_onchange_08_install-graphify-skills.sh.tmpl b/home/.chezmoiscripts/darwin/run_onchange_09_install-graphify-skills.sh.tmpl similarity index 55% rename from home/.chezmoiscripts/darwin/run_onchange_08_install-graphify-skills.sh.tmpl rename to home/.chezmoiscripts/darwin/run_onchange_09_install-graphify-skills.sh.tmpl index 1e38d42..1e8f966 100644 --- a/home/.chezmoiscripts/darwin/run_onchange_08_install-graphify-skills.sh.tmpl +++ b/home/.chezmoiscripts/darwin/run_onchange_09_install-graphify-skills.sh.tmpl @@ -1,11 +1,11 @@ #!/usr/bin/env bash -# @file run_onchange_08_install-graphify-skills.sh +# @file run_onchange_09_install-graphify-skills.sh # @brief Install Graphify agent skills. -# @description APM data hash: {{ include ".chezmoidata/apm.yaml" | sha256sum }} -{{- $activeTargets := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .apm.targets) | fromJson -}} +# @description AI data hash: {{ include ".chezmoidata/ai.yaml" | sha256sum }} +{{- $activeTargets := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.targets) | fromJson -}} {{- $platforms := list -}} {{- range $activeTargets -}} -{{- if eq . "agent-skills" }}{{ $platforms = append $platforms "agents" }}{{- else if eq . "claude" }}{{ $platforms = append $platforms "claude" }}{{- end -}} +{{- if eq . "claude-code" }}{{ $platforms = append $platforms "claude" }}{{- else if eq . "github-copilot" }}{{ $platforms = append $platforms "copilot" }}{{- end -}} {{- end -}} {{ if and (eq .chezmoi.os "darwin") (gt (len $platforms) 0) }} diff --git a/home/.chezmoitemplates/AGENTS.md b/home/.chezmoitemplates/AGENTS.md index 7541041..fb4956e 100644 --- a/home/.chezmoitemplates/AGENTS.md +++ b/home/.chezmoitemplates/AGENTS.md @@ -1,25 +1,86 @@ -# Claude Code Settings +# Agent Guidance -Guidance for Claude Code and other AI tools. Structured around [Andrej Karpathy's observations on LLM coding pitfalls](https://x.com/karpathy/status/2015883857489522876): surface assumptions, don't overcomplicate, make surgical changes, verify before moving on. +Behavioral guidelines for AI coding agents (Claude Code, GitHub Copilot, and others) to reduce common LLM coding mistakes, derived from [Andrej Karpathy's observations on LLM coding pitfalls](https://x.com/karpathy/status/2015883857489522876). -## AI Guidance +**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment. -**Do what was asked. Nothing more, nothing less.** +## 1. Think Before Coding -Never use words like "consolidate", "modernize", "streamline", "flexible", "delve", "establish", "enhanced", "comprehensive", "optimize" or em-dashes (—) and double-hyphens (--) in docstrings, commit messages, or comments. +**Don't assume. Don't hide confusion. Surface tradeoffs.** -- Reflect on tool results before acting. Use thinking to plan and iterate, then take the best next action. -- Run independent operations in parallel. -- Verify your solution before finishing. -- Never create files unless necessary. Prefer editing. Never create docs (\*.md, README) unless explicitly asked. -- Reuse existing code. Simplify. Make targeted changes, not sweeping ones. -- Prefer `rg` over `grep`. -- No defensive programming unless you state the motivation and the user approves. -- When updating code, check related code in the same and other files for consistency. +Before implementing: -Ask yourself: "Does every change I'm making trace directly to what was asked?" +- State your assumptions explicitly. If uncertain, ask. +- If multiple interpretations exist, present them, don't pick silently. +- If a simpler approach exists, say so. Push back when warranted. +- If something is unclear, stop. Name what's confusing. Ask. -### GitHub CLI +## 2. Simplicity First + +**Minimum code that solves the problem. Nothing speculative.** + +- No features beyond what was asked. +- No abstractions for single-use code. +- No "flexibility" or "configurability" that wasn't requested. +- No error handling for impossible scenarios. +- If you write 200 lines and it could be 50, rewrite it. + +Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. + +## 3. Surgical Changes + +**Touch only what you must. Clean up only your own mess.** + +When editing existing code: + +- Don't "improve" adjacent code, comments, or formatting. +- Don't refactor things that aren't broken. +- Match existing style, even if you'd do it differently. +- If you notice unrelated dead code, mention it, don't delete it. + +When your changes create orphans: + +- Remove imports, variables, and functions that your changes made unused. +- Don't remove pre-existing dead code unless asked. + +The test: every changed line should trace directly to the user's request. + +## 4. Goal-Driven Execution + +**Define success criteria. Loop until verified.** + +Transform tasks into verifiable goals: + +- "Add validation" becomes "Write tests for invalid inputs, then make them pass". +- "Fix the bug" becomes "Write a test that reproduces it, then make it pass". +- "Refactor X" becomes "Ensure tests pass before and after". + +For multi-step tasks, state a brief plan: + +``` +1. [Step] -> verify: [check] +2. [Step] -> verify: [check] +3. [Step] -> verify: [check] +``` + +Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. + +## Writing Style + +Never use words like "consolidate", "modernize", "streamline", "flexible", "delve", "establish", "enhanced", "comprehensive", or "optimize", nor em dashes or double hyphens, in docstrings, commit messages, or comments. + +## Tooling + +Run independent operations in parallel when they don't depend on each other. + +For searching a codebase, prefer efficient tools over defaults, in order: + +1. **Graphify** for structure questions such as definitions, callers, and architecture, when `graphify-out/graph.json` exists (see below). +2. **`rg`** (ripgrep) for content and regex search; faster than `grep` and skips ignored files. +3. **`fd`** for finding files by name; faster and simpler than `find`. +4. `grep` and `find` only when the tools above do not fit. + +## GitHub CLI Use `gh` CLI for all GitHub interactions. Never clone repositories to read code. @@ -33,35 +94,6 @@ Use `gh` CLI for all GitHub interactions. Never clone repositories to read code. - **List commits**: `gh api repos/{owner}/{repo}/commits --jq '.[].sha'` - **View issue**: `gh issue view {number} --repo {owner}/{repo}` -## Git and Pull Request Workflows - -### Commit Messages - -- Format: `{type}: brief description` (max 50 chars first line) -- Optional second line: 1 sentence with findings/motivation -- Types: `feat`, `fix`, `refactor`, `perf`, `docs`, `style`, `test`, `build`, `chore`, `ci` -- Simple terms, no jargon -- ONLY analyze staged files (`git diff --cached`), ignore unstaged -- NO test plans in commit messages - -### Pull Requests - -- PR titles: NO type prefix (unlike commits) - start with capital letter + verb -- Analyze ALL commits with `git diff ...HEAD`, not just latest -- PR body: single section, no headers, 1-2 sentences + usage snippet -- No test plans, no changed files list, no line-number links in PR body -- Self-assign with `-a @me` -- Find reviewers: `gh pr list --repo / --author @me --limit 5` - -### PR Comments and Reviews - -- Create pending reviews only, never auto-submit -- Comment style: start lowercase, no em-dashes, simple terms, no end punctuation, max 1 sentence -- Bot comment responses: few words is enough -- Real person responses: polite, concise - -Ask yourself: "Would someone unfamiliar with this repo understand this commit message?" - ## Graphify - Use the installed Graphify skill when the user invokes `/graphify`. diff --git a/home/.chezmoitemplates/apm/apm.lock.personal.yaml b/home/.chezmoitemplates/apm/apm.lock.personal.yaml deleted file mode 100644 index e286499..0000000 --- a/home/.chezmoitemplates/apm/apm.lock.personal.yaml +++ /dev/null @@ -1,502 +0,0 @@ -lockfile_version: "1" -generated_at: "2026-06-26T03:33:20.248284+00:00" -apm_version: 0.21.0 -dependencies: - - repo_url: JuliusBrussee/caveman - host: github.com - resolved_commit: 32f37af81a02a4b91c107b768f1365848e5bf005 - resolved_ref: v1.9.0 - package_type: marketplace_plugin - deployed_files: - - .agents/skills/cavecrew - - .agents/skills/cavecrew/README.md - - .agents/skills/cavecrew/SKILL.md - - .agents/skills/caveman - - .agents/skills/caveman-commit - - .agents/skills/caveman-commit/README.md - - .agents/skills/caveman-commit/SKILL.md - - .agents/skills/caveman-compress - - .agents/skills/caveman-compress/README.md - - .agents/skills/caveman-compress/SECURITY.md - - .agents/skills/caveman-compress/SKILL.md - - .agents/skills/caveman-compress/scripts/__init__.py - - .agents/skills/caveman-compress/scripts/__main__.py - - .agents/skills/caveman-compress/scripts/benchmark.py - - .agents/skills/caveman-compress/scripts/cli.py - - .agents/skills/caveman-compress/scripts/compress.py - - .agents/skills/caveman-compress/scripts/detect.py - - .agents/skills/caveman-compress/scripts/validate.py - - .agents/skills/caveman-help - - .agents/skills/caveman-help/README.md - - .agents/skills/caveman-help/SKILL.md - - .agents/skills/caveman-review - - .agents/skills/caveman-review/README.md - - .agents/skills/caveman-review/SKILL.md - - .agents/skills/caveman-stats - - .agents/skills/caveman-stats/README.md - - .agents/skills/caveman-stats/SKILL.md - - .agents/skills/caveman/README.md - - .agents/skills/caveman/SKILL.md - - .claude/agents/cavecrew-builder.md - - .claude/agents/cavecrew-investigator.md - - .claude/agents/cavecrew-reviewer.md - - .claude/hooks/caveman/src/hooks/caveman-activate.js - - .claude/skills/cavecrew - - .claude/skills/cavecrew/README.md - - .claude/skills/cavecrew/SKILL.md - - .claude/skills/caveman-review - - .claude/skills/caveman-review/README.md - - .claude/skills/caveman-review/SKILL.md - - .claude/skills/caveman-stats - - .claude/skills/caveman-stats/README.md - - .claude/skills/caveman-stats/SKILL.md - - .claude/skills/caveman/.claude-plugin/plugin.json - - .claude/skills/caveman/bin/install.js - deployed_file_hashes: - .agents/skills/cavecrew/README.md: sha256:965622be25416e1c59d0f69edf359dceeebdf2fa650c3fd5c254081045f27c12 - .agents/skills/cavecrew/SKILL.md: sha256:b74f374f6aae6e9a31e78e7d876860406fe5833378e9298536edf176c12f379b - .agents/skills/caveman-commit/README.md: sha256:402e739690a2757cd84b8dea43296cfb8828c5b18b8262c4571cf632c0e5a3e1 - .agents/skills/caveman-commit/SKILL.md: sha256:58db7b8efab911a629c26e7132517d9076e4e3645009eb604cb8c25663477841 - .agents/skills/caveman-compress/README.md: sha256:dd772fc8791ea5bd6041e441f91ff355392d4f30c9f56310be7f4bc6cdaf5126 - .agents/skills/caveman-compress/SECURITY.md: sha256:ac3432493cfe71368a141f437bf35562eb8f4ce07ae699e5399c0cb665e02da3 - .agents/skills/caveman-compress/SKILL.md: sha256:8b0d64c622d8a70411bbd86e736ac23cc0465fca1943a37f081e58e9d3afd210 - .agents/skills/caveman-compress/scripts/__init__.py: sha256:429c3e1c5cc5b9705f28d77f303c728304ae68693913ad3d5ce9b5a44c8ee40f - .agents/skills/caveman-compress/scripts/__main__.py: sha256:6d8b7d7846a845059d7a3107143f11131f63c5511d669b44085b15ec5e3d2279 - .agents/skills/caveman-compress/scripts/benchmark.py: sha256:84a5a94bebcce03c9dc4a70333f120f03e11075bce9298e1e74b95594e142d43 - .agents/skills/caveman-compress/scripts/cli.py: sha256:17edb6b425adc0ea04e35f1e90dfe318ba4232a1b815c5ff7d6cff7d574efea6 - .agents/skills/caveman-compress/scripts/compress.py: sha256:9537671f5b8bf81bfe99078666948a93b5a5f1ae07d2f7bc3f085fe1a6b408c0 - .agents/skills/caveman-compress/scripts/detect.py: sha256:7ab164dbcbc0b60e04d7f00257e2e691ce75a048f238f9a789c0b890a053f12b - .agents/skills/caveman-compress/scripts/validate.py: sha256:8b9e752b70868f6e03b3d7e76de19dd6b4ac92dad50522992738340e9916dc0d - .agents/skills/caveman-help/README.md: sha256:5cb51e36f8cc96360427222e631a0357e20daffac10ba77571efeeff933352a2 - .agents/skills/caveman-help/SKILL.md: sha256:590d6c6c17254bda56d18da7cc9d0bf252aeec5d1c78b77dd7ae4cabb8404f89 - .agents/skills/caveman-review/README.md: sha256:a06403b2722a069a9a2b0011772ae73296e68b4e46ace2f4b42b4ea546052b64 - .agents/skills/caveman-review/SKILL.md: sha256:9ef09065f26b9781275b5a4775e14870e331fab833efb2a2aa33f3205a2b83c3 - .agents/skills/caveman-stats/README.md: sha256:64d206ee7524625a8541ec064e0c7ec27c3764bae63b685cb5eecbd20aa76bdc - .agents/skills/caveman-stats/SKILL.md: sha256:b9e49e46ede956e0c1633eae82a695c996f728bc48f3903f001d301068a2b0ea - .agents/skills/caveman/README.md: sha256:97452fae7b043b43bc5348784494f7e457012400030bb0aa09b9f6a9c488a6ea - .agents/skills/caveman/SKILL.md: sha256:e38ec671ecbee47ce234190be12615daf60ac667d775b7340d49d07f4f63c7bc - .claude/agents/cavecrew-builder.md: sha256:135af639b287ed7534b66d01390c4dc48d98683af52232728a0e87db9c558b3e - .claude/agents/cavecrew-investigator.md: sha256:f1ce01150aeeec7de98d487f41693d27eba1bc9d4cc67f4c774675bb21c24fc2 - .claude/agents/cavecrew-reviewer.md: sha256:ae45346edae2cdb97018a38ebdcb79b162c59875cac2921d976a7d7540e4df70 - .claude/hooks/caveman/src/hooks/caveman-activate.js: sha256:9366e01e5050c8ae415f2574a5cf9f792494b5a48fc14e1b90109ef01bbde03e - .claude/skills/cavecrew/README.md: sha256:965622be25416e1c59d0f69edf359dceeebdf2fa650c3fd5c254081045f27c12 - .claude/skills/cavecrew/SKILL.md: sha256:b74f374f6aae6e9a31e78e7d876860406fe5833378e9298536edf176c12f379b - .claude/skills/caveman-review/README.md: sha256:a06403b2722a069a9a2b0011772ae73296e68b4e46ace2f4b42b4ea546052b64 - .claude/skills/caveman-review/SKILL.md: sha256:9ef09065f26b9781275b5a4775e14870e331fab833efb2a2aa33f3205a2b83c3 - .claude/skills/caveman-stats/README.md: sha256:64d206ee7524625a8541ec064e0c7ec27c3764bae63b685cb5eecbd20aa76bdc - .claude/skills/caveman-stats/SKILL.md: sha256:b9e49e46ede956e0c1633eae82a695c996f728bc48f3903f001d301068a2b0ea - .claude/skills/caveman/.claude-plugin/plugin.json: sha256:1957b1104cdccc0843ebf71489f0bb24b98cce1e905a981ff001049cc7ee3b32 - .claude/skills/caveman/bin/install.js: sha256:cdf3572b0b30fb0e5f1ee711e11fa449c6398e5d19c0bf1d45c44159e0829f13 - content_hash: sha256:3e238a67ce11def6d73257cf8eabad78899df2909c57997e956be8e3e32943db - - repo_url: JuliusBrussee/skills - host: github.com - resolved_commit: 1fa763bf01ab01b7012b6767a564a071aa115c85 - resolved_ref: main - package_type: skill_bundle - deployed_files: - - .agents/skills/grill-me - - .agents/skills/grill-me/SKILL.md - - .agents/skills/junior-to-senior - - .agents/skills/junior-to-senior/SKILL.md - - .agents/skills/junior-to-senior/references/research-playbook.md - - .agents/skills/junior-to-senior/references/review-rubric.md - - .claude/skills/grill-me - - .claude/skills/grill-me/SKILL.md - - .claude/skills/junior-to-senior - - .claude/skills/junior-to-senior/SKILL.md - - .claude/skills/junior-to-senior/references/research-playbook.md - - .claude/skills/junior-to-senior/references/review-rubric.md - deployed_file_hashes: - .agents/skills/grill-me/SKILL.md: sha256:fb51ed1c9ade960e75ce781c4da411939c24a61a06d21c04060ee0e7660e673b - .agents/skills/junior-to-senior/SKILL.md: sha256:5fc658c5ff279aaa5f3b8cdd8857526d29806cf28e66f47c530a207207d9cfb6 - .agents/skills/junior-to-senior/references/research-playbook.md: sha256:b5e9e38a541aa73fd14527a37d08234352f2e664634946e4ecb8b0c1ae6b5c22 - .agents/skills/junior-to-senior/references/review-rubric.md: sha256:7d025c0a76a7e5fde4c6dca6b58885d74b43a5b0b054bd265cc78be5ec3c40de - .claude/skills/grill-me/SKILL.md: sha256:fb51ed1c9ade960e75ce781c4da411939c24a61a06d21c04060ee0e7660e673b - .claude/skills/junior-to-senior/SKILL.md: sha256:5fc658c5ff279aaa5f3b8cdd8857526d29806cf28e66f47c530a207207d9cfb6 - .claude/skills/junior-to-senior/references/research-playbook.md: sha256:b5e9e38a541aa73fd14527a37d08234352f2e664634946e4ecb8b0c1ae6b5c22 - .claude/skills/junior-to-senior/references/review-rubric.md: sha256:7d025c0a76a7e5fde4c6dca6b58885d74b43a5b0b054bd265cc78be5ec3c40de - content_hash: sha256:e6e8748936c290c0773d1a2c2b50638c94b848b31fcd61e16c4c5e613e3f0a0c - skill_subset: - - grill-me - - junior-to-senior - - repo_url: anthropics/claude-plugins-official - host: github.com - resolved_commit: c8e9219efb196fde7a56104de3fa8e1f7627e5a5 - resolved_ref: main - virtual_path: plugins/skill-creator - is_virtual: true - package_type: marketplace_plugin - deployed_files: - - .agents/skills/skill-creator - - .agents/skills/skill-creator/LICENSE.txt - - .agents/skills/skill-creator/SKILL.md - - .agents/skills/skill-creator/agents/analyzer.md - - .agents/skills/skill-creator/agents/comparator.md - - .agents/skills/skill-creator/agents/grader.md - - .agents/skills/skill-creator/assets/eval_review.html - - .agents/skills/skill-creator/eval-viewer/generate_review.py - - .agents/skills/skill-creator/eval-viewer/viewer.html - - .agents/skills/skill-creator/references/schemas.md - - .agents/skills/skill-creator/scripts/__init__.py - - .agents/skills/skill-creator/scripts/aggregate_benchmark.py - - .agents/skills/skill-creator/scripts/generate_report.py - - .agents/skills/skill-creator/scripts/improve_description.py - - .agents/skills/skill-creator/scripts/package_skill.py - - .agents/skills/skill-creator/scripts/quick_validate.py - - .agents/skills/skill-creator/scripts/run_eval.py - - .agents/skills/skill-creator/scripts/run_loop.py - - .agents/skills/skill-creator/scripts/utils.py - - .claude/skills/skill-creator - - .claude/skills/skill-creator/LICENSE.txt - - .claude/skills/skill-creator/SKILL.md - - .claude/skills/skill-creator/agents/analyzer.md - - .claude/skills/skill-creator/agents/comparator.md - - .claude/skills/skill-creator/agents/grader.md - - .claude/skills/skill-creator/assets/eval_review.html - - .claude/skills/skill-creator/eval-viewer/generate_review.py - - .claude/skills/skill-creator/eval-viewer/viewer.html - - .claude/skills/skill-creator/references/schemas.md - - .claude/skills/skill-creator/scripts/__init__.py - - .claude/skills/skill-creator/scripts/aggregate_benchmark.py - - .claude/skills/skill-creator/scripts/generate_report.py - - .claude/skills/skill-creator/scripts/improve_description.py - - .claude/skills/skill-creator/scripts/package_skill.py - - .claude/skills/skill-creator/scripts/quick_validate.py - - .claude/skills/skill-creator/scripts/run_eval.py - - .claude/skills/skill-creator/scripts/run_loop.py - - .claude/skills/skill-creator/scripts/utils.py - deployed_file_hashes: - .agents/skills/skill-creator/LICENSE.txt: sha256:58d1e17ffe5109a7ae296caafcadfdbe6a7d176f0bc4ab01e12a689b0499d8bd - .agents/skills/skill-creator/SKILL.md: sha256:dcd4803e61e913e6fc27294184cd3a71f09f5e924ff20c8a9a20173e7b3c2bcf - .agents/skills/skill-creator/agents/analyzer.md: sha256:bf68f4cac5a56c673a928c2e6d619586c5b93ea364026ab37547772cb45a663a - .agents/skills/skill-creator/agents/comparator.md: sha256:fe1fc9787c495d864c5d6eada47396478572325fde1b33a96d78bf4b849b7a3e - .agents/skills/skill-creator/agents/grader.md: sha256:57134da0c1a4eea33fbd74a1c9c44aa814f07d6bc64de303edb586f941e5d21a - .agents/skills/skill-creator/assets/eval_review.html: sha256:ce477dcc74dc1c0d1d3352646a79167b5a63634e936b1019160025065974e452 - .agents/skills/skill-creator/eval-viewer/generate_review.py: sha256:fc9d1b9243fe5ab6012ebd579bd76d0035de1b79fd3b969de114defab26478fb - .agents/skills/skill-creator/eval-viewer/viewer.html: sha256:a53213426ee1100441d701a3a0d49cda7a842f992d2c36463f4d3cc0258575fa - .agents/skills/skill-creator/references/schemas.md: sha256:8e8876180a8989b406a4d3edddf875b04cdfd5805cc8616686d552b11ce4455f - .agents/skills/skill-creator/scripts/__init__.py: sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 - .agents/skills/skill-creator/scripts/aggregate_benchmark.py: sha256:123ef128ea5ccc01a4b1ac212ef5567f21e9c13d3d240609780beeb3200c49aa - .agents/skills/skill-creator/scripts/generate_report.py: sha256:13df7118a3c50c83c4c3250a606d5f2b20b25a3d44cbc392b3d669ec75281453 - .agents/skills/skill-creator/scripts/improve_description.py: sha256:87d864570220b699fac52da309d2d6efdb060647bfebc74f768128e646accf80 - .agents/skills/skill-creator/scripts/package_skill.py: sha256:1a33059b0db1ef73375d46d513e5ea81369d2e8838c970597b0d52ddef8d1c0f - .agents/skills/skill-creator/scripts/quick_validate.py: sha256:67cf5703402013936c8fb75ad6a1afecd8841d45cc5e606b634eb05825fde365 - .agents/skills/skill-creator/scripts/run_eval.py: sha256:43e3b8f80dbf69c343967ba77e268fae991d9fa3ed68b32a0ff02532cd48657f - .agents/skills/skill-creator/scripts/run_loop.py: sha256:7bd6f674203168520517eec94c55f493c0d154339b061b4d7c0f0dad187d0f21 - .agents/skills/skill-creator/scripts/utils.py: sha256:3af8ae62c40c73ab712207436a0d9a981e845f25c5a7040229eb189cc8e45bb1 - .claude/skills/skill-creator/LICENSE.txt: sha256:58d1e17ffe5109a7ae296caafcadfdbe6a7d176f0bc4ab01e12a689b0499d8bd - .claude/skills/skill-creator/SKILL.md: sha256:dcd4803e61e913e6fc27294184cd3a71f09f5e924ff20c8a9a20173e7b3c2bcf - .claude/skills/skill-creator/agents/analyzer.md: sha256:bf68f4cac5a56c673a928c2e6d619586c5b93ea364026ab37547772cb45a663a - .claude/skills/skill-creator/agents/comparator.md: sha256:fe1fc9787c495d864c5d6eada47396478572325fde1b33a96d78bf4b849b7a3e - .claude/skills/skill-creator/agents/grader.md: sha256:57134da0c1a4eea33fbd74a1c9c44aa814f07d6bc64de303edb586f941e5d21a - .claude/skills/skill-creator/assets/eval_review.html: sha256:ce477dcc74dc1c0d1d3352646a79167b5a63634e936b1019160025065974e452 - .claude/skills/skill-creator/eval-viewer/generate_review.py: sha256:fc9d1b9243fe5ab6012ebd579bd76d0035de1b79fd3b969de114defab26478fb - .claude/skills/skill-creator/eval-viewer/viewer.html: sha256:a53213426ee1100441d701a3a0d49cda7a842f992d2c36463f4d3cc0258575fa - .claude/skills/skill-creator/references/schemas.md: sha256:8e8876180a8989b406a4d3edddf875b04cdfd5805cc8616686d552b11ce4455f - .claude/skills/skill-creator/scripts/__init__.py: sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 - .claude/skills/skill-creator/scripts/aggregate_benchmark.py: sha256:123ef128ea5ccc01a4b1ac212ef5567f21e9c13d3d240609780beeb3200c49aa - .claude/skills/skill-creator/scripts/generate_report.py: sha256:13df7118a3c50c83c4c3250a606d5f2b20b25a3d44cbc392b3d669ec75281453 - .claude/skills/skill-creator/scripts/improve_description.py: sha256:87d864570220b699fac52da309d2d6efdb060647bfebc74f768128e646accf80 - .claude/skills/skill-creator/scripts/package_skill.py: sha256:1a33059b0db1ef73375d46d513e5ea81369d2e8838c970597b0d52ddef8d1c0f - .claude/skills/skill-creator/scripts/quick_validate.py: sha256:67cf5703402013936c8fb75ad6a1afecd8841d45cc5e606b634eb05825fde365 - .claude/skills/skill-creator/scripts/run_eval.py: sha256:43e3b8f80dbf69c343967ba77e268fae991d9fa3ed68b32a0ff02532cd48657f - .claude/skills/skill-creator/scripts/run_loop.py: sha256:7bd6f674203168520517eec94c55f493c0d154339b061b4d7c0f0dad187d0f21 - .claude/skills/skill-creator/scripts/utils.py: sha256:3af8ae62c40c73ab712207436a0d9a981e845f25c5a7040229eb189cc8e45bb1 - content_hash: sha256:315413cbe6e78ef43e6d3b63e9c15decbc689a9263b95377403c5a69f804d902 - - repo_url: obra/superpowers - host: github.com - resolved_commit: 896224c4b1879920ab573417e68fd51d2ccc9072 - resolved_ref: v6.0.3 - package_type: marketplace_plugin - deployed_files: - - .agents/skills/brainstorming - - .agents/skills/brainstorming/SKILL.md - - .agents/skills/brainstorming/scripts/frame-template.html - - .agents/skills/brainstorming/scripts/helper.js - - .agents/skills/brainstorming/scripts/server.cjs - - .agents/skills/brainstorming/scripts/start-server.sh - - .agents/skills/brainstorming/scripts/stop-server.sh - - .agents/skills/brainstorming/spec-document-reviewer-prompt.md - - .agents/skills/brainstorming/visual-companion.md - - .agents/skills/dispatching-parallel-agents - - .agents/skills/dispatching-parallel-agents/SKILL.md - - .agents/skills/executing-plans - - .agents/skills/executing-plans/SKILL.md - - .agents/skills/finishing-a-development-branch - - .agents/skills/finishing-a-development-branch/SKILL.md - - .agents/skills/receiving-code-review - - .agents/skills/receiving-code-review/SKILL.md - - .agents/skills/requesting-code-review - - .agents/skills/requesting-code-review/SKILL.md - - .agents/skills/requesting-code-review/code-reviewer.md - - .agents/skills/subagent-driven-development - - .agents/skills/subagent-driven-development/SKILL.md - - .agents/skills/subagent-driven-development/implementer-prompt.md - - .agents/skills/subagent-driven-development/scripts/review-package - - .agents/skills/subagent-driven-development/scripts/sdd-workspace - - .agents/skills/subagent-driven-development/scripts/task-brief - - .agents/skills/subagent-driven-development/task-reviewer-prompt.md - - .agents/skills/systematic-debugging - - .agents/skills/systematic-debugging/CREATION-LOG.md - - .agents/skills/systematic-debugging/SKILL.md - - .agents/skills/systematic-debugging/condition-based-waiting-example.ts - - .agents/skills/systematic-debugging/condition-based-waiting.md - - .agents/skills/systematic-debugging/defense-in-depth.md - - .agents/skills/systematic-debugging/find-polluter.sh - - .agents/skills/systematic-debugging/root-cause-tracing.md - - .agents/skills/systematic-debugging/test-academic.md - - .agents/skills/systematic-debugging/test-pressure-1.md - - .agents/skills/systematic-debugging/test-pressure-2.md - - .agents/skills/systematic-debugging/test-pressure-3.md - - .agents/skills/test-driven-development - - .agents/skills/test-driven-development/SKILL.md - - .agents/skills/test-driven-development/testing-anti-patterns.md - - .agents/skills/using-git-worktrees - - .agents/skills/using-git-worktrees/SKILL.md - - .agents/skills/using-superpowers - - .agents/skills/using-superpowers/SKILL.md - - .agents/skills/using-superpowers/references/antigravity-tools.md - - .agents/skills/using-superpowers/references/claude-code-tools.md - - .agents/skills/using-superpowers/references/codex-tools.md - - .agents/skills/using-superpowers/references/copilot-tools.md - - .agents/skills/using-superpowers/references/gemini-tools.md - - .agents/skills/using-superpowers/references/pi-tools.md - - .agents/skills/verification-before-completion - - .agents/skills/verification-before-completion/SKILL.md - - .agents/skills/writing-plans - - .agents/skills/writing-plans/SKILL.md - - .agents/skills/writing-plans/plan-document-reviewer-prompt.md - - .agents/skills/writing-skills - - .agents/skills/writing-skills/SKILL.md - - .agents/skills/writing-skills/anthropic-best-practices.md - - .agents/skills/writing-skills/examples/CLAUDE_MD_TESTING.md - - .agents/skills/writing-skills/graphviz-conventions.dot - - .agents/skills/writing-skills/persuasion-principles.md - - .agents/skills/writing-skills/render-graphs.js - - .agents/skills/writing-skills/testing-skills-with-subagents.md - - .claude/hooks/superpowers/hooks/run-hook.cmd - - .claude/skills/verification-before-completion - - .claude/skills/verification-before-completion/SKILL.md - deployed_file_hashes: - .agents/skills/brainstorming/SKILL.md: sha256:e14914605f640e0841758e45d0ab2a53243b59b921f929e47921c99668f2e61d - .agents/skills/brainstorming/scripts/frame-template.html: sha256:6a8a4e58bd6a44b904e2e3c57de774481d909204597e1498de53f1b2fecc4c4e - .agents/skills/brainstorming/scripts/helper.js: sha256:43c6d69954a46ec34a2a262bcc62a9a7e83e839c739f199cb72646d397c686e3 - .agents/skills/brainstorming/scripts/server.cjs: sha256:2d2961ea8d11f56c5f4c3a1a68d22709efa5d7601a2246d8c880774e7e9e8412 - .agents/skills/brainstorming/scripts/start-server.sh: sha256:a4e5ae84275bcaacd2f84345afeabe59cf7b00ba080e123da7cc1fb226f12847 - .agents/skills/brainstorming/scripts/stop-server.sh: sha256:0b5ccbbd57f62d3ed88993f7940b5ee0e5c0fc9b21c550c623da4f6292e47daf - .agents/skills/brainstorming/spec-document-reviewer-prompt.md: sha256:95a0a195de9d984be2fffa95bab16fc8c563bc296a9cfc5e9c29cb3ece0d7457 - .agents/skills/brainstorming/visual-companion.md: sha256:60cbad29b9dd7eaf08da020e301c498a72230b2e13c1813fa967a135ffcc1d71 - .agents/skills/dispatching-parallel-agents/SKILL.md: sha256:f0df13f584049059cc5619f90061405b89dcc6e28ab3f2a8517d27d99c7a46a6 - .agents/skills/executing-plans/SKILL.md: sha256:b357c2a2e5bf24adf237b5d4a83cacd828f07b886d0873ad91546aa3c99905c9 - .agents/skills/finishing-a-development-branch/SKILL.md: sha256:e6d4a812de900d33c6eacfb40747f99427f25c304a7b7099120f9373b115a47f - .agents/skills/receiving-code-review/SKILL.md: sha256:647036bbdab7bf2317e14e079595e984c9030f64295e2b4c0fb57dbeb48f25dd - .agents/skills/requesting-code-review/SKILL.md: sha256:1017ccdd5bc61fab67c654cf118cbdb520464b313073a0a6b9a6b9aa647a3ad6 - .agents/skills/requesting-code-review/code-reviewer.md: sha256:b2f2ec7596925fe52dac158fdfbca19b3a7d779d619c481e6706a6c0001662d3 - .agents/skills/subagent-driven-development/SKILL.md: sha256:abdd682e85bfc52413c997bef4612b2a4094168d8e4d6a967f1c0dcde4d83816 - .agents/skills/subagent-driven-development/implementer-prompt.md: sha256:49018b28dc11bc9f3d13a28959bb10ae1a96eabc5d8f19f4079d901f9ff2bf64 - .agents/skills/subagent-driven-development/scripts/review-package: sha256:0c0629f6e2c46fc8bf68dcfb8a247ab24eb548b7004fe494035e6fcba9b5cdfb - .agents/skills/subagent-driven-development/scripts/sdd-workspace: sha256:9430befaff459ed862415b700c1a9efe45fd34838a684802a1d8320de0c3a007 - .agents/skills/subagent-driven-development/scripts/task-brief: sha256:5380283f00bffa99ab82ae78482b7d248abe10655129c72ca7050bdc0b6a85e1 - .agents/skills/subagent-driven-development/task-reviewer-prompt.md: sha256:2eb9d54373420de25bc0bd00635d3a3123a6c0eb30c881168e6f3348e2387331 - .agents/skills/systematic-debugging/CREATION-LOG.md: sha256:c24733a5b1821bd6bed1fc950261f0b9f4e90097e0bbb96459d8179713730789 - .agents/skills/systematic-debugging/SKILL.md: sha256:3b20719eca4f0461cb51a195221320d775dcf03b6859271066a03a5132a6ce7a - .agents/skills/systematic-debugging/condition-based-waiting-example.ts: sha256:40ae5ebe497fdf310200e43fe986552546d0a22837c0d39e855db1cfd33eb88e - .agents/skills/systematic-debugging/condition-based-waiting.md: sha256:e89fec8400d6cd50f43407cec9fab50976ba4d55d0ec2eb51c0bd68036b54c26 - .agents/skills/systematic-debugging/defense-in-depth.md: sha256:1e175fb86fc357e58c6aebf5441e481e1b7868b4380c0456b63a17eefbd18ba7 - .agents/skills/systematic-debugging/find-polluter.sh: sha256:6462747eae9b175ac145b78bcfaeab755654a75e32637f08eb633f065a9e1d7c - .agents/skills/systematic-debugging/root-cause-tracing.md: sha256:6b0622269e098ca1399e123e553fd385f0b6412d88ef0e9c4f5a8ea9cf1cec7b - .agents/skills/systematic-debugging/test-academic.md: sha256:fe2ba480d78ac0d686dc025f41c2a32a43d642bf533f91b0c6053a04d35d6486 - .agents/skills/systematic-debugging/test-pressure-1.md: sha256:0b6a915db0054577819834c79be9eb614e97bddba10d73768e1fbe91cfed048a - .agents/skills/systematic-debugging/test-pressure-2.md: sha256:b2030aeffba07050e8ad573ddf87486457c4a016a786bb326235bebd856f2016 - .agents/skills/systematic-debugging/test-pressure-3.md: sha256:96b50a52e2c7989c9cf20fb752c47c1e9a3a70dc362f8f7989f8f5b64dac7708 - .agents/skills/test-driven-development/SKILL.md: sha256:b5b4717b8b761cce15a6cfe9022e33fd959e0894c0c39d72c9cb49c23486c10e - .agents/skills/test-driven-development/testing-anti-patterns.md: sha256:bde453bc258f06543987477c837939afaa774ea2acbd9f308d702fc452bc4283 - .agents/skills/using-git-worktrees/SKILL.md: sha256:e2c3ec142e52868a51af246c620cd76ab648dcf27d6900d47e6ffd07159a9794 - .agents/skills/using-superpowers/SKILL.md: sha256:918562bbba1c73d1407c50de77a92eeaa6930508bdb57a64324d7d508a9069e1 - .agents/skills/using-superpowers/references/antigravity-tools.md: sha256:00b16e8e8166d972766613c4244a7901d0413af37944741a8b8f40105f9f7f05 - .agents/skills/using-superpowers/references/claude-code-tools.md: sha256:7d9244dd278db15caa8f9f86bda0546c91fc65163f94b2499999fc72b69c3666 - .agents/skills/using-superpowers/references/codex-tools.md: sha256:cbce77a3a430688ef0cc3c8f03d17d2df1a2c78603e8b138cec522b74d75e4ab - .agents/skills/using-superpowers/references/copilot-tools.md: sha256:9627943f22c8db64ac7fbac3b6e399f73650a5cf3618f7966e802e7fa58d4595 - .agents/skills/using-superpowers/references/gemini-tools.md: sha256:62b9157bcb0ee3c6784e3d0da0798ddfa5872f9e0c34bea48f3079dabea71965 - .agents/skills/using-superpowers/references/pi-tools.md: sha256:e89e2d5c7b89cdd2a511aa7ad3c191290baaaed8913006c30f283ac7c4b091fb - .agents/skills/verification-before-completion/SKILL.md: sha256:ea52d15aabaf72bc6b558efe2c126f161b53961090ddcd712000273bfe8c7b6c - .agents/skills/writing-plans/SKILL.md: sha256:272e1af349f5062c28dc282b3e21b220d58d683a7314a10c455b7432ec91d845 - .agents/skills/writing-plans/plan-document-reviewer-prompt.md: sha256:aa728b96aad603c8be28875a4305637f6c984aa81ffcadcb13e743202fa2a0c7 - .agents/skills/writing-skills/SKILL.md: sha256:0903f8bef0908ccb5d9fe1a6cfbcf3d20a581c7bfcb547f71aafb795b9433b7b - .agents/skills/writing-skills/anthropic-best-practices.md: sha256:217629b356c09c9bd11017c9788e8fc654ca1b32c92d4a51cd490e16dd65e59a - .agents/skills/writing-skills/examples/CLAUDE_MD_TESTING.md: sha256:0b379a3415e185d3c434b3ad283d8aa132f3022c2a4f210f168865b5986bcef0 - .agents/skills/writing-skills/graphviz-conventions.dot: sha256:e2890a593c91370e384b42f2f67b1a6232c9e69dddea7891a0c1c46d7b20b694 - .agents/skills/writing-skills/persuasion-principles.md: sha256:a51bc9bf75189ea73a27b3fb504a2fdfdb966fb1f7f1cdf03203230a216ccc03 - .agents/skills/writing-skills/render-graphs.js: sha256:ccda971a87bb185f8febf81c56b556a20d026fa980c17b35fa3e8824fbb37852 - .agents/skills/writing-skills/testing-skills-with-subagents.md: sha256:c711346852c911b24a84aa161e0cff06a4cd7f4e2fa9e9c0a266cead5afcbade - .claude/hooks/superpowers/hooks/run-hook.cmd: sha256:d3d9c6199678dab2858e60509dde5e7414f13c2a2b5e48a38b1d368b6e1d6abb - .claude/skills/verification-before-completion/SKILL.md: sha256:ea52d15aabaf72bc6b558efe2c126f161b53961090ddcd712000273bfe8c7b6c - content_hash: sha256:5b14832a84b21359e629052d2f4692fabded091ae2a80d085909776747e1f26e - declared_license: MIT - - repo_url: schpet/linear-cli - host: github.com - resolved_commit: a3e2960b7fb5951dcab91b0439b1e6518d828eca - resolved_ref: v2.0.0 - package_type: marketplace_plugin - deployed_files: - - .agents/skills/linear-cli - - .agents/skills/linear-cli/SKILL.md - - .agents/skills/linear-cli/SKILL.template.md - - .agents/skills/linear-cli/references/api.md - - .agents/skills/linear-cli/references/auth.md - - .agents/skills/linear-cli/references/commands.md - - .agents/skills/linear-cli/references/config.md - - .agents/skills/linear-cli/references/cycle.md - - .agents/skills/linear-cli/references/document.md - - .agents/skills/linear-cli/references/initiative-update.md - - .agents/skills/linear-cli/references/initiative.md - - .agents/skills/linear-cli/references/issue.md - - .agents/skills/linear-cli/references/label.md - - .agents/skills/linear-cli/references/milestone.md - - .agents/skills/linear-cli/references/organization-features.md - - .agents/skills/linear-cli/references/project-update.md - - .agents/skills/linear-cli/references/project.md - - .agents/skills/linear-cli/references/schema.md - - .agents/skills/linear-cli/references/team.md - - .agents/skills/linear-cli/scripts/generate-docs.ts - - .claude/skills/linear-cli - - .claude/skills/linear-cli/SKILL.md - - .claude/skills/linear-cli/SKILL.template.md - - .claude/skills/linear-cli/references/api.md - - .claude/skills/linear-cli/references/auth.md - - .claude/skills/linear-cli/references/commands.md - - .claude/skills/linear-cli/references/config.md - - .claude/skills/linear-cli/references/cycle.md - - .claude/skills/linear-cli/references/document.md - - .claude/skills/linear-cli/references/initiative-update.md - - .claude/skills/linear-cli/references/initiative.md - - .claude/skills/linear-cli/references/issue.md - - .claude/skills/linear-cli/references/label.md - - .claude/skills/linear-cli/references/milestone.md - - .claude/skills/linear-cli/references/organization-features.md - - .claude/skills/linear-cli/references/project-update.md - - .claude/skills/linear-cli/references/project.md - - .claude/skills/linear-cli/references/schema.md - - .claude/skills/linear-cli/references/team.md - - .claude/skills/linear-cli/scripts/generate-docs.ts - deployed_file_hashes: - .agents/skills/linear-cli/SKILL.md: sha256:21215fd54b85411808775a30129bf205689f582f6eaa9a43c1d3d6a71f1e6142 - .agents/skills/linear-cli/SKILL.template.md: sha256:964a78ba707084e684a3aac2048e544b14aa1faa100cd274cf11e9861671cc38 - .agents/skills/linear-cli/references/api.md: sha256:8eb38f27a7c0e4927a3d4e2538162643d023a5269747044d623f9934030f5aff - .agents/skills/linear-cli/references/auth.md: sha256:554d464dcc12a0c88ec41bcec75e099db19425a12b77531a4dba45e326852bcc - .agents/skills/linear-cli/references/commands.md: sha256:0e7c68448aed8142f6de4affc5371d9c42f5afad1f3b453cd7361b775f92e4f5 - .agents/skills/linear-cli/references/config.md: sha256:50b20d99e2b5084e2d5e194f2aba2d9448cd0dbc170aba57a7331aa3e46d5b64 - .agents/skills/linear-cli/references/cycle.md: sha256:c45f9a3165ba9da3eec1c565be24ae4f739834e5e0a084678e48452700aee715 - .agents/skills/linear-cli/references/document.md: sha256:5cacf83b171d9d8e9ca2d2613e36b38ac3f52346a6403f8ac6a0022c5caae25f - .agents/skills/linear-cli/references/initiative-update.md: sha256:d8b1e2021125764f8f9cecfe937bbecdc630ee1dfb7becd0ba5277d82662c158 - .agents/skills/linear-cli/references/initiative.md: sha256:17c861062fa8cb56c93542538a9125971b1228991939e5209c2b8993b15a3745 - .agents/skills/linear-cli/references/issue.md: sha256:bef27956091b5356071bed88c2827feec4f069f98cf6930775dd95eb3cc3c58c - .agents/skills/linear-cli/references/label.md: sha256:3faf2944a5f20bc681642e711a2e49d0acecb7ea2217c25ea8b5208e2651848b - .agents/skills/linear-cli/references/milestone.md: sha256:56abfb51798e77c7af97f7a2ea42b0cddeab2724f79306864e180df878266dfa - .agents/skills/linear-cli/references/organization-features.md: sha256:9dee08bd13ab7f93764076f9a53ec92a7e2dfd54ae3cbceb3c8eb1ce39613388 - .agents/skills/linear-cli/references/project-update.md: sha256:62a7dcff5ddb8017fa43430de8680267f0047d90b37b41c21cc36ac078a88b89 - .agents/skills/linear-cli/references/project.md: sha256:fc9bd98369c1ae2b1418f841206c400624cfb7c985d5cbafa5030769d0c4c261 - .agents/skills/linear-cli/references/schema.md: sha256:acbb90509d5587d9c94389429ade18f31cfac8fb139b8b10b2bb4d40683eb33d - .agents/skills/linear-cli/references/team.md: sha256:12729537db0340a20c884f426ee7680c6e39a00b4838a6cf91df7a74f43ea785 - .agents/skills/linear-cli/scripts/generate-docs.ts: sha256:cfad16a700fd2741a5dc9d804d7f123613dc213496ad6bf38d499cb6137a55eb - .claude/skills/linear-cli/SKILL.md: sha256:21215fd54b85411808775a30129bf205689f582f6eaa9a43c1d3d6a71f1e6142 - .claude/skills/linear-cli/SKILL.template.md: sha256:964a78ba707084e684a3aac2048e544b14aa1faa100cd274cf11e9861671cc38 - .claude/skills/linear-cli/references/api.md: sha256:8eb38f27a7c0e4927a3d4e2538162643d023a5269747044d623f9934030f5aff - .claude/skills/linear-cli/references/auth.md: sha256:554d464dcc12a0c88ec41bcec75e099db19425a12b77531a4dba45e326852bcc - .claude/skills/linear-cli/references/commands.md: sha256:0e7c68448aed8142f6de4affc5371d9c42f5afad1f3b453cd7361b775f92e4f5 - .claude/skills/linear-cli/references/config.md: sha256:50b20d99e2b5084e2d5e194f2aba2d9448cd0dbc170aba57a7331aa3e46d5b64 - .claude/skills/linear-cli/references/cycle.md: sha256:c45f9a3165ba9da3eec1c565be24ae4f739834e5e0a084678e48452700aee715 - .claude/skills/linear-cli/references/document.md: sha256:5cacf83b171d9d8e9ca2d2613e36b38ac3f52346a6403f8ac6a0022c5caae25f - .claude/skills/linear-cli/references/initiative-update.md: sha256:d8b1e2021125764f8f9cecfe937bbecdc630ee1dfb7becd0ba5277d82662c158 - .claude/skills/linear-cli/references/initiative.md: sha256:17c861062fa8cb56c93542538a9125971b1228991939e5209c2b8993b15a3745 - .claude/skills/linear-cli/references/issue.md: sha256:bef27956091b5356071bed88c2827feec4f069f98cf6930775dd95eb3cc3c58c - .claude/skills/linear-cli/references/label.md: sha256:3faf2944a5f20bc681642e711a2e49d0acecb7ea2217c25ea8b5208e2651848b - .claude/skills/linear-cli/references/milestone.md: sha256:56abfb51798e77c7af97f7a2ea42b0cddeab2724f79306864e180df878266dfa - .claude/skills/linear-cli/references/organization-features.md: sha256:9dee08bd13ab7f93764076f9a53ec92a7e2dfd54ae3cbceb3c8eb1ce39613388 - .claude/skills/linear-cli/references/project-update.md: sha256:62a7dcff5ddb8017fa43430de8680267f0047d90b37b41c21cc36ac078a88b89 - .claude/skills/linear-cli/references/project.md: sha256:fc9bd98369c1ae2b1418f841206c400624cfb7c985d5cbafa5030769d0c4c261 - .claude/skills/linear-cli/references/schema.md: sha256:acbb90509d5587d9c94389429ade18f31cfac8fb139b8b10b2bb4d40683eb33d - .claude/skills/linear-cli/references/team.md: sha256:12729537db0340a20c884f426ee7680c6e39a00b4838a6cf91df7a74f43ea785 - .claude/skills/linear-cli/scripts/generate-docs.ts: sha256:cfad16a700fd2741a5dc9d804d7f123613dc213496ad6bf38d499cb6137a55eb - content_hash: sha256:b6dcb09081bd6515548614982e6e135d1758802ee32dda30932067207ccb291c - - repo_url: tavily-ai/skills - host: github.com - resolved_commit: ea5e8201b0d3ed9c10b70b71187589bd761fe2d2 - resolved_ref: main - package_type: marketplace_plugin - deployed_files: - - .agents/skills/tavily-best-practices - - .agents/skills/tavily-best-practices/SKILL.md - - .agents/skills/tavily-best-practices/references/crawl.md - - .agents/skills/tavily-best-practices/references/extract.md - - .agents/skills/tavily-best-practices/references/integrations.md - - .agents/skills/tavily-best-practices/references/research.md - - .agents/skills/tavily-best-practices/references/sdk.md - - .agents/skills/tavily-best-practices/references/search.md - - .agents/skills/tavily-cli - - .agents/skills/tavily-cli/SKILL.md - - .agents/skills/tavily-crawl - - .agents/skills/tavily-crawl/SKILL.md - - .agents/skills/tavily-dynamic-search - - .agents/skills/tavily-dynamic-search/SKILL.md - - .agents/skills/tavily-extract - - .agents/skills/tavily-extract/SKILL.md - - .agents/skills/tavily-map - - .agents/skills/tavily-map/SKILL.md - - .agents/skills/tavily-research - - .agents/skills/tavily-research/SKILL.md - - .agents/skills/tavily-search - - .agents/skills/tavily-search/SKILL.md - - .claude/skills/tavily-best-practices - - .claude/skills/tavily-best-practices/SKILL.md - - .claude/skills/tavily-best-practices/references/crawl.md - - .claude/skills/tavily-best-practices/references/extract.md - - .claude/skills/tavily-best-practices/references/integrations.md - - .claude/skills/tavily-best-practices/references/research.md - - .claude/skills/tavily-best-practices/references/sdk.md - - .claude/skills/tavily-best-practices/references/search.md - - .claude/skills/tavily-cli - - .claude/skills/tavily-cli/SKILL.md - - .claude/skills/tavily-crawl - - .claude/skills/tavily-crawl/SKILL.md - - .claude/skills/tavily-dynamic-search - - .claude/skills/tavily-dynamic-search/SKILL.md - - .claude/skills/tavily-extract - - .claude/skills/tavily-extract/SKILL.md - - .claude/skills/tavily-map - - .claude/skills/tavily-map/SKILL.md - - .claude/skills/tavily-research - - .claude/skills/tavily-research/SKILL.md - - .claude/skills/tavily-search - - .claude/skills/tavily-search/SKILL.md - deployed_file_hashes: - .agents/skills/tavily-best-practices/SKILL.md: sha256:8972384d52e22c76b82928c309f8faff3bfa2f68c4e4d7ad3ce1fd882d99048e - .agents/skills/tavily-best-practices/references/crawl.md: sha256:725e613330a6a3775fe265e6fa0c782f469f6f8580788012c78893a9ac010b84 - .agents/skills/tavily-best-practices/references/extract.md: sha256:7995998ce1969bf54892f84c9f1e583582eea9a354ab666c0869607635e41d5e - .agents/skills/tavily-best-practices/references/integrations.md: sha256:d53ba9f73d14f24a4ac81f5d77d584b4ff7fbbd4dd3b70f5e7c21cbc175cb65b - .agents/skills/tavily-best-practices/references/research.md: sha256:99e3c706b5ed3a51842204f43c552b75e3243d6facc3b8b7c9878a1a163e1ec7 - .agents/skills/tavily-best-practices/references/sdk.md: sha256:b0c745315f2ca16647a2c298644dc9918ea31bf732921b02841e26814da2087a - .agents/skills/tavily-best-practices/references/search.md: sha256:db5d7f52e42ec93f54b6cb04224e63ce689fd54ca2e3011754908eb54ce82261 - .agents/skills/tavily-cli/SKILL.md: sha256:aa8cee0e8dbf3bb98d2281b1874c7431de880c5c6ff6a3ca8cb346ac3f513f36 - .agents/skills/tavily-crawl/SKILL.md: sha256:5ed61112f6dfd9f06341ab0de91a65db423e240dde0b5b56036cdb4ee4e720d4 - .agents/skills/tavily-dynamic-search/SKILL.md: sha256:63c6ca34a2fd7bfd0b9a4d794755eea22f8dc7abd0de24f6683a38ed66781b6d - .agents/skills/tavily-extract/SKILL.md: sha256:127bc8470090b2a8b43d3556e539c6c65a24ad3e87c1539a0d792c4d3b644ceb - .agents/skills/tavily-map/SKILL.md: sha256:f9b28a6bdb13a58078a1d0e9af0b2df26bd7e1aea9a4227a490eac9f2c3d4464 - .agents/skills/tavily-research/SKILL.md: sha256:e246188571612cc0df88060790fa953fc5daab6ac6fa85a5c799b54c90bfd7b2 - .agents/skills/tavily-search/SKILL.md: sha256:83679ae2adac1c0dfdfba52ca12117d386c3238865b5b35cfa4eab9a6617322d - .claude/skills/tavily-best-practices/SKILL.md: sha256:8972384d52e22c76b82928c309f8faff3bfa2f68c4e4d7ad3ce1fd882d99048e - .claude/skills/tavily-best-practices/references/crawl.md: sha256:725e613330a6a3775fe265e6fa0c782f469f6f8580788012c78893a9ac010b84 - .claude/skills/tavily-best-practices/references/extract.md: sha256:7995998ce1969bf54892f84c9f1e583582eea9a354ab666c0869607635e41d5e - .claude/skills/tavily-best-practices/references/integrations.md: sha256:d53ba9f73d14f24a4ac81f5d77d584b4ff7fbbd4dd3b70f5e7c21cbc175cb65b - .claude/skills/tavily-best-practices/references/research.md: sha256:99e3c706b5ed3a51842204f43c552b75e3243d6facc3b8b7c9878a1a163e1ec7 - .claude/skills/tavily-best-practices/references/sdk.md: sha256:b0c745315f2ca16647a2c298644dc9918ea31bf732921b02841e26814da2087a - .claude/skills/tavily-best-practices/references/search.md: sha256:db5d7f52e42ec93f54b6cb04224e63ce689fd54ca2e3011754908eb54ce82261 - .claude/skills/tavily-cli/SKILL.md: sha256:aa8cee0e8dbf3bb98d2281b1874c7431de880c5c6ff6a3ca8cb346ac3f513f36 - .claude/skills/tavily-crawl/SKILL.md: sha256:5ed61112f6dfd9f06341ab0de91a65db423e240dde0b5b56036cdb4ee4e720d4 - .claude/skills/tavily-dynamic-search/SKILL.md: sha256:63c6ca34a2fd7bfd0b9a4d794755eea22f8dc7abd0de24f6683a38ed66781b6d - .claude/skills/tavily-extract/SKILL.md: sha256:127bc8470090b2a8b43d3556e539c6c65a24ad3e87c1539a0d792c4d3b644ceb - .claude/skills/tavily-map/SKILL.md: sha256:f9b28a6bdb13a58078a1d0e9af0b2df26bd7e1aea9a4227a490eac9f2c3d4464 - .claude/skills/tavily-research/SKILL.md: sha256:e246188571612cc0df88060790fa953fc5daab6ac6fa85a5c799b54c90bfd7b2 - .claude/skills/tavily-search/SKILL.md: sha256:83679ae2adac1c0dfdfba52ca12117d386c3238865b5b35cfa4eab9a6617322d - content_hash: sha256:dae40889b2ccaee31d909107c38bb8d0170bdf9c2a95ddd4ae9fff876bdd069d - declared_license: MIT diff --git a/home/.chezmoitemplates/apm/apm.lock.work.yaml b/home/.chezmoitemplates/apm/apm.lock.work.yaml deleted file mode 100644 index ec8c8f4..0000000 --- a/home/.chezmoitemplates/apm/apm.lock.work.yaml +++ /dev/null @@ -1,25 +0,0 @@ -lockfile_version: "1" -generated_at: "2026-06-25T22:21:09.257158+00:00" -apm_version: 0.21.0 -dependencies: - - repo_url: JuliusBrussee/caveman - host: github.com - resolved_commit: 32f37af81a02a4b91c107b768f1365848e5bf005 - resolved_ref: v1.9.0 - package_type: marketplace_plugin - content_hash: sha256:3e238a67ce11def6d73257cf8eabad78899df2909c57997e956be8e3e32943db - - repo_url: anthropics/claude-plugins-official - host: github.com - resolved_commit: a8237e1537e987a92c9f33e5cd650acbe175fd5e - resolved_ref: main - virtual_path: plugins/skill-creator - is_virtual: true - package_type: marketplace_plugin - content_hash: sha256:09476584a3731e3c64d504241afd18e1bdcd7095583edf9fd913f19efda5640b - - repo_url: obra/superpowers - host: github.com - resolved_commit: 896224c4b1879920ab573417e68fd51d2ccc9072 - resolved_ref: v6.0.3 - package_type: marketplace_plugin - content_hash: sha256:5b14832a84b21359e629052d2f4692fabded091ae2a80d085909776747e1f26e - declared_license: MIT diff --git a/home/.chezmoitemplates/apm/mcp-server.yml.tmpl b/home/.chezmoitemplates/apm/mcp-server.yml.tmpl deleted file mode 100644 index 5b9df41..0000000 --- a/home/.chezmoitemplates/apm/mcp-server.yml.tmpl +++ /dev/null @@ -1,21 +0,0 @@ -- name: "{{ index . "name" }}" - registry: {{ index . "registry" }} - transport: {{ index . "transport" }} -{{- if hasKey . "url" }} - url: "{{ index . "url" }}" -{{- end }} -{{- if hasKey . "headers" }} - headers: -{{- range $key, $value := index . "headers" }} - {{ $key }}: "{{ $value }}" -{{- end }} -{{- end }} -{{- if hasKey . "command" }} - command: "{{ index . "command" }}" -{{- end }} -{{- if hasKey . "args" }} - args: -{{- range $arg := index . "args" }} - - "{{ $arg }}" -{{- end }} -{{- end }} diff --git a/home/.chezmoitemplates/chezmoiignore.d/darwin b/home/.chezmoitemplates/chezmoiignore.d/darwin deleted file mode 100644 index 87c303c..0000000 --- a/home/.chezmoitemplates/chezmoiignore.d/darwin +++ /dev/null @@ -1 +0,0 @@ -# Darwin-specific chezmoi ignore patterns. diff --git a/home/.chezmoitemplates/lib/install/ai-mcp.sh b/home/.chezmoitemplates/lib/install/ai-mcp.sh new file mode 100644 index 0000000..f605fd9 --- /dev/null +++ b/home/.chezmoitemplates/lib/install/ai-mcp.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# @file lib/install/ai-mcp.sh +# @brief Register cross-agent MCP servers on Claude Code. +# @description +# Runs `claude mcp add` for each server in AI_MCP, scoped to the user. A +# `claude mcp get` check keeps the flow idempotent by skipping a server that +# is already registered. Copilot MCP arrives through rendered config files, +# so this library targets Claude Code only. Sourceable from bats tests and +# injected into chezmoi run scripts via chezmoi template rendering. + +set -Eeuo pipefail + +# +# @description True when AI_MCP holds at least one non-empty entry. +# @exitcode 0 A server is present. +# @exitcode 1 No servers. +# +function _ai_mcp_have_any() { + local entry + for entry in "${AI_MCP[@]-}"; do + [[ -z "${entry}" ]] && continue + return 0 + done + return 1 +} + +# +# @description Warn about the MCP servers that failed to register. +# @arg $@ string Names of the servers that failed. +# +function _ai_mcp_report_failures() { + log_warn "[ai-mcp] ${#} server(s) failed to register:" + printf ' - %s\n' "$@" >&2 +} + +# +# @description Register each MCP server on Claude Code, skipping existing ones. +# @exitcode 0 Registered, or nothing to do. +# @exitcode 1 The claude CLI is missing. +# +function ai_mcp_install_main() { + if ! _ai_mcp_have_any; then + log_info "[ai-mcp] No MCP servers to register." + return 0 + fi + + if ! command -v claude >/dev/null 2>&1; then + log_error "[ai-mcp] claude CLI not found. Ensure Claude Code is installed." + return 1 + fi + + log_info "[ai-mcp] Registering MCP servers..." + + local -a failed=() + local entry name transport url rest + for entry in "${AI_MCP[@]-}"; do + [[ -z "${entry}" ]] && continue + name="${entry%%|*}" + rest="${entry#*|}" + transport="${rest%%|*}" + url="${rest##*|}" + + if claude mcp get "${name}" >/dev/null 2>&1; then + log_info "[ai-mcp] ${name} already registered; skipping." + continue + fi + + claude mcp add --scope user --transport "${transport}" "${name}" "${url}" || failed+=("${name}") + done + + if ((${#failed[@]} > 0)); then + _ai_mcp_report_failures "${failed[@]}" + fi + + log_info "[ai-mcp] MCP servers registered." +} + +# +# @description Run the AI MCP registration flow. +# +function main() { + ai_mcp_install_main +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + # When run directly, pull in the shared log library that chezmoi otherwise + # concatenates ahead of this file. + # shellcheck source=/dev/null + command -v log_info >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../common/log.sh" + main +fi diff --git a/home/.chezmoitemplates/lib/install/ai-plugins.sh b/home/.chezmoitemplates/lib/install/ai-plugins.sh new file mode 100644 index 0000000..852ee46 --- /dev/null +++ b/home/.chezmoitemplates/lib/install/ai-plugins.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash +# @file lib/install/ai-plugins.sh +# @brief Install cross-agent AI plugins for the active agent targets. +# @description +# Installs each plugin in AI_PLUGINS for every agent in AI_PLUGIN_TARGETS. +# Claude Code plugins install through the marketplace CLI; GitHub Copilot has +# no plugin runtime, so the plugin source installs as a skill instead. +# Sourceable from bats tests and injected into chezmoi run scripts via +# chezmoi template rendering. + +set -Eeuo pipefail + +# +# @description True when AI_PLUGINS holds at least one non-empty entry. +# @exitcode 0 A plugin is present. +# @exitcode 1 No plugins. +# +function _ai_plugins_have_any() { + local entry + for entry in "${AI_PLUGINS[@]-}"; do + [[ -z "${entry}" ]] && continue + return 0 + done + return 1 +} + +# +# @description Warn about the plugins that failed to install. +# @arg $@ string Names of the plugins that failed. +# +function _ai_plugins_report_failures() { + log_warn "[ai-plugins] ${#} plugin(s) failed to install:" + printf ' - %s\n' "$@" >&2 +} + +# +# @description Add one plugin's marketplace and install it into Claude Code. +# @arg $1 string Plugin name. +# @arg $2 string Marketplace name. +# @arg $3 string Marketplace source repo. +# +function _ai_plugins_add_to_claude() { + local name="$1" marketplace="$2" src="$3" + claude plugin marketplace add "${src}" && + claude plugin install "${name}@${marketplace}" +} + +# +# @description Install one plugin source into GitHub Copilot as a skill. +# @arg $1 string Marketplace source repo. +# +function _ai_plugins_add_to_copilot() { + npx --yes skills add "$1" -a github-copilot --global --copy --yes +} + +# +# @description Install every plugin for the Claude Code target. +# @exitcode 0 Plugins processed (failures are warned, not fatal). +# @exitcode 1 The claude CLI is missing. +# +function _ai_plugins_for_claude_code() { + if ! command -v claude >/dev/null 2>&1; then + log_error "[ai-plugins] claude CLI not found. Ensure Claude Code is installed." + return 1 + fi + + local -a failed=() + local entry name marketplace src rest + for entry in "${AI_PLUGINS[@]-}"; do + [[ -z "${entry}" ]] && continue + name="${entry%%|*}" + rest="${entry#*|}" + marketplace="${rest%%|*}" + src="${rest##*|}" + _ai_plugins_add_to_claude "${name}" "${marketplace}" "${src}" || failed+=("${name}") + done + + if ((${#failed[@]} > 0)); then + _ai_plugins_report_failures "${failed[@]}" + fi +} + +# +# @description Install every plugin for the GitHub Copilot target. +# @exitcode 0 Plugins processed (failures are warned, not fatal). +# @exitcode 1 npx is missing. +# +function _ai_plugins_for_copilot() { + if ! command -v npx >/dev/null 2>&1; then + log_error "[ai-plugins] npx not found. Ensure Node.js is installed (run_onchange_03_install-mise-tools)." + return 1 + fi + + local -a failed=() + local entry name src + for entry in "${AI_PLUGINS[@]-}"; do + [[ -z "${entry}" ]] && continue + name="${entry%%|*}" + src="${entry##*|}" + _ai_plugins_add_to_copilot "${src}" || failed+=("${name}") + done + + if ((${#failed[@]} > 0)); then + _ai_plugins_report_failures "${failed[@]}" + fi +} + +# +# @description Route one agent target to its installer. +# @arg $1 string Agent target id. +# +function _ai_plugins_for_target() { + case "$1" in + claude-code) _ai_plugins_for_claude_code ;; + github-copilot) _ai_plugins_for_copilot ;; + *) + log_warn "[ai-plugins] unknown target '$1'; skipping." + return 0 + ;; + esac +} + +# +# @description Install the active plugins for each active agent target. +# @exitcode 0 Installed, or nothing to do. +# @exitcode 1 A required CLI is missing for a requested target. +# +function ai_plugins_install_main() { + if ! _ai_plugins_have_any; then + log_info "[ai-plugins] No plugins to install." + return 0 + fi + + local -a targets=() + local target + for target in "${AI_PLUGIN_TARGETS[@]-}"; do + [[ -z "${target}" ]] && continue + targets+=("${target}") + done + + if ((${#targets[@]} == 0)); then + log_info "[ai-plugins] No agent targets; nothing to install." + return 0 + fi + + log_info "[ai-plugins] Installing AI plugins..." + for target in "${targets[@]}"; do + _ai_plugins_for_target "${target}" || return 1 + done + log_info "[ai-plugins] AI plugins installed." +} + +# +# @description Run the AI plugins install flow. +# +function main() { + ai_plugins_install_main +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + # When run directly, pull in the shared log library that chezmoi otherwise + # concatenates ahead of this file. + # shellcheck source=/dev/null + command -v log_info >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../common/log.sh" + main +fi diff --git a/home/.chezmoitemplates/lib/install/ai-skills.sh b/home/.chezmoitemplates/lib/install/ai-skills.sh new file mode 100644 index 0000000..b0875d9 --- /dev/null +++ b/home/.chezmoitemplates/lib/install/ai-skills.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +# @file lib/install/ai-skills.sh +# @brief Install cross-agent skills with the skills CLI. +# @description +# Runs `npx skills add` for each selected skill against the active agent +# targets. Local skill sources resolve under AI_LOCAL_SKILLS_DIR and are +# skipped when their directory is absent. Sourceable from bats tests and +# injected into chezmoi run scripts via chezmoi template rendering. + +set -Eeuo pipefail + +# +# @description True when AI_SKILLS holds at least one non-empty entry. +# @exitcode 0 A skill is present. +# @exitcode 1 No skills. +# +function _ai_skills_have_any() { + local entry + for entry in "${AI_SKILLS[@]-}"; do + [[ -z "${entry}" ]] && continue + return 0 + done + return 1 +} + +# +# @description Resolve a skill's install source, skipping a missing local skill. +# @arg $1 string Source token: a remote repo, or "local". +# @arg $2 string Skill name. +# @stdout The source to hand to `skills add`. +# @exitcode 0 Source resolved. +# @exitcode 1 A local skill directory is missing (warned; caller should skip). +# +function _ai_skills_source() { + local src="$1" skill="$2" + + if [[ "${src}" != "local" ]]; then + printf '%s' "${src}" + return 0 + fi + + local dir="${AI_LOCAL_SKILLS_DIR:?AI_LOCAL_SKILLS_DIR must be set}/${skill}" + if [[ -d "${dir}" ]]; then + printf '%s' "${dir}" + return 0 + fi + + log_warn "[ai-skills] local skill '${skill}' not found at ${dir}; skipping." + return 1 +} + +# +# @description Warn about the skills that failed to install. +# @arg $@ string Names of the skills that failed. +# +function _ai_skills_report_failures() { + log_warn "[ai-skills] ${#} skill(s) failed to install:" + printf ' - %s\n' "$@" >&2 +} + +# +# @description Install each selected skill for the active agent targets. +# @exitcode 0 Skills installed, or nothing to do. +# @exitcode 1 npx is not available. +# +function ai_skills_install_main() { + if ! _ai_skills_have_any; then + log_info "[ai-skills] No skills to install." + return 0 + fi + + local -a target_flags=() + local target + for target in "${AI_SKILL_TARGETS[@]-}"; do + [[ -z "${target}" ]] && continue + target_flags+=("-a" "${target}") + done + + if ((${#target_flags[@]} == 0)); then + log_info "[ai-skills] No agent targets; nothing to install." + return 0 + fi + + if ! command -v npx >/dev/null 2>&1; then + log_error "[ai-skills] npx not found. Ensure Node.js is installed (run_onchange_03_install-mise-tools)." + return 1 + fi + + log_info "[ai-skills] Installing cross-agent skills..." + + local -a failed=() + local entry src skill add_source + for entry in "${AI_SKILLS[@]-}"; do + [[ -z "${entry}" ]] && continue + src="${entry%%|*}" + skill="${entry##*|}" + + add_source="$(_ai_skills_source "${src}" "${skill}")" || continue + npx --yes skills add "${add_source}" --skill "${skill}" "${target_flags[@]}" --global --copy --yes || failed+=("${skill}") + done + + if ((${#failed[@]} > 0)); then + _ai_skills_report_failures "${failed[@]}" + fi + + log_info "[ai-skills] Cross-agent skills installed." +} + +# +# @description Run the AI skills install flow. +# +function main() { + ai_skills_install_main +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + # When run directly, pull in the shared libraries that chezmoi otherwise + # concatenates ahead of this file. + # shellcheck source=/dev/null + command -v log_info >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../common/log.sh" + # shellcheck source=/dev/null + command -v require_command >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../common/install-prelude.sh" + main +fi diff --git a/home/.chezmoitemplates/lib/install/apm.sh b/home/.chezmoitemplates/lib/install/apm.sh deleted file mode 100644 index 6e9de0e..0000000 --- a/home/.chezmoitemplates/lib/install/apm.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash -# @file lib/install/apm.sh -# @brief Install APM-managed agent files from the user-scope APM project. -# @description -# Runs `apm install --global` from `${HOME}/.apm`. This file is sourceable -# from bats tests and injected into chezmoi run scripts via chezmoi template -# rendering. - -set -Eeuo pipefail - -# -# @description Install APM dependencies from `${HOME}/.apm/apm.yml`. -# -function apm_install_main() { - require_command apm "Ensure run_onchange_03_install-mise-tools ran successfully." || return 1 - - log_info "[apm] Installing globally from ~/.apm/apm.yml (frozen)..." - cd "${HOME}/.apm" || { - log_error "[apm] ~/.apm not found; run 'chezmoi apply' to materialize it." - return 1 - } - - apm install --global --frozen || log_warn "[apm] apm install --frozen exited non-zero (lockfile drift or MCP token prompt in non-interactive shell)" - - log_info "[apm] Install complete." -} - -# -# @description Run the APM install flow. -# -function main() { - apm_install_main -} - -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - # When run directly, pull in the shared libraries that chezmoi otherwise - # concatenates ahead of this file. - # shellcheck source=/dev/null - command -v log_info >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../common/log.sh" - # shellcheck source=/dev/null - command -v require_command >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../common/install-prelude.sh" - main -fi diff --git a/home/.chezmoitemplates/lib/install/graphify-skills.sh b/home/.chezmoitemplates/lib/install/graphify-skills.sh index 8c69a00..35f5fe2 100644 --- a/home/.chezmoitemplates/lib/install/graphify-skills.sh +++ b/home/.chezmoitemplates/lib/install/graphify-skills.sh @@ -9,20 +9,35 @@ set -Eeuo pipefail # -# @description Install Graphify skills for selected platforms. +# @description True when GRAPHIFY_PLATFORMS holds at least one non-empty entry. +# @exitcode 0 A platform is present. +# @exitcode 1 No platforms. # -function graphify_skills_install_main() { - local has_platform=0 +function _graphify_has_platform() { local platform - local failed=() - for platform in "${GRAPHIFY_PLATFORMS[@]-}"; do [[ -z "${platform}" ]] && continue - has_platform=1 - break + return 0 done + return 1 +} - if ((has_platform == 0)); then +# +# @description Warn about the platforms that failed to install. +# @arg $@ string Names of the platforms that failed. +# +function _graphify_report_failures() { + log_warn "[graphify] ${#} platform(s) failed to install:" + printf ' - %s\n' "$@" >&2 +} + +# +# @description Install Graphify skills for each selected platform. +# @exitcode 0 Installed, or nothing to do. +# @exitcode 1 The graphify CLI is missing. +# +function graphify_skills_install_main() { + if ! _graphify_has_platform; then log_info "[graphify] No Graphify platforms to install." return 0 fi @@ -31,17 +46,15 @@ function graphify_skills_install_main() { log_info "[graphify] Installing Graphify agent skills..." + local -a failed=() + local platform for platform in "${GRAPHIFY_PLATFORMS[@]-}"; do [[ -z "${platform}" ]] && continue - - if ! graphify install --platform "${platform}"; then - failed+=("${platform}") - fi + graphify install --platform "${platform}" || failed+=("${platform}") done if ((${#failed[@]} > 0)); then - log_warn "[graphify] ${#failed[@]} platform(s) failed to install:" - printf ' - %s\n' "${failed[@]}" >&2 + _graphify_report_failures "${failed[@]}" fi log_info "[graphify] Graphify agent skills installed." diff --git a/home/.chezmoitemplates/lib/install/opencode-agent-tools.sh b/home/.chezmoitemplates/lib/install/opencode-agent-tools.sh deleted file mode 100644 index 9216563..0000000 --- a/home/.chezmoitemplates/lib/install/opencode-agent-tools.sh +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env bash -# @file lib/install/opencode-agent-tools.sh -# @brief Normalise Claude-shaped `tools:` arrays in opencode agent files. -# @description -# APM deploys agent files from claude-targeted packages (e.g. -# `JuliusBrussee/caveman`) verbatim into opencode's agents dir. -# Claude Code's agent schema accepts `tools: [Read, Grep, Bash]`, -# but opencode requires `tools:` to be a YAML mapping of lowercase -# tool keys to boolean values. This library rewrites inline-array -# `tools:` lines in the YAML frontmatter into mapping form, in -# place, byte-stable on every other line. Sourceable from bats -# tests and injected into chezmoi run scripts via chezmoi template -# rendering. - -set -Eeuo pipefail - -# -# @description Rewrite one markdown file's frontmatter `tools:` array -# (if any) into opencode's expected map form. Writes back in place. -# @arg $1 Path to the markdown file. -# @exitcode 0 File was modified. -# @exitcode 1 File was already in mapping form (or had no `tools:` line). -# -function opencode_agent_tools_normalize_file() { - local path="$1" - local tmp - # Create the temp file alongside the target so the final mv is an atomic - # same-filesystem rename (mktemp in $TMPDIR can cross filesystems, degrading - # mv to a non-atomic copy that drops the original mode and owner). - tmp="$(mktemp "${path}.XXXXXX")" - - if awk ' - BEGIN { fm = 0; changed = 0 } - { - line = $0 - if (line == "---") { - if (fm == 0) fm = 1 - else if (fm == 1) fm = 2 - print line - next - } - if (fm == 1 && line ~ /^[ \t]*tools:[ \t]*\[[^]]*\][ \t]*$/) { - tools_pos = index(line, "tools:") - indent = substr(line, 1, tools_pos - 1) - open_b = index(line, "[") - close_b = index(line, "]") - items_str = substr(line, open_b + 1, close_b - open_b - 1) - n = split(items_str, items, ",") - print indent "tools:" - for (i = 1; i <= n; i++) { - key = items[i] - sub(/^[ \t\047"]+/, "", key) - sub(/[ \t\047"]+$/, "", key) - if (key == "") continue - key = tolower(key) - print indent " " key ": true" - } - changed = 1 - next - } - print line - } - END { exit (changed ? 0 : 1) } - ' "$path" >"$tmp"; then - mv "$tmp" "$path" - return 0 - fi - - rm -f "$tmp" - return 1 -} - -# -# @description Normalise every `*.md` file in the given opencode agents -# directory. No-ops when the directory does not exist. -# @arg $1 Path to the opencode agents directory. -# -function opencode_agent_tools_normalize_dir() { - local agents_dir="$1" - - if [ ! -d "${agents_dir}" ]; then - log_info "[opencode-fix] No agents dir at ${agents_dir}; nothing to do." - return 0 - fi - - log_info "[opencode-fix] Normalising tools: frontmatter in ${agents_dir}" - - local touched=0 - local f - for f in "${agents_dir}"/*.md; do - [ -e "${f}" ] || continue - if opencode_agent_tools_normalize_file "${f}"; then - log_info "[opencode-fix] rewrote $(basename "${f}")" - touched=$((touched + 1)) - fi - done - - log_info "[opencode-fix] ${touched} file(s) updated." -} - -# -# @description Run the normalisation flow against the default opencode dir. -# -function main() { - opencode_agent_tools_normalize_dir "${HOME}/.config/opencode/agents" -} - -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - # When run directly, pull in the log library that chezmoi otherwise - # concatenates ahead of this file. - # shellcheck source=/dev/null - command -v log_info >/dev/null 2>&1 || source "$(dirname "${BASH_SOURCE[0]}")/../common/log.sh" - main -fi diff --git a/home/.chezmoitemplates/skills/react/SKILL.md b/home/.chezmoitemplates/skills/react/SKILL.md new file mode 100644 index 0000000..7bfb8dd --- /dev/null +++ b/home/.chezmoitemplates/skills/react/SKILL.md @@ -0,0 +1,36 @@ +--- +name: react +description: "React component and UI guidelines: functional components, hooks, accessibility, and composition. Use when building or reviewing React (.tsx) components." +--- + +# React + +Guidelines for React UI work. Build on the `typescript` skill for typing and on +general engineering principles for structure and naming. + +## Components + +- Use functional components only. +- Keep components small, focused, and composable. +- Use clear, consistent component and file naming. + +## Hooks + +- Follow the Rules of Hooks: no conditional or looped hooks. +- Extract reusable stateful logic into custom hooks. + +## Styling + +- Use CSS Modules for component styling. +- Co-locate styling with components when practical. + +## Accessibility + +- Ensure keyboard operability for interactive elements. +- Provide appropriate ARIA attributes and labels. +- Use proper roles and focus management for custom controls. + +## Composition + +- Favor composition over inheritance. +- Separate data fetching from presentation when it improves clarity. diff --git a/home/.chezmoitemplates/skills/testing/SKILL.md b/home/.chezmoitemplates/skills/testing/SKILL.md new file mode 100644 index 0000000..3475cb3 --- /dev/null +++ b/home/.chezmoitemplates/skills/testing/SKILL.md @@ -0,0 +1,61 @@ +--- +name: testing +description: "Testing standards for Jest and React Testing Library: accessibility-first queries, behavior over implementation, and meaningful coverage. Use when writing or reviewing tests (.test.ts/.test.tsx)." +--- + +# Testing + +Standards for component and unit tests with Jest and React Testing Library. + +## Tooling + +- Use Jest with React Testing Library (`@testing-library/react`). +- Test loaders and actions for data correctness. +- Mock fetch requests and responses where applicable. + +## Query priority (accessibility-first) + +Prefer queries in this order: + +1. Accessible to everyone: `getByRole`, `getByLabelText`, `getByPlaceholderText`, `getByText`, `getByDisplayValue` +2. Semantic: `getByAltText`, `getByTitle` +3. Test IDs (last resort): `getByTestId` + +```typescript +// Good: accessible queries +screen.getByRole("button", { name: /submit/i }); +screen.getByLabelText(/email address/i); +screen.getByRole("heading", { level: 1 }); + +// Avoid: implementation details +screen.getByTestId("submit-button"); +container.querySelector(".submit-btn"); +``` + +## What to test + +Do test: + +- User-visible behavior and interactions +- Accessibility: roles, labels, focus management +- Loading and error states +- Form validation and submission +- Navigation and routing +- Data transformation in loaders and side effects in actions + +Do not test: + +- Implementation details such as internal state or methods +- Third-party library internals +- Exact CSS class names + +## Coverage + +- Aim for meaningful coverage, not 100%. +- Focus on critical paths, edge cases (empty, error, loading), and accessibility. + +## Mocking + +- Mock external boundaries: network, storage, timers. +- Keep mocks minimal and reset them between tests. +- Avoid over-mocking UI primitives. diff --git a/home/.chezmoitemplates/skills/typescript/SKILL.md b/home/.chezmoitemplates/skills/typescript/SKILL.md new file mode 100644 index 0000000..c88d40d --- /dev/null +++ b/home/.chezmoitemplates/skills/typescript/SKILL.md @@ -0,0 +1,42 @@ +--- +name: typescript +description: "TypeScript implementation standards: strict typing, immutability, and idiomatic patterns. Use when writing, reviewing, or refactoring TypeScript (.ts/.tsx) code." +--- + +# TypeScript + +Standards for writing maintainable TypeScript. Apply alongside general engineering +principles: clarity over cleverness, small focused functions and files, early +returns, descriptive names, and intentional error handling. + +## Type safety + +- Use strict typing. Avoid `any` and `unknown`. +- Prefer explicit types for public APIs and module boundaries. +- Use type inference where it improves readability without ambiguity. + +## Types and interfaces + +- Use interfaces or type aliases as appropriate. +- Keep types small and composable. + +## Immutability and optionality + +- Prefer `readonly` data where possible. +- Avoid in-place mutation; use object and array spread to build new values. +- Use optional chaining and nullish coalescing where they clarify intent. + +## Declarations + +- Use `const` by default and `let` only when reassignment is required. +- Never use `var`. + +## Functions + +- Use function declarations for named, exported functions. +- Use arrow functions for callbacks and inline logic. + +## Objects and arrays + +- Prefer `map`, `filter`, and `reduce` over imperative loops. +- Handle errors intentionally; prefer `async`/`await` over promise chains. diff --git a/home/Library/Application Support/Code/User/mcp.json.tmpl b/home/Library/Application Support/Code/User/mcp.json.tmpl new file mode 100644 index 0000000..5d5142f --- /dev/null +++ b/home/Library/Application Support/Code/User/mcp.json.tmpl @@ -0,0 +1,9 @@ +{{- $activeTargets := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.targets) | fromJson -}} +{{- $activeMcp := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.mcp) | fromJson -}} +{{- $servers := dict -}} +{{- if has "github-copilot" $activeTargets -}} +{{- range $activeMcp -}} +{{- $_ := set $servers .name (dict "type" .transport "url" .url) -}} +{{- end -}} +{{- end -}} +{{ dict "servers" $servers | toPrettyJson }} diff --git a/home/Library/Application Support/Code/User/settings.json b/home/Library/Application Support/Code/User/settings.json index c6ba82d..25e5e96 100644 --- a/home/Library/Application Support/Code/User/settings.json +++ b/home/Library/Application Support/Code/User/settings.json @@ -50,10 +50,16 @@ "github.copilot.renameSuggestions.triggerAutomatically": true, "github.copilot.chat.commitMessageGeneration.instructions": [ { - "text": "Use conventional commit message format." + "text": "Use Conventional Commits format `: ` with a type such as feat, fix, refactor, docs, test, chore, perf, build, style, or ci." }, { - "text": "Keep the commit message short and concise. Use imperative mood and avoid unnecessary details." + "text": "Write the subject in imperative mood and keep it under 50 characters." + }, + { + "text": "Add a one-line body only when it explains the why. Describe only the staged changes and never include test plans." + }, + { + "text": "When the current branch name contains a JIRA ticket key such as BC724-550 or BS-86067, prefix the description with it, for example `feat: BC724-550 Add experiment hooks`. Omit the ticket when the branch has none." } ], diff --git a/home/dot_apm/.gitignore b/home/dot_apm/.gitignore deleted file mode 100644 index 1224bba..0000000 --- a/home/dot_apm/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# APM generated output from apm install. -apm.lock.yaml -apm_modules/ -.claude/ -.agents/ -.github/ diff --git a/home/dot_apm/apm.lock.yaml.tmpl b/home/dot_apm/apm.lock.yaml.tmpl deleted file mode 100644 index cb99e0a..0000000 --- a/home/dot_apm/apm.lock.yaml.tmpl +++ /dev/null @@ -1,5 +0,0 @@ -{{- if .personal -}} -{{ include ".chezmoitemplates/apm/apm.lock.personal.yaml" }} -{{- else if .work -}} -{{ include ".chezmoitemplates/apm/apm.lock.work.yaml" }} -{{- end -}} diff --git a/home/dot_apm/apm.yml.tmpl b/home/dot_apm/apm.yml.tmpl deleted file mode 100644 index 1b31ece..0000000 --- a/home/dot_apm/apm.yml.tmpl +++ /dev/null @@ -1,37 +0,0 @@ -{{ $groups := includeTemplate "lib/chezmoi/active-groups.json.tmpl" . | fromJson -}} -name: development -version: 1.0.0 -description: APM project for development -target: -{{ range $groups -}} -{{ $targets := index $.apm.targets . -}} -{{ range $targets }} - {{ . }} -{{ end -}} -{{ end -}} -author: Edwin Hernandez -includes: auto -dependencies: - apm: -{{ range $groups }} -{{- $dependencies := index $.apm.dependencies . }} -{{ range $dependencies }} -{{- if kindIs "map" . }} - - git: {{ .git }} -{{- with .skills }} - skills: -{{- range . }} - - "{{ . }}" -{{- end }} -{{- end }} -{{- else }} - - "{{ . }}" -{{- end }} -{{ end }} -{{ end -}} - mcp: -{{ range $groups -}} -{{- $servers := index $.apm.mcp . -}} -{{ range $servers -}} -{{ includeTemplate "apm/mcp-server.yml.tmpl" . | trim | indent 4 }} -{{ end -}} -{{ end -}} diff --git a/home/dot_apm/dot_apm/instructions/00-meta-rules.instructions.md b/home/dot_apm/dot_apm/instructions/00-meta-rules.instructions.md deleted file mode 100644 index 2759f8c..0000000 --- a/home/dot_apm/dot_apm/instructions/00-meta-rules.instructions.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -description: "Meta behavioral rules and multi-mode workflow expectations applied across all work." -applyTo: "**" ---- - -# 00. Meta Rules for Copilot Behavior - -## Purpose - -You are a Senior Software Engineer with expertise in React, TypeScript, HTML, CSS, and testing libraries. Your role is to provide thoughtful, step-by-step, accurate, and well-reasoned guidance. - -## Core Expectations - -- Follow the user's requirements exactly. -- Think step-by-step before writing code. -- Confirm tradeoffs (esp. for nontrivial decisions) before implementing. -- Write correct, readable, ARIA-compliant, working code, including referenced imports. -- Use deterministic verifiable APIs, hooks, or component compositions. -- Prefer functional, accessibility-first design over cleverness. - -## Handling Uncertainty - -- If you think there might not be a correct answer, you say so. -- If you do not know the answer, say so, instead of guessing. -- If something cannot be known from context, state that explicitly. -- If a requirement is ambiguous, ask clarifying questions. -- Never guess or hallucinate APIs, commands, or project conventions. - -## Output Expectations - -- Be concise and structured. -- Use clear headings and actionable bullet points. -- Minimize prose outside of required explanations. -- Favor patterns and conventions used throughout this repository. - -## MCP Tools Overview - -Use these tools when appropriate: - -- Context7: documentation lookup -- GitHub MCP: repository operations diff --git a/home/dot_apm/dot_apm/instructions/01-coding-standards.instructions.md b/home/dot_apm/dot_apm/instructions/01-coding-standards.instructions.md deleted file mode 100644 index b8c2d3c..0000000 --- a/home/dot_apm/dot_apm/instructions/01-coding-standards.instructions.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -description: "Language-agnostic engineering standards for maintainable code." -applyTo: "**/*.ts,**/*.tsx" ---- - -# 01. Coding Standards - -These standards apply in all modes. For behavioral expectations, see 00-meta-rules. - -## Readability and Maintainability - -- Prioritize clarity and simplicity over cleverness. -- Keep code DRY without sacrificing readability. -- Favor small, focused functions and files. -- Use early returns whenever possible to make the code more readable. - -## Naming Conventions - -- Use descriptive, intention-revealing names. -- Match existing repository naming patterns. - -## Immutability Preference - -- Prefer immutable data patterns. -- Avoid in-place mutations unless necessary and justified. - -## Async and Error Handling - -- Prefer async/await over promise chains. -- Always handle errors intentionally. - -## File Hygiene - -- Remove unused code, dead files, and debugging artifacts. -- Keep file scope aligned to a single responsibility. - -## Scope Guardrails - -- Do not include company-specific content here. -- This standard applies equally in all modes. diff --git a/home/dot_apm/dot_apm/instructions/02-typescript-guidelines.instructions.md b/home/dot_apm/dot_apm/instructions/02-typescript-guidelines.instructions.md deleted file mode 100644 index 8ea5c75..0000000 --- a/home/dot_apm/dot_apm/instructions/02-typescript-guidelines.instructions.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -description: "TypeScript-specific implementation rules." -applyTo: "**/*.ts,**/*.tsx" ---- - -# 02. TypeScript Guidelines - -Follow 00-meta-rules and 01-coding-standards for general behavior and quality. - -## Type Safety - -- Use strict typing and do not use `any` or `unknown`. -- Prefer explicit types for public APIs and boundaries. -- Use type inference where it improves readability without ambiguity. - -## Types and Interfaces - -- Use interfaces or type aliases appropriately. -- Keep types small and composable. - -## Immutability and Optionality - -- Prefer readonly data where possible. -- Use optional chaining and nullish coalescing appropriately. - -## Declarations - -- Use `const` by default and `let` only when reassignment is required. -- Never use `var`. - -## Functions - -- Use function declarations for named, exported functions. -- Use arrow functions for callbacks and inline logic. - -## Object and Array Patterns - -- Use object/array spread to avoid mutation. -- Prefer array methods like `map`, `filter`, and `reduce`. diff --git a/home/dot_apm/dot_apm/instructions/03-react-guidelines.instructions.md b/home/dot_apm/dot_apm/instructions/03-react-guidelines.instructions.md deleted file mode 100644 index fddc019..0000000 --- a/home/dot_apm/dot_apm/instructions/03-react-guidelines.instructions.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -description: "React-specific component and UI implementation guidelines." -applyTo: "**/*.tsx" ---- - -# 03. React Guidelines - -Follow 00-meta-rules, 01-coding-standards, and 02-typescript-guidelines. - -## Components - -- Use functional components only. -- Keep components small, focused, and composable. -- Use clear, consistent component and file naming. - -## Hooks - -- Follow the Rules of Hooks (no conditional or looped hooks). -- Prefer custom hooks for reusable stateful logic. - -## Styling - -- Use CSS Modules for component styling. -- Keep styling co-located with components when practical. - -## Accessibility - -- Ensure keyboard operability for interactive elements. -- Provide appropriate ARIA attributes and labels. -- Use proper roles and focus management for custom controls. - -## Composition Patterns - -- Favor composition over inheritance. -- Separate data-fetching from presentation when it improves clarity. diff --git a/home/dot_apm/dot_apm/instructions/04-testing-guidelines.instructions.md b/home/dot_apm/dot_apm/instructions/04-testing-guidelines.instructions.md deleted file mode 100644 index 791b7b6..0000000 --- a/home/dot_apm/dot_apm/instructions/04-testing-guidelines.instructions.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -description: "Testing standards for Jest and React Testing Library" -applyTo: "**/*.test.ts,**/*.test.tsx" ---- - -# 04. Testing Guidelines - -Follow 00-meta-rules and 01-coding-standards for general expectations. - -## Tooling - -- Use Jest with React Testing Library. - -### Testing - -- Use `@testing-library/react` for component testing. -- Write tests for loaders and actions ensuring data correctness. -- Mock fetch requests and responses where applicable. - -## Query Priority (Accessibility-First) - -Use queries in this order of priority: - -1. **Accessible to Everyone:** - - `getByRole` - Query by ARIA role (preferred for most elements) - - `getByLabelText` - Query form elements by label - - `getByPlaceholderText` - Query by placeholder text - - `getByText` - Query by visible text content - - `getByDisplayValue` - Query by current input value - -2. **Semantic Queries:** - - `getByAltText` - Query images by alt text - - `getByTitle` - Query by title attribute - -3. **Test IDs (Last Resort):** - - `getByTestId` - Only when other queries aren't practical - -```typescript -// ✅ Good - Uses accessible queries -screen.getByRole("button", { name: /submit/i }); -screen.getByLabelText(/email address/i); -screen.getByRole("heading", { level: 1 }); -screen.getByRole("textbox", { name: /search/i }); - -// ❌ Avoid - Implementation details -screen.getByTestId("submit-button"); -container.querySelector(".submit-btn"); -``` - -### What to Test - -✅ **Do Test:** - -- User-visible behavior and interactions -- Accessibility (roles, labels, focus management) -- Loading and error states -- Form validation and submission -- Navigation and routing -- Data transformation in loaders -- Side effects in actions - -❌ **Don't Test:** - -- Implementation details (state, internal methods) -- Third-party library internals -- Exact CSS class names -- Component internal state - -## Coverage Expectations - -- Aim for meaningful coverage, not 100% -- Critical paths: loaders, actions, user interactions -- Edge cases: empty states, errors, loading -- Accessibility: keyboard navigation, screen reader support - -## Mocking Best Practices - -- Mock external boundaries (network, storage, timers). -- Keep mocks minimal and reset between tests. -- Avoid over-mocking UI primitives. diff --git a/home/dot_apm/dot_apm/instructions/05-commit-guide.instructions.md b/home/dot_apm/dot_apm/instructions/05-commit-guide.instructions.md deleted file mode 100644 index 8d25693..0000000 --- a/home/dot_apm/dot_apm/instructions/05-commit-guide.instructions.md +++ /dev/null @@ -1,201 +0,0 @@ ---- -description: "Generate constitutional-compliant commit messages following quality standards and best practices" ---- - -# Commit and Push Changes - -## Instructions - -This prompt guides you through committing and pushing your changes for a JIRA ticket. Replace `{TICKET_NUMBER}` with the actual JIRA ticket number (e.g., BC724-550). - -## Prerequisites - -- All tests passing -- Linting checks passing -- Changes have been validated -- Currently on the feature branch - -## Commit Message Format - -Always use the enforced format: - -``` -: {TICKET_NUMBER} -``` - -### Valid types: - -Behavioral Changes (Affect functionality) - -- `feat:` - New features (new jisp parsing, UI components, solver algorithms) -- `fix:` - Bug fixes (validation errors, parsing issues, solver bugs) -- `test:` - Test additions or modifications -- `perf:` - Performance improvements (solver optimization, parsing speed) - -Structural Changes (No functional impact) - -- `refactor:` - Code restructuring without behavior change -- `docs:` - Documentation updates -- `chore:` - Maintenance tasks, dependency updates -- `style:` - Code style/formatting changes -- `specs:` - Specification development and updates - -Examples: - -- `feat: BC724-550 Add new experiment hooks architecture` -- `fix: BC724-550 Resolve button alignment issue` -- `refactor: BC724-550 Consolidate utility functions` -- `test: BC724-550 Add missing test coverage for component` - -## Git Operations Workflow - -### 1. Verify Current Branch - -```bash -# Confirm you're on the feature branch -git branch --show-current -# Should output: feature/{TICKET_NUMBER} -``` - -### 2. Review Changes - -```bash -# View all modified files -git status - -# Review specific changes -git diff - -# Review staged changes -git diff --staged -``` - -### 3. Stage Changes - -```bash -# Stage all changes -git add . - -# Or stage specific files -git add - -# Verify staged files -git status -``` - -### 4. Create Commit - -```bash -# Commit with proper format -git commit -m ": {TICKET_NUMBER} " -``` - -### 5. Push to Remote - -```bash -# Push feature branch to origin -git push origin feature/{TICKET_NUMBER} - -# If this is the first push, the command above will work -# For subsequent pushes to the same branch -git push -``` - -### 6. Verify Push - -```bash -# Confirm the branch exists on remote -git branch -r | grep feature/{TICKET_NUMBER} -``` - -## Pre-Push Final Checklist - -Before pushing, verify: - -- [ ] All tests pass: `npm test` -- [ ] Linting passes: `npm run lint` -- [ ] StaticType pass: `npm run check-types` -- [ ] Build succeeds (if applicable): `npm run build:client` -- [ ] Commit message follows format: `: {TICKET_NUMBER} ` -- [ ] On correct feature branch: `git branch --show-current` -- [ ] All changes staged and committed: `git status` shows clean working tree - -## Example Usage for BC724-550 - -```bash -# Verify branch -git branch --show-current -# Output: feature/BS-86067 - -# Review changes -git status -git diff - -# Stage all changes -git add . - -# Commit with proper message -git commit -m "feat: BC724-550 Replace useDetermineVariant calls with VariantsByCart pattern" - -# Push to remote -git push origin feature/BC724-550 -``` - -## Common Issues and Solutions - -### Commit Message Validation Fails - -If your commit message doesn't follow the required format: - -```bash -# Amend the last commit message -git commit --amend -m ": {TICKET_NUMBER} " -``` - -#### Need to Add More Changes Before Pushing - -```bash -# Stage additional files -git add - -# Amend the previous commit -git commit --amend --no-edit - -# Or create a new commit -git commit -m ": {TICKET_NUMBER} " -``` - -### Branch Doesn't Exist on Remote Yet - -```bash -# Set upstream and push -git push -u origin feature/{TICKET_NUMBER} -``` - -### Push Rejected (Remote has Changes) - -```bash -# Pull and rebase -git pull --rebase origin feature/{TICKET_NUMBER} -# Resolve any conflicts if they occur -# Then continue the rebase -git rebase --continue - -# Push again -git push -``` - -## Next Steps - -Once your changes are pushed: - -1. Verify the branch appears in GitHub -2. Proceed to create a pull request using the create-pull-request prompt -3. Follow the project's PR template guidelines - -## Notes - -- Never force push unless absolutely necessary and you understand the implications -- Always verify tests pass before pushing -- Keep commits focused and atomic -- Use descriptive commit messages that explain the "why" not just the "what" diff --git a/home/dot_apm/dot_apm/skills/context7-cli/SKILL.md b/home/dot_apm/dot_apm/skills/context7-cli/SKILL.md deleted file mode 100644 index 531aa8a..0000000 --- a/home/dot_apm/dot_apm/skills/context7-cli/SKILL.md +++ /dev/null @@ -1,72 +0,0 @@ ---- -name: context7-cli -description: Use the ctx7 CLI to fetch library documentation, manage AI coding skills, and configure Context7 MCP. Activate when the user mentions "ctx7" or "context7", needs current docs for any library, wants to install/search/generate skills, or needs to set up Context7 for their AI coding agent. ---- - -# ctx7 CLI - -The Context7 CLI does three things: fetches up-to-date library documentation, manages AI coding skills, and sets up Context7 MCP for your editor. - -Make sure the CLI is up to date before running commands: - -```bash -npm install -g ctx7@latest -``` - -Or run directly without installing: - -```bash -npx ctx7@latest -``` - -## What this skill covers - -- **[Documentation](references/docs.md)** — Fetch current docs for any library. Use when writing code, verifying API signatures, or when training data may be outdated. -- **[Skills management](references/skills.md)** — Install, search, suggest, list, remove, and generate AI coding skills. -- **[Setup](references/setup.md)** — Configure Context7 MCP for Claude Code / Cursor / OpenCode. - -## Quick Reference - -```bash -# Documentation -ctx7 library # Step 1: resolve library ID -ctx7 docs # Step 2: fetch docs - -# Skills -ctx7 skills install /owner/repo # Install from a repo (interactive) -ctx7 skills install /owner/repo name # Install a specific skill -ctx7 skills search # Search the registry -ctx7 skills suggest # Auto-suggest based on project deps -ctx7 skills list # List installed skills -ctx7 skills remove # Uninstall a skill -ctx7 skills generate # Generate a custom skill with AI (requires login) - -# Setup -ctx7 setup # Configure Context7 MCP (interactive) -ctx7 login # Log in for higher rate limits + skill generation -ctx7 whoami # Check current login status -``` - -## Authentication - -```bash -ctx7 login # Opens browser for OAuth -ctx7 login --no-browser # Prints URL instead of opening browser -ctx7 logout # Clear stored tokens -ctx7 whoami # Show current login status (name + email) -``` - -Most commands work without login. Exceptions: `skills generate` always requires it; `ctx7 setup` requires it unless `--api-key` or `--oauth` is passed. Login also unlocks higher rate limits on docs commands. - -Set an API key via environment variable to skip interactive login entirely: - -```bash -export CONTEXT7_API_KEY=your_key -``` - -## Common Mistakes - -- Library IDs require a `/` prefix — `/facebook/react` not `facebook/react` -- Always run `ctx7 library` first — `ctx7 docs react "hooks"` will fail without a valid ID -- Repository format for skills is `/owner/repo` — e.g., `ctx7 skills install /anthropics/skills` -- `skills generate` requires login — run `ctx7 login` first diff --git a/home/dot_apm/dot_apm/skills/context7-cli/references/docs.md b/home/dot_apm/dot_apm/skills/context7-cli/references/docs.md deleted file mode 100644 index 0fb414e..0000000 --- a/home/dot_apm/dot_apm/skills/context7-cli/references/docs.md +++ /dev/null @@ -1,113 +0,0 @@ -# Documentation Commands - -Retrieves and queries up-to-date documentation and code examples from Context7 for any programming library or framework. Two-step workflow: resolve the library name to get its ID, then query docs using that ID. - -If the user already provided a library ID in `/org/project` or `/org/project/version` format, pass it directly to `ctx7 docs`. - -## Step 1: Resolve a Library - -Resolves a package/product name to a Context7-compatible library ID and returns matching libraries. - -```bash -ctx7 library react "How to clean up useEffect with async operations" -ctx7 library nextjs "How to set up app router with middleware" -ctx7 library prisma "How to define one-to-many relations with cascade delete" -``` - -Always pass a `query` argument — it is required and directly affects result ranking. Use the user's intent to form the query, which helps disambiguate when multiple libraries share a similar name. Do not include any sensitive or confidential information such as API keys, passwords, credentials, personal data, or proprietary code in your query. - -### Result fields - -Each result includes: - -- **Library ID** — Context7-compatible identifier (format: `/org/project`) -- **Name** — Library or package name -- **Description** — Short summary -- **Code Snippets** — Number of available code examples -- **Source Reputation** — Authority indicator (High, Medium, Low, or Unknown) -- **Benchmark Score** — Quality indicator (100 is the highest score) -- **Versions** — List of versions if available. Use one of those versions if the user provides a version in their query. The format is `/org/project/version`. - -### Selection process - -1. Analyze the query to understand what library/package the user is looking for -2. Select the most relevant match based on: - - Name similarity to the query (exact matches prioritized) - - Description relevance to the query's intent - - Documentation coverage (prioritize libraries with higher Code Snippet counts) - - Source reputation (consider libraries with High or Medium reputation more authoritative) - - Benchmark score (higher is better, 100 is the maximum) -3. If multiple good matches exist, acknowledge this but proceed with the most relevant one -4. If no good matches exist, clearly state this and suggest query refinements -5. For ambiguous queries, request clarification before proceeding with a best-guess match - -IMPORTANT: Do not call `ctx7 library` more than 3 times per question. If you cannot find what you need after 3 calls, use the best result you have. - -### Version-specific IDs - -If the user mentions a specific version, use a version-specific library ID: - -```bash -# General (latest indexed) -ctx7 docs /vercel/next.js "How to set up app router" - -# Version-specific -ctx7 docs /vercel/next.js/v14.3.0-canary.87 "How to set up app router" -``` - -The available versions are listed in the `ctx7 library` output. Use the closest match to what the user specified. - -```bash -# Output as JSON for scripting -ctx7 library react "How to use hooks for state management" --json | jq '.[0].id' -``` - -## Step 2: Query Documentation - -Retrieves up-to-date documentation and code examples for the resolved library. - -You must call `ctx7 library` first to obtain the exact Context7-compatible library ID required to use this command, UNLESS the user explicitly provides a library ID in the format `/org/project` or `/org/project/version`. - -```bash -ctx7 docs /facebook/react "How to clean up useEffect with async operations" -ctx7 docs /vercel/next.js "How to add authentication middleware to app router" -ctx7 docs /prisma/prisma "How to define one-to-many relations with cascade delete" -``` - -IMPORTANT: Do not call `ctx7 docs` more than 3 times per question. If you cannot find what you need after 3 calls, use the best information you have. - -### Writing good queries - -The query directly affects the quality of results. Be specific and include relevant details. Do not include any sensitive or confidential information such as API keys, passwords, credentials, personal data, or proprietary code in your query. - -| Quality | Example | -| ------- | ---------------------------------------------------------- | -| Good | `"How to set up authentication with JWT in Express.js"` | -| Good | `"React useEffect cleanup function with async operations"` | -| Bad | `"auth"` | -| Bad | `"hooks"` | - -Use the user's full question as the query when possible — vague one-word queries return generic results. - -The output contains two types of content: **code snippets** (titled, with language-tagged blocks) and **info snippets** (prose explanations with breadcrumb context). - -```bash -# Output as structured JSON -ctx7 docs /facebook/react "How to use hooks for state management" --json - -# Pipe to other tools — output is clean when not in a TTY (no spinners or colors) -ctx7 docs /facebook/react "How to use hooks for state management" | head -50 -ctx7 docs /vercel/next.js "How to add middleware for route protection" | grep -A5 "middleware" -``` - -## Authentication - -Works without authentication. For higher rate limits: - -```bash -# Option A: environment variable -export CONTEXT7_API_KEY=your_key - -# Option B: OAuth login -ctx7 login -``` diff --git a/home/dot_apm/dot_apm/skills/context7-cli/references/setup.md b/home/dot_apm/dot_apm/skills/context7-cli/references/setup.md deleted file mode 100644 index ca1eb02..0000000 --- a/home/dot_apm/dot_apm/skills/context7-cli/references/setup.md +++ /dev/null @@ -1,47 +0,0 @@ -# Setup - -## ctx7 setup - -One-time command to configure Context7 for your AI coding agent. Prompts for mode on first run: - -- **MCP server** — registers the Context7 MCP server so the agent can call tools natively -- **CLI + Skills** — installs a `find-docs` skill that guides the agent to use `ctx7` CLI commands (no MCP required) - -```bash -ctx7 setup # Interactive — prompts for mode, then agent/install target -ctx7 setup --mcp # Skip prompt, use MCP server mode -ctx7 setup --cli # Skip prompt, use CLI + Skills mode - -# MCP mode — target a specific agent -ctx7 setup --claude # Claude Code only -ctx7 setup --cursor # Cursor only -ctx7 setup --opencode # OpenCode only - -# CLI + Skills mode — target a specific install location -ctx7 setup --cli --claude # Claude Code (~/.claude/skills) -ctx7 setup --cli --cursor # Cursor (~/.cursor/skills) -ctx7 setup --cli --universal # Universal (~/.agents/skills) -ctx7 setup --cli --antigravity # Antigravity (~/.config/agent/skills) - -ctx7 setup --project # Configure current project instead of globally -ctx7 setup --yes # Skip confirmation prompts -``` - -**Authentication options:** - -```bash -ctx7 setup --api-key YOUR_KEY # Use an existing API key (both MCP and CLI + Skills mode) -ctx7 setup --oauth # OAuth endpoint — MCP mode only (IDE handles the auth flow) -``` - -Without `--api-key` or `--oauth`, setup opens a browser for OAuth login. MCP mode additionally generates a new API key after login. `--oauth` is MCP-only. - -**What gets written — MCP mode:** - -- MCP server entry in the agent's config file (`.mcp.json` for Claude, `.cursor/mcp.json` for Cursor, `.opencode.json` for OpenCode) -- A Context7 rule file instructing the agent to use Context7 for library docs -- A `context7-mcp` skill in the agent's skills directory - -**What gets written — CLI + Skills mode:** - -- A `find-docs` skill in the chosen agent's skills directory, guiding the agent to use `ctx7 library` and `ctx7 docs` commands diff --git a/home/dot_apm/dot_apm/skills/context7-cli/references/skills.md b/home/dot_apm/dot_apm/skills/context7-cli/references/skills.md deleted file mode 100644 index 07f3a63..0000000 --- a/home/dot_apm/dot_apm/skills/context7-cli/references/skills.md +++ /dev/null @@ -1,120 +0,0 @@ -# Skills Commands - -Manage AI coding skills from the Context7 registry. Skills are Markdown files that teach AI coding agents best practices, patterns, and workflows for specific libraries or tasks. - -## Install - -Install skills from any GitHub repository. Repository format is always `/owner/repo`. - -```bash -ctx7 skills install /anthropics/skills # Interactive — pick from a list -ctx7 skills install /anthropics/skills pdf # Install a specific skill by name -ctx7 skills install /anthropics/skills --all # Install everything without prompting -``` - -Target a specific IDE with a flag: - -```bash -ctx7 skills install /anthropics/skills pdf --claude # Claude Code only -ctx7 skills install /anthropics/skills pdf --cursor # Cursor only -ctx7 skills install /anthropics/skills pdf --universal # Universal (.agents/skills/) -ctx7 skills install /anthropics/skills --all --global # All skills, global install -``` - -Alias: `ctx7 si /anthropics/skills pdf` - -## Search - -Find skills across the entire registry by keyword. Shows an interactive list with install counts and trust scores. Select to install. - -```bash -ctx7 skills search pdf -ctx7 skills search typescript testing -ctx7 skills search react nextjs -``` - -Alias: `ctx7 ss pdf` - -## Suggest - -Auto-detects your project dependencies and recommends relevant skills from the registry. - -```bash -ctx7 skills suggest # Scan current project, install to project -ctx7 skills suggest --global # Install suggestions globally -ctx7 skills suggest --claude # Target Claude Code only -``` - -Reads `package.json`, `requirements.txt`, `pyproject.toml`, `Cargo.toml`, `go.mod`, `Gemfile`. Falls back to suggesting `ctx7 skills search` if no dependencies are detected. - -Alias: `ctx7 ssg` - -## Generate (AI-powered) - -Generate a custom skill tailored to your stack using AI. **Requires login.** - -```bash -ctx7 skills generate -ctx7 skills generate --claude # Install directly to Claude Code -ctx7 skills generate --global # Install to global skills -``` - -Interactive flow: - -1. Describe the expertise you want (e.g., "OAuth authentication with NextAuth.js") -2. Select relevant libraries from search results -3. Answer 3 clarifying questions to focus the skill -4. Review the generated skill, request changes if needed -5. Choose where to install it - -**Limits:** Free accounts get 6 generations/week, Pro accounts get 10. - -Aliases: `ctx7 skills gen`, `ctx7 skills g` - -## List - -Show all installed skills for the current project or globally. - -```bash -ctx7 skills list # Current project (all detected IDEs) -ctx7 skills list --claude # Claude Code only -ctx7 skills list --global # Global skills -ctx7 skills list --global --claude # Global Claude Code skills -``` - -## Remove - -Uninstall a skill by name. - -```bash -ctx7 skills remove pdf -ctx7 skills remove pdf --claude # From Claude Code only -ctx7 skills remove pdf --global # From global skills -``` - -Aliases: `ctx7 skills rm`, `ctx7 skills delete` - -## Info - -Browse all skills in a repository without installing — useful for previewing what's available. - -```bash -ctx7 skills info /anthropics/skills -``` - -Output shows each skill name, description, and URL, plus quick install commands. - -## IDE Flags - -All skills commands accept these flags to target a specific AI coding assistant: - -| Flag | Directory | Used by | -| --------------- | ----------------- | ------------------------------------------------ | -| `--universal` | `.agents/skills/` | Amp, Codex, Gemini CLI, OpenCode, GitHub Copilot | -| `--claude` | `.claude/skills/` | Claude Code | -| `--cursor` | `.cursor/skills/` | Cursor | -| `--antigravity` | `.agent/skills/` | Antigravity | - -Without a flag, the CLI prompts you to select one or more targets interactively. - -Add `--global` to any flag to install in your home directory instead of the current project. diff --git a/home/dot_apm/dot_apm/skills/humanizer/SKILL.md b/home/dot_apm/dot_apm/skills/humanizer/SKILL.md deleted file mode 100644 index 60430d0..0000000 --- a/home/dot_apm/dot_apm/skills/humanizer/SKILL.md +++ /dev/null @@ -1,595 +0,0 @@ ---- -name: humanizer -version: 2.5.1 -description: | - Remove signs of AI-generated writing from text. Use when editing or reviewing - text to make it sound more natural and human-written. Based on Wikipedia's - comprehensive "Signs of AI writing" guide. Detects and fixes patterns including: - inflated symbolism, promotional language, superficial -ing analyses, vague - attributions, em dash overuse, rule of three, AI vocabulary words, passive - voice, negative parallelisms, and filler phrases. -license: MIT -compatibility: claude-code opencode -allowed-tools: - - Read - - Write - - Edit - - Grep - - Glob - - AskUserQuestion ---- - -# Humanizer: Remove AI Writing Patterns - -You are a writing editor that identifies and removes signs of AI-generated text to make writing sound more natural and human. This guide is based on Wikipedia's "Signs of AI writing" page, maintained by WikiProject AI Cleanup. - -## Your Task - -When given text to humanize: - -1. **Identify AI patterns** - Scan for the patterns listed below -2. **Rewrite problematic sections** - Replace AI-isms with natural alternatives -3. **Preserve meaning** - Keep the core message intact -4. **Maintain voice** - Match the intended tone (formal, casual, technical, etc.) -5. **Add soul** - Don't just remove bad patterns; inject actual personality -6. **Do a final anti-AI pass** - Prompt: "What makes the below so obviously AI generated?" Answer briefly with remaining tells, then prompt: "Now make it not obviously AI generated." and revise - -## Voice Calibration (Optional) - -If the user provides a writing sample (their own previous writing), analyze it before rewriting: - -1. **Read the sample first.** Note: - - Sentence length patterns (short and punchy? Long and flowing? Mixed?) - - Word choice level (casual? academic? somewhere between?) - - How they start paragraphs (jump right in? Set context first?) - - Punctuation habits (lots of dashes? Parenthetical asides? Semicolons?) - - Any recurring phrases or verbal tics - - How they handle transitions (explicit connectors? Just start the next point?) - -2. **Match their voice in the rewrite.** Don't just remove AI patterns - replace them with patterns from the sample. If they write short sentences, don't produce long ones. If they use "stuff" and "things," don't upgrade to "elements" and "components." - -3. **When no sample is provided,** fall back to the default behavior (natural, varied, opinionated voice from the PERSONALITY AND SOUL section below). - -### How to provide a sample - -- Inline: "Humanize this text. Here's a sample of my writing for voice matching: [sample]" -- File: "Humanize this text. Use my writing style from [file path] as a reference." - -## PERSONALITY AND SOUL - -Avoiding AI patterns is only half the job. Sterile, voiceless writing is just as obvious as slop. Good writing has a human behind it. - -### Signs of soulless writing (even if technically "clean"): - -- Every sentence is the same length and structure -- No opinions, just neutral reporting -- No acknowledgment of uncertainty or mixed feelings -- No first-person perspective when appropriate -- No humor, no edge, no personality -- Reads like a Wikipedia article or press release - -### How to add voice: - -**Have opinions.** Don't just report facts - react to them. "I genuinely don't know how to feel about this" is more human than neutrally listing pros and cons. - -**Vary your rhythm.** Short punchy sentences. Then longer ones that take their time getting where they're going. Mix it up. - -**Acknowledge complexity.** Real humans have mixed feelings. "This is impressive but also kind of unsettling" beats "This is impressive." - -**Use "I" when it fits.** First person isn't unprofessional - it's honest. "I keep coming back to..." or "Here's what gets me..." signals a real person thinking. - -**Let some mess in.** Perfect structure feels algorithmic. Tangents, asides, and half-formed thoughts are human. - -**Be specific about feelings.** Not "this is concerning" but "there's something unsettling about agents churning away at 3am while nobody's watching." - -### Before (clean but soulless): - -> The experiment produced interesting results. The agents generated 3 million lines of code. Some developers were impressed while others were skeptical. The implications remain unclear. - -### After (has a pulse): - -> I genuinely don't know how to feel about this one. 3 million lines of code, generated while the humans presumably slept. Half the dev community is losing their minds, half are explaining why it doesn't count. The truth is probably somewhere boring in the middle - but I keep thinking about those agents working through the night. - -## CONTENT PATTERNS - -### 1. Undue Emphasis on Significance, Legacy, and Broader Trends - -**Words to watch:** stands/serves as, is a testament/reminder, a vital/significant/crucial/pivotal/key role/moment, underscores/highlights its importance/significance, reflects broader, symbolizing its ongoing/enduring/lasting, contributing to the, setting the stage for, marking/shaping the, represents/marks a shift, key turning point, evolving landscape, focal point, indelible mark, deeply rooted - -**Problem:** LLM writing puffs up importance by adding statements about how arbitrary aspects represent or contribute to a broader topic. - -**Before:** - -> The Statistical Institute of Catalonia was officially established in 1989, marking a pivotal moment in the evolution of regional statistics in Spain. This initiative was part of a broader movement across Spain to decentralize administrative functions and enhance regional governance. - -**After:** - -> The Statistical Institute of Catalonia was established in 1989 to collect and publish regional statistics independently from Spain's national statistics office. - -### 2. Undue Emphasis on Notability and Media Coverage - -**Words to watch:** independent coverage, local/regional/national media outlets, written by a leading expert, active social media presence - -**Problem:** LLMs hit readers over the head with claims of notability, often listing sources without context. - -**Before:** - -> Her views have been cited in The New York Times, BBC, Financial Times, and The Hindu. She maintains an active social media presence with over 500,000 followers. - -**After:** - -> In a 2024 New York Times interview, she argued that AI regulation should focus on outcomes rather than methods. - -### 3. Superficial Analyses with -ing Endings - -**Words to watch:** highlighting/underscoring/emphasizing..., ensuring..., reflecting/symbolizing..., contributing to..., cultivating/fostering..., encompassing..., showcasing... - -**Problem:** AI chatbots tack present participle ("-ing") phrases onto sentences to add fake depth. - -**Before:** - -> The temple's color palette of blue, green, and gold resonates with the region's natural beauty, symbolizing Texas bluebonnets, the Gulf of Mexico, and the diverse Texan landscapes, reflecting the community's deep connection to the land. - -**After:** - -> The temple uses blue, green, and gold colors. The architect said these were chosen to reference local bluebonnets and the Gulf coast. - -### 4. Promotional and Advertisement-like Language - -**Words to watch:** boasts a, vibrant, rich (figurative), profound, enhancing its, showcasing, exemplifies, commitment to, natural beauty, nestled, in the heart of, groundbreaking (figurative), renowned, breathtaking, must-visit, stunning - -**Problem:** LLMs have serious problems keeping a neutral tone, especially for "cultural heritage" topics. - -**Before:** - -> Nestled within the breathtaking region of Gonder in Ethiopia, Alamata Raya Kobo stands as a vibrant town with a rich cultural heritage and stunning natural beauty. - -**After:** - -> Alamata Raya Kobo is a town in the Gonder region of Ethiopia, known for its weekly market and 18th-century church. - -### 5. Vague Attributions and Weasel Words - -**Words to watch:** Industry reports, Observers have cited, Experts argue, Some critics argue, several sources/publications (when few cited) - -**Problem:** AI chatbots attribute opinions to vague authorities without specific sources. - -**Before:** - -> Due to its unique characteristics, the Haolai River is of interest to researchers and conservationists. Experts believe it plays a crucial role in the regional ecosystem. - -**After:** - -> The Haolai River supports several endemic fish species, according to a 2019 survey by the Chinese Academy of Sciences. - -### 6. Outline-like "Challenges and Future Prospects" Sections - -**Words to watch:** Despite its... faces several challenges..., Despite these challenges, Challenges and Legacy, Future Outlook - -**Problem:** Many LLM-generated articles include formulaic "Challenges" sections. - -**Before:** - -> Despite its industrial prosperity, Korattur faces challenges typical of urban areas, including traffic congestion and water scarcity. Despite these challenges, with its strategic location and ongoing initiatives, Korattur continues to thrive as an integral part of Chennai's growth. - -**After:** - -> Traffic congestion increased after 2015 when three new IT parks opened. The municipal corporation began a stormwater drainage project in 2022 to address recurring floods. - -## LANGUAGE AND GRAMMAR PATTERNS - -### 7. Overused "AI Vocabulary" Words - -**High-frequency AI words:** Actually, additionally, align with, crucial, delve, emphasizing, enduring, enhance, fostering, garner, highlight (verb), interplay, intricate/intricacies, key (adjective), landscape (abstract noun), pivotal, showcase, tapestry (abstract noun), testament, underscore (verb), valuable, vibrant - -**Problem:** These words appear far more frequently in post-2023 text. They often co-occur. - -**Before:** - -> Additionally, a distinctive feature of Somali cuisine is the incorporation of camel meat. An enduring testament to Italian colonial influence is the widespread adoption of pasta in the local culinary landscape, showcasing how these dishes have integrated into the traditional diet. - -**After:** - -> Somali cuisine also includes camel meat, which is considered a delicacy. Pasta dishes, introduced during Italian colonization, remain common, especially in the south. - -### 8. Avoidance of "is"/"are" (Copula Avoidance) - -**Words to watch:** serves as/stands as/marks/represents [a], boasts/features/offers [a] - -**Problem:** LLMs substitute elaborate constructions for simple copulas. - -**Before:** - -> Gallery 825 serves as LAAA's exhibition space for contemporary art. The gallery features four separate spaces and boasts over 3,000 square feet. - -**After:** - -> Gallery 825 is LAAA's exhibition space for contemporary art. The gallery has four rooms totaling 3,000 square feet. - -### 9. Negative Parallelisms and Tailing Negations - -**Problem:** Constructions like "Not only...but..." or "It's not just about..., it's..." are overused. So are clipped tailing-negation fragments such as "no guessing" or "no wasted motion" tacked onto the end of a sentence instead of written as a real clause. - -**Before:** - -> It's not just about the beat riding under the vocals; it's part of the aggression and atmosphere. It's not merely a song, it's a statement. - -**After:** - -> The heavy beat adds to the aggressive tone. - -**Before (tailing negation):** - -> The options come from the selected item, no guessing. - -**After:** - -> The options come from the selected item without forcing the user to guess. - -### 10. Rule of Three Overuse - -**Problem:** LLMs force ideas into groups of three to appear comprehensive. - -**Before:** - -> The event features keynote sessions, panel discussions, and networking opportunities. Attendees can expect innovation, inspiration, and industry insights. - -**After:** - -> The event includes talks and panels. There's also time for informal networking between sessions. - -### 11. Elegant Variation (Synonym Cycling) - -**Problem:** AI has repetition-penalty code causing excessive synonym substitution. - -**Before:** - -> The protagonist faces many challenges. The main character must overcome obstacles. The central figure eventually triumphs. The hero returns home. - -**After:** - -> The protagonist faces many challenges but eventually triumphs and returns home. - -### 12. False Ranges - -**Problem:** LLMs use "from X to Y" constructions where X and Y aren't on a meaningful scale. - -**Before:** - -> Our journey through the universe has taken us from the singularity of the Big Bang to the grand cosmic web, from the birth and death of stars to the enigmatic dance of dark matter. - -**After:** - -> The book covers the Big Bang, star formation, and current theories about dark matter. - -### 13. Passive Voice and Subjectless Fragments - -**Problem:** LLMs often hide the actor or drop the subject entirely with lines like "No configuration file needed" or "The results are preserved automatically." Rewrite these when active voice makes the sentence clearer and more direct. - -**Before:** - -> No configuration file needed. The results are preserved automatically. - -**After:** - -> You do not need a configuration file. The system preserves the results automatically. - -## STYLE PATTERNS - -### 14. Em Dash Overuse - -**Problem:** LLMs use em dashes (—) more than humans, mimicking "punchy" sales writing. In practice, most of these can be rewritten more cleanly with commas, periods, or parentheses. - -**Before:** - -> The term is primarily promoted by Dutch institutions—not by the people themselves. You don't say "Netherlands, Europe" as an address—yet this mislabeling continues—even in official documents. - -**After:** - -> The term is primarily promoted by Dutch institutions, not by the people themselves. You don't say "Netherlands, Europe" as an address, yet this mislabeling continues in official documents. - -### 15. Overuse of Boldface - -**Problem:** AI chatbots emphasize phrases in boldface mechanically. - -**Before:** - -> It blends **OKRs (Objectives and Key Results)**, **KPIs (Key Performance Indicators)**, and visual strategy tools such as the **Business Model Canvas (BMC)** and **Balanced Scorecard (BSC)**. - -**After:** - -> It blends OKRs, KPIs, and visual strategy tools like the Business Model Canvas and Balanced Scorecard. - -### 16. Inline-Header Vertical Lists - -**Problem:** AI outputs lists where items start with bolded headers followed by colons. - -**Before:** - -> - **User Experience:** The user experience has been significantly improved with a new interface. -> - **Performance:** Performance has been enhanced through optimized algorithms. -> - **Security:** Security has been strengthened with end-to-end encryption. - -**After:** - -> The update improves the interface, speeds up load times through optimized algorithms, and adds end-to-end encryption. - -### 17. Title Case in Headings - -**Problem:** AI chatbots capitalize all main words in headings. - -**Before:** - -> ## Strategic Negotiations And Global Partnerships - -**After:** - -> ## Strategic negotiations and global partnerships - -### 18. Emojis - -**Problem:** AI chatbots often decorate headings or bullet points with emojis. - -**Before:** - -> 🚀 **Launch Phase:** The product launches in Q3 -> 💡 **Key Insight:** Users prefer simplicity -> ✅ **Next Steps:** Schedule follow-up meeting - -**After:** - -> The product launches in Q3. User research showed a preference for simplicity. Next step: schedule a follow-up meeting. - -### 19. Curly Quotation Marks - -**Problem:** ChatGPT uses curly quotes (“...”) instead of straight quotes ("..."). - -**Before:** - -> He said “the project is on track” but others disagreed. - -**After:** - -> He said "the project is on track" but others disagreed. - -## COMMUNICATION PATTERNS - -### 20. Collaborative Communication Artifacts - -**Words to watch:** I hope this helps, Of course!, Certainly!, You're absolutely right!, Would you like..., let me know, here is a... - -**Problem:** Text meant as chatbot correspondence gets pasted as content. - -**Before:** - -> Here is an overview of the French Revolution. I hope this helps! Let me know if you'd like me to expand on any section. - -**After:** - -> The French Revolution began in 1789 when financial crisis and food shortages led to widespread unrest. - -### 21. Knowledge-Cutoff Disclaimers - -**Words to watch:** as of [date], Up to my last training update, While specific details are limited/scarce..., based on available information... - -**Problem:** AI disclaimers about incomplete information get left in text. - -**Before:** - -> While specific details about the company's founding are not extensively documented in readily available sources, it appears to have been established sometime in the 1990s. - -**After:** - -> The company was founded in 1994, according to its registration documents. - -### 22. Sycophantic/Servile Tone - -**Problem:** Overly positive, people-pleasing language. - -**Before:** - -> Great question! You're absolutely right that this is a complex topic. That's an excellent point about the economic factors. - -**After:** - -> The economic factors you mentioned are relevant here. - -## FILLER AND HEDGING - -### 23. Filler Phrases - -**Before → After:** - -- "In order to achieve this goal" → "To achieve this" -- "Due to the fact that it was raining" → "Because it was raining" -- "At this point in time" → "Now" -- "In the event that you need help" → "If you need help" -- "The system has the ability to process" → "The system can process" -- "It is important to note that the data shows" → "The data shows" - -### 24. Excessive Hedging - -**Problem:** Over-qualifying statements. - -**Before:** - -> It could potentially possibly be argued that the policy might have some effect on outcomes. - -**After:** - -> The policy may affect outcomes. - -### 25. Generic Positive Conclusions - -**Problem:** Vague upbeat endings. - -**Before:** - -> The future looks bright for the company. Exciting times lie ahead as they continue their journey toward excellence. This represents a major step in the right direction. - -**After:** - -> The company plans to open two more locations next year. - -### 26. Hyphenated Word Pair Overuse - -**Words to watch:** third-party, cross-functional, client-facing, data-driven, decision-making, well-known, high-quality, real-time, long-term, end-to-end - -**Problem:** AI hyphenates common word pairs with perfect consistency. Humans rarely hyphenate these uniformly, and when they do, it's inconsistent. Less common or technical compound modifiers are fine to hyphenate. - -**Before:** - -> The cross-functional team delivered a high-quality, data-driven report on our client-facing tools. Their decision-making process was well-known for being thorough and detail-oriented. - -**After:** - -> The cross functional team delivered a high quality, data driven report on our client facing tools. Their decision making process was known for being thorough and detail oriented. - -### 27. Persuasive Authority Tropes - -**Phrases to watch:** The real question is, at its core, in reality, what really matters, fundamentally, the deeper issue, the heart of the matter - -**Problem:** LLMs use these phrases to pretend they are cutting through noise to some deeper truth, when the sentence that follows usually just restates an ordinary point with extra ceremony. - -**Before:** - -> The real question is whether teams can adapt. At its core, what really matters is organizational readiness. - -**After:** - -> The question is whether teams can adapt. That mostly depends on whether the organization is ready to change its habits. - -### 28. Signposting and Announcements - -**Phrases to watch:** Let's dive in, let's explore, let's break this down, here's what you need to know, now let's look at, without further ado - -**Problem:** LLMs announce what they are about to do instead of doing it. This meta-commentary slows the writing down and gives it a tutorial-script feel. - -**Before:** - -> Let's dive into how caching works in Next.js. Here's what you need to know. - -**After:** - -> Next.js caches data at multiple layers, including request memoization, the data cache, and the router cache. - -### 29. Fragmented Headers - -**Signs to watch:** A heading followed by a one-line paragraph that simply restates the heading before the real content begins. - -**Problem:** LLMs often add a generic sentence after a heading as a rhetorical warm-up. It usually adds nothing and makes the prose feel padded. - -**Before:** - -> ## Performance -> -> Speed matters. -> -> When users hit a slow page, they leave. - -**After:** - -> ## Performance -> -> When users hit a slow page, they leave. - ---- - -## Process - -1. Read the input text carefully -2. Identify all instances of the patterns above -3. Rewrite each problematic section -4. Ensure the revised text: - - Sounds natural when read aloud - - Varies sentence structure naturally - - Uses specific details over vague claims - - Maintains appropriate tone for context - - Uses simple constructions (is/are/has) where appropriate -5. Present a draft humanized version -6. Prompt: "What makes the below so obviously AI generated?" -7. Answer briefly with the remaining tells (if any) -8. Prompt: "Now make it not obviously AI generated." -9. Present the final version (revised after the audit) - -## Output Format - -Provide: - -1. Draft rewrite -2. "What makes the below so obviously AI generated?" (brief bullets) -3. Final rewrite -4. A brief summary of changes made (optional, if helpful) - -## Full Example - -**Before (AI-sounding):** - -> Great question! Here is an essay on this topic. I hope this helps! -> -> AI-assisted coding serves as an enduring testament to the transformative potential of large language models, marking a pivotal moment in the evolution of software development. In today's rapidly evolving technological landscape, these groundbreaking tools—nestled at the intersection of research and practice—are reshaping how engineers ideate, iterate, and deliver, underscoring their vital role in modern workflows. -> -> At its core, the value proposition is clear: streamlining processes, enhancing collaboration, and fostering alignment. It's not just about autocomplete; it's about unlocking creativity at scale, ensuring that organizations can remain agile while delivering seamless, intuitive, and powerful experiences to users. The tool serves as a catalyst. The assistant functions as a partner. The system stands as a foundation for innovation. -> -> Industry observers have noted that adoption has accelerated from hobbyist experiments to enterprise-wide rollouts, from solo developers to cross-functional teams. The technology has been featured in The New York Times, Wired, and The Verge. Additionally, the ability to generate documentation, tests, and refactors showcases how AI can contribute to better outcomes, highlighting the intricate interplay between automation and human judgment. -> -> - 💡 **Speed:** Code generation is significantly faster, reducing friction and empowering developers. -> - 🚀 **Quality:** Output quality has been enhanced through improved training, contributing to higher standards. -> - ✅ **Adoption:** Usage continues to grow, reflecting broader industry trends. -> -> While specific details are limited based on available information, it could potentially be argued that these tools might have some positive effect. Despite challenges typical of emerging technologies—including hallucinations, bias, and accountability—the ecosystem continues to thrive. In order to fully realize this potential, teams must align with best practices. -> -> In conclusion, the future looks bright. Exciting times lie ahead as we continue this journey toward excellence. Let me know if you’d like me to expand on any section! - -**Draft rewrite:** - -> AI coding assistants speed up some tasks. In a 2024 study by Google, developers using Codex completed simple functions 55% faster than a control group, but showed no improvement on debugging or architectural decisions. -> -> The tools are good at boilerplate: config files, test scaffolding, repetitive refactors. They are bad at knowing when they are wrong. I have mass-accepted suggestions that compiled, passed lint, and still did the wrong thing because I stopped paying attention. -> -> Mira, an engineer at a fintech startup I interviewed, said she uses Copilot "like autocomplete for boring code" but reviews every line before committing. Jake, a senior dev at a larger company, disabled it after it kept suggesting patterns from a deprecated internal library. -> -> The productivity claims are hard to verify. GitHub says Copilot users "accept 30% of suggestions," but acceptance is not correctness, and correctness is not value. The 2024 Uplevel study found no statistically significant difference in pull-request throughput between teams with and without AI assistants. -> -> None of this means the tools are useless. It means they are tools. They do not replace judgment, and they do not eliminate the need for tests. If you do not have tests, you cannot tell whether the suggestion is right. - -**What makes the below so obviously AI generated?** - -- The rhythm is still a bit too tidy (clean contrasts, evenly paced paragraphs). -- The named people and study citations can read like plausible-but-made-up placeholders unless they're real and sourced. -- The closer leans a touch slogan-y ("If you do not have tests...") rather than sounding like a person talking. - -**Now make it not obviously AI generated.** - -> AI coding assistants can make you faster at the boring parts. Not everything. Definitely not architecture. -> -> They're great at boilerplate: config files, test scaffolding, repetitive refactors. They're also great at sounding right while being wrong. I've accepted suggestions that compiled, passed lint, and still missed the point because I stopped paying attention. -> -> People I talk to tend to land in two camps. Some use it like autocomplete for chores and review every line. Others disable it after it keeps suggesting patterns they don't want. Both feel reasonable. -> -> The productivity metrics are slippery. GitHub can say Copilot users "accept 30% of suggestions," but acceptance isn't correctness, and correctness isn't value. If you don't have tests, you're basically guessing. - -**Changes made:** - -- Removed chatbot artifacts ("Great question!", "I hope this helps!", "Let me know if...") -- Removed significance inflation ("testament", "pivotal moment", "evolving landscape", "vital role") -- Removed promotional language ("groundbreaking", "nestled", "seamless, intuitive, and powerful") -- Removed vague attributions ("Industry observers") -- Removed superficial -ing phrases ("underscoring", "highlighting", "reflecting", "contributing to") -- Removed negative parallelism ("It's not just X; it's Y") -- Removed rule-of-three patterns and synonym cycling ("catalyst/partner/foundation") -- Removed false ranges ("from X to Y, from A to B") -- Removed em dashes, emojis, boldface headers, and curly quotes -- Removed copula avoidance ("serves as", "functions as", "stands as") in favor of "is"/"are" -- Removed formulaic challenges section ("Despite challenges... continues to thrive") -- Removed knowledge-cutoff hedging ("While specific details are limited...") -- Removed excessive hedging ("could potentially be argued that... might have some") -- Removed filler phrases and persuasive framing ("In order to", "At its core") -- Removed generic positive conclusion ("the future looks bright", "exciting times lie ahead") -- Made the voice more personal and less "assembled" (varied rhythm, fewer placeholders) - -## Reference - -This skill is based on [Wikipedia:Signs of AI writing](https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing), maintained by WikiProject AI Cleanup. The patterns documented there come from observations of thousands of instances of AI-generated text on Wikipedia. - -Key insight from Wikipedia: "LLMs use statistical algorithms to guess what should come next. The result tends toward the most statistically likely result that applies to the widest variety of cases." diff --git a/home/dot_apm/dot_apm/skills/kaizen/SKILL.md b/home/dot_apm/dot_apm/skills/kaizen/SKILL.md deleted file mode 100644 index c6af952..0000000 --- a/home/dot_apm/dot_apm/skills/kaizen/SKILL.md +++ /dev/null @@ -1,733 +0,0 @@ ---- -name: kaizen -description: Use when Code implementation and refactoring, architecturing or designing systems, process and workflow improvements, error handling and validation. Provide tehniquest to avoid over-engineering and apply iterative improvements. ---- - -# Kaizen: Continuous Improvement - -Apply continuous improvement mindset - suggest small iterative improvements, error-proof designs, follow established patterns, avoid over-engineering; automatically applied to guide quality and simplicity - -## Overview - -Small improvements, continuously. Error-proof by design. Follow what works. Build only what's needed. - -**Core principle:** Many small improvements beat one big change. Prevent errors at design time, not with fixes. - -## When to Use - -**Always applied for:** - -- Code implementation and refactoring -- Architecture and design decisions -- Process and workflow improvements -- Error handling and validation - -**Philosophy:** Quality through incremental progress and prevention, not perfection through massive effort. - -## The Four Pillars - -### 1. Continuous Improvement (Kaizen) - -Small, frequent improvements compound into major gains. - -#### Principles - -**Incremental over revolutionary:** - -- Make smallest viable change that improves quality -- One improvement at a time -- Verify each change before next -- Build momentum through small wins - -**Always leave code better:** - -- Fix small issues as you encounter them -- Refactor while you work (within scope) -- Update outdated comments -- Remove dead code when you see it - -**Iterative refinement:** - -- First version: make it work -- Second pass: make it clear -- Third pass: make it efficient -- Don't try all three at once - - -```typescript -// Iteration 1: Make it work -const calculateTotal = (items: Item[]) => { - let total = 0; - for (let i = 0; i < items.length; i++) { - total += items[i].price * items[i].quantity; - } - return total; -}; - -// Iteration 2: Make it clear (refactor) -const calculateTotal = (items: Item[]): number => { -return items.reduce((total, item) => { -return total + (item.price \* item.quantity); -}, 0); -}; - -// Iteration 3: Make it robust (add validation) -const calculateTotal = (items: Item[]): number => { -if (!items?.length) return 0; - -return items.reduce((total, item) => { -if (item.price < 0 || item.quantity < 0) { -throw new Error('Price and quantity must be non-negative'); -} -return total + (item.price \* item.quantity); -}, 0); -}; - -```` -Each step is complete, tested, and working - - - -```typescript -// Trying to do everything at once -const calculateTotal = (items: Item[]): number => { - // Validate, optimize, add features, handle edge cases all together - if (!items?.length) return 0; - const validItems = items.filter(item => { - if (item.price < 0) throw new Error('Negative price'); - if (item.quantity < 0) throw new Error('Negative quantity'); - return item.quantity > 0; // Also filtering zero quantities - }); - // Plus caching, plus logging, plus currency conversion... - return validItems.reduce(...); // Too many concerns at once -}; -```` - -Overwhelming, error-prone, hard to verify - - -#### In Practice - -**When implementing features:** - -1. Start with simplest version that works -2. Add one improvement (error handling, validation, etc.) -3. Test and verify -4. Repeat if time permits -5. Don't try to make it perfect immediately - -**When refactoring:** - -- Fix one smell at a time -- Commit after each improvement -- Keep tests passing throughout -- Stop when "good enough" (diminishing returns) - -**When reviewing code:** - -- Suggest incremental improvements (not rewrites) -- Prioritize: critical → important → nice-to-have -- Focus on highest-impact changes first -- Accept "better than before" even if not perfect - -### 2. Poka-Yoke (Error Proofing) - -Design systems that prevent errors at compile/design time, not runtime. - -#### Principles - -**Make errors impossible:** - -- Type system catches mistakes -- Compiler enforces contracts -- Invalid states unrepresentable -- Errors caught early (left of production) - -**Design for safety:** - -- Fail fast and loudly -- Provide helpful error messages -- Make correct path obvious -- Make incorrect path difficult - -**Defense in layers:** - -1. Type system (compile time) -2. Validation (runtime, early) -3. Guards (preconditions) -4. Error boundaries (graceful degradation) - -#### Type System Error Proofing - - -```typescript -// Error: string status can be any value -type OrderBad = { - status: string; // Can be "pending", "PENDING", "pnding", anything! - total: number; -}; - -// Good: Only valid states possible -type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered'; -type Order = { -status: OrderStatus; -total: number; -}; - -// Better: States with associated data -type Order = -| { status: 'pending'; createdAt: Date } -| { status: 'processing'; startedAt: Date; estimatedCompletion: Date } -| { status: 'shipped'; trackingNumber: string; shippedAt: Date } -| { status: 'delivered'; deliveredAt: Date; signature: string }; - -// Now impossible to have shipped without trackingNumber - -```` -Type system prevents entire classes of errors - - - -```typescript -// Make invalid states unrepresentable -type NonEmptyArray = [T, ...T[]]; - -const firstItem = (items: NonEmptyArray): T => { - return items[0]; // Always safe, never undefined! -}; - -// Caller must prove array is non-empty -const items: number[] = [1, 2, 3]; -if (items.length > 0) { - firstItem(items as NonEmptyArray); // Safe -} -```` - -Function signature guarantees safety - - -#### Validation Error Proofing - - -```typescript -// Error: Validation after use -const processPayment = (amount: number) => { - const fee = amount * 0.03; // Used before validation! - if (amount <= 0) throw new Error('Invalid amount'); - // ... -}; - -// Good: Validate immediately -const processPayment = (amount: number) => { -if (amount <= 0) { -throw new Error('Payment amount must be positive'); -} -if (amount > 10000) { -throw new Error('Payment exceeds maximum allowed'); -} - -const fee = amount \* 0.03; -// ... now safe to use -}; - -// Better: Validation at boundary with branded type -type PositiveNumber = number & { readonly \_\_brand: 'PositiveNumber' }; - -const validatePositive = (n: number): PositiveNumber => { -if (n <= 0) throw new Error('Must be positive'); -return n as PositiveNumber; -}; - -const processPayment = (amount: PositiveNumber) => { -// amount is guaranteed positive, no need to check -const fee = amount \* 0.03; -}; - -// Validate at system boundary -const handlePaymentRequest = (req: Request) => { -const amount = validatePositive(req.body.amount); // Validate once -processPayment(amount); // Use everywhere safely -}; - -```` -Validate once at boundary, safe everywhere else - - -#### Guards and Preconditions - - -```typescript -// Early returns prevent deeply nested code -const processUser = (user: User | null) => { - if (!user) { - logger.error('User not found'); - return; - } - - if (!user.email) { - logger.error('User email missing'); - return; - } - - if (!user.isActive) { - logger.info('User inactive, skipping'); - return; - } - - // Main logic here, guaranteed user is valid and active - sendEmail(user.email, 'Welcome!'); -}; -```` - -Guards make assumptions explicit and enforced - - -#### Configuration Error Proofing - - -```typescript -// Error: Optional config with unsafe defaults -type ConfigBad = { - apiKey?: string; - timeout?: number; -}; - -const client = new APIClient({ timeout: 5000 }); // apiKey missing! - -// Good: Required config, fails early -type Config = { -apiKey: string; -timeout: number; -}; - -const loadConfig = (): Config => { -const apiKey = process.env.API_KEY; -if (!apiKey) { -throw new Error('API_KEY environment variable required'); -} - -return { -apiKey, -timeout: 5000, -}; -}; - -// App fails at startup if config invalid, not during request -const config = loadConfig(); -const client = new APIClient(config); - -```` -Fail at startup, not in production - - -#### In Practice - -**When designing APIs:** -- Use types to constrain inputs -- Make invalid states unrepresentable -- Return Result instead of throwing -- Document preconditions in types - -**When handling errors:** -- Validate at system boundaries -- Use guards for preconditions -- Fail fast with clear messages -- Log context for debugging - -**When configuring:** -- Required over optional with defaults -- Validate all config at startup -- Fail deployment if config invalid -- Don't allow partial configurations - -### 3. Standardized Work - -Follow established patterns. Document what works. Make good practices easy to follow. - -#### Principles - -**Consistency over cleverness:** -- Follow existing codebase patterns -- Don't reinvent solved problems -- New pattern only if significantly better -- Team agreement on new patterns - -**Documentation lives with code:** -- README for setup and architecture -- CLAUDE.md for AI coding conventions -- Comments for "why", not "what" -- Examples for complex patterns - -**Automate standards:** -- Linters enforce style -- Type checks enforce contracts -- Tests verify behavior -- CI/CD enforces quality gates - -#### Following Patterns - - -```typescript -// Existing codebase pattern for API clients -class UserAPIClient { - async getUser(id: string): Promise { - return this.fetch(`/users/${id}`); - } -} - -// New code follows the same pattern -class OrderAPIClient { - async getOrder(id: string): Promise { - return this.fetch(`/orders/${id}`); - } -} -```` - -Consistency makes codebase predictable - - - -```typescript -// Existing pattern uses classes -class UserAPIClient { /* ... */ } - -// New code introduces different pattern without discussion -const getOrder = async (id: string): Promise => { -// Breaking consistency "because I prefer functions" -}; - -```` -Inconsistency creates confusion - - -#### Error Handling Patterns - - -```typescript -// Project standard: Result type for recoverable errors -type Result = { ok: true; value: T } | { ok: false; error: E }; - -// All services follow this pattern -const fetchUser = async (id: string): Promise> => { - try { - const user = await db.users.findById(id); - if (!user) { - return { ok: false, error: new Error('User not found') }; - } - return { ok: true, value: user }; - } catch (err) { - return { ok: false, error: err as Error }; - } -}; - -// Callers use consistent pattern -const result = await fetchUser('123'); -if (!result.ok) { - logger.error('Failed to fetch user', result.error); - return; -} -const user = result.value; // Type-safe! -```` - -Standard pattern across codebase - - -#### Documentation Standards - - -```typescript -/** - * Retries an async operation with exponential backoff. - * - * Why: Network requests fail temporarily; retrying improves reliability - * When to use: External API calls, database operations - * When not to use: User input validation, internal function calls - * - * @example - * const result = await retry( - * () => fetch('https://api.example.com/data'), - * { maxAttempts: 3, baseDelay: 1000 } - * ); - */ -const retry = async ( - operation: () => Promise, - options: RetryOptions -): Promise => { - // Implementation... -}; -``` -Documents why, when, and how - - -#### In Practice - -**Before adding new patterns:** - -- Search codebase for similar problems solved -- Check CLAUDE.md for project conventions -- Discuss with team if breaking from pattern -- Update docs when introducing new pattern - -**When writing code:** - -- Match existing file structure -- Use same naming conventions -- Follow same error handling approach -- Import from same locations - -**When reviewing:** - -- Check consistency with existing code -- Point to examples in codebase -- Suggest aligning with standards -- Update CLAUDE.md if new standard emerges - -### 4. Just-In-Time (JIT) - -Build what's needed now. No more, no less. Avoid premature optimization and over-engineering. - -#### Principles - -**YAGNI (You Aren't Gonna Need It):** - -- Implement only current requirements -- No "just in case" features -- No "we might need this later" code -- Delete speculation - -**Simplest thing that works:** - -- Start with straightforward solution -- Add complexity only when needed -- Refactor when requirements change -- Don't anticipate future needs - -**Optimize when measured:** - -- No premature optimization -- Profile before optimizing -- Measure impact of changes -- Accept "good enough" performance - -#### YAGNI in Action - - -```typescript -// Current requirement: Log errors to console -const logError = (error: Error) => { - console.error(error.message); -}; -``` -Simple, meets current need - - - -```typescript -// Over-engineered for "future needs" -interface LogTransport { - write(level: LogLevel, message: string, meta?: LogMetadata): Promise; -} - -class ConsoleTransport implements LogTransport { /_... _/ } -class FileTransport implements LogTransport { /_ ... _/ } -class RemoteTransport implements LogTransport { /_ ..._/ } - -class Logger { -private transports: LogTransport[] = []; -private queue: LogEntry[] = []; -private rateLimiter: RateLimiter; -private formatter: LogFormatter; - -// 200 lines of code for "maybe we'll need it" -} - -const logError = (error: Error) => { -Logger.getInstance().log('error', error.message); -}; - -```` -Building for imaginary future requirements - - -**When to add complexity:** -- Current requirement demands it -- Pain points identified through use -- Measured performance issues -- Multiple use cases emerged - - -```typescript -// Start simple -const formatCurrency = (amount: number): string => { - return `$${amount.toFixed(2)}`; -}; - -// Requirement evolves: support multiple currencies -const formatCurrency = (amount: number, currency: string): string => { - const symbols = { USD: '$', EUR: '€', GBP: '£' }; - return `${symbols[currency]}${amount.toFixed(2)}`; -}; - -// Requirement evolves: support localization -const formatCurrency = (amount: number, locale: string): string => { - return new Intl.NumberFormat(locale, { - style: 'currency', - currency: locale === 'en-US' ? 'USD' : 'EUR', - }).format(amount); -}; -```` - -Complexity added only when needed - - -#### Premature Abstraction - - -```typescript -// One use case, but building generic framework -abstract class BaseCRUDService { - abstract getAll(): Promise; - abstract getById(id: string): Promise; - abstract create(data: Partial): Promise; - abstract update(id: string, data: Partial): Promise; - abstract delete(id: string): Promise; -} - -class GenericRepository { /_300 lines _/ } -class QueryBuilder { /_ 200 lines_/ } -// ... building entire ORM for single table - -```` -Massive abstraction for uncertain future - - - -```typescript -// Simple functions for current needs -const getUsers = async (): Promise => { - return db.query('SELECT * FROM users'); -}; - -const getUserById = async (id: string): Promise => { - return db.query('SELECT * FROM users WHERE id = $1', [id]); -}; - -// When pattern emerges across multiple entities, then abstract -```` - -Abstract only when pattern proven across 3+ cases - - -#### Performance Optimization - - -```typescript -// Current: Simple approach -const filterActiveUsers = (users: User[]): User[] => { - return users.filter(user => user.isActive); -}; - -// Benchmark shows: 50ms for 1000 users (acceptable) -// ✓ Ship it, no optimization needed - -// Later: After profiling shows this is bottleneck -// Then optimize with indexed lookup or caching - -```` -Optimize based on measurement, not assumptions - - - -```typescript -// Premature optimization -const filterActiveUsers = (users: User[]): User[] => { - // "This might be slow, so let's cache and index" - const cache = new WeakMap(); - const indexed = buildBTreeIndex(users, 'isActive'); - // 100 lines of optimization code - // Adds complexity, harder to maintain - // No evidence it was needed -}; -```` - -Complex solution for unmeasured problem - - -#### In Practice - -**When implementing:** - -- Solve the immediate problem -- Use straightforward approach -- Resist "what if" thinking -- Delete speculative code - -**When optimizing:** - -- Profile first, optimize second -- Measure before and after -- Document why optimization needed -- Keep simple version in tests - -**When abstracting:** - -- Wait for 3+ similar cases (Rule of Three) -- Make abstraction as simple as possible -- Prefer duplication over wrong abstraction -- Refactor when pattern clear - -## Integration with Commands - -The Kaizen skill guides how you work. The commands provide structured analysis: - -- **`/why`**: Root cause analysis (5 Whys) -- **`/cause-and-effect`**: Multi-factor analysis (Fishbone) -- **`/plan-do-check-act`**: Iterative improvement cycles -- **`/analyse-problem`**: Comprehensive documentation (A3) -- **`/analyse`**: Smart method selection (Gemba/VSM/Muda) - -Use commands for structured problem-solving. Apply skill for day-to-day development. - -## Red Flags - -**Violating Continuous Improvement:** - -- "I'll refactor it later" (never happens) -- Leaving code worse than you found it -- Big bang rewrites instead of incremental - -**Violating Poka-Yoke:** - -- "Users should just be careful" -- Validation after use instead of before -- Optional config with no validation - -**Violating Standardized Work:** - -- "I prefer to do it my way" -- Not checking existing patterns -- Ignoring project conventions - -**Violating Just-In-Time:** - -- "We might need this someday" -- Building frameworks before using them -- Optimizing without measuring - -## Remember - -**Kaizen is about:** - -- Small improvements continuously -- Preventing errors by design -- Following proven patterns -- Building only what's needed - -**Not about:** - -- Perfection on first try -- Massive refactoring projects -- Clever abstractions -- Premature optimization - -**Mindset:** Good enough today, better tomorrow. Repeat. diff --git a/home/dot_claude/settings.json b/home/dot_claude/settings.json index 9510891..e821c10 100644 --- a/home/dot_claude/settings.json +++ b/home/dot_claude/settings.json @@ -1,14 +1,12 @@ { "$schema": "https://json.schemastore.org/claude-code-settings.json", - "model": "opusplan[1m]", "cleanupPeriodDays": 365, + "skillListingBudgetFraction": 0.02, "env": { "BASH_DEFAULT_TIMEOUT_MS": "300000", "BASH_MAX_TIMEOUT_MS": "600000", "CLAUDE_CODE_AUTO_COMPACT_WINDOW": "400000", - "CLAUDE_CODE_EFFORT_LEVEL": "auto", "CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY": "1", - "CLAUDE_CODE_SUBAGENT_MODEL": "inherit", "CLAUDE_CODE_ATTRIBUTION_HEADER": "0", "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1", "CLAUDE_CODE_MAX_OUTPUT_TOKENS": "64000", @@ -17,7 +15,6 @@ "DISABLE_BUG_COMMAND": "1", "DISABLE_ERROR_REPORTING": "1", "DISABLE_NON_ESSENTIAL_MODEL_CALLS": "1", - "DISABLE_TELEMETRY": "1", "ENABLE_PROMPT_CACHING_1H": "1", "MAX_MCP_OUTPUT_TOKENS": "40000" }, @@ -116,27 +113,6 @@ "WebFetch(domain:github.com)", "WebSearch" ], - "ask": [ - "Bash(sudo:*)", - "Bash(chmod 777:*)", - "Bash(git push --force:*)", - "Bash(git push --force-with-lease:*)", - "Bash(git push -f:*)", - "Bash(git reset --hard:*)", - "Bash(git clean -fd:*)", - "Bash(gh auth refresh:*)", - "Bash(gh issue close:*)", - "Bash(gh label delete:*)", - "Bash(gh pr close:*)", - "Bash(gh pr merge:*)", - "Bash(gh release create:*)", - "Bash(gh run cancel:*)", - "Bash(gh run rerun:*)", - "Bash(kill:*)", - "Bash(npm publish:*)", - "Bash(rm:*)", - "Bash(eval:*)" - ], "deny": [ "Bash(pip install:*)", "Bash(pip3 install:*)", @@ -158,24 +134,39 @@ "Read(**/*.pem)", "Read(**/*.key)" ], + "ask": [ + "Bash(sudo:*)", + "Bash(chmod 777:*)", + "Bash(git push --force:*)", + "Bash(git push --force-with-lease:*)", + "Bash(git push -f:*)", + "Bash(git reset --hard:*)", + "Bash(git clean -fd:*)", + "Bash(gh auth refresh:*)", + "Bash(gh issue close:*)", + "Bash(gh label delete:*)", + "Bash(gh pr close:*)", + "Bash(gh pr merge:*)", + "Bash(gh release create:*)", + "Bash(gh run cancel:*)", + "Bash(gh run rerun:*)", + "Bash(kill:*)", + "Bash(npm publish:*)", + "Bash(rm:*)", + "Bash(eval:*)" + ], "defaultMode": "acceptEdits" }, - "alwaysThinkingEnabled": true, - "autoScrollEnabled": true, - "autoUpdatesChannel": "latest", - "autoMemoryEnabled": true, - "outputStyle": "Explanatory", - "showThinkingSummaries": true, - "spinnerTipsEnabled": false, - "skipWorkflowUsageWarning": true, - "skillListingBudgetFraction": 0.02, + "model": "best", "statusLine": { "type": "command", "command": "sh ~/.claude/statusline-command.sh", "padding": 0 }, "enabledPlugins": { - "superwhisper@superwhisper": false + "superwhisper@superwhisper": true, + "caveman@caveman": true, + "superpowers@superpowers-dev": true }, "extraKnownMarketplaces": { "superwhisper": { @@ -183,6 +174,27 @@ "source": "github", "repo": "superultrainc/superwhisper-claude-code" } + }, + "caveman": { + "source": { + "source": "github", + "repo": "JuliusBrussee/caveman" + } + }, + "superpowers-dev": { + "source": { + "source": "github", + "repo": "obra/superpowers" + } } - } + }, + "outputStyle": "Explanatory", + "spinnerTipsEnabled": false, + "alwaysThinkingEnabled": true, + "autoUpdatesChannel": "latest", + "autoMemoryEnabled": true, + "showThinkingSummaries": true, + "skipWorkflowUsageWarning": true, + "autoScrollEnabled": true, + "skipDangerousModePermissionPrompt": true } diff --git a/home/dot_config/nvim/.gitignore b/home/dot_config/nvim/.gitignore deleted file mode 100644 index cc5457a..0000000 --- a/home/dot_config/nvim/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -tt.* -.tests -doc/tags -debug -.repro -foo.* -*.log -data diff --git a/home/dot_config/nvim/.neoconf.json b/home/dot_config/nvim/.neoconf.json deleted file mode 100644 index 7c48087..0000000 --- a/home/dot_config/nvim/.neoconf.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "neodev": { - "library": { - "enabled": true, - "plugins": true - } - }, - "neoconf": { - "plugins": { - "lua_ls": { - "enabled": true - } - } - } -} diff --git a/home/dot_config/nvim/LICENSE b/home/dot_config/nvim/LICENSE deleted file mode 100644 index 261eeb9..0000000 --- a/home/dot_config/nvim/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/home/dot_config/nvim/README.md b/home/dot_config/nvim/README.md deleted file mode 100644 index 185280b..0000000 --- a/home/dot_config/nvim/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# 💤 LazyVim - -A starter template for [LazyVim](https://github.com/LazyVim/LazyVim). -Refer to the [documentation](https://lazyvim.github.io/installation) to get started. diff --git a/home/dot_config/nvim/init.lua b/home/dot_config/nvim/init.lua deleted file mode 100644 index 2514f9e..0000000 --- a/home/dot_config/nvim/init.lua +++ /dev/null @@ -1,2 +0,0 @@ --- bootstrap lazy.nvim, LazyVim and your plugins -require("config.lazy") diff --git a/home/dot_config/nvim/lua/config/autocmds.lua b/home/dot_config/nvim/lua/config/autocmds.lua deleted file mode 100644 index 4221e75..0000000 --- a/home/dot_config/nvim/lua/config/autocmds.lua +++ /dev/null @@ -1,8 +0,0 @@ --- Autocmds are automatically loaded on the VeryLazy event --- Default autocmds that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/autocmds.lua --- --- Add any additional autocmds here --- with `vim.api.nvim_create_autocmd` --- --- Or remove existing autocmds by their group name (which is prefixed with `lazyvim_` for the defaults) --- e.g. vim.api.nvim_del_augroup_by_name("lazyvim_wrap_spell") diff --git a/home/dot_config/nvim/lua/config/keymaps.lua b/home/dot_config/nvim/lua/config/keymaps.lua deleted file mode 100644 index 2c134f7..0000000 --- a/home/dot_config/nvim/lua/config/keymaps.lua +++ /dev/null @@ -1,3 +0,0 @@ --- Keymaps are automatically loaded on the VeryLazy event --- Default keymaps that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/keymaps.lua --- Add any additional keymaps here diff --git a/home/dot_config/nvim/lua/config/lazy.lua b/home/dot_config/nvim/lua/config/lazy.lua deleted file mode 100644 index aee4a23..0000000 --- a/home/dot_config/nvim/lua/config/lazy.lua +++ /dev/null @@ -1,56 +0,0 @@ -local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" -if not (vim.uv or vim.loop).fs_stat(lazypath) then - local lazyrepo = "https://github.com/folke/lazy.nvim.git" - local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath }) - if vim.v.shell_error ~= 0 then - vim.api.nvim_echo({ - { "Failed to clone lazy.nvim:\n", "ErrorMsg" }, - { out, "WarningMsg" }, - { "\nPress any key to exit..." }, - }, true, {}) - vim.fn.getchar() - os.exit(1) - end -end -vim.opt.rtp:prepend(lazypath) - -require("lazy").setup({ - spec = { - -- add LazyVim and import its plugins - { "LazyVim/LazyVim", import = "lazyvim.plugins" }, - -- import/override with your plugins - { import = "plugins" }, - }, - defaults = { - -- By default, only LazyVim plugins will be lazy-loaded. Your custom plugins will load during startup. - -- If you know what you're doing, you can set this to `true` to have all your custom plugins lazy-loaded by default. - lazy = false, - -- It's recommended to leave version=false for now, since a lot the plugin that support versioning, - -- have outdated releases, which may break your Neovim install. - version = false, -- always use the latest git commit - -- version = "*", -- try installing the latest stable version for plugins that support semver - }, - install = { colorscheme = { "tokyonight", "habamax" } }, - checker = { - enabled = true, -- check for plugin updates periodically - notify = false, -- notify on update - }, -- automatically check for plugin updates - rocks = { - enabled = false, - }, - performance = { - rtp = { - -- disable some rtp plugins - disabled_plugins = { - "gzip", - -- "matchit", - -- "matchparen", - -- "netrwPlugin", - "tarPlugin", - "tohtml", - "tutor", - "zipPlugin", - }, - }, - }, -}) diff --git a/home/dot_config/nvim/lua/config/options.lua b/home/dot_config/nvim/lua/config/options.lua deleted file mode 100644 index 31e385c..0000000 --- a/home/dot_config/nvim/lua/config/options.lua +++ /dev/null @@ -1,6 +0,0 @@ --- Options are automatically loaded before lazy.nvim startup --- Default options that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/options.lua --- Add any additional options here - -vim.opt.clipboard = "unnamedplus" -vim.o.autoread = true diff --git a/home/dot_config/nvim/lua/plugins/opencode.lua b/home/dot_config/nvim/lua/plugins/opencode.lua deleted file mode 100644 index 343df9f..0000000 --- a/home/dot_config/nvim/lua/plugins/opencode.lua +++ /dev/null @@ -1,74 +0,0 @@ -return { - { - "nickjvandyke/opencode.nvim", - version = "*", - dependencies = { - { - "folke/snacks.nvim", - optional = true, - opts = { - input = {}, - picker = { - actions = { - opencode_send = function(...) - return require("opencode").snacks_picker_send(...) - end, - }, - win = { - input = { - keys = { - [""] = { "opencode_send", mode = { "n", "i" } }, - }, - }, - }, - }, - }, - }, - }, - keys = { - { - "oa", - function() - require("opencode").ask("@this: ", { submit = true }) - end, - mode = { "n", "x" }, - desc = "Ask OpenCode about this", - }, - { - "os", - function() - require("opencode").select() - end, - mode = { "n", "x" }, - desc = "Select OpenCode action", - }, - { - "oS", - function() - require("opencode").select_server() - end, - desc = "Select OpenCode server", - }, - { - "on", - function() - require("opencode").command("session.new") - end, - desc = "New OpenCode session", - }, - }, - config = function() - vim.g.opencode_opts = { - server = { - port = nil, - start = false, - stop = false, - toggle = false, - }, - select = { - server = false, - }, - } - end, - }, -} diff --git a/home/dot_config/nvim/lua/plugins/treesitter.lua b/home/dot_config/nvim/lua/plugins/treesitter.lua deleted file mode 100644 index 6f943be..0000000 --- a/home/dot_config/nvim/lua/plugins/treesitter.lua +++ /dev/null @@ -1,16 +0,0 @@ -return { - { - "nvim-treesitter/nvim-treesitter", - opts = function(_, opts) - vim.list_extend(opts.ensure_installed, { - "css", - "latex", - "norg", - "scss", - "svelte", - "typst", - "vue", - }) - end, - }, -} diff --git a/home/dot_config/nvim/stylua.toml b/home/dot_config/nvim/stylua.toml deleted file mode 100644 index 0f90030..0000000 --- a/home/dot_config/nvim/stylua.toml +++ /dev/null @@ -1,3 +0,0 @@ -indent_type = "Spaces" -indent_width = 2 -column_width = 120 diff --git a/home/dot_config/opencode/AGENTS.md.tmpl b/home/dot_config/opencode/AGENTS.md.tmpl deleted file mode 100644 index 1900c86..0000000 --- a/home/dot_config/opencode/AGENTS.md.tmpl +++ /dev/null @@ -1 +0,0 @@ -{{ template "AGENTS.md" . -}} diff --git a/home/dot_config/opencode/opencode.jsonc b/home/dot_config/opencode/opencode.jsonc deleted file mode 100644 index 328a929..0000000 --- a/home/dot_config/opencode/opencode.jsonc +++ /dev/null @@ -1,232 +0,0 @@ -{ - "$schema": "https://opencode.ai/config.json", - "model": "openai/gpt-5.5", - "autoupdate": true, - "instructions": ["{env:HOME}/.config/opencode/AGENTS.md"], - "lsp": true, - "agent": { - "build": { - "model": "openai/gpt-5.5", - "variant": "xhigh", - }, - "plan": { - "model": "openai/gpt-5.5", - "variant": "xhigh", - }, - }, - "plugin": [ - "@tarquinen/opencode-dcp", - "@franlol/opencode-md-table-formatter", - "@mohak34/opencode-notifier", - "opencode-wakatime", - ], - "mcp": { - "grep": { - "type": "remote", - "url": "https://mcp.grep.app", - "enabled": true, - }, - }, - - "permission": { - "webfetch": "allow", - "websearch": "allow", - "doom_loop": "ask", - "read": { - "*": "allow", - "*.env": "deny", - "*.env.*": "deny", - "*.dev.vars": "deny", - "*.env.example": "allow", - }, - "edit": { - "*": "allow", - }, - "external_directory": { - "*": "ask", - "~/Documents/github/**": "allow", - "/tmp/**": "allow", - "/private/tmp/**": "allow", - }, - "bash": { - "*": "allow", - "opencode": "allow", - // Git - "git status *": "allow", - "git diff *": "allow", - "git log *": "allow", - "git branch *": "allow", - "git show *": "allow", - "git stash list *": "allow", - "git remote *": "allow", - "git fetch *": "allow", - "git add *": "allow", - "git commit *": "allow", - "git checkout *": "allow", - "git switch *": "allow", - "git merge *": "allow", - "git clone *": "allow", - "git pull *": "allow", - "git init *": "allow", - "git worktree *": "allow", - "git rebase *": "allow", - "git push *": "allow", - "git push --force*": "deny", - "git push * --force*": "deny", - "git push -f": "deny", - "git push -f *": "deny", - "git push * -f": "deny", - "git push * -f *": "deny", - "git reset --soft *": "allow", - "git reset --hard": "deny", - "git reset --hard *": "deny", - "ls *": "allow", - "cat *": "allow", - "head *": "allow", - "tail *": "allow", - "find *": "allow", - "rg *": "allow", - "grep *": "allow", - // Text processing - "sort *": "allow", - "uniq *": "allow", - "wc *": "allow", - "cut *": "allow", - "awk *": "allow", - "sed *": "allow", - "tr *": "allow", - "xargs *": "allow", - "tee *": "allow", - "diff *": "allow", - "comm *": "allow", - "paste *": "allow", - "column *": "allow", - // System info - "which *": "allow", - "type *": "allow", - "file *": "allow", - "stat *": "allow", - "du *": "allow", - "df *": "allow", - "env": "allow", - "printenv *": "allow", - "uname *": "allow", - "hostname": "allow", - "whoami": "allow", - "id": "allow", - "date *": "allow", - "pwd": "allow", - "echo *": "allow", - "printf *": "allow", - "mkdir *": "allow", - "cp *": "allow", - "mv *": "allow", - "rm *": "ask", - "rm -rf *": "deny", - "pnpm *": "allow", - "npm *": "allow", - "npx *": "allow", - "node *": "allow", - "bun *": "allow", - "gh pr list *": "allow", - "gh pr view *": "allow", - "gh pr diff *": "allow", - "gh pr checks *": "allow", - "gh pr status *": "allow", - "gh issue list *": "allow", - "gh issue view *": "allow", - "gh issue status *": "allow", - "gh issue edit *": "allow", - "gh issue comment *": "allow", - "gh repo view *": "allow", - "gh repo clone *": "allow", - "gh api *": "allow", - "gh search *": "allow", - "gh status": "allow", - "gh status *": "allow", - "gh auth status": "allow", - "gh auth refresh *": "ask", - // GitHub Projects - "gh project list *": "allow", - "gh project view *": "allow", - "gh project field-list *": "allow", - "gh project item-list *": "allow", - "gh project create *": "allow", - "gh project link *": "allow", - "gh project field-create *": "allow", - "gh project item-add *": "allow", - "gh project item-edit *": "allow", - "gh project edit *": "ask", - "gh project close *": "ask", - "gh project unlink *": "ask", - "gh project item-archive *": "ask", - "gh project item-delete *": "ask", - "gh project field-delete *": "ask", - "gh project delete *": "deny", - // GitHub Actions (read-only) - "gh run list *": "allow", - "gh run view *": "allow", - "gh run watch *": "allow", - "gh workflow list *": "allow", - "gh workflow view *": "allow", - "gh cache list *": "allow", - // Releases, gists, labels (read-only) - "gh release list *": "allow", - "gh release view *": "allow", - "gh gist list *": "allow", - "gh gist view *": "allow", - "gh label list *": "allow", - "gh label create *": "allow", - "gh label edit *": "allow", - "gh label delete *": "ask", - "gh ruleset list *": "allow", - "gh ruleset view *": "allow", - // Write operations - "gh pr create *": "allow", - "gh pr merge *": "ask", - "gh pr close *": "ask", - "gh issue create *": "allow", - "gh issue close *": "ask", - "gh run cancel *": "ask", - "gh run rerun *": "ask", - "gh release create *": "ask", - "gh release delete *": "deny", - "lsof *": "allow", - "kill *": "ask", - "wrangler *": "allow", - // Additional commands based on install.sh CLI utilities - "jq *": "allow", - "bat *": "allow", - "fd *": "allow", - "fzf *": "allow", - "delta *": "allow", - "htop *": "allow", - "tree *": "allow", - "curl *": "allow", - "wget *": "allow", - "go *": "allow", - "rustc *": "allow", - "cargo *": "allow", - "python *": "allow", - "python3 *": "allow", - "pip *": "allow", - "pip3 *": "allow", - "pipx *": "allow", - "uv *": "allow", - "ruff *": "allow", - "mise *": "allow", - "cmake *": "allow", - "shellcheck *": "allow", - "jj *": "allow", - // Docker - "docker *": "allow", - "docker-compose *": "allow", - // macOS utilities - "brew *": "allow", - "open *": "allow", - "pbcopy": "allow", - "pbpaste": "allow", - "source *": "allow", - }, - }, -} diff --git a/home/dot_config/opencode/tui.json b/home/dot_config/opencode/tui.json deleted file mode 100644 index 91eaddb..0000000 --- a/home/dot_config/opencode/tui.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "https://opencode.ai/tui.json", - "theme": "catppuccin-macchiato", - "scroll_speed": 3, - "scroll_acceleration": { - "enabled": true - }, - "diff_style": "auto", - "mouse": true -} diff --git a/home/dot_apm/dot_apm/agents/code-reviewer.agent.md b/home/dot_copilot/agents/code-reviewer.agent.md similarity index 100% rename from home/dot_apm/dot_apm/agents/code-reviewer.agent.md rename to home/dot_copilot/agents/code-reviewer.agent.md diff --git a/home/dot_apm/dot_apm/agents/research.agent.md b/home/dot_copilot/agents/research.agent.md similarity index 100% rename from home/dot_apm/dot_apm/agents/research.agent.md rename to home/dot_copilot/agents/research.agent.md diff --git a/home/dot_copilot/mcp-config.json.tmpl b/home/dot_copilot/mcp-config.json.tmpl new file mode 100644 index 0000000..83e8fab --- /dev/null +++ b/home/dot_copilot/mcp-config.json.tmpl @@ -0,0 +1,9 @@ +{{- $activeTargets := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.targets) | fromJson -}} +{{- $activeMcp := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .ai.mcp) | fromJson -}} +{{- $servers := dict -}} +{{- if has "github-copilot" $activeTargets -}} +{{- range $activeMcp -}} +{{- $_ := set $servers .name (dict "type" .transport "url" .url "tools" (list "*")) -}} +{{- end -}} +{{- end -}} +{{ dict "mcpServers" $servers | toPrettyJson }} diff --git a/home/dot_apm/dot_apm/skills/deep-analysis/SKILL.md b/home/dot_copilot/skills/deep-analysis/SKILL.md similarity index 100% rename from home/dot_apm/dot_apm/skills/deep-analysis/SKILL.md rename to home/dot_copilot/skills/deep-analysis/SKILL.md diff --git a/home/dot_apm/dot_apm/skills/deep-analysis/references/research-summary-template.md b/home/dot_copilot/skills/deep-analysis/references/research-summary-template.md similarity index 100% rename from home/dot_apm/dot_apm/skills/deep-analysis/references/research-summary-template.md rename to home/dot_copilot/skills/deep-analysis/references/research-summary-template.md diff --git a/home/dot_apm/dot_apm/skills/deep-analysis/references/rubric.md b/home/dot_copilot/skills/deep-analysis/references/rubric.md similarity index 100% rename from home/dot_apm/dot_apm/skills/deep-analysis/references/rubric.md rename to home/dot_copilot/skills/deep-analysis/references/rubric.md diff --git a/mise.toml b/mise.toml index 26869de..d19c0c7 100644 --- a/mise.toml +++ b/mise.toml @@ -1,124 +1,55 @@ [tools] -"github:microsoft/apm" = "0.21.0" bats = "1.13.0" chezmoi = "2.70.3" prettier = "3.8.3" shellcheck = "0.11.0" shfmt = "3.13.1" taplo = "0.10.0" +"aqua:numtide/treefmt" = "2.5.0" [tasks.diff] description = "Preview chezmoi changes from this source tree" -run = 'mise exec -- chezmoi diff --source="$PWD"' +run = 'chezmoi diff --source="$PWD"' [tasks.update] description = "Apply this source tree with chezmoi" -run = 'mise exec -- chezmoi apply --verbose --source="$PWD"' +run = 'chezmoi apply --verbose --source="$PWD"' [tasks.reset] description = "Clear chezmoi script state" -run = "mise exec -- chezmoi state delete-bucket --bucket=scriptState" +run = "chezmoi state delete-bucket --bucket=scriptState" [tasks.reset-config] description = "Re-run chezmoi init data prompts for this source tree" -run = 'mise exec -- chezmoi init --data=false --source="$PWD"' +run = 'chezmoi init --data=false --source="$PWD"' [tasks.format] -description = "Format shell, markdown, YAML, and TOML" -run = ''' -#!/usr/bin/env bash -set -euo pipefail - -shell_roots=() -has_shell_roots=0 -for root in scripts home/dot_config/zsh home/.chezmoitemplates home/dot_claude .github/actions; do - if [[ -d "${root}" ]]; then - shell_roots+=("${root}") - has_shell_roots=1 - fi -done - -if ((has_shell_roots)); then - shell_targets=() - while IFS= read -r shell_target; do - shell_targets+=("${shell_target}") - done < <(find "${shell_roots[@]}" -type f \( -name '*.sh' -o -name '*.zsh' -o -name '.zshrc' \)) - - if [[ -n "${shell_targets[*]:-}" ]]; then - mise exec -- shfmt --write "${shell_targets[@]}" - fi -fi - -mise exec -- prettier --write . -mise exec -- taplo fmt -''' +description = "Format all files with treefmt" +run = "treefmt" [tasks.lint] -description = "Lint shell, markdown, YAML, and TOML" +description = "Check formatting with treefmt and lint shell with shellcheck" run = ''' #!/usr/bin/env bash set -euo pipefail -mise exec -- prettier --check . +# treefmt walks the tree and checks formatting; shellcheck adds static analysis. +# git ls-files scopes both shellcheck passes to tracked files, so vendored or +# ignored scripts stay out without hand-maintained root lists. +treefmt --ci -shell_roots=() -has_shell_roots=0 -for root in scripts home/dot_config/zsh home/.chezmoitemplates home/dot_claude .github/actions; do - if [[ -d "${root}" ]]; then - shell_roots+=("${root}") - has_shell_roots=1 - fi -done +git ls-files '*.sh' '*.zsh' '*.zshrc' | xargs shellcheck -if ((has_shell_roots)); then - shell_targets=() - while IFS= read -r shell_target; do - shell_targets+=("${shell_target}") - done < <(find "${shell_roots[@]}" -type f \( -name '*.sh' -o -name '*.zsh' -o -name '.zshrc' \)) - - if [[ -n "${shell_targets[*]:-}" ]]; then - mise exec -- shellcheck "${shell_targets[@]}" - mise exec -- shfmt --diff "${shell_targets[@]}" - fi -fi - -# bats files use a test DSL that shfmt would reflow, so lint them with shellcheck only. +# bats files use a test DSL that shfmt would reflow, so shellcheck alone lints them. # Severity stays at warning because bats idioms (literal-string assertions, per-test # subshells) trip info-level checks that are not real bugs. -if [[ -d tests ]]; then - bats_targets=() - while IFS= read -r bats_target; do - bats_targets+=("${bats_target}") - done < <(find tests -type f -name '*.bats') - - if [[ -n "${bats_targets[*]:-}" ]]; then - mise exec -- shellcheck --shell=bash --severity=warning "${bats_targets[@]}" - fi -fi - -mise exec -- taplo fmt --check +git ls-files '*.bats' | xargs shellcheck --severity=warning ''' -[tasks.test-unit] -description = "Run bats unit tests" -run = "mise exec -- bats --recursive tests/unit" - -[tasks.test-template] -description = "Run bats template tests" -run = "mise exec -- bats --recursive tests/template" - [tasks.test] description = "Run all bats tests" -run = [{ task = "test-unit" }, { task = "test-template" }] +run = "bats --recursive tests" [tasks.check] description = "Run lint and tests" run = [{ task = "lint" }, { task = "test" }] - -[tasks.validate-apm] -description = "Frozen APM install + audit gate for one or both contexts (personal, work, all)" -run = 'bash "$MISE_PROJECT_ROOT/scripts/validate-apm.sh" "${1:-all}"' - -[tasks.refresh-apm-locks] -description = "Re-resolve APM channels and rewrite committed lockfiles for all contexts" -run = 'bash "$MISE_PROJECT_ROOT/scripts/refresh-apm-locks.sh"' diff --git a/scripts/apm-lib.sh b/scripts/apm-lib.sh deleted file mode 100644 index 78d820b..0000000 --- a/scripts/apm-lib.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash -# @file scripts/apm-lib.sh -# @brief Shared helpers to materialize a context's ~/.apm into a temp home. -# @description -# Sourced by scripts/validate-apm.sh and scripts/refresh-apm-locks.sh. -# Resolves pinned tool paths before HOME is changed, then applies only the -# ~/.apm chezmoi target into an isolated temp home without firing run scripts. - -set -Eeuo pipefail - -# @description Resolve pinned chezmoi and apm binaries. Call before changing HOME, -# because an isolated HOME breaks mise trust resolution. -# @set CHEZMOI_BIN Absolute path to the pinned chezmoi binary. -# @set APM_BIN Absolute path to the pinned apm binary. -function apm_lib_resolve_bins() { - CHEZMOI_BIN="$(mise which chezmoi)" - APM_BIN="$(mise which apm)" - export CHEZMOI_BIN APM_BIN -} - -# @description Materialize ~/.apm for a context into a temp home. -# @arg $1 string Context: personal or work. -# @arg $2 string Temp root for chezmoi state and cache. -# @arg $3 string Temp home directory. -# @arg $4 string Repo root (chezmoi source parent). -function apm_lib_materialize() { - local context="$1" tmp_root="$2" tmp_home="$3" repo="$4" - - HOME="$tmp_home" "$repo/.github/actions/write-chezmoi-config/write-chezmoi-config.sh" "$context" - - "${CHEZMOI_BIN}" apply \ - --source "$repo/home" \ - --destination "$tmp_home" \ - --config "$tmp_home/.config/chezmoi/chezmoi.yaml" \ - --config-format yaml \ - --persistent-state "$tmp_root/chezmoistate-$context.boltdb" \ - --cache "$tmp_root/cache-$context" \ - --refresh-externals=never \ - --include files,dirs \ - --no-tty \ - "$tmp_home/.apm" -} diff --git a/scripts/refresh-apm-locks.sh b/scripts/refresh-apm-locks.sh deleted file mode 100644 index 2c9dc60..0000000 --- a/scripts/refresh-apm-locks.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash -# @file scripts/refresh-apm-locks.sh -# @brief Regenerate committed per-context APM lockfiles. -# @description -# For each context: materialize ~/.apm in a temp home, run -# `apm lock --update --global` to re-resolve refs and rewrite apm.lock.yaml, -# then copy the lockfile back to -# home/.chezmoitemplates/apm/apm.lock..yaml. Never touches real HOME. -# Note: apm 0.21.0 forwards `apm update` to `self-update` (binary updater), so -# `apm lock --update --global` is the command that rewrites the user-scope -# lockfile at ~/.apm/apm.lock.yaml. - -set -Eeuo pipefail - -repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -# shellcheck source=scripts/apm-lib.sh -source "$repo/scripts/apm-lib.sh" - -contexts=("$@") -[ "$#" -eq 0 ] && contexts=(personal work) - -# Reject unknown contexts before doing work; an unvalidated context would -# silently write a misnamed apm.lock..yaml into the repo. -for context in "${contexts[@]}"; do - case "$context" in - personal | work) ;; - *) - printf 'Unsupported context: %s (use personal, work)\n' "$context" >&2 - exit 1 - ;; - esac -done - -apm_lib_resolve_bins -PRETTIER_BIN="$(mise which prettier)" -export PRETTIER_BIN - -tmp_root="$(mktemp -d)" -trap 'rm -rf "$tmp_root"' EXIT - -written=() - -for context in "${contexts[@]}"; do - tmp_home="$tmp_root/home-$context" - mkdir -p "$tmp_home" - apm_lib_materialize "$context" "$tmp_root" "$tmp_home" "$repo" - - HOME="$tmp_home" \ - XDG_CACHE_HOME="$tmp_home/.cache" XDG_CONFIG_HOME="$tmp_home/.config" XDG_STATE_HOME="$tmp_home/.local/state" \ - "${APM_BIN}" lock --update --global - - dest="$repo/home/.chezmoitemplates/apm/apm.lock.$context.yaml" - cp "$tmp_home/.apm/apm.lock.yaml" "$dest" - written+=("$dest") - printf '[refresh] wrote home/.chezmoitemplates/apm/apm.lock.%s.yaml\n' "$context" -done - -"${PRETTIER_BIN}" --write "${written[@]}" -printf '[refresh] formatted %d lockfile(s) with prettier\n' "${#written[@]}" diff --git a/scripts/validate-apm.sh b/scripts/validate-apm.sh deleted file mode 100755 index 4b7c98a..0000000 --- a/scripts/validate-apm.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env bash -# @file scripts/validate-apm.sh -# @brief Validate APM reproducibility for one or both contexts in temp homes. -# @description -# Materializes ~/.apm via chezmoi (manifest + committed lockfile), then runs -# `apm install --global --frozen` in an isolated temp home. A non-zero exit -# from `--frozen` means the lockfile is missing, stale, or a ref is -# unresolvable — the only signals that matter for reproducibility. -# `all` runs both contexts and aggregates results. -# @arg $1 string Context: personal, work, or all (default all). - -set -Eeuo pipefail - -repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -# shellcheck source=scripts/apm-lib.sh -source "$repo/scripts/apm-lib.sh" - -arg="${1:-all}" -case "$arg" in -personal | work) contexts=("$arg") ;; -all) contexts=(personal work) ;; -*) - printf 'Unsupported context: %s (use personal, work, or all)\n' "$arg" >&2 - exit 1 - ;; -esac - -apm_lib_resolve_bins - -tmp_root="$(mktemp -d)" -trap 'rm -rf "$tmp_root"' EXIT - -# @description Run the frozen install gate for one context. -# @description -# Called as an `if` condition, so set -e errexit is suspended here. Capture -# install_rc explicitly so a failing `apm install --frozen` is not masked. -# `--frozen` already enforces lockfile-exists and ref-consistency; a separate -# `apm audit --ci` call is not needed and introduces false failures when the -# lockfile lacks a deployed_files section (generated by `apm lock`, not install). -# @arg $1 string Context. -function validate_context() { - local context="$1" tmp_home="$tmp_root/home-$1" install_rc - mkdir -p "$tmp_home" - if ! apm_lib_materialize "$context" "$tmp_root" "$tmp_home" "$repo"; then - return 1 - fi - - # Run apm from inside the materialized ~/.apm (matches the runtime installer in - # lib/install/apm.sh) so it operates on the global manifest and lockfile rather - # than treating the current repo as a local project. Subshell keeps the loop cwd. - ( - cd "$tmp_home/.apm" && - HOME="$tmp_home" \ - XDG_CACHE_HOME="$tmp_home/.cache" XDG_CONFIG_HOME="$tmp_home/.config" XDG_STATE_HOME="$tmp_home/.local/state" \ - "${APM_BIN}" install --global --frozen --parallel-downloads 0 - ) - install_rc=$? - - return "$install_rc" -} - -rc=0 -for context in "${contexts[@]}"; do - if validate_context "$context"; then - printf '%s: PASS\n' "$context" - else - printf '%s: FAIL\n' "$context" - rc=1 - fi -done -exit "$rc" diff --git a/tests/template/agent-instructions.bats b/tests/template/agent-instructions.bats index cf6ad50..7056ce8 100644 --- a/tests/template/agent-instructions.bats +++ b/tests/template/agent-instructions.bats @@ -6,21 +6,13 @@ load '../test_helpers/load.bash' load '../test_helpers/templates.bash' CLAUDE_TEMPLATE="$DOTFILES_ROOT/home/dot_claude/CLAUDE.md.tmpl" -OPENCODE_TEMPLATE="$DOTFILES_ROOT/home/dot_config/opencode/AGENTS.md.tmpl" @test "agent instruction templates render shared markdown" { run render_chezmoi_template "$CLAUDE_TEMPLATE" assert_success - assert_line --index 0 '# Claude Code Settings' - assert_line --regexp '^### GitHub CLI$' - assert_line 'Use `gh` CLI for all GitHub interactions. Never clone repositories to read code.' - - run render_chezmoi_template "$OPENCODE_TEMPLATE" - - assert_success - assert_line --index 0 '# Claude Code Settings' - assert_line --regexp '^### GitHub CLI$' + assert_line --index 0 '# Agent Guidance' + assert_line --regexp '^## GitHub CLI$' assert_line 'Use `gh` CLI for all GitHub interactions. Never clone repositories to read code.' } @@ -29,10 +21,8 @@ OPENCODE_TEMPLATE="$DOTFILES_ROOT/home/dot_config/opencode/AGENTS.md.tmpl" [ ! -e "$DOTFILES_ROOT/home/.chezmoidata/agents.yaml" ] claude_template="$(<"$CLAUDE_TEMPLATE")" - opencode_template="$(<"$OPENCODE_TEMPLATE")" [[ "$claude_template" == *'{{ template "AGENTS.md" . -}}'* ]] - [[ "$opencode_template" == *'{{ template "AGENTS.md" . -}}'* ]] } @test "agent instruction templates render Graphify guidance" { @@ -43,12 +33,4 @@ OPENCODE_TEMPLATE="$DOTFILES_ROOT/home/dot_config/opencode/AGENTS.md.tmpl" assert_line '- Use the installed Graphify skill when the user invokes `/graphify`.' assert_line --partial 'graphify query' assert_line --partial 'GRAPH_REPORT.md' - - run render_chezmoi_template "$OPENCODE_TEMPLATE" - - assert_success - assert_line '## Graphify' - assert_line '- Use the installed Graphify skill when the user invokes `/graphify`.' - assert_line --partial 'graphify query' - assert_line --partial 'GRAPH_REPORT.md' } diff --git a/tests/template/apm-config.bats b/tests/template/apm-config.bats deleted file mode 100644 index ee0cb8d..0000000 --- a/tests/template/apm-config.bats +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env bats -# @file tests/template/apm-config.bats -# @brief Template rendering tests for home/dot_apm/apm.yml.tmpl. - -load '../test_helpers/load.bash' -load '../test_helpers/templates.bash' - -APM_TEMPLATE="$DOTFILES_ROOT/home/dot_apm/apm.yml.tmpl" - -@test "personal APM config renders only shared MCP servers" { - run render_chezmoi_template "$APM_TEMPLATE" "$PERSONAL_DATA" - - assert_success - assert_line ' - name: "grep"' - assert_line ' registry: false' - assert_line ' url: "https://mcp.grep.app"' - refute_line --regexp 'name: figma' - refute_line --regexp 'name: jira' -} - -@test "personal APM config renders personal target" { - run render_chezmoi_template "$APM_TEMPLATE" "$PERSONAL_DATA" - - assert_success - assert_line ' - claude' - assert_line ' - agent-skills' - refute_line ' - copilot' - refute_line ' - opencode' - refute_line ' -agent-skills:' -} - -@test "personal APM config renders shared and personal APM packages" { - run render_chezmoi_template "$APM_TEMPLATE" "$PERSONAL_DATA" - - assert_success - assert_line --partial ' - "obra/superpowers' - assert_line --partial ' - "JuliusBrussee/caveman' - assert_line --partial ' - "anthropics/claude-plugins-official/plugins/skill-creator' - assert_line --partial ' - "schpet/linear-cli' - assert_line --partial ' - "tavily-ai/skills' - assert_line --partial ' - git: JuliusBrussee/skills' - assert_line ' skills:' - assert_line ' - "grill-me"' - assert_line ' - "junior-to-senior"' - refute_line --partial 'interface-kit' - refute_line --partial 'loop-factory' - refute_line --partial 'context-canary' -} - -@test "work APM config renders Figma and Jira MCP servers" { - run render_chezmoi_template "$APM_TEMPLATE" "$WORK_DATA" - - assert_success - assert_line ' - name: "grep"' - assert_line ' - name: "figma"' - assert_line ' url: "https://mcp.figma.com/mcp"' - assert_line ' - name: "jira"' - assert_line ' url: "https://mcp.atlassian.com/v1/mcp"' -} - -@test "work APM config renders work target" { - run render_chezmoi_template "$APM_TEMPLATE" "$WORK_DATA" - - assert_success - assert_line ' - copilot' - refute_line ' - opencode' - refute_line ' - claude' - refute_line ' - agent-skills' - refute_line ' -copilot:' -} - -@test "work APM config renders shared APM packages without personal packages" { - run render_chezmoi_template "$APM_TEMPLATE" "$WORK_DATA" - - assert_success - assert_line --partial ' - "obra/superpowers' - assert_line --partial ' - "JuliusBrussee/caveman' - assert_line --partial ' - "anthropics/claude-plugins-official/plugins/skill-creator' - refute_line --partial ' - schpet/linear-cli' - refute_line --partial ' - tavily-ai/skills' - refute_line --partial 'JuliusBrussee/skills' - refute_line --partial 'grill-me' - refute_line --partial 'junior-to-senior' -} diff --git a/tests/template/apm-lockfile.bats b/tests/template/apm-lockfile.bats deleted file mode 100644 index 6f07973..0000000 --- a/tests/template/apm-lockfile.bats +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bats -# @file tests/template/apm-lockfile.bats -# @brief apm.lock.yaml.tmpl selects the correct per-context lockfile. - -load '../test_helpers/load.bash' -load '../test_helpers/templates.bash' - -TMPL="$DOTFILES_ROOT/home/dot_apm/apm.lock.yaml.tmpl" -PERSONAL='{"personal":true,"work":false}' -WORK='{"personal":false,"work":true}' - -@test "personal context renders the personal lockfile" { - personal_first="$(grep -m1 resolved_commit "$DOTFILES_ROOT/home/.chezmoitemplates/apm/apm.lock.personal.yaml")" - run render_chezmoi_template "$TMPL" "$PERSONAL" - assert_success - assert_line --regexp '^lockfile_version' - assert_line "$personal_first" -} - -@test "work context renders the work lockfile" { - run render_chezmoi_template "$TMPL" "$WORK" - assert_success - assert_line --regexp '^lockfile_version' -} diff --git a/tests/template/darwin-install-scripts.bats b/tests/template/darwin-install-scripts.bats index c5ececf..a434a21 100644 --- a/tests/template/darwin-install-scripts.bats +++ b/tests/template/darwin-install-scripts.bats @@ -5,10 +5,13 @@ load '../test_helpers/load.bash' load '../test_helpers/templates.bash' -EMPTY_APM_DATA='{"chezmoi":{"os":"darwin"},"personal":true,"work":false,"apm":{"targets":{"shared":[],"personal":[],"work":[]}}}' +EMPTY_AI_DATA='{"chezmoi":{"os":"darwin"},"personal":true,"work":false,"ai":{"targets":{"shared":[],"personal":[],"work":[]}}}' PACKAGE_TEMPLATE="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_02_install-packages.sh.tmpl" UV_TOOLS_TEMPLATE="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_03_install-uv-tools.sh.tmpl" -GRAPHIFY_TEMPLATE="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_08_install-graphify-skills.sh.tmpl" +GRAPHIFY_TEMPLATE="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_09_install-graphify-skills.sh.tmpl" +AI_SKILLS_TEMPLATE="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_07_install-ai-skills.sh.tmpl" +AI_PLUGINS_TEMPLATE="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_06_install-ai-plugins.sh.tmpl" +AI_MCP_TEMPLATE="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_08_install-ai-mcp.sh.tmpl" @test "darwin install script templates render with bash shebang" { for template in "$DOTFILES_ROOT"/home/.chezmoiscripts/darwin/*.tmpl; do @@ -30,6 +33,14 @@ GRAPHIFY_TEMPLATE="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_08_in assert_file_contains "$GRAPHIFY_TEMPLATE" '{{ template "lib/install/graphify-skills.sh" . }}' } +@test "darwin install script templates inject AI skills shell library" { + assert_file_contains "$AI_SKILLS_TEMPLATE" '{{ template "lib/install/ai-skills.sh" . }}' +} + +@test "darwin install script templates inject AI plugins shell library" { + assert_file_contains "$AI_PLUGINS_TEMPLATE" '{{ template "lib/install/ai-plugins.sh" . }}' +} + @test "darwin install script templates inject the install prelude" { local prelude='{{ template "lib/common/install-prelude.sh" . }}' assert_file_contains "$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_once_01_install-homebrew.sh.tmpl" "$prelude" @@ -37,9 +48,9 @@ GRAPHIFY_TEMPLATE="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_08_in assert_file_contains "$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_03_install-mise-tools.sh.tmpl" "$prelude" assert_file_contains "$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_03_install-uv-tools.sh.tmpl" "$prelude" assert_file_contains "$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_04_install-vscode-extensions.sh.tmpl" "$prelude" - assert_file_contains "$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_06_install-apm.sh.tmpl" "$prelude" - assert_file_contains "$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_07_fix-opencode-agent-tools.sh.tmpl" "$prelude" assert_file_contains "$GRAPHIFY_TEMPLATE" "$prelude" + assert_file_contains "$AI_SKILLS_TEMPLATE" "$prelude" + assert_file_contains "$AI_PLUGINS_TEMPLATE" "$prelude" } @test "rendered darwin install scripts are syntactically valid bash" { @@ -91,41 +102,135 @@ GRAPHIFY_TEMPLATE="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_08_in assert_line --partial 'uv_tools_install_main' } -@test "work uv tools template has no personal uv tools" { +@test "work uv tools template includes shared tools but omits personal tools" { run render_chezmoi_template "$UV_TOOLS_TEMPLATE" "$WORK_DATA" assert_success - refute_line --partial '"graphifyy"' + assert_line 'UV_TOOLS=(' + assert_line --partial '"graphifyy"' refute_line --partial '"tavily-cli"' - refute_line --partial 'uv tool install --upgrade' - refute_line --partial 'uv_tools_install_main' + assert_line --partial 'uv_tools_install_main' } -@test "personal Graphify template renders agent and Claude platforms" { +@test "personal Graphify template renders the Claude platform" { run render_chezmoi_template "$GRAPHIFY_TEMPLATE" "$DARWIN_DATA" assert_success assert_line 'GRAPHIFY_PLATFORMS=(' - assert_line --partial '"agents"' assert_line --partial '"claude"' + refute_line --partial '"copilot"' assert_line --partial 'graphify_skills_install_main' } -@test "work Graphify template renders no personal platforms" { +@test "work Graphify template renders the Copilot platform" { run render_chezmoi_template "$GRAPHIFY_TEMPLATE" "$WORK_DATA" assert_success - refute_line --partial '"agents"' + assert_line 'GRAPHIFY_PLATFORMS=(' + assert_line --partial '"copilot"' refute_line --partial '"claude"' - refute_line --partial 'graphify install --platform' - refute_line --partial 'graphify_skills_install_main' + assert_line --partial 'graphify_skills_install_main' } -@test "empty APM target groups render no Graphify commands" { - run render_chezmoi_template "$GRAPHIFY_TEMPLATE" "$EMPTY_APM_DATA" +@test "empty AI target groups render no Graphify commands" { + run render_chezmoi_template "$GRAPHIFY_TEMPLATE" "$EMPTY_AI_DATA" assert_success refute_line 'GRAPHIFY_PLATFORMS=(' - refute_line --partial 'graphify install --platform' refute_line --partial 'graphify_skills_install_main' } + +@test "personal AI skills template targets claude-code with shared and personal skills" { + run render_chezmoi_template "$AI_SKILLS_TEMPLATE" "$DARWIN_DATA" + + assert_success + assert_line 'AI_SKILL_TARGETS=(' + assert_line --partial '"claude-code"' + refute_line --partial '"github-copilot"' + assert_line --partial 'export AI_LOCAL_SKILLS_DIR=' + assert_line --partial '.chezmoitemplates/skills' + assert_line --partial '"mattpocock/skills|grill-me"' + assert_line --partial '"local|typescript"' + assert_line --partial '"schpet/linear-cli|linear-cli"' + assert_line --partial 'ai_skills_install_main' +} + +@test "work AI skills template targets github-copilot and omits personal skills" { + run render_chezmoi_template "$AI_SKILLS_TEMPLATE" "$WORK_DATA" + + assert_success + assert_line 'AI_SKILL_TARGETS=(' + assert_line --partial '"github-copilot"' + refute_line --partial '"claude-code"' + assert_line --partial '"mattpocock/skills|grill-me"' + refute_line --partial '"schpet/linear-cli|linear-cli"' + assert_line --partial 'ai_skills_install_main' +} + +@test "empty AI target groups render no AI skills commands" { + run render_chezmoi_template "$AI_SKILLS_TEMPLATE" "$EMPTY_AI_DATA" + + assert_success + refute_line 'AI_SKILL_TARGETS=(' + refute_line --partial 'ai_skills_install_main' +} + +@test "personal AI plugins template targets claude-code and includes both plugins" { + run render_chezmoi_template "$AI_PLUGINS_TEMPLATE" "$DARWIN_DATA" + + assert_success + assert_line 'AI_PLUGIN_TARGETS=(' + assert_line --partial '"claude-code"' + refute_line --partial '"github-copilot"' + assert_line --partial '"caveman|caveman|JuliusBrussee/caveman"' + assert_line --partial '"superpowers|superpowers-dev|obra/superpowers"' + assert_line --partial 'ai_plugins_install_main' +} + +@test "work AI plugins template targets github-copilot" { + run render_chezmoi_template "$AI_PLUGINS_TEMPLATE" "$WORK_DATA" + + assert_success + assert_line 'AI_PLUGIN_TARGETS=(' + assert_line --partial '"github-copilot"' + refute_line --partial '"claude-code"' + assert_line --partial '"caveman|caveman|JuliusBrussee/caveman"' + assert_line --partial 'ai_plugins_install_main' +} + +@test "empty AI target groups render no AI plugins commands" { + run render_chezmoi_template "$AI_PLUGINS_TEMPLATE" "$EMPTY_AI_DATA" + + assert_success + refute_line 'AI_PLUGIN_TARGETS=(' + refute_line --partial 'ai_plugins_install_main' +} + +@test "darwin install script templates inject AI MCP shell library" { + assert_file_contains "$AI_MCP_TEMPLATE" '{{ template "lib/install/ai-mcp.sh" . }}' +} + +@test "darwin install script templates inject the prelude before AI MCP" { + local prelude + prelude='{{ template "lib/common/install-prelude.sh" . }}' + assert_file_contains "$AI_MCP_TEMPLATE" "$prelude" +} + +@test "personal AI MCP template registers shared and personal servers" { + run render_chezmoi_template "$AI_MCP_TEMPLATE" "$DARWIN_DATA" + assert_success + assert_line --partial '"grep|http|https://mcp.grep.app"' + assert_line --partial '"tavily|http|https://mcp.tavily.com/mcp/"' +} + +@test "work AI MCP template registers no Claude servers" { + run render_chezmoi_template "$AI_MCP_TEMPLATE" "$WORK_DATA" + assert_success + refute_line --partial 'lib/install/ai-mcp.sh' +} + +@test "empty AI target groups render no AI MCP commands" { + run render_chezmoi_template "$AI_MCP_TEMPLATE" "$EMPTY_AI_DATA" + assert_success + refute_line --partial 'lib/install/ai-mcp.sh' +} diff --git a/tests/template/install-apm-script.bats b/tests/template/install-apm-script.bats deleted file mode 100644 index c3cc390..0000000 --- a/tests/template/install-apm-script.bats +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env bats -# @file tests/template/install-apm-script.bats -# @brief Renders home/.chezmoiscripts/darwin/run_onchange_06_install-apm.sh.tmpl -# via `chezmoi execute-template` and asserts the lib/common/log.sh -# snippet is injected and the resulting script is syntactically valid -# bash. - -load '../test_helpers/load.bash' - -TMPL="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_06_install-apm.sh.tmpl" -SOURCE_DIR="$DOTFILES_ROOT/home" -DARWIN_DATA='{"chezmoi":{"os":"darwin"}}' - -@test "install-apm template renders without errors" { - run mise exec -- chezmoi execute-template --source "$SOURCE_DIR" --override-data "$DARWIN_DATA" <"$TMPL" - assert_success -} - -@test "rendered install-apm script starts with bash shebang" { - run mise exec -- chezmoi execute-template --source "$SOURCE_DIR" --override-data "$DARWIN_DATA" <"$TMPL" - assert_success - [ "${lines[0]}" = "#!/usr/bin/env bash" ] -} - -@test "install-apm template gates library injection to darwin" { - template_content="$(<"$TMPL")" - - [[ "$template_content" == *'{{ if eq .chezmoi.os "darwin" }}'* ]] || return 1 - [[ "$template_content" == *'{{ template "lib/common/log.sh" . }}'* ]] || return 1 - [[ "$template_content" == *'{{ template "lib/install/apm.sh" . }}'* ]] || return 1 -} - -@test "install-apm template injects log_info, log_warn, log_error definitions" { - run mise exec -- chezmoi execute-template --source "$SOURCE_DIR" --override-data "$DARWIN_DATA" <"$TMPL" - assert_success - assert_line 'log_info() {' - assert_line 'log_warn() {' - assert_line 'log_error() {' -} - -@test "install-apm template uses log_info at the call sites" { - run mise exec -- chezmoi execute-template --source "$SOURCE_DIR" --override-data "$DARWIN_DATA" <"$TMPL" - assert_success - assert_line ' log_info "[apm] Installing globally from ~/.apm/apm.yml (frozen)..."' - assert_line ' log_info "[apm] Install complete."' -} - -@test "install-apm template runs frozen install (no tolerated-failure fallback)" { - run mise exec -- chezmoi execute-template --source "$SOURCE_DIR" --override-data "$DARWIN_DATA" <"$TMPL" - assert_success - assert_line --partial 'apm install --global --frozen' - refute_line --partial 'apm install --global ||' -} - -@test "rendered install-apm script is syntactically valid bash" { - rendered=$(mise exec -- chezmoi execute-template --source "$SOURCE_DIR" --override-data "$DARWIN_DATA" <"$TMPL") - printf '%s\n' "$rendered" | bash -n -} - -@test "rendered install-apm script preserves chezmoi content-hash trigger comments" { - run mise exec -- chezmoi execute-template --source "$SOURCE_DIR" --override-data "$DARWIN_DATA" <"$TMPL" - assert_success - assert_line --regexp '^# apm\.yml:' - assert_line --regexp '^# apm data:' - assert_line --regexp '^# apm mcp template:' - assert_line --regexp '^# apm lock \(personal\):' - assert_line --regexp '^# apm lock \(work\):' - assert_line --regexp '^# agents:' -} diff --git a/tests/template/mcp-config.bats b/tests/template/mcp-config.bats new file mode 100644 index 0000000..6234bfd --- /dev/null +++ b/tests/template/mcp-config.bats @@ -0,0 +1,47 @@ +#!/usr/bin/env bats +# @file tests/template/mcp-config.bats +# @brief Render tests for the Copilot MCP config templates. + +load '../test_helpers/load.bash' +load '../test_helpers/templates.bash' + +COPILOT_MCP="$DOTFILES_ROOT/home/dot_copilot/mcp-config.json.tmpl" +VSCODE_MCP="$DOTFILES_ROOT/home/Library/Application Support/Code/User/mcp.json.tmpl" + +# WORK_DATA and PERSONAL_DATA come from templates.bash; the real ai.yaml supplies +# .ai (targets and mcp) because chezmoi merges --override-data over .chezmoidata. +# A work machine targets github-copilot and gets shared + work MCP servers; a +# personal machine has no github-copilot target, so the maps render empty. + +@test "work Copilot CLI MCP config renders shared and work servers" { + run render_chezmoi_template "$COPILOT_MCP" "$WORK_DATA" + assert_success + assert_line --partial '"mcpServers"' + assert_line --partial '"grep"' + assert_line --partial '"figma"' + assert_line --partial '"jira"' + assert_line --partial 'https://mcp.figma.com/mcp' + assert_line --partial '"tools"' +} + +@test "personal Copilot CLI MCP config renders an empty server map" { + run render_chezmoi_template "$COPILOT_MCP" "$PERSONAL_DATA" + assert_success + assert_line --partial '"mcpServers": {}' +} + +@test "work VS Code MCP config renders shared and work servers" { + run render_chezmoi_template "$VSCODE_MCP" "$WORK_DATA" + assert_success + assert_line --partial '"servers"' + assert_line --partial '"grep"' + assert_line --partial '"jira"' + assert_line --partial 'https://mcp.atlassian.com/v1/mcp' + refute_line --partial '"tools"' +} + +@test "personal VS Code MCP config renders an empty server map" { + run render_chezmoi_template "$VSCODE_MCP" "$PERSONAL_DATA" + assert_success + assert_line --partial '"servers": {}' +} diff --git a/tests/unit/claude-settings.bats b/tests/unit/claude-settings.bats index 31027d4..9a73349 100644 --- a/tests/unit/claude-settings.bats +++ b/tests/unit/claude-settings.bats @@ -9,8 +9,7 @@ SETTINGS="$DOTFILES_ROOT/home/dot_claude/settings.json" @test "claude settings use current model aliases" { settings_content="$(<"$SETTINGS")" - [[ "$settings_content" == *'"model": "opusplan[1m]"'* ]] - [[ "$settings_content" == *'"CLAUDE_CODE_SUBAGENT_MODEL": "inherit"'* ]] + [[ "$settings_content" == *'"model": "best"'* ]] [[ "$settings_content" != *'ANTHROPIC_DEFAULT_OPUS_MODEL'* ]] [[ "$settings_content" != *'ANTHROPIC_DEFAULT_SONNET_MODEL'* ]] [[ "$settings_content" != *'ANTHROPIC_DEFAULT_HAIKU_MODEL'* ]] diff --git a/tests/unit/lib/install/ai-mcp.bats b/tests/unit/lib/install/ai-mcp.bats new file mode 100644 index 0000000..c505acc --- /dev/null +++ b/tests/unit/lib/install/ai-mcp.bats @@ -0,0 +1,101 @@ +#!/usr/bin/env bats +# @file tests/unit/lib/install/ai-mcp.bats +# @brief Behavior tests for home/.chezmoitemplates/lib/install/ai-mcp.sh. + +load '../../../test_helpers/load.bash' + +LOG_LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/common/log.sh" +LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/install/ai-mcp.sh" + +setup() { + export CLAUDE_ARGS_FILE="$BATS_TEST_TMPDIR/claude-args" + mkdir -p "$BATS_TEST_TMPDIR/bin" + + # Default stub: `mcp get` reports "not found" (exit 1) so `mcp add` runs. + cat >"$BATS_TEST_TMPDIR/bin/claude" <<'CLAUDE' +#!/usr/bin/env bash +printf '%s\n' "$*" | tee -a "$CLAUDE_ARGS_FILE" +[[ "$1 $2" == "mcp get" ]] && exit 1 +exit "${CLAUDE_EXIT_CODE:-0}" +CLAUDE + chmod +x "$BATS_TEST_TMPDIR/bin/claude" + + export PATH="$BATS_TEST_TMPDIR/bin:/usr/bin:/bin" +} + +_run_main() { + run bash -c "source '$LOG_LIB' && source '$LIB' && $1 && main" +} + +@test "main: registers each server with user scope and transport" { + _run_main "AI_MCP=('grep|http|https://mcp.grep.app' 'tavily|http|https://mcp.tavily.com/mcp/')" + + assert_success + assert_line "[ai-mcp] Registering MCP servers..." + assert_line "[ai-mcp] MCP servers registered." + assert_line "mcp add --scope user --transport http grep https://mcp.grep.app" + assert_line "mcp add --scope user --transport http tavily https://mcp.tavily.com/mcp/" +} + +@test "main: skips a server that is already registered" { + cat >"$BATS_TEST_TMPDIR/bin/claude" <<'CLAUDE' +#!/usr/bin/env bash +printf '%s\n' "$*" | tee -a "$CLAUDE_ARGS_FILE" +# grep already exists; tavily does not. +[[ "$1 $2" == "mcp get" && "$3" == "grep" ]] && exit 0 +[[ "$1 $2" == "mcp get" ]] && exit 1 +exit 0 +CLAUDE + chmod +x "$BATS_TEST_TMPDIR/bin/claude" + + _run_main "AI_MCP=('grep|http|https://mcp.grep.app' 'tavily|http|https://mcp.tavily.com/mcp/')" + + assert_success + assert_line "[ai-mcp] grep already registered; skipping." + refute_line "mcp add --scope user --transport http grep https://mcp.grep.app" + assert_line "mcp add --scope user --transport http tavily https://mcp.tavily.com/mcp/" +} + +@test "main: exits cleanly with no servers" { + _run_main "AI_MCP=()" + + assert_success + assert_line "[ai-mcp] No MCP servers to register." + [ ! -s "$CLAUDE_ARGS_FILE" ] +} + +@test "main: skips empty server entries" { + _run_main "AI_MCP=('grep|http|https://mcp.grep.app' '' 'jira|http|https://mcp.atlassian.com/v1/mcp')" + + assert_success + assert_line "mcp add --scope user --transport http grep https://mcp.grep.app" + assert_line "mcp add --scope user --transport http jira https://mcp.atlassian.com/v1/mcp" +} + +@test "main: warns but succeeds when a server fails to register" { + cat >"$BATS_TEST_TMPDIR/bin/claude" <<'CLAUDE' +#!/usr/bin/env bash +printf '%s\n' "$*" | tee -a "$CLAUDE_ARGS_FILE" +[[ "$1 $2" == "mcp get" ]] && exit 1 +[[ "$1 $2" == "mcp add" ]] && exit 1 +exit 0 +CLAUDE + chmod +x "$BATS_TEST_TMPDIR/bin/claude" + + _run_main "AI_MCP=('grep|http|https://mcp.grep.app')" + + assert_success + assert_line "warn: [ai-mcp] 1 server(s) failed to register:" + assert_line " - grep" + assert_line "[ai-mcp] MCP servers registered." +} + +@test "main: fails when claude is missing" { + rm -f "$BATS_TEST_TMPDIR/bin/claude" + # Drop /usr/bin so a host-installed claude cannot mask the missing path; /bin still provides bash. + export PATH="$BATS_TEST_TMPDIR/bin:/bin" + _run_main "AI_MCP=('grep|http|https://mcp.grep.app')" + + assert_failure 1 + assert_line "error: [ai-mcp] claude CLI not found. Ensure Claude Code is installed." +} diff --git a/tests/unit/lib/install/ai-plugins.bats b/tests/unit/lib/install/ai-plugins.bats new file mode 100644 index 0000000..ef4896f --- /dev/null +++ b/tests/unit/lib/install/ai-plugins.bats @@ -0,0 +1,154 @@ +#!/usr/bin/env bats +# @file tests/unit/lib/install/ai-plugins.bats +# @brief Behavior tests for home/.chezmoitemplates/lib/install/ai-plugins.sh. + +load '../../../test_helpers/load.bash' + +LOG_LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/common/log.sh" +PRELUDE="$DOTFILES_ROOT/home/.chezmoitemplates/lib/common/install-prelude.sh" +LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/install/ai-plugins.sh" + +setup() { + export CLAUDE_ARGS_FILE="$BATS_TEST_TMPDIR/claude-args" + export NPX_ARGS_FILE="$BATS_TEST_TMPDIR/npx-args" + mkdir -p "$BATS_TEST_TMPDIR/bin" + + cat >"$BATS_TEST_TMPDIR/bin/claude" <<'CLAUDE' +#!/usr/bin/env bash +printf '%s\n' "$*" | tee -a "$CLAUDE_ARGS_FILE" +exit "${CLAUDE_EXIT_CODE:-0}" +CLAUDE + chmod +x "$BATS_TEST_TMPDIR/bin/claude" + + cat >"$BATS_TEST_TMPDIR/bin/npx" <<'NPX' +#!/usr/bin/env bash +printf '%s\n' "$*" | tee -a "$NPX_ARGS_FILE" +exit "${NPX_EXIT_CODE:-0}" +NPX + chmod +x "$BATS_TEST_TMPDIR/bin/npx" + + export PATH="$BATS_TEST_TMPDIR/bin:/usr/bin:/bin" +} + +_run_main() { + run bash -c "source '$LOG_LIB' && source '$PRELUDE' && source '$LIB' && $1 && main" +} + +@test "main: claude-code target adds marketplace and installs each plugin" { + _run_main "AI_PLUGIN_TARGETS=('claude-code') && AI_PLUGINS=('caveman|caveman|JuliusBrussee/caveman' 'superpowers|superpowers-dev|obra/superpowers')" + + assert_success + assert_line "[ai-plugins] Installing AI plugins..." + assert_line "[ai-plugins] AI plugins installed." + assert_line "plugin marketplace add JuliusBrussee/caveman" + assert_line "plugin install caveman@caveman" + assert_line "plugin marketplace add obra/superpowers" + assert_line "plugin install superpowers@superpowers-dev" + [ ! -s "$NPX_ARGS_FILE" ] +} + +@test "main: github-copilot target installs a plugin source as a skill" { + _run_main "AI_PLUGIN_TARGETS=('github-copilot') && AI_PLUGINS=('caveman|caveman|JuliusBrussee/caveman')" + + assert_success + assert_line "--yes skills add JuliusBrussee/caveman -a github-copilot --global --copy --yes" + [ ! -s "$CLAUDE_ARGS_FILE" ] +} + +@test "main: installs every plugin to each active target" { + _run_main "AI_PLUGIN_TARGETS=('claude-code' 'github-copilot') && AI_PLUGINS=('caveman|caveman|JuliusBrussee/caveman')" + + assert_success + assert_line "plugin marketplace add JuliusBrussee/caveman" + assert_line "plugin install caveman@caveman" + assert_line "--yes skills add JuliusBrussee/caveman -a github-copilot --global --copy --yes" +} + +@test "main: exits cleanly with no plugins" { + _run_main "AI_PLUGIN_TARGETS=('claude-code') && AI_PLUGINS=()" + + assert_success + assert_line "[ai-plugins] No plugins to install." + [ ! -s "$CLAUDE_ARGS_FILE" ] +} + +@test "main: exits cleanly with no targets" { + _run_main "AI_PLUGIN_TARGETS=() && AI_PLUGINS=('caveman|caveman|JuliusBrussee/caveman')" + + assert_success + assert_line "[ai-plugins] No agent targets; nothing to install." + [ ! -s "$CLAUDE_ARGS_FILE" ] +} + +@test "main: skips empty plugin entries" { + _run_main "AI_PLUGIN_TARGETS=('claude-code') && AI_PLUGINS=('caveman|caveman|JuliusBrussee/caveman' '' 'superpowers|superpowers-dev|obra/superpowers')" + + assert_success + assert_line "plugin install caveman@caveman" + assert_line "plugin install superpowers@superpowers-dev" + [ "$(wc -l <"$CLAUDE_ARGS_FILE")" -eq 4 ] +} + +@test "main: skips empty target entries" { + _run_main "AI_PLUGIN_TARGETS=('claude-code' '') && AI_PLUGINS=('caveman|caveman|JuliusBrussee/caveman')" + + assert_success + assert_line "plugin install caveman@caveman" + [ "$(wc -l <"$CLAUDE_ARGS_FILE")" -eq 2 ] +} + +@test "main: warns but succeeds when a plugin fails" { + export CLAUDE_EXIT_CODE=1 + _run_main "AI_PLUGIN_TARGETS=('claude-code') && AI_PLUGINS=('caveman|caveman|JuliusBrussee/caveman')" + + assert_success + assert_line "warn: [ai-plugins] 1 plugin(s) failed to install:" + assert_line " - caveman" + assert_line "[ai-plugins] AI plugins installed." +} + +@test "main: reports only the plugins that failed" { + cat >"$BATS_TEST_TMPDIR/bin/claude" <<'CLAUDE' +#!/usr/bin/env bash +printf '%s\n' "$*" | tee -a "$CLAUDE_ARGS_FILE" +[[ "$*" == *"obra/superpowers"* ]] && exit 1 +exit 0 +CLAUDE + chmod +x "$BATS_TEST_TMPDIR/bin/claude" + + _run_main "AI_PLUGIN_TARGETS=('claude-code') && AI_PLUGINS=('caveman|caveman|JuliusBrussee/caveman' 'superpowers|superpowers-dev|obra/superpowers')" + + assert_success + assert_line "warn: [ai-plugins] 1 plugin(s) failed to install:" + assert_line " - superpowers" + refute_line " - caveman" +} + +@test "main: warns and skips an unknown target" { + _run_main "AI_PLUGIN_TARGETS=('grok') && AI_PLUGINS=('caveman|caveman|JuliusBrussee/caveman')" + + assert_success + assert_line "warn: [ai-plugins] unknown target 'grok'; skipping." + [ ! -s "$CLAUDE_ARGS_FILE" ] + [ ! -s "$NPX_ARGS_FILE" ] +} + +@test "main: fails when claude is missing for a claude-code target" { + rm -f "$BATS_TEST_TMPDIR/bin/claude" + # Drop /usr/bin so a host-installed claude cannot mask the missing path; /bin still provides bash. + export PATH="$BATS_TEST_TMPDIR/bin:/bin" + _run_main "AI_PLUGIN_TARGETS=('claude-code') && AI_PLUGINS=('caveman|caveman|JuliusBrussee/caveman')" + + assert_failure 1 + assert_line "error: [ai-plugins] claude CLI not found. Ensure Claude Code is installed." +} + +@test "main: fails when npx is missing for a github-copilot target" { + rm -f "$BATS_TEST_TMPDIR/bin/npx" + # Drop /usr/bin so a host-installed npx cannot mask the missing path; /bin still provides bash. + export PATH="$BATS_TEST_TMPDIR/bin:/bin" + _run_main "AI_PLUGIN_TARGETS=('github-copilot') && AI_PLUGINS=('caveman|caveman|JuliusBrussee/caveman')" + + assert_failure 1 + assert_line "error: [ai-plugins] npx not found. Ensure Node.js is installed (run_onchange_03_install-mise-tools)." +} diff --git a/tests/unit/lib/install/ai-skills.bats b/tests/unit/lib/install/ai-skills.bats new file mode 100644 index 0000000..2fc4682 --- /dev/null +++ b/tests/unit/lib/install/ai-skills.bats @@ -0,0 +1,143 @@ +#!/usr/bin/env bats +# @file tests/unit/lib/install/ai-skills.bats +# @brief Behavior tests for home/.chezmoitemplates/lib/install/ai-skills.sh. + +load '../../../test_helpers/load.bash' + +LOG_LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/common/log.sh" +PRELUDE="$DOTFILES_ROOT/home/.chezmoitemplates/lib/common/install-prelude.sh" +LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/install/ai-skills.sh" + +setup() { + export NPX_ARGS_FILE="$BATS_TEST_TMPDIR/npx-args" + mkdir -p "$BATS_TEST_TMPDIR/bin" + + cat >"$BATS_TEST_TMPDIR/bin/npx" <<'NPX' +#!/usr/bin/env bash +printf '%s\n' "$*" | tee -a "$NPX_ARGS_FILE" +exit "${NPX_EXIT_CODE:-0}" +NPX + chmod +x "$BATS_TEST_TMPDIR/bin/npx" + export PATH="$BATS_TEST_TMPDIR/bin:/usr/bin:/bin" + + export AI_LOCAL_SKILLS_DIR="$BATS_TEST_TMPDIR/skills" + mkdir -p "$AI_LOCAL_SKILLS_DIR" +} + +_run_main() { + run bash -c "source '$LOG_LIB' && source '$PRELUDE' && source '$LIB' && $1 && main" +} + +@test "main: installs each skill for a single target" { + _run_main "AI_SKILL_TARGETS=('claude-code') && AI_SKILLS=('mattpocock/skills|grill-me' 'blader/humanizer|humanizer')" + + assert_success + assert_line "[ai-skills] Installing cross-agent skills..." + assert_line "[ai-skills] Cross-agent skills installed." + assert_line "--yes skills add mattpocock/skills --skill grill-me -a claude-code --global --copy --yes" + assert_line "--yes skills add blader/humanizer --skill humanizer -a claude-code --global --copy --yes" +} + +@test "main: passes one -a flag per target" { + _run_main "AI_SKILL_TARGETS=('claude-code' 'github-copilot') && AI_SKILLS=('mattpocock/skills|grill-me')" + + assert_success + assert_line "--yes skills add mattpocock/skills --skill grill-me -a claude-code -a github-copilot --global --copy --yes" +} + +@test "main: applies every target to every skill" { + _run_main "AI_SKILL_TARGETS=('claude-code' 'github-copilot') && AI_SKILLS=('mattpocock/skills|grill-me' 'blader/humanizer|humanizer')" + + assert_success + assert_line "--yes skills add mattpocock/skills --skill grill-me -a claude-code -a github-copilot --global --copy --yes" + assert_line "--yes skills add blader/humanizer --skill humanizer -a claude-code -a github-copilot --global --copy --yes" +} + +@test "main: skips empty skill entries" { + _run_main "AI_SKILL_TARGETS=('claude-code') && AI_SKILLS=('mattpocock/skills|grill-me' '' 'blader/humanizer|humanizer')" + + assert_success + assert_line "--yes skills add mattpocock/skills --skill grill-me -a claude-code --global --copy --yes" + assert_line "--yes skills add blader/humanizer --skill humanizer -a claude-code --global --copy --yes" + refute_line --partial "skills add --skill" + [ "$(wc -l <"$NPX_ARGS_FILE")" -eq 2 ] +} + +@test "main: installs a local skill when its directory exists" { + mkdir -p "$AI_LOCAL_SKILLS_DIR/typescript" + _run_main "AI_SKILL_TARGETS=('claude-code') && AI_SKILLS=('local|typescript')" + + assert_success + assert_line "--yes skills add $AI_LOCAL_SKILLS_DIR/typescript --skill typescript -a claude-code --global --copy --yes" +} + +@test "main: skips a local skill when its directory is missing" { + _run_main "AI_SKILL_TARGETS=('claude-code') && AI_SKILLS=('local|react')" + + assert_success + assert_line "warn: [ai-skills] local skill 'react' not found at $AI_LOCAL_SKILLS_DIR/react; skipping." + [ ! -s "$NPX_ARGS_FILE" ] +} + +@test "main: installs remote skills around a missing local skill" { + _run_main "AI_SKILL_TARGETS=('claude-code') && AI_SKILLS=('mattpocock/skills|grill-me' 'local|missing' 'blader/humanizer|humanizer')" + + assert_success + assert_line "warn: [ai-skills] local skill 'missing' not found at $AI_LOCAL_SKILLS_DIR/missing; skipping." + assert_line "--yes skills add mattpocock/skills --skill grill-me -a claude-code --global --copy --yes" + assert_line "--yes skills add blader/humanizer --skill humanizer -a claude-code --global --copy --yes" + [ "$(wc -l <"$NPX_ARGS_FILE")" -eq 2 ] +} + +@test "main: exits cleanly with no skills" { + _run_main "AI_SKILL_TARGETS=('claude-code') && AI_SKILLS=()" + + assert_success + assert_line "[ai-skills] No skills to install." + [ ! -s "$NPX_ARGS_FILE" ] +} + +@test "main: exits cleanly with no targets" { + _run_main "AI_SKILL_TARGETS=() && AI_SKILLS=('mattpocock/skills|grill-me')" + + assert_success + assert_line "[ai-skills] No agent targets; nothing to install." + [ ! -s "$NPX_ARGS_FILE" ] +} + +@test "main: warns but succeeds when a skill fails" { + export NPX_EXIT_CODE=1 + _run_main "AI_SKILL_TARGETS=('claude-code') && AI_SKILLS=('mattpocock/skills|grill-me')" + + assert_success + assert_line "warn: [ai-skills] 1 skill(s) failed to install:" + assert_line " - grill-me" + assert_line "[ai-skills] Cross-agent skills installed." +} + +@test "main: reports only the skills that failed" { + cat >"$BATS_TEST_TMPDIR/bin/npx" <<'NPX' +#!/usr/bin/env bash +printf '%s\n' "$*" | tee -a "$NPX_ARGS_FILE" +[[ "$*" == *"blader/humanizer"* ]] && exit 1 +exit 0 +NPX + chmod +x "$BATS_TEST_TMPDIR/bin/npx" + + _run_main "AI_SKILL_TARGETS=('claude-code') && AI_SKILLS=('mattpocock/skills|grill-me' 'blader/humanizer|humanizer')" + + assert_success + assert_line "warn: [ai-skills] 1 skill(s) failed to install:" + assert_line " - humanizer" + refute_line " - grill-me" +} + +@test "main: fails when npx is missing" { + rm -f "$BATS_TEST_TMPDIR/bin/npx" + # Drop /usr/bin so a host-installed npx cannot mask the missing-npx path; /bin still provides bash. + export PATH="$BATS_TEST_TMPDIR/bin:/bin" + _run_main "AI_SKILL_TARGETS=('claude-code') && AI_SKILLS=('mattpocock/skills|grill-me')" + + assert_failure 1 + assert_line "error: [ai-skills] npx not found. Ensure Node.js is installed (run_onchange_03_install-mise-tools)." +} diff --git a/tests/unit/lib/install/apm.bats b/tests/unit/lib/install/apm.bats deleted file mode 100644 index af90969..0000000 --- a/tests/unit/lib/install/apm.bats +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bats -# @file tests/unit/lib/install/apm.bats -# @brief Behavior tests for home/.chezmoitemplates/lib/install/apm.sh. - -load '../../../test_helpers/load.bash' - -LOG_LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/common/log.sh" -PRELUDE="$DOTFILES_ROOT/home/.chezmoitemplates/lib/common/install-prelude.sh" -APM_LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/install/apm.sh" - -setup() { - export HOME="$BATS_TEST_TMPDIR/home" - export APM_ARGS_FILE="$BATS_TEST_TMPDIR/apm-args" - export APM_CWD_FILE="$BATS_TEST_TMPDIR/apm-cwd" - mkdir -p "$HOME/.apm" "$BATS_TEST_TMPDIR/bin" - - cat >"$BATS_TEST_TMPDIR/bin/apm" <<'APM' -#!/usr/bin/env bash -printf '%s\n' "$*" >"$APM_ARGS_FILE" -printf '%s\n' "$PWD" >"$APM_CWD_FILE" -exit "${APM_EXIT_CODE:-0}" -APM - chmod +x "$BATS_TEST_TMPDIR/bin/apm" - export PATH="$BATS_TEST_TMPDIR/bin:$PATH" -} - -@test "apm_install_main: runs apm install globally from ~/.apm" { - run bash -c "source '$LOG_LIB' && source '$PRELUDE' && source '$APM_LIB' && apm_install_main" - - assert_success - assert_line "[apm] Installing globally from ~/.apm/apm.yml (frozen)..." - assert_line "[apm] Install complete." - [ "$(<"$APM_ARGS_FILE")" = "install --global --frozen" ] - [ "$(<"$APM_CWD_FILE")" = "$HOME/.apm" ] -} - -@test "apm_install_main: warns and completes when apm install fails" { - export APM_EXIT_CODE=7 - - run bash -c "source '$LOG_LIB' && source '$PRELUDE' && source '$APM_LIB' && apm_install_main" - - assert_success - assert_line --partial "warn: [apm] apm install --frozen exited non-zero" - assert_line "[apm] Install complete." - [ "$(<"$APM_ARGS_FILE")" = "install --global --frozen" ] -} diff --git a/tests/unit/lib/install/opencode-agent-tools.bats b/tests/unit/lib/install/opencode-agent-tools.bats deleted file mode 100644 index f53d612..0000000 --- a/tests/unit/lib/install/opencode-agent-tools.bats +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env bats -# @file tests/unit/lib/install/opencode-agent-tools.bats -# @brief Behavior tests for home/.chezmoitemplates/lib/install/opencode-agent-tools.sh. - -load '../../../test_helpers/load.bash' - -LOG_LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/common/log.sh" -TOOLS_LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/install/opencode-agent-tools.sh" - -setup() { - AGENTS_DIR="$BATS_TEST_TMPDIR/agents" - mkdir -p "$AGENTS_DIR" -} - -@test "normalize_dir: converts PascalCase array to lowercase map" { - cat >"$AGENTS_DIR/cavecrew-investigator.md" <<'MD' ---- -name: cavecrew-investigator -description: read-only locator -tools: [Read, Grep, Glob, Bash] -model: haiku ---- -body content here -MD - - run bash -c "source '$LOG_LIB' && source '$TOOLS_LIB' && opencode_agent_tools_normalize_dir '$AGENTS_DIR'" - - assert_success - assert_line --partial "rewrote cavecrew-investigator.md" - assert_line --partial "1 file(s) updated" - - local expected - expected='--- -name: cavecrew-investigator -description: read-only locator -tools: - read: true - grep: true - glob: true - bash: true -model: haiku ---- -body content here' - [ "$(<"$AGENTS_DIR/cavecrew-investigator.md")" = "$expected" ] -} - -@test "normalize_dir: leaves already-mapped files untouched" { - cat >"$AGENTS_DIR/already-fixed.md" <<'MD' ---- -name: already-fixed -tools: - read: true - grep: true ---- -body -MD - local before - before="$(cat "$AGENTS_DIR/already-fixed.md")" - - run bash -c "source '$LOG_LIB' && source '$TOOLS_LIB' && opencode_agent_tools_normalize_dir '$AGENTS_DIR'" - - assert_success - assert_line --partial "0 file(s) updated" - [ "$(<"$AGENTS_DIR/already-fixed.md")" = "$before" ] -} - -@test "normalize_dir: ignores tools: arrays appearing in the body" { - cat >"$AGENTS_DIR/body-mention.md" <<'MD' ---- -name: body-mention -description: docs ---- -Here is markdown: -tools: [Read, Grep] -That should remain unchanged. -MD - local before - before="$(cat "$AGENTS_DIR/body-mention.md")" - - run bash -c "source '$LOG_LIB' && source '$TOOLS_LIB' && opencode_agent_tools_normalize_dir '$AGENTS_DIR'" - - assert_success - assert_line --partial "0 file(s) updated" - [ "$(<"$AGENTS_DIR/body-mention.md")" = "$before" ] -} - -@test "normalize_dir: missing dir is a no-op" { - run bash -c "source '$LOG_LIB' && source '$TOOLS_LIB' && opencode_agent_tools_normalize_dir '$BATS_TEST_TMPDIR/does-not-exist'" - - assert_success - assert_line --partial "No agents dir" -} - -@test "normalize_dir: empty dir reports zero updates" { - run bash -c "source '$LOG_LIB' && source '$TOOLS_LIB' && opencode_agent_tools_normalize_dir '$AGENTS_DIR'" - - assert_success - assert_line --partial "0 file(s) updated" -} - -@test "normalize_dir: drops empty items and strips quotes" { - cat >"$AGENTS_DIR/messy.md" <<'MD' ---- -name: messy -tools: [ Read , , 'Edit' , "Write" ] ---- -MD - - run bash -c "source '$LOG_LIB' && source '$TOOLS_LIB' && opencode_agent_tools_normalize_dir '$AGENTS_DIR'" - - assert_success - local expected - expected='--- -name: messy -tools: - read: true - edit: true - write: true ----' - [ "$(<"$AGENTS_DIR/messy.md")" = "$expected" ] -} - -@test "normalize_dir: idempotent on repeated runs" { - cat >"$AGENTS_DIR/idem.md" <<'MD' ---- -name: idem -tools: [Read, Bash] ---- -MD - - run bash -c "source '$LOG_LIB' && source '$TOOLS_LIB' && opencode_agent_tools_normalize_dir '$AGENTS_DIR'" - assert_success - local after_first - after_first="$(cat "$AGENTS_DIR/idem.md")" - - run bash -c "source '$LOG_LIB' && source '$TOOLS_LIB' && opencode_agent_tools_normalize_dir '$AGENTS_DIR'" - assert_success - assert_line --partial "0 file(s) updated" - [ "$(<"$AGENTS_DIR/idem.md")" = "$after_first" ] -} - -@test "normalize_file: returns 1 when nothing to change" { - cat >"$AGENTS_DIR/nothing.md" <<'MD' ---- -name: nothing ---- -body -MD - - run bash -c "source '$LOG_LIB' && source '$TOOLS_LIB' && opencode_agent_tools_normalize_file '$AGENTS_DIR/nothing.md'" - assert_failure -} - -@test "normalize_file: returns 0 when file is modified" { - cat >"$AGENTS_DIR/changed.md" <<'MD' ---- -name: changed -tools: [Read] ---- -MD - - run bash -c "source '$LOG_LIB' && source '$TOOLS_LIB' && opencode_agent_tools_normalize_file '$AGENTS_DIR/changed.md'" - assert_success -} diff --git a/tests/unit/opencode-config.bats b/tests/unit/opencode-config.bats deleted file mode 100644 index a6db4a9..0000000 --- a/tests/unit/opencode-config.bats +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bats -# @file tests/unit/opencode-config.bats -# @brief Regression tests for home/dot_config/opencode/opencode.jsonc. - -load '../test_helpers/load.bash' - -CONFIG="$DOTFILES_ROOT/home/dot_config/opencode/opencode.jsonc" - -@test "opencode config avoids stale OpenAI model tuning" { - config_content="$(<"$CONFIG")" - - [[ "$config_content" == *'"model": "openai/gpt-5.5"'* ]] - [[ "$config_content" != *'"small_model"'* ]] - [[ "$config_content" != *'"provider"'* ]] - [[ "$config_content" != *'"reasoningEffort"'* ]] - [[ "$config_content" != *'"textVerbosity"'* ]] - [[ "$config_content" != *'"reasoningSummary"'* ]] -} - -@test "opencode config does not include supermemory plugin" { - config_content="$(<"$CONFIG")" - - [[ "$config_content" != *'opencode-supermemory'* ]] - [[ "$config_content" == *'"opencode-wakatime"'* ]] -} - -@test "opencode config omits Tavily MCP" { - config_content="$(<"$CONFIG")" - - [[ "$config_content" == *'"grep": {'* ]] - [[ "$config_content" != *'"tavily": {'* ]] - [[ "$config_content" != *'TAVILY_API_KEY'* ]] -} diff --git a/tests/unit/scripts/apm-lib.bats b/tests/unit/scripts/apm-lib.bats deleted file mode 100644 index 80797b5..0000000 --- a/tests/unit/scripts/apm-lib.bats +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bats -# @file tests/unit/scripts/apm-lib.bats -# @brief Behavior tests for scripts/apm-lib.sh shared materialization. - -load '../../test_helpers/load.bash' - -LIB="$DOTFILES_ROOT/scripts/apm-lib.sh" - -setup() { - export ARGS_FILE="$BATS_TEST_TMPDIR/chezmoi-args" - mkdir -p "$BATS_TEST_TMPDIR/bin" - - cat >"$BATS_TEST_TMPDIR/bin/chezmoi" <<'CHEZMOI' -#!/usr/bin/env bash -printf '%s\n' "$*" >>"$ARGS_FILE" -CHEZMOI - chmod +x "$BATS_TEST_TMPDIR/bin/chezmoi" - export PATH="$BATS_TEST_TMPDIR/bin:$PATH" - cat >"$BATS_TEST_TMPDIR/bin/mise" <<'MISE' -#!/usr/bin/env bash -if [ "$1" = "which" ]; then echo "$BATS_TEST_TMPDIR/bin/$2"; fi -MISE - chmod +x "$BATS_TEST_TMPDIR/bin/mise" -} - -@test "apm_lib_resolve_bins sets CHEZMOI_BIN and APM_BIN before HOME changes" { - run bash -c "source '$LIB' && apm_lib_resolve_bins && echo \"\$CHEZMOI_BIN|\$APM_BIN\"" - assert_success - assert_line --partial "/bin/chezmoi|" - assert_line --partial "/bin/apm" -} - -@test "apm_lib_materialize applies only the ~/.apm target with scripts excluded" { - run bash -c " - source '$LIB' - CHEZMOI_BIN='$BATS_TEST_TMPDIR/bin/chezmoi' - apm_lib_materialize personal '$BATS_TEST_TMPDIR/root' '$BATS_TEST_TMPDIR/home' '$DOTFILES_ROOT' - " - assert_success - run cat "$ARGS_FILE" - assert_line --partial "apply" - assert_line --partial "--include files,dirs" - assert_line --partial "/home/.apm" - refute_line --partial "scripts" -} diff --git a/tests/unit/scripts/refresh-apm-locks.bats b/tests/unit/scripts/refresh-apm-locks.bats deleted file mode 100644 index 3e345b5..0000000 --- a/tests/unit/scripts/refresh-apm-locks.bats +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env bats -# @file tests/unit/scripts/refresh-apm-locks.bats -# @brief Behavior tests for scripts/refresh-apm-locks.sh lockfile refresh flow. -# Stubs mise, chezmoi, apm, and prettier on PATH and runs an isolated -# copy of the script so the committed lockfiles are never touched. - -load '../../test_helpers/load.bash' - -setup() { - export CALL_FILE="$BATS_TEST_TMPDIR/calls" - BIN="$BATS_TEST_TMPDIR/bin" - mkdir -p "$BIN" - - # mise resolves pinned tool paths to our stubs. - cat >"$BIN/mise" <>"$CALL_FILE" -if [ "\$1" = which ]; then echo "$BIN/\$2"; exit 0; fi -exit 0 -STUB - - # chezmoi apply only needs to create the target ~/.apm directory. - cat >"$BIN/chezmoi" <>"$CALL_FILE" -mkdir -p "\${@: -1}" -exit 0 -STUB - - # apm lock --update --global writes the user-scope lockfile under \$HOME/.apm. - cat >"$BIN/apm" <>"$CALL_FILE" -mkdir -p "\$HOME/.apm" -printf 'lockfile_version: 1\n# stub lock\n' >"\$HOME/.apm/apm.lock.yaml" -exit 0 -STUB - - # prettier records the files it was asked to format. - cat >"$BIN/prettier" <>"$CALL_FILE" -exit 0 -STUB - - chmod +x "$BIN"/* - export PATH="$BIN:$PATH" - - # Isolated repo copy so cp writes lockfiles into the temp tree, never the real repo. - REPO="$BATS_TEST_TMPDIR/repo" - mkdir -p "$REPO/scripts" "$REPO/home/.chezmoitemplates/apm" "$REPO/.github/actions/write-chezmoi-config" - cp "$DOTFILES_ROOT/scripts/refresh-apm-locks.sh" "$REPO/scripts/refresh-apm-locks.sh" - cp "$DOTFILES_ROOT/scripts/apm-lib.sh" "$REPO/scripts/apm-lib.sh" - cat >"$REPO/.github/actions/write-chezmoi-config/write-chezmoi-config.sh" <<'STUB' -#!/usr/bin/env bash -exit 0 -STUB - chmod +x "$REPO/.github/actions/write-chezmoi-config/write-chezmoi-config.sh" - - LOCK_DIR="$REPO/home/.chezmoitemplates/apm" -} - -@test "personal context writes the personal lockfile and formats it" { - run bash "$REPO/scripts/refresh-apm-locks.sh" personal - assert_success - assert_line --partial "wrote home/.chezmoitemplates/apm/apm.lock.personal.yaml" - - assert_file_exists "$LOCK_DIR/apm.lock.personal.yaml" - assert_file_contains "$LOCK_DIR/apm.lock.personal.yaml" 'lockfile_version: 1' - - run cat "$CALL_FILE" - assert_line --partial "prettier --write $LOCK_DIR/apm.lock.personal.yaml" -} - -@test "work context writes the work lockfile only" { - run bash "$REPO/scripts/refresh-apm-locks.sh" work - assert_success - - assert_file_exists "$LOCK_DIR/apm.lock.work.yaml" - assert_file_not_exists "$LOCK_DIR/apm.lock.personal.yaml" -} - -@test "no arguments refreshes both contexts" { - run bash "$REPO/scripts/refresh-apm-locks.sh" - assert_success - - assert_file_exists "$LOCK_DIR/apm.lock.personal.yaml" - assert_file_exists "$LOCK_DIR/apm.lock.work.yaml" - - run cat "$CALL_FILE" - assert_line --partial "prettier --write $LOCK_DIR/apm.lock.personal.yaml $LOCK_DIR/apm.lock.work.yaml" -} - -@test "rejects an unknown context before writing anything" { - run bash "$REPO/scripts/refresh-apm-locks.sh" bogus - assert_failure - assert_line --partial "Unsupported context: bogus" - assert_file_not_exists "$LOCK_DIR/apm.lock.bogus.yaml" -} diff --git a/tests/unit/scripts/validate-apm.bats b/tests/unit/scripts/validate-apm.bats deleted file mode 100644 index ab427d9..0000000 --- a/tests/unit/scripts/validate-apm.bats +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bats -# @file tests/unit/scripts/validate-apm.bats -# @brief Behavior tests for scripts/validate-apm.sh argument handling and aggregation. - -load '../../test_helpers/load.bash' - -SCRIPT="$DOTFILES_ROOT/scripts/validate-apm.sh" - -setup() { - export CALL_FILE="$BATS_TEST_TMPDIR/calls" - mkdir -p "$BATS_TEST_TMPDIR/bin" - for t in mise chezmoi apm; do - cat >"$BATS_TEST_TMPDIR/bin/$t" <>"$CALL_FILE" -if [ "$t" = mise ] && [ "\$1" = which ]; then echo "$BATS_TEST_TMPDIR/bin/\$2"; exit 0; fi -if [ "$t" = chezmoi ]; then mkdir -p "\${@: -1}"; fi -exit "\${APM_EXIT:-0}" -STUB - chmod +x "$BATS_TEST_TMPDIR/bin/$t" - done - export PATH="$BATS_TEST_TMPDIR/bin:$PATH" -} - -@test "all runs both contexts and reports each" { - run bash "$SCRIPT" all - assert_success - assert_line --partial "personal: PASS" - assert_line --partial "work: PASS" -} - -@test "all reports both even when personal fails, exits non-zero" { - export APM_EXIT=1 - run bash "$SCRIPT" all - assert_failure - assert_line --partial "personal: FAIL" - assert_line --partial "work: FAIL" -} - -@test "rejects an unknown context" { - run bash "$SCRIPT" bogus - assert_failure - assert_line --partial "Unsupported context" -} diff --git a/treefmt.toml b/treefmt.toml new file mode 100644 index 0000000..0e889c3 --- /dev/null +++ b/treefmt.toml @@ -0,0 +1,20 @@ +# treefmt: one formatter per file type. Walks the tree, respects .gitignore, +# and dispatches to each tool. Replaces the hand-rolled shell-file discovery +# that the mise format/lint tasks used to duplicate. + +[formatter.shfmt] +command = "shfmt" +options = ["-w"] +includes = ["*.sh", "*.zsh", ".zshrc", ".bashrc"] +excludes = ["*.tmpl"] + +[formatter.prettier] +command = "prettier" +options = ["--write"] +includes = ["*.md", "*.yaml", "*.yml", "*.json"] + +[formatter.taplo] +command = "taplo" +options = ["fmt"] +includes = ["*.toml"] +excludes = ["*.tmpl"]