diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d60b1a5..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "editor.defaultFormatter": "esbenp.prettier-vscode", - "json.schemaDownload.trustedDomains": { - "https://developer.microsoft.com/json-schemas/": true, - "https://json-schema.org/": true, - "https://json.schemastore.org/": true, - "https://models.dev/model-schema.json": true, - "https://opencode.ai": true, - "https://raw.githubusercontent.com/devcontainers/spec/": true, - "https://raw.githubusercontent.com/microsoft/vscode/": true, - "https://schemastore.azurewebsites.net/": true, - "https://www.schemastore.org/": true - } -} diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index fc9d981..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,60 +0,0 @@ -# AGENTS.md - -## Repository Context - -- This repository is managed with [`chezmoi`](https://www.chezmoi.io/) ([GitHub](https://github.com/twpayne/chezmoi)). -- Files under `home/` are the public source state and are applied by `chezmoi` into the user's `$HOME` directory. - -## Response Rule - -- After reading this `CLAUDE.md`, say: `🤖 I read the CLAUDE.md for edwinhern/dotfiles.` - -## Comment Policy - -- When adding or updating comments for shell scripts or shell-based executables, always write them in English using shdoc-compatible format. - -## Planning Workflow - -- Linear is the source of truth for features, bugs, enhancements, cleanup work, research, and follow-up tasks. -- Use the Linear team `dotfiles` with key `DOT` for this repository. -- Use Linear workflow states: `Backlog`, `Todo`, `In Progress`, `In Review`, and `Done`. -- Treat `Backlog` as the backlog lane and `Todo` as ready but not active. -- Do not use GitHub Issues or GitHub Projects for new work after the Linear cutover. -- Do not use `docs/` as the issue tracker, roadmap, or long-lived backlog. -- Superpowers specs and plans live under `docs/superpowers/` and should be linked from the related Linear issue description or a Linear comment. - -## Task Selection - -- Use Linear native priority to choose the next task: `High`, then `Medium`, then `Low`. -- Use `Urgent` only for security, broken reproducibility, or a blocked daily workflow that needs immediate attention. -- `High` means security, reproducibility, broken workflow, or work that unlocks important follow-up work. -- `Medium` means useful cleanup, maintenance, or decision work with clear value but no current breakage. -- `Low` means speculative, optional, or only worth doing when current pain appears. -- If priority is missing, set or ask for the priority before implementation starts. -- When priorities tie, prefer security and reproducibility work first, then blockers, then the smallest clear issue. -- Do not add story points unless the user explicitly asks for them. -- When starting work, move the selected Linear issue to `In Progress` and link the relevant Superpowers spec or plan before code changes. - -## Agent Work Tracking - -- When new work is discovered, create or update a Linear issue instead of adding a backlog item under `docs/`. -- Keep issues small enough to close with one PR or a short linked PR series. -- Link the relevant Superpowers spec or plan from the Linear issue before implementation starts. -- Track active work by moving the Linear issue through the team workflow instead of editing a progress document. -- Link PRs to Linear issues by including the issue ID in the branch, PR title, or PR body. -- Use Linear closing keywords in PR text, such as `Fixes DOT-123`, when merge should move the Linear issue to `Done`. -- If a thought is not ready for an issue, keep it in the current conversation or a Superpowers spec until it becomes actionable. - -## Linear / GitHub PR Flow - -- Start branch work from a Linear `DOT-*` issue. -- If no issue exists, create one in the Linear `dotfiles` team, set priority, and keep it in `Backlog` or `Todo` until work starts. -- When work starts, assign yourself and move the issue to `In Progress`. -- Prefer Linear's generated branch name from `linear issue start DOT-123` or Copy git branch name, such as `feature/dot-123-short-title`. -- Open the PR from that branch so Linear links it by branch name. The PR title or body may also include the same `DOT-*` ID. -- Use `Fixes DOT-123` in the PR body only when merge should move the issue to `Done`. Use `Refs DOT-123` when merge should not close it. -- After opening the PR, verify the Linear issue shows the GitHub PR attachment and the PR references the correct `DOT-*` issue. - -## Git / PR Workflow - -- After pushing to GitHub, always check the GitHub Actions CI results. If CI fails, investigate the failure, fix the issue, push again, and repeat until all CI checks pass. diff --git a/CLAUDE.md b/CLAUDE.md index 43c994c..44c0b96 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1 +1,60 @@ -@AGENTS.md +# AGENTS.md + +## Repository Context + +- This repository is managed with [`chezmoi`](https://www.chezmoi.io/) ([GitHub](https://github.com/twpayne/chezmoi)). +- Files under `home/` are the public source state and are applied by `chezmoi` into the user's `$HOME` directory. + +## Response Rule + +- After reading this `AGENTS.md`, say: `🤖 I read the CLAUDE.md for edwinhern/dotfiles.` + +## Comment Policy + +- When adding or updating comments for shell scripts or shell-based executables, always write them in English using shdoc-compatible format. + +## Planning Workflow + +- Linear is the source of truth for features, bugs, enhancements, cleanup work, research, and follow-up tasks. +- Use the Linear team `dotfiles` with key `DOT` for this repository. +- Use Linear workflow states: `Backlog`, `Todo`, `In Progress`, `In Review`, and `Done`. +- Treat `Backlog` as the backlog lane and `Todo` as ready but not active. +- Do not use GitHub Issues or GitHub Projects for new work after the Linear cutover. +- Do not use `docs/` as the issue tracker, roadmap, or long-lived backlog. +- Superpowers specs and plans live under `docs/superpowers/` and should be linked from the related Linear issue description or a Linear comment. + +## Task Selection + +- Use Linear native priority to choose the next task: `High`, then `Medium`, then `Low`. +- Use `Urgent` only for security, broken reproducibility, or a blocked daily workflow that needs immediate attention. +- `High` means security, reproducibility, broken workflow, or work that unlocks important follow-up work. +- `Medium` means useful cleanup, maintenance, or decision work with clear value but no current breakage. +- `Low` means speculative, optional, or only worth doing when current pain appears. +- If priority is missing, set or ask for the priority before implementation starts. +- When priorities tie, prefer security and reproducibility work first, then blockers, then the smallest clear issue. +- Do not add story points unless the user explicitly asks for them. +- When starting work, move the selected Linear issue to `In Progress` and link the relevant Superpowers spec or plan before code changes. + +## Agent Work Tracking + +- When new work is discovered, create or update a Linear issue instead of adding a backlog item under `docs/`. +- Keep issues small enough to close with one PR or a short linked PR series. +- Link the relevant Superpowers spec or plan from the Linear issue before implementation starts. +- Track active work by moving the Linear issue through the team workflow instead of editing a progress document. +- Link PRs to Linear issues by including the issue ID in the branch, PR title, or PR body. +- Use Linear closing keywords in PR text, such as `Fixes DOT-123`, when merge should move the Linear issue to `Done`. +- If a thought is not ready for an issue, keep it in the current conversation or a Superpowers spec until it becomes actionable. + +## Linear / GitHub PR Flow + +- Start branch work from a Linear `DOT-*` issue. +- If no issue exists, create one in the Linear `dotfiles` team, set priority, and keep it in `Backlog` or `Todo` until work starts. +- When work starts, assign yourself and move the issue to `In Progress`. +- Prefer Linear's generated branch name from `linear issue start DOT-123` or Copy git branch name, such as `feature/dot-123-short-title`. +- Open the PR from that branch so Linear links it by branch name. The PR title or body may also include the same `DOT-*` ID. +- Use `Fixes DOT-123` in the PR body only when merge should move the issue to `Done`. Use `Refs DOT-123` when merge should not close it. +- After opening the PR, verify the Linear issue shows the GitHub PR attachment and the PR references the correct `DOT-*` issue. + +## Git / PR Workflow + +- After pushing to GitHub, always check the GitHub Actions CI results. If CI fails, investigate the failure, fix the issue, push again, and repeat until all CI checks pass. diff --git a/docs/superpowers/plans/2026-05-15-apm-install-library.md b/docs/superpowers/plans/2026-05-15-apm-install-library.md deleted file mode 100644 index 494253b..0000000 --- a/docs/superpowers/plans/2026-05-15-apm-install-library.md +++ /dev/null @@ -1,163 +0,0 @@ -# APM Install Library 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:** Extract APM install logic from the chezmoi run script into a sourceable and directly testable bash library under `home/.chezmoitemplates/lib/install/`. - -**Architecture:** `home/.chezmoitemplates/lib/install/apm.sh` is the source of truth and follows the executable bash script shape with `apm_install_main`, `main`, and a `BASH_SOURCE` guard. The chezmoi run script injects `lib/common/log.sh` and `lib/install/apm.sh` with `{{ template }}` and keeps content-hash trigger comments. Bats tests source the library directly and use a fake `apm` executable on `PATH`. - -**Tech Stack:** Chezmoi templates, bash, bats-core, bats-support, bats-assert, mise. - ---- - -## File Structure - -- Create `home/.chezmoitemplates/lib/install/apm.sh`: bash library for APM install behavior. -- Create `tests/unit/lib/install/apm.bats`: unit tests that source `apm.sh` and fake the `apm` binary. -- Modify `home/.chezmoiscripts/darwin/run_onchange_06_install-apm.sh.tmpl`: replace inline shell logic with library includes and keep hash-trigger comments. -- Modify `docs/superpowers/specs/2026-05-15-ci-refactor-design.md`: record the final APM library shape. - -### Task 1: Add APM Install Library Tests - -**Files:** - -- Create: `tests/unit/lib/install/apm.bats` -- Read: `tests/test_helpers/load.bash` - -- [ ] **Step 1: Write the failing tests** - -Create `tests/unit/lib/install/apm.bats` with tests that source `home/.chezmoitemplates/lib/common/log.sh`, source `home/.chezmoitemplates/lib/install/apm.sh`, put a fake `apm` executable on `PATH`, call `apm_install_main`, and assert `apm install --global` was called. - -- [ ] **Step 2: Run tests and verify they fail because the library is missing** - -Run: `mise exec -- bats tests/unit/lib/install/apm.bats` - -Expected: FAIL because `home/.chezmoitemplates/lib/install/apm.sh` does not exist. - -### Task 2: Implement APM Install Library - -**Files:** - -- Create: `home/.chezmoitemplates/lib/install/apm.sh` -- Test: `tests/unit/lib/install/apm.bats` - -- [ ] **Step 1: Create the library** - -Create `home/.chezmoitemplates/lib/install/apm.sh`: - -```bash -#!/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 - -if [ "${DOTFILES_DEBUG:-}" ]; then - set -x -fi - -# -# @description Install APM dependencies from `${HOME}/.apm/apm.yml`. -# -function apm_install_main() { - log_info "[apm] Installing globally from ~/.apm/apm.yml..." - cd "${HOME}/.apm" - - apm install --global || log_warn "[apm] apm install exited with errors (likely MCP token prompts 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 - main -fi -``` - -- [ ] **Step 2: Run tests and verify they pass** - -Run: `mise exec -- bats tests/unit/lib/install/apm.bats` - -Expected: PASS. - -### Task 3: Refactor Chezmoi APM Run Script - -**Files:** - -- Modify: `home/.chezmoiscripts/darwin/run_onchange_06_install-apm.sh.tmpl` -- Test: `home/.chezmoiscripts/darwin/run_onchange_06_install-apm.sh.tmpl` - -- [ ] **Step 1: Replace inline APM shell logic with template includes** - -Update `home/.chezmoiscripts/darwin/run_onchange_06_install-apm.sh.tmpl` so it keeps the Darwin gate around the library includes: - -```go-template -{{ if eq .chezmoi.os "darwin" }} -{{ template "lib/common/log.sh" . }} -{{ template "lib/install/apm.sh" . }} -{{ end }} -``` - -Keep the existing content-hash trigger comments in the file before the conditional includes. - -- [ ] **Step 2: Render the template and verify bash syntax** - -Run: `mise exec -- chezmoi execute-template --source home < home/.chezmoiscripts/darwin/run_onchange_06_install-apm.sh.tmpl | bash -n` - -Expected: exit code 0. - -### Task 4: Update Spec For Final Library Shape - -**Files:** - -- Modify: `docs/superpowers/specs/2026-05-15-ci-refactor-design.md` - -- [ ] **Step 1: Update current PR checklist** - -Add these checked items under the current PR section: - -```markdown -- [x] Add `home/.chezmoitemplates/lib/install/apm.sh` with executable bash script shape and `apm_install_main`. -- [x] Refactor `run_onchange_06_install-apm.sh.tmpl` to inject `lib/common/log.sh` and `lib/install/apm.sh`. -- [x] Add `tests/unit/lib/install/apm.bats` with fake `apm` on `PATH`. -``` - -### Task 5: Final Verification - -**Files:** - -- Verify: all changed files - -- [ ] **Step 1: Run shell library tests** - -Run: `mise exec -- bats tests/unit/lib/common/log.bats tests/unit/lib/install/apm.bats` - -Expected: PASS. - -- [ ] **Step 2: Run all tests** - -Run: `mise test` - -Expected: PASS. - -- [ ] **Step 3: Run lint** - -Run: `mise lint` - -Expected: PASS. - -- [ ] **Step 4: Check git diff** - -Run: `git diff --check && git status --short --branch` - -Expected: no whitespace errors; branch shows only intended changed files. diff --git a/docs/superpowers/plans/2026-05-16-planning-workflow.md b/docs/superpowers/plans/2026-05-16-planning-workflow.md deleted file mode 100644 index 3effda8..0000000 --- a/docs/superpowers/plans/2026-05-16-planning-workflow.md +++ /dev/null @@ -1,142 +0,0 @@ -# Planning Workflow 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:** Make GitHub Issues the source of truth for tracked work and use a GitHub Project as the simple Kanban board for this repo. - -**Architecture:** Repo instructions tell agents how to handle work items, thoughts, specs, plans, and PR links. GitHub labels, issue templates, and a `dotfiles` project provide the live tracking surface. `docs/superpowers/` stores linked specs and plans, but `docs/` is not the backlog. - -**Tech Stack:** Markdown, GitHub Issues, GitHub Projects, `gh`, mise linting. - ---- - -## File Structure - -- Modify `AGENTS.md`: add the planning source-of-truth rule and the agent workflow. -- Modify `home/dot_config/opencode/opencode.jsonc`: allow targeted `gh` issue, label, and project commands. -- Create `.github/ISSUE_TEMPLATE/bug_report.md`: small bug template. -- Create `.github/ISSUE_TEMPLATE/task.yml`: required Superpowers doc link for feature, enhancement, cleanup, or research work. -- Delete `docs/PROGRESS.md`: remove the stale local backlog. -- Use `gh`: create labels, create the `dotfiles` project, and seed current follow-up issues. - -### Task 1: Update Agent Instructions - -**Files:** - -- Modify: `AGENTS.md` - -- [ ] **Step 1: Add a Planning Workflow section** - -Add a section that says GitHub Issues are the source of truth, the GitHub Project is the visual Kanban board, and `docs/` is not the backlog. - -- [ ] **Step 2: Add agent rules for thoughts and plans** - -Specify that agents should capture new work as issues, link specs and plans from issues, and use closing keywords in PR bodies. - -- [ ] **Step 3: Review wording** - -Confirm the file still says `CLAUDE.md` points at `AGENTS.md` and that all providers can read the same repo instructions. - -### Task 2: Add Issue Templates - -**Files:** - -- Create: `.github/ISSUE_TEMPLATE/bug_report.md` -- Create: `.github/ISSUE_TEMPLATE/task.yml` - -- [ ] **Step 1: Create the issue template directory** - -Run: `mkdir .github/ISSUE_TEMPLATE` - -Expected: directory exists. - -- [ ] **Step 2: Add bug template** - -The template should ask for summary, steps, expected behavior, actual behavior, and context. - -- [ ] **Step 3: Add task issue form** - -The issue form should ask for outcome, scope, area, acceptance checks, and a required linked Superpowers spec or plan. - -### Task 3: Add opencode GitHub Permissions - -**Files:** - -- Modify: `home/dot_config/opencode/opencode.jsonc` - -- [ ] **Step 1: Allow issue and label management** - -Allow `gh issue edit *`, `gh issue comment *`, `gh label create *`, and `gh label edit *`. - -- [ ] **Step 2: Allow project board management** - -Allow `gh project list *`, `gh project view *`, `gh project field-list *`, `gh project item-list *`, `gh project create *`, `gh project link *`, `gh project field-create *`, `gh project item-add *`, and `gh project item-edit *`. - -- [ ] **Step 3: Keep destructive project operations gated** - -Keep project delete denied and project close, unlink, item delete, item archive, and field delete as ask. - -### Task 4: Stop Tracking Work In `docs/` - -**Files:** - -- Delete: `docs/PROGRESS.md` - -- [ ] **Step 1: Remove stale progress file** - -Delete `docs/PROGRESS.md`. - -- [ ] **Step 2: Keep Superpowers docs tracked** - -Do not ignore or remove `docs/superpowers/` files. - -### Task 5: Configure GitHub Tracking - -**Tools:** - -- `gh label` -- `gh project` -- `gh issue` - -- [ ] **Step 1: Add labels** - -Create or update `priority:high` and area labels for `apm`, `chezmoi`, `ci`, `docs`, `agents`, and `editor`. - -- [ ] **Step 2: Create project** - -Create a user project named `dotfiles` if one does not exist. - -- [ ] **Step 3: Seed current issues** - -Create issues for unfinished follow-ups that still matter: - -- Remove remaining `curl | sh` chezmoi installs from CI. -- Decide whether mise tool caching is worth adding to CI. -- Decide whether docs-only PRs should skip heavier checks. -- Decide whether SARIF audit reports or `apm-policy.yml` governance are needed. -- Capture current Superpowers workflow conventions in Claude instructions if still useful. -- Design Bitwarden-backed APM secrets handling. -- Decide whether a business APM package is needed. -- Evaluate reducing the lint toolchain. - -### Task 6: Verify - -**Commands:** - -- `mise lint` -- `gh label list --repo edwinhern/dotfiles --limit 100` -- `gh project list --owner edwinhern --format json --limit 20` -- `gh issue list --repo edwinhern/dotfiles --state open --limit 20` -- `git status --short` - -- [ ] **Step 1: Run repo lint** - -Expected: exit 0. - -- [ ] **Step 2: Confirm GitHub setup** - -Expected: labels, project, and seeded issues are visible. - -- [ ] **Step 3: Confirm file changes** - -Expected: only intended repo files changed or deleted. diff --git a/docs/superpowers/plans/2026-05-20-work-apm-mcp-data.md b/docs/superpowers/plans/2026-05-20-work-apm-mcp-data.md deleted file mode 100644 index 99f0680..0000000 --- a/docs/superpowers/plans/2026-05-20-work-apm-mcp-data.md +++ /dev/null @@ -1,100 +0,0 @@ -# Work APM MCP Data 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:** Render shared and work APM MCP servers from chezmoi data while keeping work tokens and Atlassian resource URLs as shell environment placeholders. - -**Architecture:** `home/.chezmoidata/apm.yaml` owns the MCP server list. `home/dot_apm/apm.yml.tmpl` remains a template and builds a `shared`/`personal`/`work` group list like the package installer before rendering MCP servers. - -**Tech Stack:** chezmoi templates, YAML source data, APM manifest syntax, Bats template tests, mise checks. - ---- - -## File Structure - -- Create `home/.chezmoidata/apm.yaml`: shared and work MCP server declarations. -- Create `home/.chezmoitemplates/apm/mcp-server.yml.tmpl`: reusable MCP server YAML snippet. -- Modify `home/dot_apm/apm.yml.tmpl`: render MCP servers from the active `.apm.mcp` groups. -- Create `tests/template/apm-config.bats`: template rendering tests for personal and work APM manifests. - -### Task 1: APM MCP Render Tests - -**Files:** - -- Create: `tests/template/apm-config.bats` -- Test: `tests/template/apm-config.bats` - -- [ ] **Step 1: Add failing tests** - -Create `tests/template/apm-config.bats` with three tests: - -- personal rendering includes `name: grep` and excludes `figma`, `atlassian-jira`, `atlassian-knowledge`, `${FIGMA_TOKEN}`, `${ATLASSIAN_JIRA_RESOURCE_URL}`, and `${ATLASSIAN_KNOWLEDGE_RESOURCE_URL}`. -- work rendering includes `name: grep`, `name: figma`, `Authorization: "Bearer ${FIGMA_TOKEN}"`, `name: atlassian-jira`, `name: atlassian-knowledge`, `"${ATLASSIAN_JIRA_RESOURCE_URL}"`, and `"${ATLASSIAN_KNOWLEDGE_RESOURCE_URL}"`. -- work rendering succeeds without legacy `.atlassian_resource_url` data and excludes `atlassian_resource_url` from output. - -- [ ] **Step 2: Verify tests fail before implementation** - -Run: `mise exec -- bats tests/template/apm-config.bats` - -Expected: FAIL because the current work render still depends on `.atlassian_resource_url` and has a single legacy `atlassian` MCP entry. - -### Task 2: Data-Driven MCP Rendering - -**Files:** - -- Create: `home/.chezmoidata/apm.yaml` -- Create: `home/.chezmoitemplates/apm/mcp-server.yml.tmpl` -- Modify: `home/dot_apm/apm.yml.tmpl` -- Test: `tests/template/apm-config.bats` - -- [ ] **Step 1: Add APM MCP data** - -Create `home/.chezmoidata/apm.yaml` with shared `grep` and work `figma`, `atlassian-jira`, and `atlassian-knowledge` MCP servers. Use literal placeholders `${FIGMA_TOKEN}`, `${ATLASSIAN_JIRA_RESOURCE_URL}`, and `${ATLASSIAN_KNOWLEDGE_RESOURCE_URL}`. - -- [ ] **Step 2: Render MCP servers from data** - -Create `home/.chezmoitemplates/apm/mcp-server.yml.tmpl` with the reusable MCP server YAML snippet flush-left. Update `home/dot_apm/apm.yml.tmpl` so it keeps the APM manifest header and dependencies, builds a group list starting with `shared`, appends `personal` or `work` when active, indexes each group from `.apm.mcp`, and injects the snippet with `{{ includeTemplate "apm/mcp-server.yml.tmpl" . | trim | indent 4 }}`. Use `hasKey` before optional fields such as `url`, `headers`, `command`, and `args` to avoid missing-key render failures. - -- [ ] **Step 3: Verify APM template tests pass** - -Run: `mise exec -- bats tests/template/apm-config.bats` - -Expected: PASS, 3 tests. - -- [ ] **Step 4: Verify existing APM install script tests pass** - -Run: `mise exec -- bats tests/template/install-apm-script.bats` - -Expected: PASS, existing tests. - -### Task 3: Verification and PR - -**Files:** - -- Verify: `home/.chezmoidata/apm.yaml` -- Verify: `home/.chezmoitemplates/apm/mcp-server.yml.tmpl` -- Verify: `home/dot_apm/apm.yml.tmpl` -- Verify: `tests/template/apm-config.bats` -- Verify: `docs/superpowers/specs/2026-05-20-work-apm-mcp-data-design.md` - -- [ ] **Step 1: Run formatting and checks** - -Run: `mise format && mise check` - -Expected: formatting completes, Prettier passes, shell lint passes, taplo passes, unit Bats tests pass, and template Bats tests pass. - -- [ ] **Step 2: Render work APM config and inspect key lines** - -Run: `mise exec -- chezmoi execute-template --source home --override-data '{"chezmoi":{"os":"darwin"},"personal":false,"work":true}' < home/dot_apm/apm.yml.tmpl` - -Expected output includes `name: figma`, `Authorization: "Bearer ${FIGMA_TOKEN}"`, `name: atlassian-jira`, `name: atlassian-knowledge`, `"${ATLASSIAN_JIRA_RESOURCE_URL}"`, and `"${ATLASSIAN_KNOWLEDGE_RESOURCE_URL}"`. - -Expected output does not include `atlassian_resource_url` or `${input:figma-token}`. - -- [ ] **Step 3: Commit implementation** - -Run: `git add docs/superpowers/plans/2026-05-20-work-apm-mcp-data.md docs/superpowers/specs/2026-05-20-work-apm-mcp-data-design.md home/.chezmoidata/apm.yaml home/.chezmoitemplates/apm/mcp-server.yml.tmpl home/dot_apm/apm.yml.tmpl tests/template/apm-config.bats && git diff --cached --check && git commit -m "feat: render work APM MCP servers from data"` - -- [ ] **Step 4: Push and create PR** - -Push `apm/work-mcp-data`, create a PR titled `Render work APM MCP servers from data`, include `Closes #41`, self-assign it, and verify PR checks. diff --git a/docs/superpowers/plans/2026-05-20-work-laptop-migration.md b/docs/superpowers/plans/2026-05-20-work-laptop-migration.md deleted file mode 100644 index 3591c6f..0000000 --- a/docs/superpowers/plans/2026-05-20-work-laptop-migration.md +++ /dev/null @@ -1,539 +0,0 @@ -# Work Laptop Migration 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:** Update the dotfiles so a work Mac can install the approved work apps and get a work-specific Dock layout while preserving the personal setup. - -**Architecture:** Package declarations stay in `home/.chezmoidata/packages.yaml` and are rendered by the existing Homebrew Bundle script. Dock layout logic lives in `home/.chezmoitemplates/lib/darwin/defaults.sh`, with `run_onchange_05_defaults.sh.tmpl` passing a rendered `DOTFILES_CONTEXT` value so the Bash library stays sourceable in unit tests. - -**Tech Stack:** chezmoi templates, Homebrew Bundle, Mac App Store `mas`, Bash, Bats, `dockutil`, mise tasks. - ---- - -## File Structure - -- Modify `home/.chezmoidata/packages.yaml`: move `mas` to shared formulas, add work casks, and add Be Focused as a work MAS app. -- Modify `tests/template/darwin-install-scripts.bats`: add package-rendering tests for personal and work contexts. -- Modify `home/.chezmoitemplates/lib/darwin/defaults.sh`: add Dock helper functions and personal/work layouts. -- Modify `home/.chezmoiscripts/darwin/run_onchange_05_defaults.sh.tmpl`: render `DOTFILES_CONTEXT` before injecting the defaults library. -- Modify `tests/unit/lib/darwin/defaults.bats`: test Dock helper ordering for personal and work layouts. -- Do not modify `home/dot_config/mise/config.toml.tmpl`. - -### Task 1: Work Package Declarations - -**Files:** - -- Modify: `tests/template/darwin-install-scripts.bats` -- Modify: `home/.chezmoidata/packages.yaml` -- Verify unchanged: `home/dot_config/mise/config.toml.tmpl` - -- [ ] **Step 1: Add failing package-rendering tests** - -Update `tests/template/darwin-install-scripts.bats` to this shape: - -```bash -#!/usr/bin/env bats -# @file tests/template/darwin-install-scripts.bats -# @brief Template rendering tests for Darwin chezmoi install scripts. - -load '../test_helpers/load.bash' - -SOURCE_DIR="$DOTFILES_ROOT/home" -DARWIN_DATA='{"chezmoi":{"os":"darwin"},"personal":true,"work":false}' -WORK_DATA='{"chezmoi":{"os":"darwin"},"personal":false,"work":true}' -PACKAGE_TEMPLATE="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_02_install-packages.sh.tmpl" - -render_template() { - mise exec -- chezmoi execute-template --source "$SOURCE_DIR" --override-data "$DARWIN_DATA" <"$1" -} - -render_template_with_data() { - local template="$1" - local data="$2" - - mise exec -- chezmoi execute-template --source "$SOURCE_DIR" --override-data "$data" <"$template" -} - -@test "darwin install script templates render with bash shebang" { - for template in "$DOTFILES_ROOT"/home/.chezmoiscripts/darwin/*.tmpl; do - run render_template "$template" - assert_success - [ "${lines[0]}" = "#!/usr/bin/env bash" ] - done -} - -@test "darwin install script templates inject shell libraries" { - assert_file_contains "$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_02_install-packages.sh.tmpl" '{{ template "lib/install/homebrew-bundle.sh" . }}' - assert_file_contains "$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_03_install-mise-tools.sh.tmpl" '{{ template "lib/install/mise.sh" . }}' - assert_file_contains "$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_04_install-vscode-extensions.sh.tmpl" '{{ template "lib/install/vscode.sh" . }}' - assert_file_contains "$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_05_defaults.sh.tmpl" '{{ template "lib/darwin/defaults.sh" . }}' -} - -@test "rendered darwin install scripts are syntactically valid bash" { - for template in "$DOTFILES_ROOT"/home/.chezmoiscripts/darwin/*.tmpl; do - rendered="$(render_template "$template")" - printf '%s\n' "$rendered" | bash -n - done -} - -@test "personal package template keeps personal tools and omits work apps" { - run render_template_with_data "$PACKAGE_TEMPLATE" "$DARWIN_DATA" - - assert_success - assert_output --partial 'brew "mas"' - assert_output --partial 'brew "mise"' - assert_output --partial 'cask "discord"' - refute_output --partial 'cask "microsoft-office"' - refute_output --partial 'cask "microsoft-teams"' - refute_output --partial 'cask "slack"' - refute_output --partial 'Be Focused - Pomodoro Timer' -} - -@test "work package template renders approved work apps" { - run render_template_with_data "$PACKAGE_TEMPLATE" "$WORK_DATA" - - assert_success - assert_output --partial 'brew "mas"' - assert_output --partial 'cask "microsoft-office"' - assert_output --partial 'cask "microsoft-teams"' - assert_output --partial 'cask "slack"' - assert_output --partial 'mas "Be Focused - Pomodoro Timer", id: 973134470' - refute_output --partial 'cask "microsoft-outlook"' - refute_output --partial 'brew "java"' - refute_output --partial 'brew "gradle"' - refute_output --partial 'brew "maven"' - refute_output --partial 'brew "kafka"' -} -``` - -- [ ] **Step 2: Run the package tests to verify they fail** - -Run: `mise exec -- bats tests/template/darwin-install-scripts.bats` - -Expected: FAIL in `work package template renders approved work apps` because `microsoft-office`, `microsoft-teams`, `slack`, and Be Focused are not rendered yet. The personal test may also fail until `mas` is moved to shared. - -- [ ] **Step 3: Update package declarations** - -Edit `home/.chezmoidata/packages.yaml` so the Homebrew package data is: - -```yaml -# packages.yaml -# 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}. -# `shared` always applies; `personal` or `work` is added based on host context. - -packages: - darwin: - homebrew: - shared: - taps: - - anomalyco/tap # For Opencode - formulas: - - dockutil - - fastfetch - - gh - - git-delta - - mise - - mas - - starship - - tmux - - ripgrep - - zoxide - - fd - - fzf - - lazygit - - neovim - - tree-sitter-cli - - imagemagick - - ghostscript - - tectonic - - mermaid-cli - - anomalyco/tap/opencode - casks: - # fonts - - font-monaspace - - font-jetbrains-mono-nerd-font - # apps - - google-chrome - - ghostty - - raycast - - rectangle - - superwhisper - - visual-studio-code - mas: [] - - personal: - taps: [] - formulas: [] - casks: - - aldente - - claude-code - - cleanshot - - discord - - displaylink - - microsoft-excel - - synology-drive - mas: - - name: Klack - id: 6446206067 - - work: - taps: [] - formulas: [] - casks: - - microsoft-office - - microsoft-teams - - slack - mas: - - name: Be Focused - Pomodoro Timer - id: 973134470 -``` - -- [ ] **Step 4: Verify mise config was not changed** - -Run: `git diff -- home/dot_config/mise/config.toml.tmpl` - -Expected: no output. - -- [ ] **Step 5: Run the package tests to verify they pass** - -Run: `mise exec -- bats tests/template/darwin-install-scripts.bats` - -Expected: PASS, 5 tests. - -- [ ] **Step 6: Commit package changes** - -```bash -git add tests/template/darwin-install-scripts.bats home/.chezmoidata/packages.yaml -git diff --cached --check -git commit -m "feat: add work laptop packages" -``` - -### Task 2: Personal and Work Dock Layouts - -**Files:** - -- Modify: `tests/unit/lib/darwin/defaults.bats` -- Modify: `home/.chezmoitemplates/lib/darwin/defaults.sh` -- Modify: `home/.chezmoiscripts/darwin/run_onchange_05_defaults.sh.tmpl` - -- [ ] **Step 1: Add failing Dock helper tests** - -Replace `tests/unit/lib/darwin/defaults.bats` with: - -```bash -#!/usr/bin/env bats -# @file tests/unit/lib/darwin/defaults.bats -# @brief Behavior tests for home/.chezmoitemplates/lib/darwin/defaults.sh. - -load '../../../test_helpers/load.bash' - -LOG_LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/common/log.sh" -LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/darwin/defaults.sh" - -setup() { - export HOME="$BATS_TEST_TMPDIR/home" - export COMMAND_LOG="$BATS_TEST_TMPDIR/commands" - export DOCK_APPS_LOG="$BATS_TEST_TMPDIR/dock-apps" - mkdir -p "$HOME" "$BATS_TEST_TMPDIR/bin" - - for command_name in defaults osascript killall dockutil open; do - cat >"$BATS_TEST_TMPDIR/bin/$command_name" <<'COMMAND' -#!/usr/bin/env bash -printf '%s %s\n' "$(basename "$0")" "$*" >>"$COMMAND_LOG" -if [[ "$(basename "$0")" == defaults && "${1:-}" == read ]]; then - exit 1 -fi -COMMAND - chmod +x "$BATS_TEST_TMPDIR/bin/$command_name" - done - - export PATH="$BATS_TEST_TMPDIR/bin:$PATH" -} - -capture_dock_layout() { - local context="$1" - bash -c " - source '$LOG_LIB' - source '$LIB' - dock_add_app() { printf '%s\n' \"\$1\" >>'$DOCK_APPS_LOG'; } - DOTFILES_CONTEXT='$context' dock_apply_layout - " -} - -@test "macos_defaults_main: applies defaults and restarts affected services" { - run bash -c "source '$LOG_LIB' && source '$LIB' && macos_defaults_main" - - assert_success - assert_output --partial "[defaults] Applying macOS defaults..." - assert_output --partial "[defaults] macOS defaults applied." - assert_file_contains "$COMMAND_LOG" "defaults write NSGlobalDomain AppleShowAllExtensions -bool true" - assert_file_contains "$COMMAND_LOG" "killall Finder" - assert_file_contains "$COMMAND_LOG" "dockutil --no-restart --remove all" - assert_file_contains "$COMMAND_LOG" "open -a Google Chrome --args --make-default-browser" -} - -@test "dock_apply_layout: personal layout uses personal communication apps" { - run capture_dock_layout personal - - assert_success - assert_file_contains "$DOCK_APPS_LOG" "/Applications/Discord.app" - assert_file_contains "$DOCK_APPS_LOG" "/System/Applications/Mail.app" - assert_file_contains "$DOCK_APPS_LOG" "$HOME/Applications/Chrome Apps.localized/YouTube Music.app" - assert_file_not_contains "$DOCK_APPS_LOG" "/System/Applications/Music.app" - assert_file_not_contains "$DOCK_APPS_LOG" "/Applications/Microsoft Outlook.app" - assert_file_not_contains "$DOCK_APPS_LOG" "/Applications/Microsoft Teams.app" - assert_file_not_contains "$DOCK_APPS_LOG" "/Applications/Slack.app" -} - -@test "dock_apply_layout: work layout uses work communication apps" { - run capture_dock_layout work - - assert_success - assert_file_contains "$DOCK_APPS_LOG" "/Applications/Microsoft Outlook.app" - assert_file_contains "$DOCK_APPS_LOG" "/Applications/Microsoft Teams.app" - assert_file_contains "$DOCK_APPS_LOG" "/Applications/Slack.app" - assert_file_contains "$DOCK_APPS_LOG" "$HOME/Applications/Chrome Apps.localized/YouTube Music.app" - assert_file_not_contains "$DOCK_APPS_LOG" "/Applications/Discord.app" - assert_file_not_contains "$DOCK_APPS_LOG" "/System/Applications/Music.app" - assert_file_not_contains "$DOCK_APPS_LOG" "/System/Applications/Mail.app" -} -``` - -- [ ] **Step 2: Run the Dock tests to verify they fail** - -Run: `mise exec -- bats tests/unit/lib/darwin/defaults.bats` - -Expected: FAIL because `dock_apply_layout` is not defined yet. - -- [ ] **Step 3: Implement Dock helpers** - -Update `home/.chezmoitemplates/lib/darwin/defaults.sh` by replacing the current hard-coded Dock block with helper functions and calling `dock_configure` from `macos_defaults_main`. - -The top-level file should keep the existing Finder, screenshot, browser, and `main` logic. Add these functions before `macos_defaults_main`: - -```bash -# -# @description Add an app to the Dock when it exists. -# -function dock_add_app() { - local app="$1" - - [ -d "${app}" ] && dockutil --no-restart --add "${app}" >/dev/null -} - -# -# @description Add the personal Dock app order. -# -function dock_apply_personal_layout() { - dock_add_app "/System/Applications/Apps.app" - dock_add_app "/System/Applications/System Settings.app" - dock_add_app "/System/Applications/Utilities/Activity Monitor.app" - dock_add_app "/Applications/Google Chrome.app" - dock_add_app "${HOME}/Applications/Chrome Apps.localized/YouTube Music.app" - dock_add_app "/Applications/Visual Studio Code.app" - dock_add_app "/Applications/Ghostty.app" - dock_add_app "/Applications/Discord.app" - dock_add_app "/System/Applications/Mail.app" - dock_add_app "/System/Applications/Notes.app" - dock_add_app "/System/Applications/Reminders.app" - dock_add_app "/Applications/superwhisper.app" -} - -# -# @description Add the work Dock app order. -# -function dock_apply_work_layout() { - dock_add_app "/System/Applications/Apps.app" - dock_add_app "/System/Applications/System Settings.app" - dock_add_app "/System/Applications/Utilities/Activity Monitor.app" - dock_add_app "/Applications/Google Chrome.app" - dock_add_app "${HOME}/Applications/Chrome Apps.localized/YouTube Music.app" - dock_add_app "/Applications/Visual Studio Code.app" - dock_add_app "/Applications/Ghostty.app" - dock_add_app "/Applications/Microsoft Outlook.app" - dock_add_app "/Applications/Microsoft Teams.app" - dock_add_app "/Applications/Slack.app" - dock_add_app "/System/Applications/Notes.app" - dock_add_app "/System/Applications/Reminders.app" - dock_add_app "/Applications/superwhisper.app" -} - -# -# @description Add the Dock layout for the active dotfiles context. -# -function dock_apply_layout() { - case "${DOTFILES_CONTEXT:-personal}" in - work) - dock_apply_work_layout - ;; - *) - dock_apply_personal_layout - ;; - esac -} - -# -# @description Configure Dock defaults and app order. -# -function dock_configure() { - defaults write com.apple.dock orientation -string bottom - defaults write com.apple.dock launchanim -bool false - defaults write com.apple.dock tilesize -int 42 - defaults write com.apple.dock autohide -bool true - defaults write com.apple.dock autohide-time-modifier -float 0.5 - defaults write com.apple.dock autohide-delay -float 0 - defaults write com.apple.dock show-recents -bool false - - if command -v dockutil >/dev/null 2>&1; then - dockutil --no-restart --remove all >/dev/null 2>&1 || true - dock_apply_layout - else - log_warn "[defaults] dockutil not found. Run 'brew install dockutil' or rerun 'chezmoi apply'." - fi - - killall Dock -} -``` - -Inside `macos_defaults_main`, replace the old Dock defaults and `for app in ...` block with: - -```bash - dock_configure -``` - -- [ ] **Step 4: Render context for the defaults script** - -Update `home/.chezmoiscripts/darwin/run_onchange_05_defaults.sh.tmpl` to: - -```bash -#!/usr/bin/env bash - -{{ if eq .chezmoi.os "darwin" }} -{{ if .work -}} -export DOTFILES_CONTEXT="work" -{{ else -}} -export DOTFILES_CONTEXT="personal" -{{ end -}} -{{ template "lib/common/log.sh" . }} -{{ template "lib/darwin/defaults.sh" . }} -{{ end }} -``` - -- [ ] **Step 5: Run the Dock tests to verify they pass** - -Run: `mise exec -- bats tests/unit/lib/darwin/defaults.bats` - -Expected: PASS, 3 tests. - -- [ ] **Step 6: Run template syntax tests** - -Run: `mise exec -- bats tests/template/darwin-install-scripts.bats` - -Expected: PASS, 5 tests. - -- [ ] **Step 7: Commit Dock changes** - -```bash -git add tests/unit/lib/darwin/defaults.bats home/.chezmoitemplates/lib/darwin/defaults.sh home/.chezmoiscripts/darwin/run_onchange_05_defaults.sh.tmpl -git diff --cached --check -git commit -m "feat: add context-aware Dock layout" -``` - -### Task 3: Full Verification - -**Files:** - -- Verify: all changed files from Tasks 1 and 2 -- Verify unchanged: `home/dot_config/mise/config.toml.tmpl` - -- [ ] **Step 1: Format changed files** - -Run: `mise format` - -Expected: formatting completes without errors. - -- [ ] **Step 2: Verify no mise config change** - -Run: `git diff -- home/dot_config/mise/config.toml.tmpl` - -Expected: no output. - -- [ ] **Step 3: Run full checks** - -Run: `mise check` - -Expected: Prettier passes, shellcheck passes, shfmt passes, taplo passes, unit bats tests pass, and template bats tests pass. - -- [ ] **Step 4: Render personal package script and inspect key lines** - -Run: - -```bash -mise exec -- chezmoi execute-template \ - --source home \ - --override-data '{"chezmoi":{"os":"darwin"},"personal":true,"work":false}' \ - < home/.chezmoiscripts/darwin/run_onchange_02_install-packages.sh.tmpl -``` - -Expected output includes: - -```text -brew "mas" -cask "discord" -``` - -Expected output does not include: - -```text -cask "microsoft-office" -cask "microsoft-teams" -cask "slack" -``` - -- [ ] **Step 5: Render work package script and inspect key lines** - -Run: - -```bash -mise exec -- chezmoi execute-template \ - --source home \ - --override-data '{"chezmoi":{"os":"darwin"},"personal":false,"work":true}' \ - < home/.chezmoiscripts/darwin/run_onchange_02_install-packages.sh.tmpl -``` - -Expected output includes: - -```text -brew "mas" -cask "microsoft-office" -cask "microsoft-teams" -cask "slack" -mas "Be Focused - Pomodoro Timer", id: 973134470 -``` - -Expected output does not include: - -```text -cask "microsoft-outlook" -brew "java" -brew "gradle" -brew "maven" -brew "kafka" -``` - -- [ ] **Step 6: Commit any formatting-only changes if present** - -Run: `git status --short` - -If `mise format` changed files not included in Task 1 or 2 commits, inspect them with `git diff`, then commit only intended formatting changes: - -```bash -git add -git diff --cached --check -git commit -m "style: format work migration files" -``` - -If there are no remaining changes, do not create a commit. diff --git a/docs/superpowers/plans/2026-05-21-linear-migration.md b/docs/superpowers/plans/2026-05-21-linear-migration.md deleted file mode 100644 index 64a9ca0..0000000 --- a/docs/superpowers/plans/2026-05-21-linear-migration.md +++ /dev/null @@ -1,582 +0,0 @@ -# Linear Migration 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:** Finish moving `edwinhern/dotfiles` work tracking from GitHub Issues and GitHub Projects to Linear team `dotfiles` (`DOT`). - -**Architecture:** Linear becomes the planning source of truth. Root repo guidance explains the workflow for any agent, Linear CLI handles issue reads and updates, and GitHub remains responsible for code review, CI, releases, and PR links. - -**Tech Stack:** Markdown, Linear CLI through mise, GitHub CLI `gh`, repo-local agent skills, mise linting. - ---- - -## File Structure - -- Modify `AGENTS.md`: replace GitHub Issues and GitHub Project planning guidance with Linear team `dotfiles` (`DOT`) guidance for all repo agents. -- Modify `.agents/skills/post-merge-cleanup/SKILL.md`: keep the existing post-merge cleanup skill but change issue verification from GitHub Issues and GitHub Project status to Linear `DOT-*` issue status. -- Modify `home/dot_config/mise/config.toml.tmpl`: install `npm:@schpet/linear-cli` for personal hosts. -- Modify `tests/template/mise-config.bats`: verify the personal mise config installs Linear CLI. -- Modify `home/.chezmoidata/apm.yaml`: keep shared APM dependencies shared and keep `schpet/linear-cli` personal-only. -- Modify `home/dot_apm/apm.yml.tmpl`: render APM dependencies from data like MCP servers. -- Modify `tests/template/apm-config.bats`: verify work APM config excludes the personal Linear CLI dependency. -- Modify Linear workspace data: set native priority and state on imported open Linear issues. -- Modify GitHub remote data: close migrated open GitHub issues with migration comments and close the GitHub Project named `dotfiles` after Linear is verified. - -## Shared Rules - -- Do not add Linear MCP. -- Install Linear CLI through mise (`npm:@schpet/linear-cli`) for personal hosts when a CLI workflow is required. Do not add a Homebrew tap or formula for Linear. -- Use Linear priority values: `1` = Urgent, `2` = High, `3` = Medium, `4` = Low, `0` = No priority. -- Keep the GitHub Project closed instead of deleting it so prior PR and issue links remain recoverable if needed. - -### Task 1: Clean Up Imported Linear Issues - -**Files:** - -- Read: `docs/superpowers/specs/2026-05-21-linear-migration-design.md` -- Modify: Linear team `dotfiles` (`DOT`) workspace data - -- [ ] **Step 1: Confirm Linear CLI is available** - -Run: - -```bash -linear --version || mise exec -- linear --version -jq --version -``` - -Expected: prints the Linear CLI version and jq version. - -- [ ] **Step 2: Read current states and imported open issues** - -Run: - -```bash -for id in DOT-5 DOT-6 DOT-7 DOT-9 DOT-10 DOT-11; do - mise exec -- linear issue query --team DOT --search "$id" --include-archived --json --limit 10 | - jq -r --arg id "$id" '([.nodes[] | select(.identifier == $id)][0]) | "\(.identifier): priority \(.priority) state \(.state.name) type \(.state.type)"' -done -``` - -Expected: one team named `dotfiles` with key `DOT`; states include `Backlog` and `In Progress`; issues include `DOT-5`, `DOT-6`, `DOT-7`, `DOT-9`, `DOT-10`, and `DOT-11`. - -- [ ] **Step 3: Set native priority and state for imported open issues** - -Run: - -```bash -mise exec -- linear issue update DOT-7 --priority 2 --state "In Progress" -mise exec -- linear issue update DOT-6 --priority 3 --state Backlog -mise exec -- linear issue update DOT-11 --priority 3 --state Backlog -mise exec -- linear issue update DOT-5 --priority 4 --state Backlog -mise exec -- linear issue update DOT-9 --priority 4 --state Backlog -mise exec -- linear issue update DOT-10 --priority 4 --state Backlog -``` - -Expected output includes: - -```text -DOT-7: priority 2, state In Progress -DOT-6: priority 3, state Backlog -DOT-11: priority 3, state Backlog -DOT-5: priority 4, state Backlog -DOT-9: priority 4, state Backlog -DOT-10: priority 4, state Backlog -``` - -- [ ] **Step 4: Verify archived completed imported issues are queryable** - -Run: - -```bash -for id in DOT-8 DOT-12 DOT-13 DOT-14 DOT-15 DOT-16 DOT-17 DOT-18 DOT-19 DOT-20; do - mise exec -- linear issue query --team DOT --search "$id" --include-archived --json --limit 10 | - jq -r --arg id "$id" '([.nodes[] | select(.identifier == $id)][0]) | "\(.identifier): state \(.state.name) archived \(.archivedAt != null)"' -done -``` - -Expected: ten entries for `DOT-8`, `DOT-12`, `DOT-13`, `DOT-14`, `DOT-15`, `DOT-16`, `DOT-17`, `DOT-18`, `DOT-19`, and `DOT-20`, each with `archived: true`. - -### Task 2: Update Repo Agent Guidance - -**Files:** - -- Modify: `AGENTS.md:16-42` -- Read: `home/dot_config/opencode/AGENTS.md.tmpl` -- Read: `home/dot_claude/CLAUDE.md.tmpl` - -- [ ] **Step 1: Replace the planning sections in `AGENTS.md`** - -Replace the current `## Planning Workflow`, `## Task Selection`, and `## Agent Work Tracking` sections with: - -```markdown -## Planning Workflow - -- Linear is the source of truth for features, bugs, enhancements, cleanup work, research, and follow-up tasks. -- Use the Linear team `dotfiles` with key `DOT` for this repository. -- Use Linear workflow states: `Backlog`, `Todo`, `In Progress`, `In Review`, and `Done`. -- Treat `Backlog` as the backlog lane and `Todo` as ready but not active. -- Do not use GitHub Issues or GitHub Projects for new work after the Linear cutover. -- Do not use `docs/` as the issue tracker, roadmap, or long-lived backlog. -- Superpowers specs and plans live under `docs/superpowers/` and should be linked from the related Linear issue description or a Linear comment. - -## Task Selection - -- Use Linear native priority to choose the next task: `High`, then `Medium`, then `Low`. -- Use `Urgent` only for security, broken reproducibility, or a blocked daily workflow that needs immediate attention. -- `High` means security, reproducibility, broken workflow, or work that unlocks important follow-up work. -- `Medium` means useful cleanup, maintenance, or decision work with clear value but no current breakage. -- `Low` means speculative, optional, or only worth doing when current pain appears. -- If priority is missing, set or ask for the priority before implementation starts. -- When priorities tie, prefer security and reproducibility work first, then blockers, then the smallest clear issue. -- Do not add story points unless the user explicitly asks for them. -- When starting work, move the selected Linear issue to `In Progress` and link the relevant Superpowers spec or plan before code changes. - -## Agent Work Tracking - -- When new work is discovered, create or update a Linear issue instead of adding a backlog item under `docs/`. -- Keep issues small enough to close with one PR or a short linked PR series. -- Link the relevant Superpowers spec or plan from the Linear issue before implementation starts. -- Track active work by moving the Linear issue through the team workflow instead of editing a progress document. -- Link PRs to Linear issues by including the issue ID in the branch, PR title, or PR body. -- Use Linear closing keywords in PR text, such as `Fixes DOT-123`, when merge should move the Linear issue to `Done`. -- If a thought is not ready for an issue, keep it in the current conversation or a Superpowers spec until it becomes actionable. -``` - -- [ ] **Step 2: Verify templates render the root guidance** - -Run: - -```bash -rg '{{ template "AGENTS.md" . -}}' home/dot_config/opencode/AGENTS.md.tmpl home/dot_claude/CLAUDE.md.tmpl -``` - -Expected: both template files match. No template edit is needed because both already render the shared `AGENTS.md` template. - -- [ ] **Step 3: Check old active GitHub planning language is gone from `AGENTS.md`** - -Run: - -```bash -rg 'GitHub Issues are the source|GitHub Project named|dotfiles project `Priority`|Closes #123|Fixes #123' AGENTS.md -``` - -Expected: no matches. `rg` exits `1` when there are no matches, which is acceptable for this check. - -- [ ] **Step 4: Run formatting and lint checks** - -Run: - -```bash -mise lint -``` - -Expected: Prettier reports all matched files use Prettier code style and taplo completes without errors. - -- [ ] **Step 5: Commit the guidance change if commits are approved for this execution** - -Run only when the user has explicitly approved commits: - -```bash -git add AGENTS.md -git commit -m "docs: point agents at Linear" -``` - -Expected: one commit containing only `AGENTS.md`. - -### Task 3: Add Linear CLI Tooling - -**Files:** - -- Modify: `home/.chezmoidata/apm.yaml` -- Modify: `home/dot_apm/apm.yml.tmpl` -- Modify: `home/dot_config/mise/config.toml.tmpl` -- Modify: `tests/template/apm-config.bats` -- Modify: `tests/template/mise-config.bats` - -- [ ] **Step 1: Install Linear CLI through mise for personal hosts** - -Add `"npm:@schpet/linear-cli" = "latest"` to the personal package block in `home/dot_config/mise/config.toml.tmpl`. - -- [ ] **Step 2: Render APM dependencies from data** - -In `home/.chezmoidata/apm.yaml`, keep `obra/superpowers`, `JuliusBrussee/caveman`, and `anthropics/claude-plugins-official/plugins/skill-creator` in shared APM dependencies. Keep `schpet/linear-cli` in personal APM dependencies. Keep work APM dependencies empty. - -Update `home/dot_apm/apm.yml.tmpl` so `dependencies.apm` renders from active shared/personal/work groups, like `dependencies.mcp`. - -- [ ] **Step 3: Keep Linear out of Homebrew package data** - -Do not add `schpet/tap`, `schpet/tap/linear`, `linear`, or `schpet/tap/linear` to `home/.chezmoidata/packages.yaml`. - -- [ ] **Step 4: Verify APM and mise config render Linear CLI in the right context** - -Run: - -```bash -mise exec -- bats tests/template/apm-config.bats -mise exec -- bats tests/template/mise-config.bats -``` - -Expected: the personal mise template test asserts `"npm:@schpet/linear-cli" = "latest"`; the work APM template test refutes `schpet/linear-cli` while keeping shared APM dependencies. - -- [ ] **Step 5: Run formatting and lint checks** - -Run: - -```bash -mise lint -``` - -Expected: Prettier reports all matched files use Prettier code style and taplo completes without errors. - -- [ ] **Step 6: Commit the Linear CLI package change if commits are approved for this execution** - -Run only when the user has explicitly approved commits: - -```bash -git add home/.chezmoidata/apm.yaml home/dot_apm/apm.yml.tmpl home/dot_config/mise/config.toml.tmpl tests/template/apm-config.bats tests/template/mise-config.bats -git commit -m "feat: add Linear CLI package" -``` - -Expected: one commit containing the mise package and template test. - -### Task 4: Update Post-Merge Cleanup Skill - -**Files:** - -- Modify: `.agents/skills/post-merge-cleanup/SKILL.md` - -- [ ] **Step 1: Replace the post-merge cleanup skill content** - -Replace `.agents/skills/post-merge-cleanup/SKILL.md` with: - -````markdown ---- -name: post-merge-cleanup -description: Use after a pull request has been merged in edwinhern/dotfiles to verify local main is fast-forwarded, the PR is merged, linked Linear DOT issues are Done, latest main CI passed, and git status is clean. Use when the user says a PR merged, asks to clean up after merge, or asks to verify post-merge state. ---- - -# Post-Merge Cleanup - -Use this skill only in `edwinhern/dotfiles` after a PR has already been merged. - -## Required Input - -Use a PR number from the user, such as `#28` or `28`. - -If the user does not give a PR number, infer it only when `gh pr view --json number` clearly resolves the current branch to one PR. If it cannot be inferred, ask one short question for the PR number. - -## Scope - -- Verify post-merge state with fresh commands. -- Switch local checkout to `main` and fast-forward it. -- Confirm the merged PR references one or more Linear `DOT-*` issues. -- Confirm each linked Linear issue is `Done` or another completed workflow state. -- Confirm the latest `main` CI for the merge commit passed. -- Confirm the final git status is clean. - -Do not choose the next issue. Do not merge PRs, close issues, delete branches, delete worktrees, or change Linear issue state during cleanup. - -## Workflow - -1. Verify repository identity: - Run `gh repo view --json nameWithOwner -q .nameWithOwner`. - Continue only if it returns `edwinhern/dotfiles`. - -2. Check for local changes: - Run `git status --short --branch`. - If there are modified, staged, or untracked files, stop and report them before switching branches. - -3. Sync `main`: - Run `git switch main`. - Run `git pull --ff-only`. - -4. Verify the PR: - Run `gh pr view --repo edwinhern/dotfiles --json number,title,state,mergedAt,mergeCommit,headRefName,body,url`. - Require `state` to be `MERGED` and `mergeCommit.oid` to be present. - -5. Extract Linear issue IDs: - Run `gh pr view --repo edwinhern/dotfiles --json title,headRefName,body -q '[.headRefName,.title,.body] | join("\n")' | rg -o 'DOT-[0-9]+' | sort -u`. - If no `DOT-*` issue ID is found, report that the PR did not link a Linear issue and stop. - -6. Verify each Linear issue: - Linear CLI is intentionally installed only on personal hosts. If neither `linear` nor `mise exec -- linear` is available, report the verified GitHub/local checks and ask the user to rerun this cleanup from a personal host. - - Run the command below with the extracted issue IDs as arguments, for example `bash -s DOT-7 <<'BASH'`. - - ```bash - bash -s DOT-7 <<'BASH' - set -euo pipefail - - if command -v linear >/dev/null 2>&1; then - linear_cmd=(linear) - elif mise exec -- linear --version >/dev/null 2>&1; then - linear_cmd=(mise exec -- linear) - else - printf '%s\n' "Linear CLI unavailable. It is intentionally installed only on personal hosts; rerun cleanup from a personal host." >&2 - exit 1 - fi - - jq --version >/dev/null - - for identifier in "$@"; do - "${linear_cmd[@]}" issue query \ - --team DOT \ - --search "$identifier" \ - --include-archived \ - --json \ - --limit 10 | - jq -e --arg id "$identifier" ' - ([.nodes[] | select(.identifier == $id)][0]) as $issue - | if $issue == null then - error($id + ": not found") - else - "\($issue.identifier): \($issue.state.name) (\($issue.state.type)) \($issue.url)", - if $issue.state.type == "completed" then - empty - else - error($issue.identifier + ": state type is " + $issue.state.type) - end - end - ' - done - BASH - ``` - - Require each linked Linear issue to print state type `completed`. - -7. Verify CI for the merge commit: - Run `gh run list --repo edwinhern/dotfiles --branch main --commit --workflow CI --json databaseId,displayTitle,workflowName,status,conclusion,headSha,url --limit 1`. - Require one `CI` workflow run for `` and require it to have `status` `completed` and `conclusion` `success`. - -8. Verify final local state: - Run `git status --short --branch`. - Require the output to show `main` tracking `origin/main` with no file changes. - -## Report - -Return a concise checklist with evidence: - -- `main` sync result and current branch line -- PR number, merged state, merged time, and merge SHA -- Each Linear issue ID, state name, state type, and URL -- CI workflow conclusion and URL -- Final git status - -If any check fails, report the failed check, include the command evidence, and stop without claiming cleanup is complete. -```` - -- [ ] **Step 2: Check removed GitHub Project verification language** - -Run: - -```bash -rg 'GitHub Project|gh project|closingIssuesReferences|project\s+item' .agents/skills/post-merge-cleanup/SKILL.md -``` - -Expected: no matches. `rg` exits `1` when there are no matches, which is acceptable for this check. - -- [ ] **Step 3: Check Linear verification language exists** - -Run: - -```bash -rg 'Linear|DOT-\*|state type `completed`|linear issue query' .agents/skills/post-merge-cleanup/SKILL.md -``` - -Expected: matches in the description, scope, workflow, and report sections. - -- [ ] **Step 4: Run formatting and lint checks** - -Run: - -```bash -mise lint -``` - -Expected: Prettier reports all matched files use Prettier code style and taplo completes without errors. - -- [ ] **Step 5: Commit the post-merge skill change if commits are approved for this execution** - -Run only when the user has explicitly approved commits: - -```bash -git add .agents/skills/post-merge-cleanup/SKILL.md -git commit -m "fix: verify Linear after merges" -``` - -Expected: one commit containing only the post-merge cleanup skill update. - -### Task 5: Clean Up GitHub Tracking Surfaces - -**Files:** - -- Modify: GitHub issues `#16`, `#17`, `#18`, `#20`, `#21`, and `#22` -- Modify: GitHub Project `dotfiles` number `6` - -- [ ] **Step 1: Verify Linear issue mapping before closing GitHub issues** - -Run: - -```bash -for id in DOT-5 DOT-6 DOT-7 DOT-9 DOT-10 DOT-11; do - mise exec -- linear issue query --team DOT --search "$id" --include-archived --json --limit 10 | - jq -r --arg id "$id" '([.nodes[] | select(.identifier == $id)][0]) | "\(.identifier): priority \(.priority), state \(.state.name), \(.url)"' -done -``` - -Expected: six lines with the target priority and state values from the design spec. - -- [ ] **Step 2: Close migrated open GitHub issues with Linear migration comments** - -Run: - -```bash -gh issue close 16 --repo edwinhern/dotfiles --reason "not planned" --comment "Migrated to Linear as DOT-5. Future tracking continues in Linear." -gh issue close 17 --repo edwinhern/dotfiles --reason "not planned" --comment "Migrated to Linear as DOT-6. Future tracking continues in Linear." -gh issue close 18 --repo edwinhern/dotfiles --reason "not planned" --comment "Migrated to Linear as DOT-7. Future tracking continues in Linear." -gh issue close 20 --repo edwinhern/dotfiles --reason "not planned" --comment "Migrated to Linear as DOT-9. Future tracking continues in Linear." -gh issue close 21 --repo edwinhern/dotfiles --reason "not planned" --comment "Migrated to Linear as DOT-10. Future tracking continues in Linear." -gh issue close 22 --repo edwinhern/dotfiles --reason "not planned" --comment "Migrated to Linear as DOT-11. Future tracking continues in Linear." -``` - -Expected: each command reports the issue was closed. - -- [ ] **Step 3: Verify no open GitHub issues remain** - -Run: - -```bash -gh issue list --repo edwinhern/dotfiles --state open --limit 100 --json number,title,url -``` - -Expected: - -```json -[] -``` - -- [ ] **Step 4: Close the GitHub Project board** - -Run: - -```bash -gh project close 6 --owner edwinhern --format json -q '.closed' -``` - -Expected: - -```text -true -``` - -- [ ] **Step 5: Verify the GitHub Project is closed** - -Run: - -```bash -gh project view 6 --owner edwinhern --format json -q '{title: .title, closed: .closed}' -``` - -Expected: - -```json -{ "closed": true, "title": "dotfiles" } -``` - -### Task 6: Final Verification - -**Files:** - -- Verify: `AGENTS.md` -- Verify: `.agents/skills/post-merge-cleanup/SKILL.md` -- Verify: Linear team `dotfiles` (`DOT`) -- Verify: GitHub repo `edwinhern/dotfiles` - -- [ ] **Step 1: Verify repository text no longer points agents to GitHub planning** - -Run: - -```bash -rg 'GitHub Issues are the source|GitHub Project named|dotfiles project `Priority`|Track active work by moving the issue in the `dotfiles` project|Closes #123|Fixes #123' AGENTS.md .agents/skills/post-merge-cleanup/SKILL.md -``` - -Expected: no matches. `rg` exits `1` when there are no matches, which is acceptable for this check. - -- [ ] **Step 2: Verify Linear guidance exists in repo files** - -Run: - -```bash -rg 'Linear|DOT|Fixes DOT-123' AGENTS.md .agents/skills/post-merge-cleanup/SKILL.md -``` - -Expected: matches in both files. - -- [ ] **Step 3: Verify the post-merge cleanup skill exists in the auto-scanned project path** - -Run: - -```bash -test -f .agents/skills/post-merge-cleanup/SKILL.md && printf 'post-merge cleanup skill present\n' -``` - -Expected: prints `post-merge cleanup skill present`. - -- [ ] **Step 4: Verify Linear active and archived issue states** - -Run: - -```bash -for id in DOT-5 DOT-6 DOT-7 DOT-9 DOT-10 DOT-11; do - mise exec -- linear issue query --team DOT --search "$id" --include-archived --json --limit 10 | - jq -r --arg id "$id" '([.nodes[] | select(.identifier == $id)][0]) | "\(.identifier): priority \(.priority) state \(.state.name) type \(.state.type)"' -done - -for id in DOT-8 DOT-12 DOT-13 DOT-14 DOT-15 DOT-16 DOT-17 DOT-18 DOT-19 DOT-20; do - mise exec -- linear issue query --team DOT --search "$id" --include-archived --json --limit 10 | - jq -r --arg id "$id" '([.nodes[] | select(.identifier == $id)][0]) | "\(.identifier): state \(.state.name) type \(.state.type) archived \(.archivedAt != null)"' -done -``` - -Expected: active issues show `DOT-7` priority `2` in `In Progress`, `DOT-6` and `DOT-11` priority `3` in `Backlog`, and `DOT-5`, `DOT-9`, `DOT-10` priority `4` in `Backlog`; archived issues show ten imported completed issues with `archived: true`. - -- [ ] **Step 5: Verify GitHub tracking cleanup** - -Run: - -```bash -gh issue list --repo edwinhern/dotfiles --state open --limit 100 --json number,title,url -gh project view 6 --owner edwinhern --format json -q '{title: .title, closed: .closed}' -``` - -Expected: the issue list is `[]`, and the project output has `title` `dotfiles` and `closed` `true`. - -- [ ] **Step 6: Run repository verification** - -Run: - -```bash -git diff --check -mise lint -git status --short -``` - -Expected: `git diff --check` has no output, `mise lint` passes, and `git status --short` shows only intended files. - -- [ ] **Step 7: Commit the final verification docs if commits are approved for this execution** - -Run only when the user has explicitly approved commits and the spec or plan files are still uncommitted: - -```bash -git add docs/superpowers/specs/2026-05-21-linear-migration-design.md docs/superpowers/plans/2026-05-21-linear-migration.md -git commit -m "docs: plan Linear migration" -``` - -Expected: one docs commit, unless those files were already committed in an earlier approved step. - -## Execution Notes - -- If any Linear CLI command fails, stop and inspect that error before making another write. -- If the GitHub issue close step fails because an issue is already closed, verify the issue comment and state before continuing. -- If `gh project close` fails, keep the GitHub Project unchanged and report the exact error. diff --git a/docs/superpowers/plans/2026-06-24-graphify-global-agent-install.md b/docs/superpowers/plans/2026-06-24-graphify-global-agent-install.md deleted file mode 100644 index 8c05754..0000000 --- a/docs/superpowers/plans/2026-06-24-graphify-global-agent-install.md +++ /dev/null @@ -1,393 +0,0 @@ -# Graphify Global Agent Install 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 Graphify global agent skills on personal machines from the existing APM target data. - -**Architecture:** Add reusable chezmoi helper templates that expose active grouped data as JSON. Use those helpers in a new Darwin run-on-change script that maps personal APM targets to Graphify platforms and calls Graphify's user-scope installer. Keep per-repo graph creation, hooks, and MCP setup outside this global script. - -**Tech Stack:** chezmoi templates, Bash, bats, Graphify CLI, APM target data. - ---- - -## File Map - -- Create `home/.chezmoitemplates/lib/chezmoi/active-groups.json.tmpl`: renders active groups as JSON. -- Create `home/.chezmoitemplates/lib/chezmoi/active-group-values.json.tmpl`: renders values from active `shared`, `personal`, or `work` groups as JSON. -- Create `home/.chezmoitemplates/lib/install/graphify-skills.sh`: installs Graphify skills for `GRAPHIFY_PLATFORMS`. -- Create `home/.chezmoiscripts/darwin/run_onchange_08_install-graphify-skills.sh.tmpl`: Darwin chezmoi script that maps APM targets to Graphify platforms. -- Modify `home/.chezmoitemplates/AGENTS.md`: add global Graphify usage guidance. -- Modify `tests/template/darwin-install-scripts.bats`: cover Graphify template rendering. -- Modify `tests/template/agent-instructions.bats`: cover shared Graphify instructions. -- Create `tests/unit/lib/install/graphify-skills.bats`: unit-test the Graphify installer library. - -No commit steps are included because this session should not commit unless the user asks. - -### Task 1: Chezmoi Group Helpers - -**Files:** - -- Create: `home/.chezmoitemplates/lib/chezmoi/active-groups.json.tmpl` -- Create: `home/.chezmoitemplates/lib/chezmoi/active-group-values.json.tmpl` - -- [ ] **Step 1: Add the active group helper** - -Create `home/.chezmoitemplates/lib/chezmoi/active-groups.json.tmpl`: - -```gotemplate -{{- $groups := list "shared" -}} -{{- if .personal }}{{ $groups = append $groups "personal" }}{{ end -}} -{{- if .work }}{{ $groups = append $groups "work" }}{{ end -}} -{{- $groups | toJson -}} -``` - -- [ ] **Step 2: Add the active group values helper** - -Create `home/.chezmoitemplates/lib/chezmoi/active-group-values.json.tmpl`: - -```gotemplate -{{- $ctx := .ctx -}} -{{- $valuesByGroup := .valuesByGroup -}} -{{- $groups := includeTemplate "lib/chezmoi/active-groups.json.tmpl" $ctx | fromJson -}} -{{- $values := list -}} -{{- range $groups -}} -{{- $groupValues := index $valuesByGroup . -}} -{{- range $groupValues -}} -{{- $values = append $values . -}} -{{- end -}} -{{- end -}} -{{- $values | toJson -}} -``` - -- [ ] **Step 3: Render-check helper use through the Graphify template task** - -Do not add separate helper tests yet. The Graphify template tests in Task 3 will validate the helper behavior through the real caller. - -### Task 2: Graphify Installer Library - -**Files:** - -- Create: `tests/unit/lib/install/graphify-skills.bats` -- Create: `home/.chezmoitemplates/lib/install/graphify-skills.sh` - -- [ ] **Step 1: Write the failing unit tests** - -Create `tests/unit/lib/install/graphify-skills.bats`: - -```bash -#!/usr/bin/env bats -# @file tests/unit/lib/install/graphify-skills.bats -# @brief Behavior tests for home/.chezmoitemplates/lib/install/graphify-skills.sh. - -load '../../../test_helpers/load.bash' - -LOG_LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/common/log.sh" -LIB="$DOTFILES_ROOT/home/.chezmoitemplates/lib/install/graphify-skills.sh" - -setup() { - export GRAPHIFY_ARGS_FILE="$BATS_TEST_TMPDIR/graphify-args" - mkdir -p "$BATS_TEST_TMPDIR/bin" - - cat >"$BATS_TEST_TMPDIR/bin/graphify" <<'GRAPHIFY' -#!/usr/bin/env bash -printf '%s\n' "$*" >>"$GRAPHIFY_ARGS_FILE" -exit "${GRAPHIFY_EXIT_CODE:-0}" -GRAPHIFY - chmod +x "$BATS_TEST_TMPDIR/bin/graphify" - export PATH="$BATS_TEST_TMPDIR/bin:$PATH" -} - -@test "main: installs each Graphify platform" { - run bash -c "source '$LOG_LIB' && source '$LIB' && GRAPHIFY_PLATFORMS=('agents' 'claude') && main" - - assert_success - assert_output --partial "[graphify] Installing Graphify agent skills..." - assert_output --partial "[graphify] Graphify agent skills installed." - [ "$(<"$GRAPHIFY_ARGS_FILE")" = $'install --platform agents\ninstall --platform claude' ] -} - -@test "main: skips empty platform entries" { - run bash -c "source '$LOG_LIB' && source '$LIB' && GRAPHIFY_PLATFORMS=('agents' '' 'claude') && main" - - assert_success - [ "$(<"$GRAPHIFY_ARGS_FILE")" = $'install --platform agents\ninstall --platform claude' ] -} - -@test "main: exits cleanly with no Graphify platforms" { - rm -f "$BATS_TEST_TMPDIR/bin/graphify" - - run bash -c "source '$LOG_LIB' && source '$LIB' && GRAPHIFY_PLATFORMS=() && main" - - assert_success - assert_output --partial "[graphify] No Graphify platforms to install." - [ ! -s "$GRAPHIFY_ARGS_FILE" ] -} - -@test "main: fails when graphify is missing" { - rm -f "$BATS_TEST_TMPDIR/bin/graphify" - - run bash -c "source '$LOG_LIB' && source '$LIB' && GRAPHIFY_PLATFORMS=('agents') && main" - - assert_failure 1 - assert_output --partial "error: [graphify] graphify not found. Ensure run_onchange_03_install-uv-tools ran successfully." -} -``` - -- [ ] **Step 2: Run the failing unit tests** - -Run: `mise exec -- bats tests/unit/lib/install/graphify-skills.bats` - -Expected: FAIL because `home/.chezmoitemplates/lib/install/graphify-skills.sh` does not exist. - -- [ ] **Step 3: Add the installer library** - -Create `home/.chezmoitemplates/lib/install/graphify-skills.sh`: - -```bash -#!/usr/bin/env bash -# @file lib/install/graphify-skills.sh -# @brief Install Graphify agent skills. -# @description -# Runs Graphify's user-scope skill installer for each platform in -# GRAPHIFY_PLATFORMS. This file is sourceable from bats tests and injected -# into chezmoi run scripts via chezmoi template rendering. - -set -Eeuo pipefail - -if [ "${DOTFILES_DEBUG:-}" ]; then - set -x -fi - -# -# @description Check if graphify is installed. -# -function is_graphify_installed() { - command -v graphify >/dev/null 2>&1 -} - -# -# @description Install Graphify skills for configured platforms. -# -function graphify_skills_install_main() { - log_info "[graphify] Installing Graphify agent skills..." - - local platform - for platform in "${GRAPHIFY_PLATFORMS[@]}"; do - [ -n "${platform}" ] || continue - graphify install --platform "${platform}" - done - - log_info "[graphify] Graphify agent skills installed." -} - -# -# @description Run the Graphify skill install flow. -# -function main() { - if [ "${#GRAPHIFY_PLATFORMS[@]}" -eq 0 ]; then - log_info "[graphify] No Graphify platforms to install." - return 0 - fi - - if ! is_graphify_installed; then - log_error "[graphify] graphify not found. Ensure run_onchange_03_install-uv-tools ran successfully." - return 1 - fi - - graphify_skills_install_main -} - -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - main -fi -``` - -- [ ] **Step 4: Run the unit tests again** - -Run: `mise exec -- bats tests/unit/lib/install/graphify-skills.bats` - -Expected: PASS. - -### Task 3: Darwin Graphify Run Script - -**Files:** - -- Create: `home/.chezmoiscripts/darwin/run_onchange_08_install-graphify-skills.sh.tmpl` -- Modify: `tests/template/darwin-install-scripts.bats` - -- [ ] **Step 1: Add failing template assertions** - -Modify `tests/template/darwin-install-scripts.bats`: - -```bash -GRAPHIFY_TEMPLATE="$DOTFILES_ROOT/home/.chezmoiscripts/darwin/run_onchange_08_install-graphify-skills.sh.tmpl" -EMPTY_APM_DATA='{"chezmoi":{"os":"darwin"},"personal":true,"work":false,"apm":{"targets":{"shared":[],"personal":[],"work":[]}}}' -``` - -Add tests: - -```bash -@test "darwin install script templates inject Graphify shell library" { - assert_file_contains "$GRAPHIFY_TEMPLATE" '{{ template "lib/install/graphify-skills.sh" . }}' -} - -@test "personal Graphify template renders agent and Claude platforms" { - run render_template_with_data "$GRAPHIFY_TEMPLATE" "$DARWIN_DATA" - - assert_success - assert_output --partial 'GRAPHIFY_PLATFORMS=(' - assert_output --partial '"agents"' - assert_output --partial '"claude"' - assert_output --partial 'graphify_skills_install_main' -} - -@test "work Graphify template renders no personal platforms" { - run render_template_with_data "$GRAPHIFY_TEMPLATE" "$WORK_DATA" - - assert_success - refute_output --partial '"agents"' - refute_output --partial '"claude"' - refute_output --partial 'graphify install --platform' - refute_output --partial 'graphify_skills_install_main' -} - -@test "empty APM target groups render no Graphify commands" { - run render_template_with_data "$GRAPHIFY_TEMPLATE" "$EMPTY_APM_DATA" - - assert_success - refute_output --partial 'GRAPHIFY_PLATFORMS=(' - refute_output --partial 'graphify install --platform' - refute_output --partial 'graphify_skills_install_main' -} -``` - -- [ ] **Step 2: Run the failing template tests** - -Run: `mise exec -- bats tests/template/darwin-install-scripts.bats` - -Expected: FAIL because the Graphify run script does not exist yet. - -- [ ] **Step 3: Add the run-on-change template** - -Create `home/.chezmoiscripts/darwin/run_onchange_08_install-graphify-skills.sh.tmpl`: - -```gotemplate -#!/usr/bin/env bash -# @file run_onchange_08_install-graphify-skills.sh -# @brief Install Graphify agent skills. -# @description APM data hash: {{ include ".chezmoidata/apm.yaml" | sha256sum }} -# @description active group helper hash: {{ include ".chezmoitemplates/lib/chezmoi/active-groups.json.tmpl" | sha256sum }} -# @description active group values helper hash: {{ include ".chezmoitemplates/lib/chezmoi/active-group-values.json.tmpl" | sha256sum }} -{{- $targets := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .apm.targets) | fromJson -}} -{{- $platforms := list -}} -{{- range $target := $targets -}} -{{- if eq $target "agent-skills" -}} -{{- $platforms = append $platforms "agents" -}} -{{- else if eq $target "claude" -}} -{{- $platforms = append $platforms "claude" -}} -{{- end -}} -{{- end -}} -{{- $hasPlatforms := gt (len $platforms) 0 -}} - -{{ if and (eq .chezmoi.os "darwin") .personal $hasPlatforms }} -{{ template "lib/common/log.sh" . }} -GRAPHIFY_PLATFORMS=( -{{ range $platforms -}} - "{{ . }}" -{{ end -}} -) -{{ template "lib/install/graphify-skills.sh" . }} -{{ end }} -``` - -- [ ] **Step 4: Run the template tests again** - -Run: `mise exec -- bats tests/template/darwin-install-scripts.bats` - -Expected: PASS. - -### Task 4: Shared Agent Instructions - -**Files:** - -- Modify: `home/.chezmoitemplates/AGENTS.md` -- Modify: `tests/template/agent-instructions.bats` - -- [ ] **Step 1: Add failing instruction assertions** - -Modify `tests/template/agent-instructions.bats` by adding this test: - -```bash -@test "agent instruction templates render Graphify guidance" { - run mise exec -- chezmoi execute-template --source "$SOURCE_DIR" <"$CLAUDE_TEMPLATE" - - assert_success - assert_output --partial '## Graphify' - assert_output --partial 'Use the installed Graphify skill when the user invokes `/graphify`.' - assert_output --partial 'graphify query' - assert_output --partial 'GRAPH_REPORT.md' - - run mise exec -- chezmoi execute-template --source "$SOURCE_DIR" <"$OPENCODE_TEMPLATE" - - assert_success - assert_output --partial '## Graphify' - assert_output --partial 'Use the installed Graphify skill when the user invokes `/graphify`.' - assert_output --partial 'graphify query' - assert_output --partial 'GRAPH_REPORT.md' -} -``` - -- [ ] **Step 2: Run the failing instruction tests** - -Run: `mise exec -- bats tests/template/agent-instructions.bats` - -Expected: FAIL because the shared Graphify guidance is not present yet. - -- [ ] **Step 3: Add the shared Graphify guidance** - -Append this section to `home/.chezmoitemplates/AGENTS.md`: - -```markdown -## Graphify - -- Use the installed Graphify skill when the user invokes `/graphify`. -- If `graphify-out/graph.json` exists, prefer `graphify query`, `graphify path`, or `graphify explain` before raw file search for codebase questions. -- Read `GRAPH_REPORT.md` only for broad architecture review or when graph commands do not answer the question. -- Treat `graphify-out/` as per-repo data and do not create it from a global setup script. -``` - -- [ ] **Step 4: Run the instruction tests again** - -Run: `mise exec -- bats tests/template/agent-instructions.bats` - -Expected: PASS. - -### Task 5: Final Verification - -**Files:** - -- Verify all changed files. - -- [ ] **Step 1: Run targeted tests** - -Run: `mise exec -- bats tests/unit/lib/install/graphify-skills.bats tests/template/darwin-install-scripts.bats tests/template/agent-instructions.bats` - -Expected: PASS. - -- [ ] **Step 2: Run shell syntax checks through existing template tests** - -Run: `mise exec -- bats tests/template/darwin-install-scripts.bats` - -Expected: PASS, including rendered bash syntax validation. - -- [ ] **Step 3: Run diff whitespace check** - -Run: `git diff --check` - -Expected: no output and exit code 0. - -- [ ] **Step 4: Review final diff** - -Run: `git diff -- docs/superpowers/specs/2026-06-24-graphify-global-agent-install-design.md docs/superpowers/plans/2026-06-24-graphify-global-agent-install.md home/.chezmoitemplates/lib/chezmoi/active-groups.json.tmpl home/.chezmoitemplates/lib/chezmoi/active-group-values.json.tmpl home/.chezmoitemplates/lib/install/graphify-skills.sh home/.chezmoiscripts/darwin/run_onchange_08_install-graphify-skills.sh.tmpl home/.chezmoitemplates/AGENTS.md tests/template/darwin-install-scripts.bats tests/template/agent-instructions.bats tests/unit/lib/install/graphify-skills.bats` - -Expected: only the planned files changed. diff --git a/docs/superpowers/plans/2026-06-25-apm-reproducibility-validation.md b/docs/superpowers/plans/2026-06-25-apm-reproducibility-validation.md deleted file mode 100644 index cce3ed4..0000000 --- a/docs/superpowers/plans/2026-06-25-apm-reproducibility-validation.md +++ /dev/null @@ -1,770 +0,0 @@ -# APM Reproducibility and Validation 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:** Make APM dependency installs reproducible (committed per-context lockfiles + `--frozen`) and verifiable through one local-and-CI entrypoint, plus a scheduled APM update PR bot. - -**Architecture:** APM follows the npm two-file model: the manifest ref (`apm.yml`, templated per context) is the update channel; the committed `apm.lock.yaml` is the exact pin. The real chezmoi run-script and CI both install with `--frozen` so every apply reproduces the locked SHAs. A shared shell library (`scripts/apm-lib.sh`) materializes a context's `~/.apm` into an isolated temp home; both the validation entrypoint and the lockfile-refresh script source it. A scheduled workflow runs `apm update --yes` and opens a gated PR. - -**Tech Stack:** chezmoi, APM 0.21.0, Bash, bats (bats-support/bats-assert/bats-file), mise, GitHub Actions. - -**Spec:** `docs/superpowers/specs/2026-06-24-local-apm-validation-design.md` - ---- - -## File Map - -- Modify `home/.chezmoidata/apm.yaml`: add a channel ref (`#`) to each dependency. -- Create `home/.chezmoitemplates/apm/apm.lock.personal.yaml`, `apm.lock.work.yaml`: committed lockfiles (generated, not hand-written). -- Create `home/dot_apm/apm.lock.yaml.tmpl`: selects the context lockfile into `~/.apm/apm.lock.yaml`. -- Modify `home/.chezmoitemplates/lib/install/apm.sh`: install with `--global --frozen`. -- Modify `tests/unit/lib/install/apm.bats`: assert the frozen flag. -- Create `scripts/apm-lib.sh`: shared tool resolution + temp-home `~/.apm` materialization. -- Create `scripts/validate-apm.sh`: per-context frozen install + audit, `all` aggregation. -- Create `scripts/refresh-apm-locks.sh`: regenerate committed lockfiles via `apm update --yes`. -- Create `tests/unit/scripts/apm-lib.bats`, `tests/unit/scripts/validate-apm.bats`: behavior tests with stubbed `chezmoi`/`apm`. -- Create `tests/template/apm-lockfile.bats`: context selection of `apm.lock.yaml.tmpl`. -- Modify `mise.toml`: add `validate-apm*`, `refresh-apm-locks` tasks. -- Modify `.github/workflows/ci.yaml`: rewire `validate_apm` to the mise task + cache. -- Create `.github/workflows/apm-update.yaml`: scheduled `apm update` PR bot. - -No commit steps run unless the user asks; each task ends with a commit step the executor may skip per session policy. - ---- - -### Task 1: Pin dependency channels in apm.yaml - -APM warns that unpinned refs drift. Set a deliberate channel per dependency: a release tag where the upstream publishes releases, else the default branch. The exact SHA is captured later in the lockfile. - -**Files:** - -- Modify: `home/.chezmoidata/apm.yaml` - -- [ ] **Step 1: Discover each dependency's newest tag (empty output means no releases - use the default branch)** - -Run: - -```bash -for repo in obra/superpowers JuliusBrussee/caveman anthropics/claude-plugins-official schpet/linear-cli tavily-ai/skills JuliusBrussee/skills; do - printf '%s -> ' "$repo" - gh api "repos/$repo/tags" --jq '.[0].name' 2>/dev/null || echo "(no tags)" -done -``` - -Expected: one line per repo, either a tag like `v1.2.3` or `(no tags)`/empty. - -- [ ] **Step 2: Determine each repo's default branch (used when no tag exists)** - -Run: - -```bash -for repo in obra/superpowers JuliusBrussee/caveman anthropics/claude-plugins-official schpet/linear-cli tavily-ai/skills JuliusBrussee/skills; do - printf '%s -> ' "$repo"; gh api "repos/$repo" --jq '.default_branch' -done -``` - -Expected: one branch name per repo (e.g. `main`). - -- [ ] **Step 3: Edit `home/.chezmoidata/apm.yaml` to append `#` to each dependency** - -Use the tag from Step 1 if present, else the branch from Step 2. String deps become `owner/repo#`; the map dep keeps its shape with the ref on the git line. Example shape (substitute the refs you resolved): - -```yaml -dependencies: - shared: - - obra/superpowers# - - JuliusBrussee/caveman# - - anthropics/claude-plugins-official/plugins/skill-creator# - personal: - - schpet/linear-cli# - - tavily-ai/skills# - - git: JuliusBrussee/skills# - skills: - - grill-me - - junior-to-senior - work: [] -``` - -Leave `apm.targets` and `apm.mcp` unchanged. - -- [ ] **Step 4: Verify both contexts still render valid YAML with refs present** - -Run: - -```bash -echo "== personal =="; mise exec -- chezmoi execute-template --source home \ - --override-data '{"personal":true,"work":false}' < home/dot_apm/apm.yml.tmpl | grep -E '^\s+- ' -echo "== work =="; mise exec -- chezmoi execute-template --source home \ - --override-data '{"personal":false,"work":true}' < home/dot_apm/apm.yml.tmpl | grep -E '^\s+- ' -``` - -Expected: rendered dependency lines all carry `#`. - -- [ ] **Step 5: Commit** - -```bash -git add home/.chezmoidata/apm.yaml -git commit -m "feat: DOT-31 pin APM dependency channels" -``` - ---- - -### Task 2: Shared materialization library `scripts/apm-lib.sh` - -One implementation of "resolve pinned tools, then materialize a context's `~/.apm` into a temp home" used by both validation and lockfile refresh. - -**Files:** - -- Create: `scripts/apm-lib.sh` -- Test: `tests/unit/scripts/apm-lib.bats` - -- [ ] **Step 1: Write the failing test** - -Create `tests/unit/scripts/apm-lib.bats`: - -```bash -#!/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" - # apm_lib resolves tools via `mise which`; stub it to our fake binaries. - 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_output --partial "/bin/chezmoi|" - assert_output --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_output --partial "apply" - assert_output --partial "--include files,dirs" - assert_output --partial "/home/.apm" - refute_output --partial "scripts" -} -``` - -- [ ] **Step 2: Run the failing test** - -Run: `mise exec -- bats tests/unit/scripts/apm-lib.bats` -Expected: FAIL because `scripts/apm-lib.sh` does not exist. - -- [ ] **Step 3: Implement `scripts/apm-lib.sh`** - -```bash -#!/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" -} -``` - -- [ ] **Step 4: Run the tests** - -Run: `mise exec -- bats tests/unit/scripts/apm-lib.bats` -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add scripts/apm-lib.sh tests/unit/scripts/apm-lib.bats -git commit -m "feat: DOT-31 add shared apm materialization library" -``` - ---- - -### Task 3: Lockfile refresh script + initial committed lockfiles - -**Files:** - -- Create: `scripts/refresh-apm-locks.sh` -- Create: `home/.chezmoitemplates/apm/apm.lock.personal.yaml` (generated) -- Create: `home/.chezmoitemplates/apm/apm.lock.work.yaml` (generated) - -- [ ] **Step 1: Implement `scripts/refresh-apm-locks.sh`** - -```bash -#!/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=("${@:-personal work}") -[ "$#" -eq 0 ] && contexts=(personal work) - -apm_lib_resolve_bins - -tmp_root="$(mktemp -d)" -trap 'rm -rf "$tmp_root"' EXIT - -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 - - cp "$tmp_home/.apm/apm.lock.yaml" "$repo/home/.chezmoitemplates/apm/apm.lock.$context.yaml" - printf '[refresh] wrote home/.chezmoitemplates/apm/apm.lock.%s.yaml\n' "$context" -done -``` - -- [ ] **Step 2: Generate the lockfiles** - -Run: - -```bash -mkdir -p home/.chezmoitemplates/apm -bash scripts/refresh-apm-locks.sh -``` - -Expected: two `[refresh] wrote ...` lines, and two new lockfiles whose first line is `lockfile_version: '1'`. - -- [ ] **Step 3: Verify the lockfiles look resolved** - -Run: `grep -c resolved_commit home/.chezmoitemplates/apm/apm.lock.personal.yaml` -Expected: a non-zero count (one per resolved dependency). - -- [ ] **Step 4: Commit** - -```bash -git add scripts/refresh-apm-locks.sh home/.chezmoitemplates/apm/apm.lock.personal.yaml home/.chezmoitemplates/apm/apm.lock.work.yaml -git commit -m "feat: DOT-31 add lockfile refresh script and committed locks" -``` - ---- - -### Task 4: Deploy the right lockfile per context - -**Files:** - -- Create: `home/dot_apm/apm.lock.yaml.tmpl` -- Test: `tests/template/apm-lockfile.bats` - -- [ ] **Step 1: Write the failing test** - -Create `tests/template/apm-lockfile.bats`: - -```bash -#!/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' - -SOURCE_DIR="$DOTFILES_ROOT/home" -TMPL="$DOTFILES_ROOT/home/dot_apm/apm.lock.yaml.tmpl" -PERSONAL='{"personal":true,"work":false}' -WORK='{"personal":false,"work":true}' - -render() { - mise exec -- chezmoi execute-template --source "$SOURCE_DIR" --override-data "$1" <"$TMPL" -} - -@test "personal context renders the personal lockfile" { - personal_first="$(grep -m1 resolved_commit "$DOTFILES_ROOT/home/.chezmoitemplates/apm/apm.lock.personal.yaml")" - run render "$PERSONAL" - assert_success - assert_output --partial "lockfile_version" - assert_output --partial "$personal_first" -} - -@test "work context renders the work lockfile" { - run render "$WORK" - assert_success - assert_output --partial "lockfile_version" -} -``` - -- [ ] **Step 2: Run the failing test** - -Run: `mise exec -- bats tests/template/apm-lockfile.bats` -Expected: FAIL because `home/dot_apm/apm.lock.yaml.tmpl` does not exist. - -- [ ] **Step 3: Create `home/dot_apm/apm.lock.yaml.tmpl`** - -```gotemplate -{{- if .personal -}} -{{ include ".chezmoitemplates/apm/apm.lock.personal.yaml" }} -{{- else if .work -}} -{{ include ".chezmoitemplates/apm/apm.lock.work.yaml" }} -{{- end -}} -``` - -Use `include` (raw file read relative to the source root), not `includeTemplate`, so the committed lockfile content is never evaluated as a Go template. - -- [ ] **Step 4: Run the tests** - -Run: `mise exec -- bats tests/template/apm-lockfile.bats` -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add home/dot_apm/apm.lock.yaml.tmpl tests/template/apm-lockfile.bats -git commit -m "feat: DOT-31 deploy per-context apm lockfile" -``` - ---- - -### Task 5: Make the runtime install reproducible - -**Files:** - -- Modify: `home/.chezmoitemplates/lib/install/apm.sh` -- Modify: `tests/unit/lib/install/apm.bats` - -- [ ] **Step 1: Update the test to expect a frozen install** - -In `tests/unit/lib/install/apm.bats`, the existing happy-path test asserts the recorded apm args. Change that assertion to expect the frozen global flags. The relevant assertion becomes: - -```bash - assert_output --partial "install --global --frozen" -``` - -Keep the existing stubbed-`apm` setup (a fake `apm` in `$BATS_TEST_TMPDIR/bin` recording `$*`). - -- [ ] **Step 2: Run the test to verify it fails** - -Run: `mise exec -- bats tests/unit/lib/install/apm.bats` -Expected: FAIL because the lib still runs `apm install --global` without `--frozen`. - -- [ ] **Step 3: Update `home/.chezmoitemplates/lib/install/apm.sh`** - -Change the install line in `apm_install_main`: - -```bash -function apm_install_main() { - log_info "[apm] Installing globally from ~/.apm/apm.yml (frozen)..." - cd "${HOME}/.apm" - - 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." -} -``` - -- [ ] **Step 4: Run the test to verify it passes** - -Run: `mise exec -- bats tests/unit/lib/install/apm.bats` -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add home/.chezmoitemplates/lib/install/apm.sh tests/unit/lib/install/apm.bats -git commit -m "feat: DOT-31 install APM frozen for reproducible applies" -``` - ---- - -### Task 6: Validation entrypoint `scripts/validate-apm.sh` - -**Files:** - -- Create: `scripts/validate-apm.sh` -- Test: `tests/unit/scripts/validate-apm.bats` - -- [ ] **Step 1: Write the failing test** - -Create `tests/unit/scripts/validate-apm.bats`: - -```bash -#!/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" - # Stub mise (tool resolution), chezmoi (materialize), apm (gate). - 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"; 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_output --partial "personal: PASS" - assert_output --partial "work: PASS" -} - -@test "all reports both even when personal fails, exits non-zero" { - export APM_EXIT=1 # exported so the stubbed apm (a grandchild process) sees it - run bash "$SCRIPT" all - assert_failure - assert_output --partial "personal: FAIL" - assert_output --partial "work: FAIL" -} - -@test "rejects an unknown context" { - run bash "$SCRIPT" bogus - assert_failure - assert_output --partial "Unsupported context" -} -``` - -- [ ] **Step 2: Run the failing test** - -Run: `mise exec -- bats tests/unit/scripts/validate-apm.bats` -Expected: FAIL because `scripts/validate-apm.sh` does not exist. - -- [ ] **Step 3: Implement `scripts/validate-apm.sh`** - -```bash -#!/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` and `apm audit --ci --no-policy` in an -# isolated temp home. `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 + audit gate for one context. -# @arg $1 string Context. -# @return 0 on pass, non-zero on failure. -function validate_context() { - local context="$1" tmp_home="$tmp_root/home-$1" - 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}" install --global --frozen --parallel-downloads 0 - HOME="$tmp_home" \ - XDG_CACHE_HOME="$tmp_home/.cache" XDG_CONFIG_HOME="$tmp_home/.config" XDG_STATE_HOME="$tmp_home/.local/state" \ - "${APM_BIN}" audit --ci --no-policy -} - -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" -``` - -Note: the loop must not abort on the first failure. `validate_context` is called inside an `if`, which suppresses `set -e` for that call, so both contexts always run. - -- [ ] **Step 4: Run the tests** - -Run: `mise exec -- bats tests/unit/scripts/validate-apm.bats` -Expected: PASS. - -- [ ] **Step 5: Run a real personal validation end-to-end** - -Run: `bash scripts/validate-apm.sh personal` -Expected: `personal: PASS` (networked; needs the committed lockfile from Task 3). - -- [ ] **Step 6: Negative test - prove the gate catches drift** - -Run: - -```bash -cp home/.chezmoitemplates/apm/apm.lock.personal.yaml /tmp/apm.lock.bak -printf '\n# tampered\n' >>home/.chezmoitemplates/apm/apm.lock.personal.yaml -bash scripts/validate-apm.sh personal; echo "exit=$?" -cp /tmp/apm.lock.bak home/.chezmoitemplates/apm/apm.lock.personal.yaml -``` - -Expected: `personal: FAIL` and `exit=1`, then the lockfile is restored. - -- [ ] **Step 7: Commit** - -```bash -git add scripts/validate-apm.sh tests/unit/scripts/validate-apm.bats -git commit -m "feat: DOT-31 add apm validation entrypoint" -``` - ---- - -### Task 7: Mise tasks - -**Files:** - -- Modify: `mise.toml` - -- [ ] **Step 1: Add the tasks** - -Append to `mise.toml`: - -```toml -[tasks.validate-apm-personal] -description = "Validate APM reproducibility for the personal context" -run = "bash scripts/validate-apm.sh personal" - -[tasks.validate-apm-work] -description = "Validate APM reproducibility for the work context" -run = "bash scripts/validate-apm.sh work" - -[tasks.validate-apm] -description = "Validate APM reproducibility for both contexts" -run = "bash scripts/validate-apm.sh all" - -[tasks.refresh-apm-locks] -description = "Re-resolve APM refs and rewrite committed lockfiles" -run = "bash scripts/refresh-apm-locks.sh" -``` - -- [ ] **Step 2: Verify the tasks are registered** - -Run: `mise tasks ls | grep -E 'validate-apm|refresh-apm-locks'` -Expected: all four tasks listed. - -- [ ] **Step 3: Commit** - -```bash -git add mise.toml -git commit -m "feat: DOT-31 add apm validation and refresh mise tasks" -``` - ---- - -### Task 8: Rewire the CI validate_apm job - -**Files:** - -- Modify: `.github/workflows/ci.yaml` - -- [ ] **Step 1: Replace the job body with the shared entrypoint + cache** - -Replace the `validate_apm` job's steps (the `Render apm.yml` and `Audit APM project` steps) so each matrix entry calls the mise task, and cache the APM dir keyed on the committed lockfiles: - -```yaml -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@v4 - - name: Cache APM downloads - uses: actions/cache@v4 - with: - path: ~/.cache/apm - key: apm-${{ runner.os }}-${{ hashFiles('home/.chezmoitemplates/apm/apm.lock.*.yaml') }} - - name: Validate APM (${{ matrix.context }}) - run: mise validate-apm-${{ matrix.context }} -``` - -The script writes its own stub chezmoi config into a temp home, so the prior `write-chezmoi-config` action step is no longer needed in this job. - -- [ ] **Step 2: Lint the workflow** - -Run: `mise exec -- actionlint .github/workflows/ci.yaml || docker run --rm -v "$PWD":/repo --workdir /repo rhysd/actionlint:1.7.12 -color` -Expected: no errors. - -- [ ] **Step 3: Commit** - -```bash -git add .github/workflows/ci.yaml -git commit -m "feat: DOT-31 run apm validation through shared entrypoint in CI" -``` - ---- - -### Task 9: Scheduled APM update PR bot - -**Files:** - -- Create: `.github/workflows/apm-update.yaml` - -- [ ] **Step 1: Create the workflow** - -```yaml -name: APM update -on: - schedule: - - cron: "0 6 * * 1" - workflow_dispatch: - -permissions: - contents: write - pull-requests: write - -jobs: - refresh-locks: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: jdx/mise-action@v4 - - name: Refresh APM lockfiles - run: mise refresh-apm-locks - - name: Open PR if lockfiles changed - uses: peter-evans/create-pull-request@v7 - with: - branch: chore/apm-update - title: Update APM lockfiles - commit-message: "chore: DOT-31 refresh APM lockfiles" - body: | - Automated `apm update --yes` refresh of committed lockfiles. - The validate_apm job gates this with `apm install --frozen`. - add-paths: home/.chezmoitemplates/apm/apm.lock.*.yaml -``` - -- [ ] **Step 2: Lint the workflow** - -Run: `docker run --rm -v "$PWD":/repo --workdir /repo rhysd/actionlint:1.7.12 -color .github/workflows/apm-update.yaml` -Expected: no errors. - -- [ ] **Step 3: Commit** - -```bash -git add .github/workflows/apm-update.yaml -git commit -m "feat: DOT-31 add scheduled APM update PR bot" -``` - ---- - -### Task 10: Full verification - -**Files:** - -- Verify all changed files. - -- [ ] **Step 1: Run the full bats suite** - -Run: `mise exec -- bats -r tests/` -Expected: all tests pass, including the new apm-lib, validate-apm, and apm-lockfile tests. - -- [ ] **Step 2: Run lint** - -Run: `mise lint` -Expected: shellcheck + shfmt pass on the new `scripts/*.sh`; markdown/yaml lint clean. - -- [ ] **Step 3: Validate both contexts end-to-end** - -Run: `mise validate-apm` -Expected: `personal: PASS` and `work: PASS`. - -- [ ] **Step 4: Whitespace check** - -Run: `git diff --check` -Expected: no output, exit 0. - ---- - -## Notes for the executor - -- APM dependencies change assistant behavior; the `apm_update` PR is reviewed and merged by a human, never auto-merged. -- `apm.lock.yaml` carries a `generated_at` timestamp, so `refresh-apm-locks` produces a diff even when resolved commits are unchanged. That is expected. -- The validation and refresh scripts are networked and slower than unit tests; they are intentionally not part of `mise check`. 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 deleted file mode 100644 index cff2e64..0000000 --- a/docs/superpowers/plans/2026-06-30-apm-migration-phase-1-skills-install.md +++ /dev/null @@ -1,410 +0,0 @@ -# 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 deleted file mode 100644 index 00f1d31..0000000 --- a/docs/superpowers/plans/2026-06-30-apm-migration-phase-2-plugins-install.md +++ /dev/null @@ -1,322 +0,0 @@ -# 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 deleted file mode 100644 index de89889..0000000 --- a/docs/superpowers/plans/2026-06-30-repo-refine-cleanup.md +++ /dev/null @@ -1,137 +0,0 @@ -# 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 deleted file mode 100644 index f9845a8..0000000 --- a/docs/superpowers/plans/2026-07-01-apm-migration-phase-3-mcp-delivery.md +++ /dev/null @@ -1,599 +0,0 @@ -# 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-05-15-ci-refactor-design.md b/docs/superpowers/specs/2026-05-15-ci-refactor-design.md deleted file mode 100644 index d3da13b..0000000 --- a/docs/superpowers/specs/2026-05-15-ci-refactor-design.md +++ /dev/null @@ -1,216 +0,0 @@ -# CI Refactor + Shell Testing Design - -**Status:** PRs #10, #12, #13, #14 merged. Library extraction + shell coverage is complete. - -**Branch:** `main` - -**Merged PRs:** - -- [#10 Bootstrap bats framework for shell testing](https://github.com/edwinhern/dotfiles/pull/10) — mise-pinned bats-core, vendored companion libs, statusline tests, `make test` targets. -- [#12 Refactor CI setup with composite action and APM audit](https://github.com/edwinhern/dotfiles/pull/12) — `write-chezmoi-config` composite action, mise-managed APM, `apm audit --ci --no-drift --no-policy` step, dedicated `test` job, workflow-level concurrency. -- [#13 Adopt native tool configs for lint/format](https://github.com/edwinhern/dotfiles/pull/13) — `.shellcheckrc`, `.prettierignore`, `taplo.toml`, `.editorconfig` with `shell_variant = bash`; slim wrapper scripts; IDE-extension parity with CI. -- [#14 Extract APM install library with shell coverage](https://github.com/edwinhern/dotfiles/pull/14) - `lib/install/apm.sh`, Darwin-gated template injection, direct APM bats tests, and shebang coverage for the rendered run script. - -**Reference:** [shunk031/dotfiles](https://github.com/shunk031/dotfiles) — pattern source. - -## Context - -Original `.github/workflows/ci.yaml` friction (PR #12 resolved items 1, 2, 5; PR #13 collapsed config drift across tools; PR #10 pinned toolchain): - -1. ~~Duplicated chezmoi-config stub heredoc across `validate_apm` and `chezmoi_dry_run` jobs.~~ (resolved in PR #12) -2. ~~Inline `cp` plumbing in `validate_apm` re-implements parts of `chezmoi apply` manually.~~ (intentionally retained per PR #12 design — the rendered `$HOME/.apm` shape is what the dotfiles actually deploy; audit replays validate it) -3. No caching of mise/chezmoi/APM binaries — every CI run re-downloads via `curl`. (still open; caching deferred per Section 3 design) -4. One monolithic workflow with four jobs of mixed concerns. (deferred — splitting deferred until duplication is fully extracted) -5. ~~Ad-hoc `curl | sh` installs of chezmoi and APM repeated across jobs.~~ (APM resolved in PR #12 via mise; chezmoi `curl | sh` removal queued for a follow-up now that PR #14 pins chezmoi via mise) - -**Primary goal:** maintainability / best practices. Secondary goal: better coverage via shell-level unit tests. Speed is fine as-is. - -## Decisions made so far - -### 1. Library structure (Approach 1 — approved, implemented) - -Reusable shell snippets live in `home/.chezmoitemplates/lib/` so chezmoi can inject them into `.chezmoiscripts/` via `{{ template "..." . }}`. Files are pure shell (no Go template syntax), making them sourceable and testable in isolation. - -``` -home/.chezmoitemplates/lib/ -├── common/ # OS-agnostic helpers -│ ├── log.sh # log_info, log_warn, log_error (✅ shipped in PR #14) -│ ├── error.sh # die, retry, with_timeout (deferred until a caller needs it) -│ └── os-detect.sh # is_darwin, is_linux, has_command (deferred until a caller needs it) -├── darwin/ # macOS-specific (deferred) -│ └── homebrew.sh # brew_install_if_missing -└── install/ # cross-cutting install routines - ├── apm.sh # apm_install_main (✅ shipped in PR #14) - └── chezmoi.sh # chezmoi_install_if_missing (deferred until a caller needs it) -``` - -**YAGNI policy for the lib:** add a primitive only when a `.chezmoiscripts/*.tmpl` script actually needs it. `log.sh` and `install/apm.sh` land first because `run_onchange_06_install-apm.sh.tmpl` is the caller. `error.sh` / `os-detect.sh` / `homebrew.sh` etc. stay deferred because they have zero current callers and would be dead code on landing. - -Consuming `.chezmoiscripts/*.tmpl` files pull in lib content inside their OS guard via: - -```go-template -{{ if eq .chezmoi.os "darwin" }} -{{ template "lib/common/log.sh" . }} -{{ template "lib/install/apm.sh" . }} -{{ end }} -``` - -**Rationale:** mirrors chezmoi's own `.chezmoiscripts/{darwin,linux,windows}/` convention; OS separation is forward-looking even though only darwin exists today; pure-shell library files are unit-testable. - -**Gotcha discovered during the library-extraction PR:** Go templates parse `{{ }}` regardless of host language. Putting a literal `{{ template "lib/common/log.sh" . }}` in a _shell comment_ inside `log.sh` causes infinite recursion at render time. Document template-injection usage in plain prose, not by mirroring the directive. - -### 2. Testing setup - -**Toolchain (pinned via `mise.toml`):** - -- `bats-core` — test runner. -- `bats-support`, `bats-assert`, `bats-file` — vendored under `tests/test_helpers/` (~100 KB total, no network dependency at test time). -- `chezmoi` — pinned in PR #14 so `tests/template/` tests render without an external install. - -**Layout:** - -``` -tests/ -├── test_helpers/ # vendored bats companion libs + load.bash -├── unit/ -│ ├── lib/common/log.bats # ✅ shipped in PR #14 (9 tests) -│ ├── lib/install/apm.bats # ✅ shipped in PR #14 (2 tests) -│ ├── statusline.bats # tests/integration for home/dot_claude/statusline-command.sh (4 tests) -│ └── write-chezmoi-config.bats # ✅ shipped in PR #14 (7 tests, covers personal/work/invalid) -├── template/ -│ └── install-apm-script.bats # ✅ shipped in PR #14 (8 tests, renders + asserts lib injection) -└── fixtures/ - └── statusline/{full-payload,minimal,worktree}.json -``` - -**Mise tasks:** - -```sh -mise lint # lint shell, markdown, YAML, and TOML -mise format # format shell, markdown, YAML, and TOML -mise test # run all bats tests -mise test-unit # run bats unit tests -mise test-template # run bats template tests -mise check # lint + test -``` - -`mise check` runs `lint` and `test` in sequence. - -### 3. `mise.toml` - single source of truth for tool versions and tasks - -```toml -[tools] -"github:microsoft/apm" = "0.13.0" -bats = "1.13.0" -chezmoi = "2.70.3" # ✅ added in PR #14 (enables template tests) -prettier = "3.8.3" -shellcheck = "0.11.0" -shfmt = "3.13.1" -taplo = "0.10.0" -``` - -Pinning chezmoi here also unblocks a follow-up: the `Install chezmoi` `curl | sh` step in `validate_apm` and `chezmoi_dry_run` can be removed since `mise-action` will provide chezmoi automatically. - -## Patterns to study from shunk031/dotfiles - -Resume by reading these in their repo if/when the next slice needs them: - -1. `tests/install/common/*.bats` — concrete OS-segmented test code style. -2. `.github/workflows/test.yaml` — `changes` job using `git diff --name-only` against base ref to gate heavier matrix. -3. Their bats test wiring. -4. `bats_load_library` mechanism — would replace vendoring of bats-assert/file/support if mise gains a plugin for them. - -## Section 3 — CI workflow refactor (implemented in PR #12) - -See PR #12 for the implemented design. Summary: - -- `.github/actions/write-chezmoi-config/` composite action with `context` input. -- `mise.toml`-managed APM (`mise exec -- apm install --dry-run --global`). -- `apm audit --ci --no-drift --no-policy` against a scratch project. -- Dedicated `test` job running the bats suite. -- Workflow-level `concurrency` block cancelling superseded runs. - -### Section 3 follow-ups (still open) - -- Remove `curl | sh` chezmoi installs from `validate_apm` and `chezmoi_dry_run` now that chezmoi is pinned via mise. (~3-line PR) -- Split `ci.yaml` into `lint.yaml`, `validate.yaml`, `test.yaml` once a second source of duplication appears. (deferred — single workflow with composites is fine for current shape) -- Add `dorny/paths-filter@v3` change gating so `docs/`-only PRs skip APM/chezmoi jobs. (deferred — CI is fast enough) -- Add caching for mise tools and APM modules after measuring repeated setup time. -- Decide whether SARIF audit reports or `apm-policy.yml` governance are needed. - -## Section 4 — CLAUDE.md documentation update (deferred) - -Capture the new patterns as documented conventions in `home/dot_claude/CLAUDE.md`: - -- `.chezmoitemplates/lib/` for reusable shell snippets (pure shell; injected via `{{ template }}`). -- `tests/` for bats unit + template tests; `tests/test_helpers/load.bash` as the single loader. -- `mise test` / `mise test-unit` / `mise test-template` workflow. -- Native tool configs (`.shellcheckrc`, `.prettierignore`, `taplo.toml`, `.editorconfig`) and `mise.toml` tasks are the source of truth for dev tooling. -- YAGNI rule for `lib/` primitives: add only when a caller needs it. - -## Migration plan - -### PR #10 — bats framework bootstrap (merged) - -- [x] Add `mise.toml` pinning toolchain (APM, bats-core, prettier, shellcheck, shfmt, taplo). -- [x] Vendor `bats-support`, `bats-assert`, `bats-file` under `tests/test_helpers/`. -- [x] Add `tests/test_helpers/load.bash`. -- [x] Add statusline fixtures under `tests/fixtures/statusline/`. -- [x] Add initial `make test`, `make test-unit`, `make test-template` targets. (removed later when mise tasks became the repo entrypoint) -- [x] Add `tests/unit/statusline.bats`. -- [x] Include bats tests in the local check target. - -### PR #12 — CI setup refactor (merged) - -- [x] Add `.github/actions/write-chezmoi-config/action.yml` with a `context` input. -- [x] Replace duplicated chezmoi config heredocs in `validate_apm` and `chezmoi_dry_run`. -- [x] Remove manual APM `curl` install from CI. -- [x] Run APM commands through mise. -- [x] Keep rendered `$HOME/.apm` project setup unchanged. -- [x] Keep `apm install --dry-run --global` for the initial refactor. -- [x] Add baseline `apm audit --ci --no-drift --no-policy` after the rendered APM project is prepared. -- [x] Add a dedicated `test` job that runs bats tests. -- [x] Add workflow-level concurrency cancelling superseded runs. -- [x] Co-locate `write-chezmoi-config.sh` next to its composite action. - -### PR #13 — native tool configs for lint/format (merged) - -- [x] Add `.shellcheckrc` with `shell=bash`, `external-sources=true`. -- [x] Extend `.editorconfig` with `shell_variant = bash` for `*.sh`, `*.zsh`, `*.bash`, `.zshrc`, `.bashrc`, etc. -- [x] Add `.prettierignore` (covers `tests/test_helpers/`, `*.tmpl`, `apm.lock.yaml`). -- [x] Add `taplo.toml` (include/exclude globs). -- [x] Slim `scripts/lint.sh` and `scripts/format.sh` (drop tool-specific flag soup). - -### PR #14 - library extraction + shell coverage (merged) - -- [x] Add `home/.chezmoitemplates/lib/common/log.sh` (`log_info`, `log_warn`, `log_error`). -- [x] Add `home/.chezmoitemplates/lib/install/apm.sh` with executable bash script shape and `apm_install_main`. -- [x] Refactor `run_onchange_06_install-apm.sh.tmpl` to inject `lib/common/log.sh` and `lib/install/apm.sh` inside the Darwin gate. -- [x] Add `tests/unit/lib/common/log.bats` (9 tests covering stdout/stderr routing, prefixes, special chars). -- [x] Add `tests/unit/lib/install/apm.bats` with fake `apm` on `PATH`. -- [x] Add `tests/unit/write-chezmoi-config.bats` (7 tests covering personal/work/invalid contexts and re-runs). -- [x] Add `tests/template/install-apm-script.bats` (8 tests; verifies lib injection, Darwin rendering, shebang, and rendered-script bash syntax). -- [x] Pin `chezmoi = "2.70.3"` in `mise.toml` so template tests run anywhere with mise. -- [x] Extend `scripts/lint.sh` and `scripts/format.sh` find scopes to `home/.chezmoitemplates/`. - -### Main cleanup - mise task migration - -- [x] Move lint, format, test, and check commands into `mise.toml` tasks. -- [x] Remove `scripts/lint.sh`, `scripts/format.sh`, and `scripts/compile.sh`. -- [x] Remove the local `make validate` target because CI owns rendered APM validation. -- [x] Remove the Makefile after moving repo tasks into `mise.toml`. -- [x] Update CI lint and test jobs to call mise tasks directly. - -### Later PRs - -- [ ] Remove `curl | sh` chezmoi installs from CI workflow now that chezmoi is in `mise.toml`. (~3-line PR) -- [ ] Design Section 4: CLAUDE.md documentation update (capture new patterns from PRs #10–#N). -- [ ] Decide whether APM Action features are needed: SARIF, pack/restore, token forwarding, or bundle restore. -- [ ] Decide whether this repo needs `apm audit --ci --policy org` and an `apm-policy.yml` governance layer. -- [ ] Extend statusline coverage if hand-styled formatting is reformatted to canonical shfmt (currently out of lint scope to preserve the file). -- [ ] Split CI workflows or add changes gating after duplication grows again. -- [ ] Add caching for mise tools and APM modules after measuring repeated setup time. -- [ ] Verify whether `bats_load_library` or a future mise plugin can replace vendoring of bats companion libs. -- [ ] Read `tests/install/common/*.bats` from shunk031/dotfiles for concrete test style. -- [ ] Read `.github/workflows/test.yaml` in full from shunk031/dotfiles. -- [ ] Read shunk031's bats test wiring in full. diff --git a/docs/superpowers/specs/2026-05-16-planning-workflow-design.md b/docs/superpowers/specs/2026-05-16-planning-workflow-design.md deleted file mode 100644 index d0284da..0000000 --- a/docs/superpowers/specs/2026-05-16-planning-workflow-design.md +++ /dev/null @@ -1,135 +0,0 @@ -# Planning Workflow Design - -**Status:** Approved for implementation. - -**Branch:** `main` - -## Context - -This repository now uses Superpowers specs and plans for design and implementation work. The older `docs/PROGRESS.md` file predates that workflow and now duplicates or conflicts with merged PR history, GitHub PR state, and the current Superpowers docs. - -The repo has GitHub Issues enabled, no issue templates, no milestones, and two empty untitled user projects. GitHub Projects access is available through `gh` after refreshing auth with project scope. - -## Goal - -Create one planning workflow that gives a visual board for active work while keeping specs and implementation plans in the repository. - -## Non-Goals - -- Do not keep a long-lived local backlog in `docs/`. -- Do not migrate merged PR history into issues unless it is needed for unfinished follow-up work. -- Do not delete existing user projects unless explicitly requested. - -## Recommended Approach - -Use GitHub Issues as the source of truth for work items and one GitHub Project named `dotfiles` as the visual planning board. - -This matches the lowest-overhead solo developer setup: Issues are the task list, and the Project is a Kanban board. Superpowers docs remain in-repo artifacts for deeper design and implementation details. - -## Components - -### GitHub Issues - -Each feature, bug, cleanup, or research item starts as an issue. Issues should be small enough to close with one PR or with a short series of linked PRs. - -Core labels: - -- `bug` -- `enhancement` -- `documentation` -- `priority:high` - -Area labels: - -- `area:apm` -- `area:chezmoi` -- `area:ci` -- `area:docs` -- `area:agents` -- `area:editor` - -The existing default labels can remain. New labels should be added only when they make filtering useful. - -### GitHub Project - -Create a new user project named `dotfiles` and leave the existing empty untitled projects untouched unless the user asks to remove or rename them. - -Fields: - -- `Status`: Backlog, In progress, Done -- `Priority`: High, Normal, Low -- `Area`: APM, Chezmoi, CI, Docs, Agents, Editor - -Views: - -- `Backlog`: all open issues grouped by status -- `Active`: In progress work -- `By area`: open issues grouped by area -- `Done`: recently completed work - -### opencode Permissions - -`home/dot_config/opencode/opencode.jsonc` should allow targeted `gh` commands for issue, label, and project management so agents can maintain the tracker without repeated prompts. - -Allowed project operations are create, link, field creation, item add, and item edit. Destructive project operations should remain ask or deny. - -### Superpowers Docs - -`docs/superpowers/specs/` stores approved designs. `docs/superpowers/plans/` stores implementation plans created from specs. - -These files are tracked supporting artifacts, not the backlog. Task issues require a linked Superpowers spec or plan before implementation starts. - -### `docs/PROGRESS.md` - -Replace `docs/PROGRESS.md` with a short pointer to the new workflow or archive its still-useful information into issues and specs before removal. - -Recommended final state: remove `docs/PROGRESS.md` after any unfinished items are captured as GitHub issues. - -## Workflow - -1. Capture the idea as a GitHub issue. -2. Add the relevant core label and area label. -3. Add the issue to the `dotfiles` project. -4. Set status and priority in the project. -5. For ambiguous or larger work, write a spec under `docs/superpowers/specs/` and link it from the issue. -6. For ready work, write a plan under `docs/superpowers/plans/` and link it from the issue. -7. Implement on a branch and link the PR with `Closes #` or `Fixes #` in the PR body. -8. Let GitHub close the issue from the merged PR, then confirm the project item is Done. - -## Migration - -Initial migration should be small: - -- Create the `dotfiles` GitHub Project. -- Add area labels and `priority:high`. -- Add issue templates for bugs and general tasks. The task template requires a linked Superpowers spec or plan. -- Add opencode permissions for issue, label, and project management. -- Convert unfinished items from `docs/PROGRESS.md` and the CI refactor spec into issues. -- Remove `docs/PROGRESS.md` once unfinished work is captured elsewhere. - -Potential initial issues: - -- Remove remaining `curl | sh` chezmoi installs from CI. -- Decide whether mise tool caching is worth adding to CI. -- Decide whether docs-only PRs should skip heavier checks. -- Decide whether SARIF audit reports or `apm-policy.yml` governance are needed. -- Capture current Superpowers workflow conventions in the Claude instructions if still useful. - -## Error Handling - -If `gh project` commands fail due to scopes, refresh auth with `gh auth refresh -s project -s read:project` and retry. - -If project field automation is hard to script reliably, create the project and labels through `gh`, then finish field and view setup in the GitHub UI. - -If a `docs/PROGRESS.md` item is clearly complete or superseded, do not create an issue for it. - -## Verification - -Verify the setup with: - -- `gh issue list --repo edwinhern/dotfiles --state open --limit 20` -- `gh label list --repo edwinhern/dotfiles --limit 100` -- `gh project list --owner edwinhern --format json --limit 20` -- `mise lint` - -The work is complete when the repo has no stale local backlog, open work is represented by issues, and the project board provides the visual status view. diff --git a/docs/superpowers/specs/2026-05-16-priority-task-selection-design.md b/docs/superpowers/specs/2026-05-16-priority-task-selection-design.md deleted file mode 100644 index 06da7a6..0000000 --- a/docs/superpowers/specs/2026-05-16-priority-task-selection-design.md +++ /dev/null @@ -1,50 +0,0 @@ -# Priority Task Selection Design - -**Status:** Approved for implementation. - -**Issue:** [#26](https://github.com/edwinhern/dotfiles/issues/26) - -## Goal - -Agents should choose the next issue from the `dotfiles` GitHub Project by priority instead of guessing from issue order. - -## Priority Source - -The GitHub Project `Priority` field is the sorting source of truth. - -The task issue template also asks for priority so new issues capture intent before an agent sets the Project field. If the issue body and Project field differ, the Project field wins. - -## Levels - -- `High`: security, reproducibility, broken workflow, or work that unlocks important follow-up work. -- `Normal`: useful cleanup, maintenance, or decision work with clear value but no current breakage. -- `Low`: speculative, optional, or only worth doing when current pain appears. - -Story points are not part of this workflow. If effort matters, capture it in the issue discussion or Superpowers plan for that single task. - -## Selection Rule - -When starting work: - -1. Inspect open `Todo` items in the `dotfiles` Project. -2. Choose `High` before `Normal`, and `Normal` before `Low`. -3. If priorities tie, choose security and reproducibility work first, then blockers, then the smallest clear issue. -4. If priority is missing, set it or ask one question before implementation starts. -5. Move the selected issue to `In Progress`. -6. Link the related Superpowers spec or plan before code changes. - -## Initial Backlog Priorities - -- `#19 Remove curl-installed chezmoi from CI`: `High` -- `#18 Design Bitwarden-backed APM secrets handling`: `High` -- `#17 Decide APM audit governance needs`: `Normal` -- `#22 Evaluate reducing the lint toolchain`: `Normal` -- `#20 Evaluate mise tool caching in CI`: `Low` -- `#21 Evaluate docs-only CI gating`: `Low` -- `#16 Decide whether a business APM package is needed`: `Low` - -## Repo Changes - -- Update `AGENTS.md` with the task selection rule. -- Add a required `Priority` dropdown to `.github/ISSUE_TEMPLATE/task.yml`. -- Set priorities on current open project issues. diff --git a/docs/superpowers/specs/2026-05-17-lazyvim-opencode-terminal-design.md b/docs/superpowers/specs/2026-05-17-lazyvim-opencode-terminal-design.md deleted file mode 100644 index 3e07277..0000000 --- a/docs/superpowers/specs/2026-05-17-lazyvim-opencode-terminal-design.md +++ /dev/null @@ -1,38 +0,0 @@ -# LazyVim Opencode Terminal Design - -**Status:** Approved for implementation. - -**Issue:** [#30](https://github.com/edwinhern/dotfiles/issues/30) - -## Goal - -Add a minimal LazyVim-based Neovim source config that chezmoi can apply for terminal-first editing in this dotfiles repo. - -## Scope - -- Add Neovim to the shared Homebrew formulas. -- Keep LazyVim starter files directly under `home/dot_config/nvim/`. -- Do not track a nested LazyVim starter `.git` directory. -- Keep tmux unchanged for this pass because the existing config already enables mouse, vi copy mode, and terminal passthrough support. -- Defer `opencode.nvim` until the base Neovim config lands and the OpenCode server workflow is chosen. - -## Design - -The initial Neovim setup is the upstream LazyVim starter layout adapted to chezmoi source paths: - -- `home/dot_config/nvim/init.lua` loads `lua/config/lazy.lua`. -- `home/dot_config/nvim/lua/config/lazy.lua` bootstraps `lazy.nvim`, imports `LazyVim/LazyVim`, and imports local plugin specs from `lua/plugins/`. -- `home/dot_config/nvim/lua/plugins/example.lua` remains disabled with an early empty return, preserving the starter examples without enabling extra plugins. -- `home/dot_config/nvim/stylua.toml` keeps formatting settings for Lua config. - -## Deferred Work - -Track `opencode.nvim` in issue [#32](https://github.com/edwinhern/dotfiles/issues/32). That work should decide how the user starts OpenCode with a server port, which keymaps are useful, and whether `folke/snacks.nvim` should be used for the UI path. - -## Verification - -- `home/dot_config/nvim/init.lua` exists. -- No `home/dot_config/nvim/starter/` directory is present. -- No nested `home/dot_config/nvim/.git/` directory is tracked. -- Homebrew formulas include `neovim`. -- `mise check` passes. diff --git a/docs/superpowers/specs/2026-05-17-opencode-nvim-integration-design.md b/docs/superpowers/specs/2026-05-17-opencode-nvim-integration-design.md deleted file mode 100644 index e1f0c4d..0000000 --- a/docs/superpowers/specs/2026-05-17-opencode-nvim-integration-design.md +++ /dev/null @@ -1,49 +0,0 @@ -# Opencode Nvim Integration Design - -**Status:** Approved for implementation. - -**Issue:** [#32](https://github.com/edwinhern/dotfiles/issues/32) - -## Goal - -Connect LazyVim to a running OpenCode server while keeping tmux responsible for the editor and agent panes. - -## Workflow - -- Keep LazyVim in the left tmux pane. -- Start OpenCode in the right tmux pane with `opencode --port`. -- Use `opencode.nvim` from Neovim to send the current buffer or visual selection to the running OpenCode server. -- Keep terminal layout outside Neovim for now; Neovim should not create or manage the OpenCode terminal pane in this pass. - -## Design - -- Add `home/dot_config/nvim/lua/plugins/opencode.lua` as the LazyVim plugin spec. -- Install `nickjvandyke/opencode.nvim` through `lazy.nvim` with `version = "*"`. -- Load `opencode.nvim` at startup so `:checkhealth opencode` is available without a manual `:Lazy load` step. -- Set `vim.o.autoread = true` so buffers reload after OpenCode edits files. -- Keep `server.port = nil` so the plugin can discover the `opencode --port` server. -- Set `server.start`, `server.stop`, and `server.toggle` to `false` so `opencode.nvim` does not create a Neovim-managed OpenCode terminal. -- Hide the OpenCode server action from the generic select menu because `oS` handles server selection directly. -- Add the optional `folke/snacks.nvim` picker action because LazyVim already includes Snacks and the integration is recommended by `opencode.nvim`. -- Add LazyVim health-check tools `fd`, `fzf`, and `lazygit` to shared Homebrew formulas. -- Disable lazy.nvim rocks support because this config does not use LuaRocks plugins. - -## Keymaps - -- `oa`: ask OpenCode about the current buffer or visual selection with `@this`. -- `os`: select an OpenCode action. -- `oS`: select an OpenCode server when more than one server is running. -- `on`: start a new OpenCode session. - -The keymaps avoid ``, ``, and terminal-toggle bindings because this repo uses tmux panes for layout and those control-key mappings conflict with normal editing habits. - -## Verification - -- `home/dot_config/nvim/lua/plugins/opencode.lua` exists. -- Start OpenCode in the right pane with `opencode --port` before using the Neovim integration. -- Open Neovim in the left pane with `nvim .`. -- Run `:Lazy` and confirm `opencode.nvim` is installed. -- Run `:checkhealth opencode` after plugin installation. -- Run `:checkhealth lazy` and confirm the unused LuaRocks warning is gone. -- Run `:checkhealth lazyvim` and confirm `fd`, `fzf`, and `lazygit` are available after Homebrew applies packages. -- `mise check` passes. diff --git a/docs/superpowers/specs/2026-05-17-post-merge-cleanup-skill-design.md b/docs/superpowers/specs/2026-05-17-post-merge-cleanup-skill-design.md deleted file mode 100644 index 107d66e..0000000 --- a/docs/superpowers/specs/2026-05-17-post-merge-cleanup-skill-design.md +++ /dev/null @@ -1,47 +0,0 @@ -# Post-Merge Cleanup Skill Design - -**Status:** Approved for implementation. - -**Issue:** [#29](https://github.com/edwinhern/dotfiles/issues/29) - -## Goal - -Create a repo-local OpenCode skill that verifies post-merge cleanup for `edwinhern/dotfiles` after a PR is merged. - -## Location - -The skill lives at `.agents/skills/post-merge-cleanup/SKILL.md`. - -It is not stored under `home/`, so chezmoi will not apply it to the user's global home config. It is meant only for this repository. - -## Trigger - -Use the skill when the user says a PR was merged and asks to clean up or verify post-merge state. - -The skill needs a PR number from the user, or one clearly inferred from the current branch with `gh pr view`. - -## Workflow - -The skill verifies these checks with fresh commands: - -1. Repository is `edwinhern/dotfiles`. -2. Local checkout has no uncommitted changes before switching branches. -3. Local `main` is checked out and fast-forwarded with `git pull --ff-only`. -4. Requested PR is `MERGED` and has a merge commit SHA. -5. PR links at least one Linear `DOT-*` issue. -6. Each linked Linear issue is fetched with `linear issue query` or `mise exec -- linear`, then checked with `jq`. -7. Linked Linear issues are in a completed workflow state. -8. The latest `CI` workflow run on `main` for the merge commit completed with `success`. -9. Final git status is clean. - -## Non-Goals - -- Do not choose the next issue. -- Do not merge PRs. -- Do not close issues manually. -- Do not delete branches or worktrees. -- Do not install this under `home/`. - -## Verification - -Use a recent merged PR, such as `#28`, to verify the command sequence. The skill is complete when it can guide an agent to confirm the PR, linked Linear issue, `main` CI, and clean git state without changing repo files. diff --git a/docs/superpowers/specs/2026-05-18-lazyvim-tmux-workflow-design.md b/docs/superpowers/specs/2026-05-18-lazyvim-tmux-workflow-design.md deleted file mode 100644 index 689d57c..0000000 --- a/docs/superpowers/specs/2026-05-18-lazyvim-tmux-workflow-design.md +++ /dev/null @@ -1,36 +0,0 @@ -# LazyVim Tmux Workflow Design - -**Status:** Approved for implementation. - -**Issue:** [#37](https://github.com/edwinhern/dotfiles/issues/37) - -## Goal - -Make the daily LazyVim and tmux workflow easier to use by fixing selected health warnings, making copy and paste reliable, and adding a short cheatsheet. - -## Scope - -- Add shared Homebrew formulas for `nvim-treesitter` CLI support and Snacks image rendering checks. -- Add the Treesitter parsers reported by Snacks image health for docs and web filetypes. -- Send Neovim yanks to the macOS clipboard. -- Send tmux copy-mode yanks to the macOS clipboard through `pbcopy`. -- Enable tmux focus events for Neovim file change detection inside tmux. -- Document daily LazyVim navigation, hidden file toggles, tmux commands, copy/paste, and OpenCode usage. -- Leave optional provider warnings and which-key overlap warnings unchanged. - -## Design - -- `home/.chezmoidata/packages.yaml` adds `tree-sitter-cli`, `imagemagick`, `ghostscript`, `tectonic`, and `mermaid-cli` to shared Homebrew formulas. -- `home/dot_config/nvim/lua/plugins/treesitter.lua` extends LazyVim's `nvim-treesitter` `ensure_installed` list with `css`, `latex`, `norg`, `scss`, `svelte`, `typst`, and `vue`. -- `home/dot_config/nvim/lua/config/options.lua` sets `vim.opt.clipboard = "unnamedplus"` so normal and visual yanks use the macOS clipboard. -- `home/dot_config/tmux/tmux.conf` enables focus events and binds `y`, `Enter`, and mouse drag end in vi copy mode to `send -X copy-pipe-and-cancel "pbcopy"`. -- `.gitignore` ignores `.worktrees/` so project-local worktrees are not accidentally added. -- `docs/cheatsheets/lazyvim-tmux.md` is a short reference for this repo's LazyVim, tmux, and OpenCode workflow. - -## Verification - -- Package list contains the selected Homebrew formulas. -- Neovim Lua files parse with `luac -p` when available. -- tmux configuration parses with `tmux -f home/dot_config/tmux/tmux.conf start-server` when available. -- Markdown formatting passes through `mise check`. -- Full repo checks pass with `mise check`. diff --git a/docs/superpowers/specs/2026-05-18-zsh-cli-tool-integration-design.md b/docs/superpowers/specs/2026-05-18-zsh-cli-tool-integration-design.md deleted file mode 100644 index 12f7e66..0000000 --- a/docs/superpowers/specs/2026-05-18-zsh-cli-tool-integration-design.md +++ /dev/null @@ -1,32 +0,0 @@ -# Zsh CLI Tool Integration Design - -**Status:** Approved for implementation. - -**Issue:** [#35](https://github.com/edwinhern/dotfiles/issues/35) - -## Goal - -Wire the shared Homebrew CLI tools into zsh with small shell helpers that fit the current dotfiles layout. - -## Scope - -- Keep package declarations in `home/.chezmoidata/packages.yaml` as the source for installed tools. -- Initialize `zoxide` and `fzf` from `home/dot_config/zsh/dot_zshrc`. -- Use `fd` as the fzf file backend from `home/dot_config/zsh/exports.zsh`. -- Add only small aliases that remove repeated typing. -- Leave `tmux` and `ripgrep` as their normal commands for now. - -## Design - -- `home/dot_config/zsh/dot_zshrc` sources exports, aliases, then initializes `mise`, `starship`, `zoxide`, and `fzf` before loading zsh plugins. -- `home/dot_config/zsh/exports.zsh` sets `FZF_DEFAULT_COMMAND` to use `fd --type f --hidden --follow --exclude .git` and reuses it for `FZF_CTRL_T_COMMAND`. -- `home/dot_config/zsh/aliases.zsh` maps `find` to `fd` and adds `lg` for `lazygit`. -- `tmux` remains unaliased because pane/session commands are easier to understand explicitly while the workflow is still settling. -- `ripgrep` remains available as `rg`, its standard command name. - -## Verification - -- `home/dot_config/zsh/dot_zshrc` initializes `zoxide` and `fzf`. -- `home/dot_config/zsh/exports.zsh` uses `fd` for fzf file discovery. -- `home/dot_config/zsh/aliases.zsh` includes `find="fd"` and `lg="lazygit"`. -- `mise check` passes. diff --git a/docs/superpowers/specs/2026-05-20-work-apm-mcp-data-design.md b/docs/superpowers/specs/2026-05-20-work-apm-mcp-data-design.md deleted file mode 100644 index 1f4138a..0000000 --- a/docs/superpowers/specs/2026-05-20-work-apm-mcp-data-design.md +++ /dev/null @@ -1,106 +0,0 @@ -# Work APM MCP Data Design - -**Status:** Approved for implementation. - -**Issue:** [#41](https://github.com/edwinhern/dotfiles/issues/41) - -## Goal - -Render work-only APM MCP servers from source data so the work laptop can use Figma plus separate Atlassian Jira and Knowledge MCP resources without committing tokens or resource URL values. - -## Scope - -- Keep `home/dot_apm/apm.yml.tmpl` as a chezmoi template. -- Move MCP server definitions into a data file under `home/.chezmoidata/`. -- Keep the always-on `grep` MCP server shared. -- Render Figma, Atlassian Jira, and Atlassian Knowledge only when `.work` is true. -- Use environment placeholders for secret and work-specific values. -- Do not render actual token or Atlassian resource URL values into source-controlled files. - -## Template Design - -`home/dot_apm/apm.yml.tmpl` stays a template because the APM project still needs chezmoi context: - -- Personal machines should render only shared MCP servers. -- Work machines should render shared and work MCP servers. -- The template should build an MCP group list starting with `shared`, then append `personal` or `work` when that context is active. -- The MCP server list should be generated from data rather than hand-maintained inside the template. -- The reusable MCP server YAML snippet should live in `home/.chezmoitemplates/apm/mcp-server.yml.tmpl`, stay flush-left, and be injected with `{{ includeTemplate "apm/mcp-server.yml.tmpl" . | trim | indent 4 }}`. - -The rendered target remains `~/.apm/apm.yml` because chezmoi strips the `.tmpl` suffix when applying source state. - -## Data Design - -Add `home/.chezmoidata/apm.yaml` with this shape: - -```yaml -apm: - mcp: - shared: - - name: grep - registry: false - transport: http - url: https://mcp.grep.app - - personal: [] - - work: - - name: figma - registry: false - transport: http - url: https://mcp.figma.com/mcp - headers: - Authorization: "Bearer ${FIGMA_TOKEN}" - - - name: atlassian-jira - registry: false - transport: stdio - command: npx - args: - - "-y" - - "mcp-remote" - - "https://mcp.atlassian.com/v1/mcp" - - "--resource" - - "${ATLASSIAN_JIRA_RESOURCE_URL}" - - - name: atlassian-knowledge - registry: false - transport: stdio - command: npx - args: - - "-y" - - "mcp-remote" - - "https://mcp.atlassian.com/v1/mcp" - - "--resource" - - "${ATLASSIAN_KNOWLEDGE_RESOURCE_URL}" -``` - -APM supports `${VAR}` and `${env:VAR}` placeholders in MCP `headers` and `env` values. APM also documents `${VAR}` as the recommended cross-target form for new manifests. This design uses `${VAR}` placeholders for Figma and Atlassian values so the source repo never stores the secret values. - -## Work Laptop Setup - -On the work laptop, maintain `~/.secrets.local` by hand with the needed exports: - -```sh -export FIGMA_TOKEN="..." -export ATLASSIAN_JIRA_RESOURCE_URL="https://your-company.atlassian.net/..." -export ATLASSIAN_KNOWLEDGE_RESOURCE_URL="https://your-company.atlassian.net/..." -``` - -The zsh config already sources `~/.secrets.local` when it exists. For tools started outside an interactive zsh session, open a fresh terminal after editing `~/.secrets.local` or source it before running APM/OpenCode-related commands. - -## Testing - -- Add template tests for `home/dot_apm/apm.yml.tmpl`. -- Verify personal rendering includes `grep` but excludes Figma and Atlassian work MCP servers. -- Verify work rendering includes `grep`, `figma`, `atlassian-jira`, and `atlassian-knowledge`. -- Verify work rendering includes `${FIGMA_TOKEN}`, `${ATLASSIAN_JIRA_RESOURCE_URL}`, and `${ATLASSIAN_KNOWLEDGE_RESOURCE_URL}` literally. -- Verify rendering no longer requires `.atlassian_resource_url`. -- Run `mise check`. - -## Out of Scope - -- Managing the actual work token values with chezmoi. -- Adding Bitwarden or another secret backend in this change. -- Verifying live Figma or Atlassian MCP authentication. -- Changing non-APM OpenCode MCP configuration. diff --git a/docs/superpowers/specs/2026-05-20-work-laptop-migration-design.md b/docs/superpowers/specs/2026-05-20-work-laptop-migration-design.md deleted file mode 100644 index acb14a4..0000000 --- a/docs/superpowers/specs/2026-05-20-work-laptop-migration-design.md +++ /dev/null @@ -1,114 +0,0 @@ -# Work Laptop Migration Design - -**Status:** Approved for implementation. - -**Issue:** [#39](https://github.com/edwinhern/dotfiles/issues/39) - -## Goal - -Prepare the dotfiles to set up a work laptop with the current shared toolchain, approved work apps, and a work-specific Dock layout. - -## Scope - -- Keep `home/dot_config/mise/config.toml.tmpl` unchanged. -- Keep Python in shared mise tools. -- Keep `uv` in personal mise tools. -- Move `mas` to shared Homebrew formulas so personal and work machines can install App Store apps. -- Add work apps without carrying unused ASDF-era tools forward. -- Refactor Dock setup so personal and work layouts share one helper path. -- Add YouTube Music to the Dock only when the Chrome PWA exists. - -## Package Design - -`home/.chezmoidata/packages.yaml` remains the package source. - -Shared Homebrew formulas keep the existing repo tools, including `mise`, `node` support through mise, `dockutil`, `neovim`, `tmux`, `ripgrep`, `fd`, `fzf`, `lazygit`, and OpenCode. Move `mas` from personal formulas to shared formulas. - -Shared casks remain unchanged for apps already used across personal and work machines, including `google-chrome`, `ghostty`, `superwhisper`, and `visual-studio-code`. - -Work casks add: - -- `microsoft-office` -- `microsoft-teams` -- `slack` - -`microsoft-office` is used instead of a standalone Outlook cask because the Office cask installs Outlook and conflicts with `microsoft-outlook`. - -Work MAS apps add: - -- `Be Focused - Pomodoro Timer`, id `973134470` - -No work package should add Java, Gradle, Maven, Kafka, Postman, Edge, standalone Python, or standalone `uv`. - -## Mise Design - -`home/dot_config/mise/config.toml.tmpl` stays as-is: - -- Shared tools keep `node`, `pnpm`, and `python`. -- Personal tools keep `biome` and `uv`. -- Work tools keep `redis`. - -The work laptop migration should use this existing mise setup instead of adding ASDF compatibility. - -## Dock Design - -`home/.chezmoitemplates/lib/darwin/defaults.sh` should replace the hard-coded Dock list with small helper functions. - -Finder and Trash are not added by the script because macOS keeps them anchored in the Dock. The script only manages the app items between them. - -The helper should: - -- Remove all existing Dock items once with `dockutil --no-restart --remove all`. -- Add apps in order only when the app path exists. -- Skip missing optional apps without failing. -- Add the Chrome PWA path `$HOME/Applications/Chrome Apps.localized/YouTube Music.app` after Google Chrome only when it exists. -- Restart Dock once after the Dock list is built. - -Personal Dock layout keeps the current personal order with Apple Music removed because the YouTube Music Chrome PWA is preferred: - -- `/System/Applications/Apps.app` -- `/System/Applications/System Settings.app` -- `/System/Applications/Utilities/Activity Monitor.app` -- `/Applications/Google Chrome.app` -- optional `$HOME/Applications/Chrome Apps.localized/YouTube Music.app` -- `/Applications/Visual Studio Code.app` -- `/Applications/Ghostty.app` -- `/Applications/Discord.app` -- `/System/Applications/Mail.app` -- `/System/Applications/Notes.app` -- `/System/Applications/Reminders.app` -- `/Applications/superwhisper.app` - -Work Dock layout starts from the personal layout and replaces Discord and Mail with work communication apps: - -- `/System/Applications/Apps.app` -- `/System/Applications/System Settings.app` -- `/System/Applications/Utilities/Activity Monitor.app` -- `/Applications/Google Chrome.app` -- optional `$HOME/Applications/Chrome Apps.localized/YouTube Music.app` -- `/Applications/Visual Studio Code.app` -- `/Applications/Ghostty.app` -- `/Applications/Microsoft Outlook.app` -- `/Applications/Microsoft Teams.app` -- `/Applications/Slack.app` -- `/System/Applications/Notes.app` -- `/System/Applications/Reminders.app` -- `/Applications/superwhisper.app` - -If neither personal nor work context is set, the script should use the personal Dock layout because the chezmoi config already defaults unknown non-interactive hosts to personal. - -## Testing - -- Update `tests/unit/lib/darwin/defaults.bats` to verify Dock setup calls the helper flow and adds expected personal apps. -- Add work-context coverage for Outlook, Teams, and Slack in the Dock helper tests. -- Add package-rendering coverage for `microsoft-office`, `microsoft-teams`, `slack`, shared `mas`, and work MAS id `973134470`. -- Verify shell syntax for rendered defaults scripts. -- Run `mise check`. - -## Out of Scope - -- Installing the YouTube Music Chrome PWA automatically. -- Adding the unofficial `ytmdesktop-youtube-music` cask. -- Adding ASDF compatibility. -- Adding unused ASDF-era tools such as Java, Gradle, Maven, Kafka, or standalone `uv` for work. -- Changing OpenCode, APM, LazyVim, or tmux configuration. diff --git a/docs/superpowers/specs/2026-05-21-linear-migration-design.md b/docs/superpowers/specs/2026-05-21-linear-migration-design.md deleted file mode 100644 index 0406dd3..0000000 --- a/docs/superpowers/specs/2026-05-21-linear-migration-design.md +++ /dev/null @@ -1,239 +0,0 @@ -# Linear Migration Design - -**Status:** Approved for implementation. - -**Branch:** `main` - -**Linear team:** `dotfiles` (`DOT`) - -## Context - -This repository currently uses GitHub Issues as the work item source of truth and a GitHub Project named `dotfiles` as the visual board. The current repo instructions also tell agents to select work from the GitHub Project `Priority` field, move GitHub Project items to `In Progress`, and link Superpowers specs or plans from the related GitHub issue. - -The user wants to move the planning source of truth to Linear for hands-on Linear experience and better board tooling while keeping GitHub for code, pull requests, and CI. - -The Linear GitHub importer has already imported `edwinhern/dotfiles` into the Linear team `dotfiles` with key `DOT`. Read-only verification confirmed: - -- Linear CLI is available to local agent processes through mise. -- The visible Linear team is `dotfiles` with key `DOT`. -- Linear has 20 issues for this team: 10 active issues and 10 archived completed issues. -- The 10 archived completed issues correspond to the closed GitHub issues. -- The 6 imported open GitHub issues are active in `Backlog`. -- Linear also has 4 completed onboarding issues created by Linear. - -## Goal - -Move this repo's planning workflow from GitHub Issues and GitHub Projects to Linear while preserving imported history, keeping Superpowers specs and plans in the repo, and keeping GitHub as the code and CI platform. - -## Non-Goals - -- Do not use Linear MCP. -- Do not build a Linear OAuth app or app-actor agent in this migration. -- Do not keep GitHub Issues as an active second source of truth. -- Do not create a custom Linear priority field. -- Do not commit or expose the Linear API key. -- Do not replace GitHub pull requests, GitHub Actions, or release history. - -## Recommended Approach - -Use Linear's native GitHub importer for the historical migration, then run a focused cleanup pass with Linear-native issue properties. - -The importer preserves more issue history than a custom API import, including issue descriptions, comments, labels, sub-issues where supported, and completed issue history. The cleanup pass should fix the pieces GitHub Project imports do not carry cleanly: priority, exact active status, and any desired label naming. - -Repo agent instructions should point agents at Linear through the mise-managed `linear` CLI. Repo-local skills should document CLI workflows only. - -## Linear Structure - -### Team - -Use one Linear team for this repository: - -- Name: `dotfiles` -- Key: `DOT` - -Future Linear issue IDs will use this key, such as `DOT-7`. - -### Workflow States - -Use Linear workflow states instead of GitHub Project status fields. - -Current verified states include: - -- `Backlog` (`backlog`) -- `Todo` (`unstarted`) -- `In Progress` (`started`) -- `In Review` (`started`) -- `Done` (`completed`) -- `Canceled` (`canceled`) -- `Duplicate` (`duplicate`) - -Agent work should use: - -- `Backlog` or `Todo` for not-started work. -- `In Progress` for active implementation. -- `In Review` when a PR exists but work is not merged. -- `Done` when the work is complete. - -### Priority - -Use Linear's native issue priority. - -Mapping from the GitHub Project priority field: - -- GitHub `High` maps to Linear `High`. -- GitHub `Normal` maps to Linear `Medium`. -- GitHub `Low` maps to Linear `Low`. - -Linear also supports `Urgent`, but this repo should reserve it for security, broken reproducibility, or a blocked daily workflow that needs immediate attention. - -### Labels - -Keep area as labels because Linear does not have a native area field. - -Current imported labels include: - -- `area:apm` -- `area:agents` -- `area:chezmoi` -- `area:ci` -- `area:docs` -- `area:editor` -- `enhancement` -- `priority:high` -- `Migrated` - -Recommended cleanup: - -- Keep area labels as the repo domain filter. -- Remove or stop using `priority:high` once Linear native priority is set. -- Keep `Migrated` if it is useful for audit, otherwise remove it after verification. -- Optionally rename `area:*` to `area/...` later if Linear label grouping is configured. - -### Projects And Hierarchy - -Do not recreate GitHub Projects as Linear Projects. - -Use Linear Projects only for outcome-based work that spans multiple issues. Use labels for areas, and use sub-issues for implementation breakdown under one issue. - -Initial project candidates if they become useful: - -- `APM secrets handling` -- `CI maintenance` -- `Agent workflow` -- `Editor workflow` -- `Chezmoi migration` - -Skip Initiatives for now. Add them later only if multiple Linear Projects need higher-level grouping. - -## Current Import State - -The importer created active issues for the six open GitHub issues, but they currently have Linear priority `0` and state `Backlog`. - -Cleanup should map them as follows: - -| Linear issue | GitHub issue | Title | Target priority | Target state | -| ------------ | ------------ | ----------------------------------------------- | --------------- | ------------ | -| `DOT-7` | `#18` | Design Bitwarden-backed APM secrets handling | High | In Progress | -| `DOT-6` | `#17` | Decide APM audit governance needs | Medium | Backlog | -| `DOT-11` | `#22` | Evaluate reducing the lint toolchain | Medium | Backlog | -| `DOT-5` | `#16` | Decide whether a business APM package is needed | Low | Backlog | -| `DOT-9` | `#20` | Evaluate mise tool caching in CI | Low | Backlog | -| `DOT-10` | `#21` | Evaluate docs-only CI gating | Low | Backlog | - -The archived completed issues verified through Linear are: - -- `DOT-8`: Remove curl-installed chezmoi from CI -- `DOT-12`: Set up issue and project planning workflow -- `DOT-13`: Add priority-based task selection workflow -- `DOT-14`: Create repo-local post-merge cleanup skill -- `DOT-15`: Set up LazyVim tmux opencode workflow -- `DOT-16`: Add opencode.nvim integration -- `DOT-17`: Integrate zsh CLI tool helpers -- `DOT-18`: Improve LazyVim and tmux workflow -- `DOT-19`: Prepare work laptop migration -- `DOT-20`: Make work APM MCP servers data-driven - -## Authentication And Tooling - -Use the `linear` CLI for local agent automation. - -Rules: - -- Install `npm:@schpet/linear-cli` through mise for personal hosts. -- Prefer `linear` when it is on `PATH`; otherwise run `mise exec -- linear`. -- Use `linear issue query --team DOT --include-archived --json` for issue verification. -- Use `jq` to inspect CLI JSON before trusting the result. - -OAuth is a later project only if the repo needs a native Linear app actor, assignment handling, webhooks, or Codex-style agent progress updates. - -## Agent Workflow - -Agents working in this repo should treat Linear as the planning source of truth, regardless of which agent reads the repo guidance. - -When starting work: - -1. Query Linear team `dotfiles` (`DOT`) for active issues. -2. Prefer `High`, then `Medium`, then `Low` priority. -3. If priorities tie, choose security and reproducibility work first, then blockers, then the smallest clear issue. -4. If priority is missing, set it or ask one question before implementation starts. -5. Move the selected Linear issue to `In Progress` before code changes. -6. Link the related Superpowers spec or plan from the Linear issue description or a Linear comment before implementation starts. - -During PR work: - -1. Keep GitHub as the code review and CI platform. -2. Include the Linear issue ID in the branch, PR title, or PR body. -3. Use closing words such as `Fixes DOT-7` when merging should complete the Linear issue. -4. After merge, verify GitHub CI and Linear issue status. - -## Repo Changes Needed - -Implementation should update repo guidance and automation around Linear: - -- Update `AGENTS.md` so Linear replaces GitHub Issues and GitHub Projects as the work tracking source of truth. -- Update the post-merge cleanup skill so it verifies Linear issue status instead of GitHub Project item status. -- Add Linear CLI to the personal mise config and cover it with a template test. -- Render APM dependencies from `home/.chezmoidata/apm.yaml` so shared packages stay shared and `schpet/linear-cli` stays personal-only. -- Remove or de-emphasize GitHub issue/project commands from the active planning workflow. - -Install `npm:@schpet/linear-cli` through mise for personal hosts so CLI-backed workflows can use `linear` or `mise exec -- linear`. Install the APM `schpet/linear-cli` skill dependency only for personal hosts. Do not add a Homebrew tap or formula for Linear. - -## Migration And Cutover - -The import is already complete, so cutover should focus on validation and instruction changes. - -1. Verify the importer preserved issue descriptions, comments, and labels for a sample of active and archived issues. -2. Set Linear native priority on the six imported open issues. -3. Move `DOT-7` to `In Progress` because it was active in the GitHub Project. -4. Keep the other five imported open issues in `Backlog` until they are selected for work. -5. Stop using GitHub Issues for new work. -6. Update repo agent instructions to point agents at Linear. -7. Use GitHub PR integration and Linear issue IDs for future code changes. -8. Keep long-term GitHub Issues sync disabled unless there is a later explicit reason to turn it on. -9. After Linear is verified as the source of truth, close the remaining open GitHub issues with a migration note that points to the matching Linear issue. -10. Archive or delete the GitHub Project named `dotfiles` so stale board data is no longer visible as a parallel backlog. - -GitHub should remain responsible for code, pull requests, CI, releases, and old PR links. GitHub Issues and the GitHub Project should not remain visible as a parallel backlog after cutover. - -## Error Handling - -If an agent process cannot run `linear`, retry through `mise exec -- linear`. If that fails on a non-personal host, stop and rerun the Linear-dependent workflow from a personal host because Linear CLI is intentionally personal-only. - -If importer data is broadly wrong, use Linear's importer rollback window before doing manual repair. - -If only priority, area labels, or active status are wrong, repair them in Linear UI or through the Linear CLI without rolling back the import. - -If GitHub PR linking does not update Linear as expected, verify the GitHub integration settings and use explicit issue IDs or closing words in the PR body. - -## Verification - -Verify the migration and implementation with: - -- `linear --version` or `mise exec -- linear --version`. -- `linear issue query --team DOT --include-archived --json` checks for active and archived issues. -- A sample check of at least one active issue and one archived completed issue in the Linear UI. -- The exact UI filter, saved view, or CLI query used to inspect archived migrated issues if the default Linear view hides them. -- `git diff --check` -- `mise lint` - -The work is complete when Linear is the documented source of truth, the six open migrated issues have correct native Linear priority and status, archived completed issues are searchable, repo agent guidance points to Linear, stale GitHub tracking surfaces are cleaned up, and GitHub remains responsible for PRs and CI. diff --git a/docs/superpowers/specs/2026-06-23-personal-ai-tools-design.md b/docs/superpowers/specs/2026-06-23-personal-ai-tools-design.md deleted file mode 100644 index 5559a96..0000000 --- a/docs/superpowers/specs/2026-06-23-personal-ai-tools-design.md +++ /dev/null @@ -1,93 +0,0 @@ -# Personal AI Tools Design - -## Goal - -Set up personal-only AI tooling for the Claude Code migration without adding those tools to work hosts. - -## Scope - -- Install personal uv tools for `graphifyy` and `tavily-cli`. -- Install `tavily-ai/skills` through APM for personal hosts. -- Write personal APM skills to `~/.agents/skills` by adding the `agent-skills` target. -- Keep work APM target behavior limited to `copilot`. -- Update Claude Code settings so model choices follow current aliases instead of pinned old model IDs. -- Bring Claude Code bash permissions closer to the existing OpenCode bash allowlist while keeping existing deny and ask safeguards. - -## Current Repo Shape - -- `home/.chezmoidata/mise.yaml` already gates `uv` to personal hosts. -- `home/.chezmoidata/apm.yaml` already gates APM targets and dependencies by `shared`, `personal`, and `work` groups. -- `home/dot_apm/apm.yml.tmpl` renders APM targets, dependencies, and MCP servers from those groups. -- `home/.chezmoiscripts/darwin/run_onchange_03_install-mise-tools.sh.tmpl` installs mise tools before the APM install script runs. -- `home/dot_claude/settings.json` is the user-scoped Claude Code settings source. -- `home/dot_config/opencode/opencode.jsonc` is the user-scoped OpenCode settings source. - -## Decisions - -### APM Targets - -Personal APM should target both `claude` and `agent-skills`. - -Context7 docs for `/microsoft/apm` confirm that `claude` writes skills to `.claude/skills`, while `agent-skills` writes skill bundles to `.agents/skills`. `agent-skills` is not auto-detected, so it must be explicit in the personal target list. - -This keeps Claude Code agents and instructions installed for Claude Code, while skill bundles like `tavily-ai/skills` are also available from the vendor-neutral skills path. - -### OpenCode Impact - -Do not add `opencode` as an APM target. Some APM agent packages carry Claude-specific model frontmatter that can break OpenCode when Anthropic model access changes. Skills are safer to share through `~/.agents/skills` because they do not need tool-specific model frontmatter. - -### uv Tool Installs - -Use a new personal-only chezmoi data group for uv tools and a new Darwin run script after mise installs `uv`. - -The script should install each declared uv tool with `uv tool install --upgrade `. This keeps the install repeatable and lets later chezmoi applies pick up package updates. The personal data should use the PyPI package names: - -- `graphifyy`, which provides the `graphify` and `graphify-mcp` commands. -- `tavily-cli`, which provides the `tvly` command. - -Work hosts should not render or run this installer. - -### Claude Code Models - -Use Claude Code model aliases instead of pinned `ANTHROPIC_DEFAULT_*_MODEL` values. - -Preferred setting: - -```json -"model": "opusplan[1m]" -``` - -This keeps planning on the latest Opus alias with a 1M context request and execution on the latest Sonnet alias. Removing the pinned default model environment variables avoids stale values as Opus and Sonnet aliases move forward. Keep other Claude Code environment tuning values unless they are clearly obsolete. - -### Claude Code Permissions - -Keep the existing Claude Code deny list for secrets and destructive commands. Add missing allow entries that mirror common OpenCode bash permissions for local development and dotfiles work, including read-only shell utilities, formatting tools, package managers, GitHub CLI read paths, Docker, Homebrew, and `linear`. - -Do not copy OpenCode rules mechanically. Claude Code evaluates deny, then ask, then allow, so ask and deny rules should stay explicit for force pushes, hard resets, broad removals, sudo, and publish-like operations. - -## Files To Change - -- `home/.chezmoidata/apm.yaml`: add `agent-skills` to personal targets and `tavily-ai/skills` to personal dependencies. -- `home/.chezmoidata/uv.yaml`: add personal uv tool data with `graphifyy` and `tavily-cli`. -- `home/.chezmoiscripts/darwin/run_onchange_03_install-uv-tools.sh.tmpl`: new personal-only Darwin script that renders configured uv tools and injects a shell library. The filename shares the `03` prefix with the mise installer and sorts after it, so `uv` is installed before uv tools are installed. -- `home/.chezmoitemplates/lib/install/uv-tools.sh`: new sourceable shell library to install tools from a `UV_TOOLS` array. -- `tests/template/mise-config.bats`: assert `uv` remains personal-only and work excludes it. -- `tests/template/apm-config.bats`: assert personal renders `claude`, `agent-skills`, and `tavily-ai/skills`; assert work excludes personal targets and dependencies. -- `tests/template/darwin-install-scripts.bats`: assert the new uv tools script renders only for personal context and remains syntactically valid bash. -- `tests/unit/lib/install/uv-tools.bats`: test the uv tool installer behavior using a fake `uv` binary. -- `home/dot_claude/settings.json`: update model aliases and expand bash permission allows. - -## Validation - -- Run `mise exec -- bats tests/template/apm-config.bats tests/template/mise-config.bats tests/template/darwin-install-scripts.bats`. -- Run `mise exec -- bats tests/unit/lib/install/uv-tools.bats`. -- Run `mise test`. -- Run `mise lint`. -- Run `git diff --check`. - -## Out Of Scope - -- Adding personal tools to work hosts. -- Adding OpenCode as an APM target. -- Pinning exact Claude model IDs. -- Managing Tavily API keys or other secrets in chezmoi. diff --git a/docs/superpowers/specs/2026-06-24-ai-tooling-separation-design.md b/docs/superpowers/specs/2026-06-24-ai-tooling-separation-design.md deleted file mode 100644 index cc0310c..0000000 --- a/docs/superpowers/specs/2026-06-24-ai-tooling-separation-design.md +++ /dev/null @@ -1,80 +0,0 @@ -# AI Tooling Separation Design - -## Goal - -Keep Claude Code, OpenCode, APM, and CLI tooling clear enough that new tools install in the right place and agent skills trigger only when they add value. - -Linear issue: DOT-28 - -## Current State - -- Source Claude Code settings use `opusplan[1m]`, alias-based model behavior, and `CLAUDE_CODE_SUBAGENT_MODEL=inherit`. -- Live Claude Code settings still have old exact Anthropic model pins, so the latest chezmoi source has not been applied. -- Source OpenCode config does not include live `small_model`, OpenAI model options, or `opencode-supermemory`. -- Live OpenCode has `opencode-supermemory`, but the user wants to remove it while moving back to Claude Code. -- APM now targets personal `claude` and `agent-skills`, and work `copilot`. -- Personal uv tools include `graphifyy` and `tavily-cli`. - -## Tool Ownership Rules - -APM manages portable agent assets: - -- Shared skills that should land in `~/.agents/skills` through the `agent-skills` target. -- Claude-specific APM bundles that should land in `~/.claude` through the `claude` target. -- Work-only Copilot assets through the `copilot` target. -- MCP declarations that are safe across the chosen target. - -APM should not manage tools that mutate host app runtime state through custom installers. - -CLI tools live in package data: - -- `uv.yaml` for uv tools such as `graphifyy` and `tavily-cli`. -- `mise.yaml`, package data, or app-specific files for non-uv tools. - -Claude Code runtime integrations live outside APM unless they publish normal APM skills: - -- Cozempic should be handled as a Claude Code plugin or Claude-only install script. -- Claude-Mem should be handled by its own installer or plugin flow, not as an APM dependency. -- Graphify platform installs should be audited before source-managing generated hooks, plugins, or instructions. - -OpenCode runtime integrations live in OpenCode source files: - -- Plugins belong in `home/dot_config/opencode/opencode.jsonc` and package files if needed. -- OpenCode-specific generated files should not be mixed with Claude Code hooks. -- `opencode-supermemory` should be removed from live state and kept out of source. - -## Skill Triggering Policy - -Skills should be installed only when their `description` has a clear trigger and the skill does not conflict with the repo workflow. - -For JuliusBrussee skills: - -- Add `junior-to-senior` if it can be installed without also installing the whole repo. It is useful when reviewing plans, architecture, and design decisions. -- Add `grill-me` if it can be installed without also installing the whole repo. It is useful when stress-testing a decision before implementation. -- Consider `interface-kit` only if frontend/UI work is common enough to justify another UI skill. It should not be installed just because it exists. -- Do not add `loop-factory` by default because Linear and Superpowers already own task flow. -- Do not add `context-canary` by default because it changes every response format. -- Do not add `fuck-slop` by default because the skill name and trigger style may not be desired globally. -- Do not add `caveman` again because it is already present. - -## Cozempic Versus Claude-Mem - -Use Cozempic first if the immediate problem is Claude Code context bloat and compaction hygiene. - -Defer Claude-Mem because it is a larger memory platform with hooks, worker service, database, search UI, and cross-agent behavior. It may be useful later, but it should not be added while removing OpenCode Supermemory and cleaning the base Claude Code setup. - -## Conservative First Pass - -1. Reconcile source and live config without reintroducing OpenCode Supermemory. -2. Add only selected Julius skills if APM supports selecting individual skills from `JuliusBrussee/skills`. -3. Audit Graphify generated files before source-managing any `graphify install` output. -4. Defer Cozempic and Claude-Mem installation until Claude Code settings and installed skills are clean. -5. Run `mise update` only after source and live state are understood enough to avoid losing current preferences. - -## Validation - -- Run targeted template/unit tests for changed dotfiles. -- Run `mise test`. -- Run `mise lint`. -- Run `git diff --check`. -- Compare source and live Claude/OpenCode skill, agent, plugin, and settings state after `mise update`. diff --git a/docs/superpowers/specs/2026-06-24-graphify-global-agent-install-design.md b/docs/superpowers/specs/2026-06-24-graphify-global-agent-install-design.md deleted file mode 100644 index 18a8b1e..0000000 --- a/docs/superpowers/specs/2026-06-24-graphify-global-agent-install-design.md +++ /dev/null @@ -1,95 +0,0 @@ -# Graphify Global Agent Install Design - -## Goal - -Install Graphify's global agent skills on personal machines so Claude Code and cross-framework agents can use `/graphify` in any repo, while keeping work machines free of personal Graphify setup. - -## Current State - -- `home/.chezmoi.yaml.tmpl` sets boolean context flags: `.personal` or `.work`. -- `home/.chezmoidata/apm.yaml` already uses the same grouped data shape for `shared`, `personal`, and `work`. -- Personal APM targets are `claude` and `agent-skills`. -- Work APM target is `copilot`. -- `home/.chezmoidata/uv.yaml` installs `graphifyy` only for personal machines. -- Several templates repeat the same group-selection logic: start with `shared`, append `personal` if `.personal`, append `work` if `.work`. -- Graphify's README separates CLI installation from assistant skill registration. `graphify install --platform agents` installs into the global Agent Skills location, and `graphify install --platform claude` installs the user-scope Claude skill. - -## Chezmoi Helper Templates - -Add small reusable templates under `home/.chezmoitemplates/lib/chezmoi/`: - -- `active-groups.json.tmpl` renders a JSON array of active groups, always including `shared` and then including `personal` or `work` based on the current chezmoi data. -- `active-group-values.json.tmpl` accepts a grouped map such as `.apm.targets`, walks the active groups, and renders one JSON array with the values from those groups. - -Callers parse helper output with `fromJson`, which is supported by chezmoi. This keeps the helper output typed after parsing and avoids shell parsing for template-time decisions. - -Example use: - -```gotemplate -{{- $targets := includeTemplate "lib/chezmoi/active-group-values.json.tmpl" (dict "ctx" . "valuesByGroup" .apm.targets) | fromJson -}} -``` - -## Graphify Skill Install Script - -Add a Darwin run-on-change script after the uv tool install script: - -- Render only when `.personal` is true and the mapped platform list is not empty. -- Derive targets from `.apm.targets` through the helper templates. -- Map `agent-skills` to Graphify platform `agents`. -- Map `claude` to Graphify platform `claude`. -- Ignore `copilot` and all unknown APM targets. -- Run `graphify install --platform "${platform}"` for each mapped platform. -- Check that `graphify` is present on `PATH`; if not, fail with a clear message pointing at the uv tool install script. - -The script must not run any per-repo setup commands: - -- Do not run `graphify .`. -- Do not run `graphify hook install`. -- Do not run `graphify claude install` or `graphify opencode install`, because those mutate the current repo. -- Do not configure MCP, because Graphify MCP needs a specific graph path. - -## Managed Instructions - -Chezmoi remains the owner of global Claude and OpenCode instruction files. Add a short Graphify section to `home/.chezmoitemplates/AGENTS.md` so both rendered files keep stable guidance even if Graphify's installer rewrites or appends its own user-scope block. - -The shared instruction should say: - -- Use the installed Graphify skill when the user invokes `/graphify`. -- If `graphify-out/graph.json` exists, prefer `graphify query`, `graphify path`, or `graphify explain` before raw file search for codebase questions. -- Read `GRAPH_REPORT.md` only for broad architecture review or when graph commands do not answer the question. -- Treat `graphify-out/` as per-repo data and do not create it from a global setup script. - -## Source Files - -Expected implementation files: - -- `home/.chezmoitemplates/lib/chezmoi/active-groups.json.tmpl` -- `home/.chezmoitemplates/lib/chezmoi/active-group-values.json.tmpl` -- `home/.chezmoitemplates/lib/install/graphify-skills.sh` -- `home/.chezmoiscripts/darwin/run_onchange_08_install-graphify-skills.sh.tmpl` -- `home/.chezmoitemplates/AGENTS.md` -- `tests/template/darwin-install-scripts.bats` -- `tests/template/agent-instructions.bats` -- `tests/unit/lib/install/graphify-skills.bats` - -Existing APM and uv data files should remain the source of truth. The Graphify script should not add a separate Graphify target list unless the APM target model proves insufficient. - -## Testing - -Template tests should verify: - -- Personal data renders Graphify platforms `agents` and `claude`. -- Work data renders no Graphify skill install commands. -- Empty groups produce no commands. -- Rendered Darwin scripts remain valid bash. -- Claude and OpenCode instruction templates both render the Graphify guidance. - -Unit tests should verify: - -- Missing `graphify` returns a non-zero status and logs the dependency hint. -- `GRAPHIFY_PLATFORMS=("agents" "claude")` runs the expected `graphify install --platform ...` commands. -- Empty `GRAPHIFY_PLATFORMS` exits cleanly. - -## Follow-Up Policy - -After this change, per-repo Graphify setup remains explicit. Agents can run `/graphify .` or `graphify .` inside a target repo when requested, and `graphify hook install` should only be suggested after that repo has a graph. diff --git a/docs/superpowers/specs/2026-06-24-local-apm-validation-design.md b/docs/superpowers/specs/2026-06-24-local-apm-validation-design.md deleted file mode 100644 index 020731f..0000000 --- a/docs/superpowers/specs/2026-06-24-local-apm-validation-design.md +++ /dev/null @@ -1,208 +0,0 @@ -# APM Reproducibility and Validation Design - -**Status:** Draft for review (revised to a reproducibility-first approach). - -**Linear issue:** [DOT-31](https://linear.app/edwinhern/issue/DOT-31/add-local-apm-validation) - -## Goal - -Make APM setup reproducible and verifiable. A `chezmoi apply` on any machine should install the exact same agent dependencies every time, and a single local-or-CI command should prove that the committed state reproduces without writing to the real home directory. - -## Root Problem (why the original validation-only design was insufficient) - -The first draft added a validation script but left the underlying reproducibility holes in place: - -- APM dependencies in `home/.chezmoidata/apm.yaml` are floating refs (`obra/superpowers`, `JuliusBrussee/caveman`, ...) with no `#tag`/`#sha`. APM's own installer warns: _"dependencies unpinned ... add #tag or #sha to prevent drift."_ -- No `apm.lock.yaml` is committed. -- The real run-script `home/.chezmoitemplates/lib/install/apm.sh` runs bare `apm install --global`, which re-resolves "whatever is on the default branch today" on every apply. - -A validation that runs `apm install --global` then `apm install --global --frozen` against that just-generated lockfile only proves today's resolution is internally consistent. It cannot catch upstream drift, which is the actual risk. Reproducibility must be fixed upstream of validation. - -## Confirmed APM 0.21.0 behavior - -Verified against the pinned binary and `/microsoft/apm` docs: - -- `apm install -g | --global` installs to user scope `~/.apm/`. -- `apm install --frozen` is the npm-ci-style gate: refuses to resolve, fails with status 1 when `apm.yml` and `apm.lock.yaml` have drifted, and requires the lockfile to be present. -- Dependency pinning syntax is `owner/repo#`. -- The lockfile lives at the project root next to `apm.yml`; for `--global` that is `~/.apm/apm.lock.yaml`. Docs say to always commit it. -- `apm audit` has no `--global` flag; audit is project-scope. `apm audit --ci` runs lockfile-consistency checks. -- Official recommended CI gate: `apm install --frozen` then `apm audit --ci`. -- Version caveat: in 0.21.0 `apm update` is deprecated and forwarded to `apm self-update` (the CLI binary updater), so it does not rewrite a lockfile. The command that re-resolves refs to latest SHAs and rewrites the user-scope lockfile is `apm lock --update --global`. (The `apm update` dependency-refresh behavior described in the upstream `main` docs is not in 0.21.0.) - -## Approach (two layers) - -APM uses the npm two-file model. The manifest ref is the update channel; the lockfile is the exact pin. Reproducibility comes from the committed lockfile plus `--frozen`, not from hard-pinning the manifest. - -1. **Set each dependency's manifest ref as a channel** in `home/.chezmoidata/apm.yaml`: a release tag for upstreams that publish releases (moves only on a new tag), or the default branch for rolling repos. Do not hard-pin the manifest to a `#sha`; a fixed SHA is the "latest matching ref" forever, so `apm update` can never advance it. The exact SHA is recorded in the lockfile. -2. **Commit per-context lockfiles.** Because `apm.yml` is context-templated (personal and work differ), generate one lockfile per context and commit both. The lockfile holds the resolved SHA and content hash, so it is the reproducibility pin. -3. **Switch the real run-script** `home/.chezmoitemplates/lib/install/apm.sh` to `apm install --global --frozen` so `chezmoi apply` reproduces the locked SHAs instead of re-resolving. - -Drift control: `apm install --frozen` exits non-zero when manifest and lockfile disagree, so between updates every apply installs the exact locked SHAs and nothing drifts. - -### Layer 2 - Validation - -Keep the well-designed mechanics from the first draft and point the gate at the committed lockfile: - -- One repo-owned entrypoint `scripts/validate-apm.sh` accepting `personal`, `work`, or `all` (default `all`). -- For each context: create a temp root and temp home, write the CI chezmoi stub config, then apply only the `~/.apm` target into the temp home so reproducible files (including the lockfile) land via the real source-to-target mapping without firing run scripts. -- Run the gate in an isolated temp home: `apm install --global --frozen` then `apm audit --ci`. The frozen install proves the committed lockfile matches the committed (templated) `apm.yml`; the audit checks lockfile consistency. - -## File Organization - -``` -home/.chezmoidata/apm.yaml # deps pinned by #tag/#sha (source of truth) -home/dot_apm/apm.yml.tmpl # unchanged, context-templated manifest -home/.chezmoitemplates/apm/apm.lock.personal.yaml # committed lockfile, not a deploy target -home/.chezmoitemplates/apm/apm.lock.work.yaml # committed lockfile, not a deploy target -home/dot_apm/apm.lock.yaml.tmpl # selects the context lockfile -> ~/.apm/apm.lock.yaml -home/.chezmoitemplates/lib/install/apm.sh # runtime: apm install --global --frozen -scripts/apm-lib.sh # shared: materialize a context's ~/.apm into a temp home -scripts/validate-apm.sh # dev/CI validation entrypoint (sources apm-lib.sh) -scripts/refresh-apm-locks.sh # regenerate committed lockfiles (sources apm-lib.sh) -mise.toml # validate-apm[-personal|-work] + refresh-apm-locks tasks -.github/workflows/ci.yaml # validate_apm job calls the mise task -``` - -This preserves the repo's existing split: `lib/install/` holds runtime libraries injected into chezmoi run-scripts; `scripts/` holds dev/CI tooling (already a lint root in `mise.toml`). Lockfiles live under `.chezmoitemplates/` so chezmoi never deploys them as their own target files; `apm.lock.yaml.tmpl` selects the right one: - -```gotemplate -{{- if .personal }}{{ includeTemplate "apm/apm.lock.personal.yaml" . }} -{{- else if .work }}{{ includeTemplate "apm/apm.lock.work.yaml" . }}{{ end -}} -``` - -## Lockfile Generation (`refresh-apm-locks`) - -`scripts/refresh-apm-locks.sh` regenerates the committed lockfiles when dependencies change. For each context it renders `apm.yml` into a temp `~/.apm`, runs `apm lock --update --global` (re-resolve refs to latest SHAs and rewrite the lockfile) to produce `~/.apm/apm.lock.yaml`, then copies that file back to `home/.chezmoitemplates/apm/apm.lock..yaml`. - -The temp-home materialization (resolve pinned tools, write stub config, `chezmoi apply` the `~/.apm` target) is identical to what validation needs, so it lives once in `scripts/apm-lib.sh` and is sourced by both `scripts/validate-apm.sh` and `scripts/refresh-apm-locks.sh`. The scheduled `apm_update` CI workflow calls `refresh-apm-locks.sh` rather than re-implementing the loop. This keeps a single materialization implementation, the same anti-drift principle applied to the validation entrypoint. Both scripts resolve pinned tool paths before changing `HOME` (see Script Safety Rules) and never touch the real home directory. - -Note: the lockfile carries a `generated_at` timestamp, so regeneration produces a diff even when resolved commits are unchanged. That is expected. - -## Chezmoi Materialization (validation) - -For each context the validation script resolves pinned tools first, then writes the stub config and applies only the `~/.apm` target: - -```sh -chezmoi_bin="$(mise which chezmoi)" -apm_bin="$(mise which apm)" - -HOME="$tmp_home" "$repo/.github/actions/write-chezmoi-config/write-chezmoi-config.sh" "$context" - -"$chezmoi_bin" apply \ - --source "$repo" \ - --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" -``` - -`--include files,dirs` (omitting `scripts`) is what keeps Darwin run scripts from firing while still using the real source-to-target mapping. After apply, verify these exist: `$tmp_home/.apm/apm.yml`, `$tmp_home/.apm/.apm`, and `$tmp_home/.apm/apm.lock.yaml`. - -## APM Validation Gate - -Run with user-scope semantics and isolated XDG paths: - -```sh -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 - -HOME="$tmp_home" \ -XDG_CACHE_HOME="$tmp_home/.cache" XDG_CONFIG_HOME="$tmp_home/.config" XDG_STATE_HOME="$tmp_home/.local/state" \ -"$apm_bin" audit --ci --no-policy -``` - -The frozen install fails if the committed lockfile drifted from the committed manifest. The audit checks lockfile consistency. If either exits non-zero, validation for that context fails. - -## `all` semantics - -`scripts/validate-apm.sh all` must run both contexts and report each independently, then exit non-zero if any failed. Do not let `set -e` short-circuit the loop after the first failure; capture each context exit code, print a labeled pass/fail line per context, and aggregate. This satisfies the requirement to show whether `personal` or `work` failed. - -## Mise Tasks - -- `validate-apm-personal`: `scripts/validate-apm.sh personal`. -- `validate-apm-work`: `scripts/validate-apm.sh work`. -- `validate-apm`: `scripts/validate-apm.sh all`. -- `refresh-apm-locks`: regenerate both committed lockfiles. - -Keep these explicit; APM validation is networked and slower than template or unit tests, so it is not added to `mise check` by default. - -## Update Lifecycle and Automation - -Updates and reproducibility are separate concerns served by separate commands: - -- `apm install --global --frozen` (runtime apply and the CI gate) never fetches newer code; it installs the lockfile's resolved SHAs. -- `apm lock --update --global` re-resolves each manifest ref to its latest matching SHA and rewrites the lockfile. This is the command that advances versions in 0.21.0 (not `apm update`, which forwards to `self-update`). - -Local refresh loop (human, when intentionally updating): - -```sh -apm lock --update --global # re-resolve refs + rewrite ~/.apm/apm.lock.yaml -apm install --global --frozen # materialize the new lockfile -apm audit --ci --no-policy # confirm integrity -``` - -Dependabot does not understand APM, so split responsibilities: - -- Keep Dependabot for the GitHub Actions ecosystem it already manages (`actions/checkout`, `jdx/mise-action`, ...) via `.github/dependabot.yaml`. -- Add a scheduled `apm_update` GitHub Actions workflow as the APM analog. On a weekly cron it runs `refresh-apm-locks` (which calls `apm lock --update --global` per context in an isolated temp home and copies the regenerated lockfiles back into the chezmoi source) and opens a pull request only when a lockfile changed. The PR triggers the normal `validate_apm` job, so `apm install --frozen` + `apm audit --ci` gate the refresh before a human reviews and merges. Agent dependencies change assistant behavior, so the merge stays manual. - -This gives a Dependabot-style "here is what moved" PR flow while keeping the `--frozen` reproducibility gate on the update itself. - -## CI Design - -Update the `validate_apm` job so each matrix entry calls the shared entrypoint instead of owning custom render, copy, and install steps: - -```yaml -matrix: - context: [personal, work] -``` - -```sh -mise validate-apm-${{ matrix.context }} -``` - -This ties local and CI behavior to one script and removes the project-copy hack currently in the workflow. - -APM validation is networked (a `--frozen` install still fetches the locked SHAs). Cache the APM cache directory keyed on a hash of the committed lockfiles so unchanged locks skip re-fetching. Use `apm audit --ci --no-policy`; org policy is experimental and irrelevant here. - -## Out of Scope - -- Adding networked APM validation to `mise check` by default. -- Changing APM MCP server definitions or the set of dependencies (only their pins change). -- Org-policy audit (`apm audit --policy`); `--no-policy` is used. -- Full drift audit as the primary gate; the `--frozen` install is the primary gate, `apm audit --ci` is the secondary consistency check. - -## Script Safety Rules - -- `set -euo pipefail`; shdoc-compatible English comments. -- Create temp dirs with `mktemp -d`; remove them via a trap unless `DOTFILES_KEEP_APM_VALIDATION_TMP` is set. -- Never run a broad `chezmoi apply`; always target `~/.apm`. -- Never set `HOME` before resolving the pinned `chezmoi` and `apm` binaries. -- Print context labels so a reader can see whether `personal` or `work` failed. - -## Validation Plan - -- Pin deps, regenerate both lockfiles with `mise refresh-apm-locks`, commit them. -- Run `mise validate-apm-personal`, `mise validate-apm-work`, and `mise validate-apm`. -- Confirm a deliberately edited lockfile makes `--frozen` fail (negative test). -- Run `mise test-template` so existing APM template tests still pass, plus a new test for `apm.lock.yaml.tmpl` context selection. -- Run `mise lint` so the new script passes shellcheck and shfmt. - -## Acceptance Criteria - -- Each APM dependency manifest ref is a deliberate channel (release tag or default branch), not a hard SHA. -- Per-context `apm.lock.yaml` files are committed and selected correctly by context, and are the reproducibility pin. -- A scheduled `apm_update` workflow opens a PR when `apm update --yes` changes a lockfile, and that PR is gated by the `--frozen` validation. -- Dependabot continues to manage the GitHub Actions ecosystem; APM updates are handled by the scheduled workflow. -- The real run-script installs with `--frozen`, so `chezmoi apply` reproduces the locked state. -- One local command validates each context; `all` runs both and reports each. -- CI uses the same entrypoint as local development. -- Validation uses chezmoi to create `~/.apm` (manifest and lockfile) rather than manually rendering and copying. -- Validation never writes to the real home directory and never runs Homebrew, APM install scripts, or other chezmoi run scripts while preparing the temp home. 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 deleted file mode 100644 index 6356b87..0000000 --- a/docs/superpowers/specs/2026-06-30-apm-to-claude-marketplace-migration-design.md +++ /dev/null @@ -1,235 +0,0 @@ -# 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/.chezmoitemplates/lib/install/ai-skills.sh b/home/.chezmoitemplates/lib/install/ai-skills.sh index b0875d9..7c37b45 100644 --- a/home/.chezmoitemplates/lib/install/ai-skills.sh +++ b/home/.chezmoitemplates/lib/install/ai-skills.sh @@ -114,11 +114,9 @@ function main() { } if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - # When run directly, pull in the shared libraries that chezmoi otherwise + # 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" - # 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/homebrew.sh b/home/.chezmoitemplates/lib/install/homebrew.sh index 164c972..620a4a6 100644 --- a/home/.chezmoitemplates/lib/install/homebrew.sh +++ b/home/.chezmoitemplates/lib/install/homebrew.sh @@ -5,6 +5,7 @@ # Installs Homebrew from the official installation script. This file is sourceable # from bats tests and injected into chezmoi run scripts via chezmoi template # rendering. + set -Eeuo pipefail # diff --git a/home/.chezmoitemplates/lib/install/uv-tools.sh b/home/.chezmoitemplates/lib/install/uv-tools.sh index b1ca97f..3625a67 100644 --- a/home/.chezmoitemplates/lib/install/uv-tools.sh +++ b/home/.chezmoitemplates/lib/install/uv-tools.sh @@ -14,7 +14,7 @@ set -Eeuo pipefail function uv_tools_install_main() { log_info "[uv] Installing uv tools..." - local failed=() + local -a failed=() local tool for tool in "${UV_TOOLS[@]-}"; do [[ -z "${tool}" ]] && continue diff --git a/home/.chezmoitemplates/lib/install/vscode.sh b/home/.chezmoitemplates/lib/install/vscode.sh index ec10482..e290bf6 100644 --- a/home/.chezmoitemplates/lib/install/vscode.sh +++ b/home/.chezmoitemplates/lib/install/vscode.sh @@ -16,7 +16,7 @@ function vscode_install_extensions_main() { log_info "[vscode] Installing VS Code extensions..." - local failed=() + local -a failed=() local extension for extension in "${VSCODE_EXTENSIONS[@]-}"; do [[ -z "${extension}" ]] && continue diff --git a/home/dot_claude/settings.json b/home/dot_claude/settings.json index e821c10..c99ea3a 100644 --- a/home/dot_claude/settings.json +++ b/home/dot_claude/settings.json @@ -28,6 +28,7 @@ "Bash(brew:*)", "Bash(bun:*)", "Bash(cargo:*)", + "Bash(ctx7 *)", "Bash(uv:*)", "Bash(uvx:*)", "Bash(ruff:*)", diff --git a/home/dot_copilot/agents/research.agent.md b/home/dot_copilot/agents/research.agent.md index 0be6814..c5e0aa7 100644 --- a/home/dot_copilot/agents/research.agent.md +++ b/home/dot_copilot/agents/research.agent.md @@ -65,7 +65,7 @@ Execute the research strategy. You must: After research, score yourself using the rubric at: ``` -@.apm/skills/deep-analysis/references/rubric.md +@.copilot/skills/deep-analysis/references/rubric.md ``` | Dimension | Weight | Self-Check | @@ -85,7 +85,7 @@ confidence = (earned_points / 100) × 100 Generate a Research Summary using the template at: ``` -@.apm/skills/deep-analysis/references/research-summary-template.md +@.copilot/skills/deep-analysis/references/research-summary-template.md ``` Then recommend: _"Research complete. Invoke `superpowers:writing-plans` with this summary to generate the implementation plan."_ diff --git a/tests/template/chezmoi-helpers.bats b/tests/template/chezmoi-helpers.bats index 9b4a8d6..a7bc5c9 100644 --- a/tests/template/chezmoi-helpers.bats +++ b/tests/template/chezmoi-helpers.bats @@ -11,8 +11,8 @@ ACTIVE_GROUP_VALUES_TMPL="$DOTFILES_ROOT/home/.chezmoitemplates/lib/chezmoi/acti SHARED_DATA='{"chezmoi":{"os":"darwin"},"personal":false,"work":false}' BOTH_DATA='{"chezmoi":{"os":"darwin"},"personal":true,"work":true}' -APM_VALUES_BY_GROUP='{"shared":["graphify"],"personal":["claude","agent-skills"],"work":["copilot"]}' -APM_VALUES_SPARSE='{"shared":["graphify"]}' +VALUES_BY_GROUP='{"shared":["graphify"],"personal":["claude","agent-skills"],"work":["copilot"]}' +VALUES_SPARSE='{"shared":["graphify"]}' @test "active-groups returns shared and personal for personal context" { run render_chezmoi_template "$ACTIVE_GROUPS_TMPL" "$DARWIN_DATA" @@ -35,9 +35,9 @@ APM_VALUES_SPARSE='{"shared":["graphify"]}' assert_output '["shared"]' } -@test "active-group-values merges shared and personal apm targets for personal context" { +@test "active-group-values merges shared and personal values for personal context" { local data - data=$(printf '{"ctx":{"personal":true,"work":false},"valuesByGroup":%s}' "$APM_VALUES_BY_GROUP") + data=$(printf '{"ctx":{"personal":true,"work":false},"valuesByGroup":%s}' "$VALUES_BY_GROUP") run render_chezmoi_template "$ACTIVE_GROUP_VALUES_TMPL" "$data" @@ -45,9 +45,9 @@ APM_VALUES_SPARSE='{"shared":["graphify"]}' assert_output '["graphify","claude","agent-skills"]' } -@test "active-group-values merges shared and work apm targets for work context" { +@test "active-group-values merges shared and work values for work context" { local data - data=$(printf '{"ctx":{"personal":false,"work":true},"valuesByGroup":%s}' "$APM_VALUES_BY_GROUP") + data=$(printf '{"ctx":{"personal":false,"work":true},"valuesByGroup":%s}' "$VALUES_BY_GROUP") run render_chezmoi_template "$ACTIVE_GROUP_VALUES_TMPL" "$data" @@ -55,9 +55,9 @@ APM_VALUES_SPARSE='{"shared":["graphify"]}' assert_output '["graphify","copilot"]' } -@test "active-group-values returns only shared apm targets when neither personal nor work" { +@test "active-group-values returns only shared values when neither personal nor work" { local data - data=$(printf '{"ctx":{"personal":false,"work":false},"valuesByGroup":%s}' "$APM_VALUES_BY_GROUP") + data=$(printf '{"ctx":{"personal":false,"work":false},"valuesByGroup":%s}' "$VALUES_BY_GROUP") run render_chezmoi_template "$ACTIVE_GROUP_VALUES_TMPL" "$data" @@ -74,7 +74,7 @@ APM_VALUES_SPARSE='{"shared":["graphify"]}' @test "active-group-values silently skips groups absent from valuesByGroup" { local data - data=$(printf '{"ctx":{"personal":true,"work":false},"valuesByGroup":%s}' "$APM_VALUES_SPARSE") + data=$(printf '{"ctx":{"personal":true,"work":false},"valuesByGroup":%s}' "$VALUES_SPARSE") run render_chezmoi_template "$ACTIVE_GROUP_VALUES_TMPL" "$data" diff --git a/tests/test_helpers/load.bash b/tests/test_helpers/load.bash index 752871d..b3c02f5 100644 --- a/tests/test_helpers/load.bash +++ b/tests/test_helpers/load.bash @@ -7,7 +7,7 @@ # load '../test_helpers/load.bash' # tests/unit/foo.bats # load '../../test_helpers/load.bash' # tests/unit/lib/common/log.bats # -# Then assertions like `assert_success` and `assert_output --partial` are available. +# Then assertions like `assert_success` and `assert_line` are available. # Absolute paths to the companion libs. `load` itself resolves relative to # the caller's test dir, not to this file, so absolute paths are required. diff --git a/tests/unit/lib/install/ai-plugins.bats b/tests/unit/lib/install/ai-plugins.bats index ef4896f..eff9a3b 100644 --- a/tests/unit/lib/install/ai-plugins.bats +++ b/tests/unit/lib/install/ai-plugins.bats @@ -5,7 +5,6 @@ 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() { @@ -31,7 +30,7 @@ NPX } _run_main() { - run bash -c "source '$LOG_LIB' && source '$PRELUDE' && source '$LIB' && $1 && main" + run bash -c "source '$LOG_LIB' && source '$LIB' && $1 && main" } @test "main: claude-code target adds marketplace and installs each plugin" { diff --git a/tests/unit/lib/install/ai-skills.bats b/tests/unit/lib/install/ai-skills.bats index 2fc4682..ac25740 100644 --- a/tests/unit/lib/install/ai-skills.bats +++ b/tests/unit/lib/install/ai-skills.bats @@ -5,7 +5,6 @@ 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() { @@ -25,7 +24,7 @@ NPX } _run_main() { - run bash -c "source '$LOG_LIB' && source '$PRELUDE' && source '$LIB' && $1 && main" + run bash -c "source '$LOG_LIB' && source '$LIB' && $1 && main" } @test "main: installs each skill for a single target" {