From 15a4387eb295f60a4dbbeb8e9438f12ae94b3922 Mon Sep 17 00:00:00 2001 From: Defame1297 Date: Fri, 15 May 2026 08:46:25 +0000 Subject: [PATCH] feat: deploy skills pipeline with provider adapter pattern --- {.claude => .agents}/skills/caveman/SKILL.md | 0 {.claude => .agents}/skills/diagnose/SKILL.md | 0 .../diagnose/scripts/hitl-loop.template.sh | 0 {.claude => .agents}/skills/grill-me/SKILL.md | 0 .../skills/grill-with-docs/ADR-FORMAT.md | 0 .../skills/grill-with-docs/CONTEXT-FORMAT.md | 0 .../skills/grill-with-docs/SKILL.md | 0 .../DEEPENING.md | 0 .../INTERFACE-DESIGN.md | 0 .../improve-codebase-architecture/LANGUAGE.md | 0 .../improve-codebase-architecture/SKILL.md | 0 .../skills/prototype/LOGIC.md | 0 .../skills/prototype/SKILL.md | 0 {.claude => .agents}/skills/prototype/UI.md | 0 {.claude => .agents}/skills/tdd/SKILL.md | 0 .../skills/tdd/deep-modules.md | 0 .../skills/tdd/interface-design.md | 0 {.claude => .agents}/skills/tdd/mocking.md | 0 .../skills/tdd/refactoring.md | 0 {.claude => .agents}/skills/tdd/tests.md | 0 .../skills/to-issues/SKILL.md | 0 {.claude => .agents}/skills/to-prd/SKILL.md | 0 .../skills/triage/AGENT-BRIEF.md | 0 .../skills/triage/OUT-OF-SCOPE.md | 0 {.claude => .agents}/skills/triage/SKILL.md | 0 .../skills/write-a-skill/SKILL.md | 0 {.claude => .agents}/skills/zoom-out/SKILL.md | 0 CLAUDE.md | 2 +- CONTEXT.md | 2 +- docs/ROADMAP.md | 5 +- docs/VISION.md | 13 +- docs/adr/0004-skills-in-agents-dir.md | 4 +- docs/adr/0006-install-always-overwrites.md | 2 + .../adr/0007-provider-adapters-as-symlinks.md | 9 ++ providers/claude-code/provider-manifest.sh | 6 + scripts/deploy-manifest.sh | 8 +- scripts/install.sh | 33 ++++- skills-lock.json | 77 ------------ tests/test-install.sh | 118 ++++++++++++++++-- 39 files changed, 175 insertions(+), 104 deletions(-) rename {.claude => .agents}/skills/caveman/SKILL.md (100%) rename {.claude => .agents}/skills/diagnose/SKILL.md (100%) rename {.claude => .agents}/skills/diagnose/scripts/hitl-loop.template.sh (100%) rename {.claude => .agents}/skills/grill-me/SKILL.md (100%) rename {.claude => .agents}/skills/grill-with-docs/ADR-FORMAT.md (100%) rename {.claude => .agents}/skills/grill-with-docs/CONTEXT-FORMAT.md (100%) rename {.claude => .agents}/skills/grill-with-docs/SKILL.md (100%) rename {.claude => .agents}/skills/improve-codebase-architecture/DEEPENING.md (100%) rename {.claude => .agents}/skills/improve-codebase-architecture/INTERFACE-DESIGN.md (100%) rename {.claude => .agents}/skills/improve-codebase-architecture/LANGUAGE.md (100%) rename {.claude => .agents}/skills/improve-codebase-architecture/SKILL.md (100%) rename {.claude => .agents}/skills/prototype/LOGIC.md (100%) rename {.claude => .agents}/skills/prototype/SKILL.md (100%) rename {.claude => .agents}/skills/prototype/UI.md (100%) rename {.claude => .agents}/skills/tdd/SKILL.md (100%) rename {.claude => .agents}/skills/tdd/deep-modules.md (100%) rename {.claude => .agents}/skills/tdd/interface-design.md (100%) rename {.claude => .agents}/skills/tdd/mocking.md (100%) rename {.claude => .agents}/skills/tdd/refactoring.md (100%) rename {.claude => .agents}/skills/tdd/tests.md (100%) rename {.claude => .agents}/skills/to-issues/SKILL.md (100%) rename {.claude => .agents}/skills/to-prd/SKILL.md (100%) rename {.claude => .agents}/skills/triage/AGENT-BRIEF.md (100%) rename {.claude => .agents}/skills/triage/OUT-OF-SCOPE.md (100%) rename {.claude => .agents}/skills/triage/SKILL.md (100%) rename {.claude => .agents}/skills/write-a-skill/SKILL.md (100%) rename {.claude => .agents}/skills/zoom-out/SKILL.md (100%) create mode 100644 docs/adr/0007-provider-adapters-as-symlinks.md create mode 100644 providers/claude-code/provider-manifest.sh delete mode 100644 skills-lock.json diff --git a/.claude/skills/caveman/SKILL.md b/.agents/skills/caveman/SKILL.md similarity index 100% rename from .claude/skills/caveman/SKILL.md rename to .agents/skills/caveman/SKILL.md diff --git a/.claude/skills/diagnose/SKILL.md b/.agents/skills/diagnose/SKILL.md similarity index 100% rename from .claude/skills/diagnose/SKILL.md rename to .agents/skills/diagnose/SKILL.md diff --git a/.claude/skills/diagnose/scripts/hitl-loop.template.sh b/.agents/skills/diagnose/scripts/hitl-loop.template.sh similarity index 100% rename from .claude/skills/diagnose/scripts/hitl-loop.template.sh rename to .agents/skills/diagnose/scripts/hitl-loop.template.sh diff --git a/.claude/skills/grill-me/SKILL.md b/.agents/skills/grill-me/SKILL.md similarity index 100% rename from .claude/skills/grill-me/SKILL.md rename to .agents/skills/grill-me/SKILL.md diff --git a/.claude/skills/grill-with-docs/ADR-FORMAT.md b/.agents/skills/grill-with-docs/ADR-FORMAT.md similarity index 100% rename from .claude/skills/grill-with-docs/ADR-FORMAT.md rename to .agents/skills/grill-with-docs/ADR-FORMAT.md diff --git a/.claude/skills/grill-with-docs/CONTEXT-FORMAT.md b/.agents/skills/grill-with-docs/CONTEXT-FORMAT.md similarity index 100% rename from .claude/skills/grill-with-docs/CONTEXT-FORMAT.md rename to .agents/skills/grill-with-docs/CONTEXT-FORMAT.md diff --git a/.claude/skills/grill-with-docs/SKILL.md b/.agents/skills/grill-with-docs/SKILL.md similarity index 100% rename from .claude/skills/grill-with-docs/SKILL.md rename to .agents/skills/grill-with-docs/SKILL.md diff --git a/.claude/skills/improve-codebase-architecture/DEEPENING.md b/.agents/skills/improve-codebase-architecture/DEEPENING.md similarity index 100% rename from .claude/skills/improve-codebase-architecture/DEEPENING.md rename to .agents/skills/improve-codebase-architecture/DEEPENING.md diff --git a/.claude/skills/improve-codebase-architecture/INTERFACE-DESIGN.md b/.agents/skills/improve-codebase-architecture/INTERFACE-DESIGN.md similarity index 100% rename from .claude/skills/improve-codebase-architecture/INTERFACE-DESIGN.md rename to .agents/skills/improve-codebase-architecture/INTERFACE-DESIGN.md diff --git a/.claude/skills/improve-codebase-architecture/LANGUAGE.md b/.agents/skills/improve-codebase-architecture/LANGUAGE.md similarity index 100% rename from .claude/skills/improve-codebase-architecture/LANGUAGE.md rename to .agents/skills/improve-codebase-architecture/LANGUAGE.md diff --git a/.claude/skills/improve-codebase-architecture/SKILL.md b/.agents/skills/improve-codebase-architecture/SKILL.md similarity index 100% rename from .claude/skills/improve-codebase-architecture/SKILL.md rename to .agents/skills/improve-codebase-architecture/SKILL.md diff --git a/.claude/skills/prototype/LOGIC.md b/.agents/skills/prototype/LOGIC.md similarity index 100% rename from .claude/skills/prototype/LOGIC.md rename to .agents/skills/prototype/LOGIC.md diff --git a/.claude/skills/prototype/SKILL.md b/.agents/skills/prototype/SKILL.md similarity index 100% rename from .claude/skills/prototype/SKILL.md rename to .agents/skills/prototype/SKILL.md diff --git a/.claude/skills/prototype/UI.md b/.agents/skills/prototype/UI.md similarity index 100% rename from .claude/skills/prototype/UI.md rename to .agents/skills/prototype/UI.md diff --git a/.claude/skills/tdd/SKILL.md b/.agents/skills/tdd/SKILL.md similarity index 100% rename from .claude/skills/tdd/SKILL.md rename to .agents/skills/tdd/SKILL.md diff --git a/.claude/skills/tdd/deep-modules.md b/.agents/skills/tdd/deep-modules.md similarity index 100% rename from .claude/skills/tdd/deep-modules.md rename to .agents/skills/tdd/deep-modules.md diff --git a/.claude/skills/tdd/interface-design.md b/.agents/skills/tdd/interface-design.md similarity index 100% rename from .claude/skills/tdd/interface-design.md rename to .agents/skills/tdd/interface-design.md diff --git a/.claude/skills/tdd/mocking.md b/.agents/skills/tdd/mocking.md similarity index 100% rename from .claude/skills/tdd/mocking.md rename to .agents/skills/tdd/mocking.md diff --git a/.claude/skills/tdd/refactoring.md b/.agents/skills/tdd/refactoring.md similarity index 100% rename from .claude/skills/tdd/refactoring.md rename to .agents/skills/tdd/refactoring.md diff --git a/.claude/skills/tdd/tests.md b/.agents/skills/tdd/tests.md similarity index 100% rename from .claude/skills/tdd/tests.md rename to .agents/skills/tdd/tests.md diff --git a/.claude/skills/to-issues/SKILL.md b/.agents/skills/to-issues/SKILL.md similarity index 100% rename from .claude/skills/to-issues/SKILL.md rename to .agents/skills/to-issues/SKILL.md diff --git a/.claude/skills/to-prd/SKILL.md b/.agents/skills/to-prd/SKILL.md similarity index 100% rename from .claude/skills/to-prd/SKILL.md rename to .agents/skills/to-prd/SKILL.md diff --git a/.claude/skills/triage/AGENT-BRIEF.md b/.agents/skills/triage/AGENT-BRIEF.md similarity index 100% rename from .claude/skills/triage/AGENT-BRIEF.md rename to .agents/skills/triage/AGENT-BRIEF.md diff --git a/.claude/skills/triage/OUT-OF-SCOPE.md b/.agents/skills/triage/OUT-OF-SCOPE.md similarity index 100% rename from .claude/skills/triage/OUT-OF-SCOPE.md rename to .agents/skills/triage/OUT-OF-SCOPE.md diff --git a/.claude/skills/triage/SKILL.md b/.agents/skills/triage/SKILL.md similarity index 100% rename from .claude/skills/triage/SKILL.md rename to .agents/skills/triage/SKILL.md diff --git a/.claude/skills/write-a-skill/SKILL.md b/.agents/skills/write-a-skill/SKILL.md similarity index 100% rename from .claude/skills/write-a-skill/SKILL.md rename to .agents/skills/write-a-skill/SKILL.md diff --git a/.claude/skills/zoom-out/SKILL.md b/.agents/skills/zoom-out/SKILL.md similarity index 100% rename from .claude/skills/zoom-out/SKILL.md rename to .agents/skills/zoom-out/SKILL.md diff --git a/CLAUDE.md b/CLAUDE.md index 05ab80a..d941cc4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ This repo is the global AI development configuration repository — the authorit ## Structure - `core/` — provider-agnostic source of truth (plain language, no tool-specific references) -- `.agents/skills/` — canonical skills location (Agent Skills standard); populated in Chunk 3 +- `.agents/skills/` — canonical skills location (Agent Skills standard); deployed to `~/.agents/skills/` via `install.sh` - `providers/claude-code/` — Claude Code adapter (deployed to `~/.claude/` via `install.sh`) - `docs/` — project documentation, PRDs, and issues - `scripts/` — install.sh (sync.sh and init-project.sh come in Chunk 6) diff --git a/CONTEXT.md b/CONTEXT.md index dfa73df..13e7263 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -62,7 +62,7 @@ A separate product (separate repo) for browsing, editing, and configuring AI dev The set of machines and projects under management in Phase 2 of the Management Application. A fleet member is any machine that has the config deployed and can be reached by the runtime orchestration layer. ### Skills -Reusable slash commands for AI coding tools, defined as `SKILL.md` files following the [Agent Skills open standard](https://agentskills.io). Canonical location: `.agents/skills//SKILL.md` in this repo; deployed to `~/.agents/skills/` on install. +Reusable slash commands for AI coding tools, defined as `SKILL.md` files following the [Agent Skills open standard](https://agentskills.io). Canonical location: `.agents/skills//SKILL.md` in this repo; deployed to `~/.agents/skills/` on install. Providers that don't read `~/.agents/skills/` natively get a symlink adapter declared in `providers//provider-manifest.sh` (e.g. Claude Code: `~/.claude/skills/ → ~/.agents/skills/`). ### Content types - **Instructions** — stateless rules defining AI behavior. Split into two tiers: (1) universal rules (communication, behavior) live in the always-on section of `providers/claude-code/CLAUDE.md`; (2) topic-specific rules (coding, git, testing) live in `core/instructions/.md` and are read on-demand. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index b1373e9..1d01aeb 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -33,11 +33,11 @@ A parallel workstream (not a numbered chunk) that runs alongside the chunk seque |---|---|---| | ✅ 1 | Repo skeleton + `install.sh` — structure in place, Claude Code wired up | Nothing else can be built without the structure and install working | | ⏳ 2 | Core instructions — `coding.md`, `git.md` (incl. conventional commits), `testing.md`; communication rules in `providers/claude-code/CLAUDE.md` always-on section; retire `global.md`; migrate `docs/` to subdirectory-by-type naming | Instructions are the foundation everything else references; commit convention and doc naming must be in place before history accumulates | -| 3 | First skills — initial slash commands for day-to-day use; changelog tooling (follow-on to Chunk 2 conventional commits); catalogue existing skills incl. `zoom-out` which is installed but undocumented | Skills are the most immediately useful output; validates the full pipeline | +| ⏳ 3 | First skills — initial slash commands for day-to-day use; changelog tooling (follow-on to Chunk 2 conventional commits); catalogue existing skills incl. `zoom-out` which is installed but undocumented. **Infrastructure complete**: 12 skills deployed to `~/.agents/skills/` via `install.sh`; provider adapter pattern in place; remaining scope is content (changelog tooling, zoom-out docs, content index frontmatter) | Skills are the most immediately useful output; validates the full pipeline | | 4 | Workflows — formalize the workstream workflow (kick-off types → grill → artifact → issues → implement → QA → commit); feature, bug, architecture, improvement, feedback patterns | Higher-level patterns built on top of a working skills foundation; grill feedback intake design before starting | | 5 | Agents — role definitions (reviewer, architect, developer) | More abstract definitions; benefits from workflow patterns being established first | | 6 | Sync + project init tooling — `sync.sh` and `init-project.sh` | Tooling only makes sense once there is content worth syncing and scaffolding | -| 7 | Copilot provider — adapter for GitHub Copilot | Second provider comes after the first is fully proven | +| 7 | Copilot provider — adapter for GitHub Copilot. **Provider adapter pattern established**: `install.sh` auto-discovers `providers/*/provider-manifest.sh`; Copilot adapter is a new `providers/copilot/provider-manifest.sh` declaring a symlink if needed | Second provider comes after the first is fully proven | ## Development workflow @@ -89,6 +89,7 @@ Items consciously not resolved — to be addressed in the relevant chunk PRD or ## Housekeeping reminders - **`.gitkeep` files** — placeholder files exist in `core/agents/`, `core/workflows/`, `core/prompts/`, `docs/ard/`, `docs/bug/`, `docs/notes/`. Remove each when the first real file is added to that directory. Each `.gitkeep` names the chunk that will populate it. +- **Skills pipeline verified** — `install.sh` deploys 12 skills to `~/.agents/skills/` and creates `~/.claude/skills/ → ~/.agents/skills/` symlink adapter. Tested idempotent. `skills-lock.json` removed (was a manual artifact). If `~/.claude/skills/` exists as a real directory on a machine being migrated, remove it manually and re-run install. - **Chunk 2 behavioral tests** — 8 manual scenarios in `tests/test-instructions-and-docs.sh` (MANUAL TEST PLAN section) are pending verification. Must run in a fresh Claude session before Chunk 2 is fully verified. See instruction quality finding in `CONTEXT.md` for why these cannot be skipped. - **Governance Phase 1 behavioral tests** — manual test plan in `tests/test-governance-layer.sh` (MANUAL TEST PLAN section) is pending verification. Must run in a fresh Claude session before marking governance Phase 1 fully verified. - **AI ethics/security workstream** — `docs/notes/ai-ethics-security-principles.md` exploration note is superseded. Governance Phase 1 (`core/instructions/governance.md`) covers all planned scope: credentials, data classification, HITL, scope discipline, agent autonomy, transparency, and security code review. Tier-placement architectural question resolved by the `@import` always-on model. No separate workstream needed. diff --git a/docs/VISION.md b/docs/VISION.md index 89b5b5a..c1de44e 100644 --- a/docs/VISION.md +++ b/docs/VISION.md @@ -28,7 +28,8 @@ Designed to start as a personal homelab tool and grow into something shareable w ``` this repo (global defaults) - ├── install.sh → ~/.agents/skills/ (cross-client skills, all providers) + ├── install.sh → ~/.agents/skills/ (canonical skills location) + ├── install.sh → ~/.claude/skills/ (symlink → ~/.agents/skills/, Claude Code adapter) └── install.sh → ~/.claude/ (Claude Code config + content) project repo (local overrides) @@ -43,17 +44,16 @@ Projects consume from this repo by pulling updates via `sync.sh` (chunk 6). Unti ai-development/ ├── docs/ # Workflow artifacts and issues (prd/, ard/, bug/, notes/, adr/, issues/) + research/ (raw research audit trail) ├── .agents/ # Agent Skills standard location (provider-agnostic) -│ └── skills/ # SKILL.md files — read natively by Claude Code, Copilot, Cursor, etc. +│ └── skills/ # SKILL.md files — canonical source, deployed to ~/.agents/skills/ ├── core/ # Provider-agnostic source of truth │ ├── instructions/ # AI behavior definitions (plain markdown) │ ├── agents/ # Agent role definitions │ ├── workflows/ # Workflow definitions │ └── prompts/ # Reusable prompt templates ├── providers/ # Provider-specific adapters -│ ├── claude-code/ # CLAUDE.md, settings.json +│ ├── claude-code/ # CLAUDE.md, settings.json, provider-manifest.sh │ └── copilot/ # copilot-instructions.md, hooks, agents adapter ├── templates/ # Project scaffolding templates -├── skills-lock.json # Tracks installed skills and their sources/hashes └── scripts/ ├── deploy-manifest.sh # Source→target mappings; sourced by install.sh and sync.sh ├── install.sh # Deploys to ~/.agents/skills/, ~/.claude/, etc. @@ -65,7 +65,8 @@ ai-development/ `install.sh` is a **deployer**, not a composer. It does not concatenate content into a single file. Instead: -- `.agents/skills/` → `~/.agents/skills/` — user-level cross-client skills (read by Claude Code, Copilot, Cursor, etc.) +- `.agents/skills/` → `~/.agents/skills/` — canonical skills location; each skill dir is replaced individually (parent not wiped, user-added skills preserved) +- Provider adapters declared in `providers/*/provider-manifest.sh` — symlinks from the provider's skill path to `~/.agents/skills/`; e.g. Claude Code gets `~/.claude/skills/ → ~/.agents/skills/` because it reads `~/.claude/skills/` natively. Providers that read `~/.agents/skills/` directly need no adapter. - `core/` → `~/.claude/core/` — workflows, prompts, agent definitions; agent reads on demand - `providers/claude-code/settings.json` → `~/.claude/settings.json` - Writes a lean `~/.claude/CLAUDE.md` — universal rules only, plus pointers to where detailed content lives @@ -88,7 +89,7 @@ This repo has a `CLAUDE.md` at its root — a meta file that tells Claude how to `core/` is never tool-specific. `providers/` is never shared. When adding a new provider, write an adapter in `providers//` that translates core content into the tool's expected format and location. The core content itself does not change. -Skills are the strongest shared primitive — both Claude Code and GitHub Copilot use the `SKILL.md` format and follow the [Agent Skills open standard](https://agentskills.io). +Skills are the strongest shared primitive — the `SKILL.md` format and [Agent Skills open standard](https://agentskills.io) are cross-provider. Providers that don't read `~/.agents/skills/` natively declare a symlink adapter in `providers//provider-manifest.sh`; `install.sh` discovers and wires these up automatically. ### Architectural decisions diff --git a/docs/adr/0004-skills-in-agents-dir.md b/docs/adr/0004-skills-in-agents-dir.md index 7db253d..e49bd42 100644 --- a/docs/adr/0004-skills-in-agents-dir.md +++ b/docs/adr/0004-skills-in-agents-dir.md @@ -1,3 +1,5 @@ # Skills live in .agents/skills/, not .claude/skills/ -Skills (slash commands) are stored in `.agents/skills/` following the [Agent Skills open standard](https://agentskills.io), not in `.claude/skills/` which is a Claude Code-specific location. Claude Code, GitHub Copilot, Cursor, and other tools read `.agents/skills/` natively without an adapter. Putting skills in `.claude/skills/` would make them Claude Code-only and contradict ADR-0003 (provider-agnostic where possible). Skills are the strongest shared primitive across providers — they should live at the most portable location available. +Skills (slash commands) are stored in `.agents/skills/` following the [Agent Skills open standard](https://agentskills.io), not in `.claude/skills/` which is a Claude Code-specific location. Putting skills in `.claude/skills/` would make them Claude Code-only and contradict ADR-0003 (provider-agnostic where possible). Skills are the strongest shared primitive across providers — they should live at the most portable location available. + +`install.sh` deploys skills to `~/.agents/skills/` as the single canonical location. Providers that do not read `~/.agents/skills/` natively declare a symlink adapter in `providers//provider-manifest.sh`; `install.sh` discovers and creates these automatically. Claude Code is one such provider — it reads `~/.claude/skills/` natively, so it gets a `~/.claude/skills/ → ~/.agents/skills/` symlink. See ADR-0007 for the rationale behind using symlinks for provider adapters. diff --git a/docs/adr/0006-install-always-overwrites.md b/docs/adr/0006-install-always-overwrites.md index 6d75c1c..2ee811f 100644 --- a/docs/adr/0006-install-always-overwrites.md +++ b/docs/adr/0006-install-always-overwrites.md @@ -1,3 +1,5 @@ # install.sh always overwrites deployed files `install.sh` overwrites `~/.claude/` and `~/.claude/core/` unconditionally on every run. It does not merge, diff, or ask. The rationale: the source of truth is this repo. Editing deployed files directly is a usage error — `sync.sh` would overwrite those edits on the next pull anyway. Offering a merge path would imply that editing `~/.claude/CLAUDE.md` directly is a supported workflow, which it is not. If a local customisation is needed it belongs in a project-level override file, not in the deployed global config. + +**Exception — skills**: `~/.agents/skills/` uses a merge-per-skill strategy. Each skill directory from `.agents/skills/` is replaced individually; the parent directory is never wiped. This preserves user-installed skills from other sources alongside the skills managed by this repo. The overwrite-always principle still holds for each individual managed skill — the per-skill replace is unconditional. diff --git a/docs/adr/0007-provider-adapters-as-symlinks.md b/docs/adr/0007-provider-adapters-as-symlinks.md new file mode 100644 index 0000000..d6b28bb --- /dev/null +++ b/docs/adr/0007-provider-adapters-as-symlinks.md @@ -0,0 +1,9 @@ +# Provider skill adapters are symlinks, not copies + +Provider skill adapters — the mechanism that makes `~/.agents/skills/` visible to a provider that reads a different path — are implemented as symlinks, not file copies. This is a deliberate exception to ADR-0002 (copy-not-symlink), which applies to content files. Adapters are infrastructure, not content. + +**Why symlinks here:** a provider adapter has no content of its own — it is purely a pointer to the canonical location. Copying would create a second source of truth and require install.sh to keep two directories in sync; any drift between them would be a silent bug. A symlink makes the relationship explicit and eliminates the sync problem entirely. + +**Why ADR-0002 still holds for content:** ADR-0002's concern is that symlinks break if this repo moves. Provider adapters point to `~/.agents/skills/`, not into this repo — they survive repo relocation without modification. + +Each provider that cannot read `~/.agents/skills/` natively declares its adapter path in `providers//provider-manifest.sh`. `install.sh` discovers all provider manifests and creates the symlinks. A provider that reads `~/.agents/skills/` natively needs no entry. If the adapter target already exists as a real directory, install.sh emits a warning and leaves it intact rather than destroying user data. diff --git a/providers/claude-code/provider-manifest.sh b/providers/claude-code/provider-manifest.sh new file mode 100644 index 0000000..4ef16c7 --- /dev/null +++ b/providers/claude-code/provider-manifest.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# Claude Code provider manifest — sourced by install.sh after deploy-manifest.sh. +# Appends to SKILL_ADAPTERS, which must be initialised before sourcing this file. + +# Claude Code reads skills from ~/.claude/skills/, not ~/.agents/skills/ natively. +SKILL_ADAPTERS+=(".claude/skills") diff --git a/scripts/deploy-manifest.sh b/scripts/deploy-manifest.sh index d1e9381..b655d26 100644 --- a/scripts/deploy-manifest.sh +++ b/scripts/deploy-manifest.sh @@ -19,7 +19,7 @@ DEPLOY_DIRS=( "core:.claude/core" ) -# Empty directories to create if absent -DEPLOY_EMPTY_DIRS=( - ".agents/skills" -) +# Skills — merged per-skill into canonical location (parent not wiped, user skills preserved) +DEPLOY_SKILLS_SRC=".agents/skills" + +# Provider skill adapters are declared in providers/*/provider-manifest.sh, not here. diff --git a/scripts/install.sh b/scripts/install.sh index c90355a..a5302e0 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -5,6 +5,13 @@ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # shellcheck source=deploy-manifest.sh source "$REPO_ROOT/scripts/deploy-manifest.sh" +# Collect provider skill adapters from all provider-manifest.sh files +SKILL_ADAPTERS=() +for provider_manifest in "$REPO_ROOT"/providers/*/provider-manifest.sh; do + # shellcheck source=/dev/null + source "$provider_manifest" +done + echo "Installing from $REPO_ROOT..." for entry in "${DEPLOY_FILES[@]}"; do @@ -27,8 +34,25 @@ for entry in "${DEPLOY_DIRS[@]}"; do cp -r "$REPO_ROOT/${entry%%:*}/." "$dest/" done -for dir in "${DEPLOY_EMPTY_DIRS[@]}"; do - mkdir -p "$HOME/$dir" +# Deploy skills to canonical location — merge per-skill, never wipe the parent +skills_dest="$HOME/$DEPLOY_SKILLS_SRC" +mkdir -p "$skills_dest" +for skill_dir in "$REPO_ROOT/$DEPLOY_SKILLS_SRC"/*/; do + skill_name="$(basename "$skill_dir")" + rm -rf "$skills_dest/$skill_name" + cp -r "$skill_dir" "$skills_dest/$skill_name" +done + +# Create provider skill adapters as symlinks to the canonical skills location +for adapter in "${SKILL_ADAPTERS[@]}"; do + target="$HOME/$adapter" + if [ -L "$target" ]; then + ln -sfn "$skills_dest" "$target" + elif [ -d "$target" ]; then + echo "Warning: $target exists as a real directory — remove it and re-run install to create the skill adapter symlink." + else + ln -s "$skills_dest" "$target" + fi done echo "" @@ -39,6 +63,7 @@ done for entry in "${DEPLOY_DIRS[@]}"; do echo " ~/${entry##*:}/" done -for dir in "${DEPLOY_EMPTY_DIRS[@]}"; do - echo " ~/$dir/ (directory created)" +echo " ~/$DEPLOY_SKILLS_SRC/ (skills)" +for adapter in "${SKILL_ADAPTERS[@]}"; do + echo " ~/$adapter -> ~/$DEPLOY_SKILLS_SRC (symlink)" done diff --git a/skills-lock.json b/skills-lock.json deleted file mode 100644 index 409c006..0000000 --- a/skills-lock.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "version": 1, - "skills": { - "caveman": { - "source": "mattpocock/skills", - "sourceType": "github", - "skillPath": "skills/productivity/caveman/SKILL.md", - "computedHash": "934433479903febc585bf6deb5f0cebc63137e3f86b7babe0aab1ecb94d6d7a4" - }, - "diagnose": { - "source": "mattpocock/skills", - "sourceType": "github", - "skillPath": "skills/engineering/diagnose/SKILL.md", - "computedHash": "15939a26f86edec2d4862042b8564e5a062cb81d04e047a0cea6305c8830b5f5" - }, - "grill-me": { - "source": "mattpocock/skills", - "sourceType": "github", - "skillPath": "skills/productivity/grill-me/SKILL.md", - "computedHash": "784f0dbb7403b0f00324bce9a112f715342777a0daee7bbb7385f9c6f0a170ea" - }, - "grill-with-docs": { - "source": "mattpocock/skills", - "sourceType": "github", - "skillPath": "skills/engineering/grill-with-docs/SKILL.md", - "computedHash": "31a5b1ae116558bf7d3f633f442835f54bd7645923d4f45c7823e52a97317666" - }, - "improve-codebase-architecture": { - "source": "mattpocock/skills", - "sourceType": "github", - "skillPath": "skills/engineering/improve-codebase-architecture/SKILL.md", - "computedHash": "c77b86b4332919499608f9af1880074e1fec65a59b95c70c27a9f39cd137865e" - }, - "prototype": { - "source": "mattpocock/skills", - "sourceType": "github", - "skillPath": "skills/engineering/prototype/SKILL.md", - "computedHash": "aa9d68879fb51af13d47247b5fb73474324e38985ead454e6ea85b64344b7485" - }, - "tdd": { - "source": "mattpocock/skills", - "sourceType": "github", - "skillPath": "skills/engineering/tdd/SKILL.md", - "computedHash": "15a7b5e36383ebadb2dec5e586679e55e9663d292da418926b8da6fc0ef27d84" - }, - "to-issues": { - "source": "mattpocock/skills", - "sourceType": "github", - "skillPath": "skills/engineering/to-issues/SKILL.md", - "computedHash": "47f648f3414848ccfc62cb41d2828b7e575fb5e7cbd6c4bdf630c063b5dc5e82" - }, - "to-prd": { - "source": "mattpocock/skills", - "sourceType": "github", - "skillPath": "skills/engineering/to-prd/SKILL.md", - "computedHash": "6d741474efd4bc3db55fabc2722ed78ca9c374cabcb6212936d79d4fd4a30fcb" - }, - "triage": { - "source": "mattpocock/skills", - "sourceType": "github", - "skillPath": "skills/engineering/triage/SKILL.md", - "computedHash": "2b6efb6da12d92551772fcc04acf331f4e0e6f7bd9d4cb23ce0b301e0b128feb" - }, - "write-a-skill": { - "source": "mattpocock/skills", - "sourceType": "github", - "skillPath": "skills/productivity/write-a-skill/SKILL.md", - "computedHash": "b44d8aab2ead83c716e01af4c9a24ccc4575ce70ad58ec4f1749fb88c9cc82ba" - }, - "zoom-out": { - "source": "mattpocock/skills", - "sourceType": "github", - "skillPath": "skills/engineering/zoom-out/SKILL.md", - "computedHash": "8357aeaece3b709c442eab67e64b86844e05e2f1ea95b109565eba50b6def36e" - } - } -} diff --git a/tests/test-install.sh b/tests/test-install.sh index b6090f7..ef090a8 100755 --- a/tests/test-install.sh +++ b/tests/test-install.sh @@ -18,6 +18,8 @@ echo "" echo "--- providers/claude-code/ → ~/.claude/ ---" while IFS= read -r -d '' src; do rel="${src#"$REPO_ROOT/providers/claude-code/"}" + # provider-manifest.sh is sourced by install.sh, not deployed to ~/.claude/ + [[ "$rel" == "provider-manifest.sh" ]] && continue dest="$TEMP_HOME/.claude/$rel" if diff -q "$src" "$dest" > /dev/null 2>&1; then pass "$rel deployed and matches source" @@ -51,19 +53,119 @@ while IFS= read -r -d '' deployed; do done < <(find "$TEMP_HOME/.claude/core" -type f -print0) echo "" -echo "--- directories ---" -if [[ -d "$TEMP_HOME/.agents/skills" ]]; then - pass "~/.agents/skills/ created" +echo "--- skills deployed to ~/.agents/skills/ ---" +while IFS= read -r -d '' src_skill; do + skill_name="$(basename "$src_skill")" + dest_skill="$TEMP_HOME/.agents/skills/$skill_name" + if [[ -d "$dest_skill" ]]; then + pass "$skill_name deployed to ~/.agents/skills/" + else + fail "$skill_name missing from ~/.agents/skills/" + fi +done < <(find "$REPO_ROOT/.agents/skills" -mindepth 1 -maxdepth 1 -type d -print0) + +echo "" +echo "--- skill files match source ---" +while IFS= read -r -d '' src; do + rel="${src#"$REPO_ROOT/.agents/skills/"}" + dest="$TEMP_HOME/.agents/skills/$rel" + if diff -q "$src" "$dest" > /dev/null 2>&1; then + pass "skills/$rel matches source" + else + fail "skills/$rel — missing or differs from source" + fi +done < <(find "$REPO_ROOT/.agents/skills" -type f -print0) + +echo "" +echo "--- provider adapter: ~/.claude/skills/ is a symlink to ~/.agents/skills/ ---" +adapter="$TEMP_HOME/.claude/skills" +skills_canonical="$TEMP_HOME/.agents/skills" +if [[ -L "$adapter" ]]; then + resolved="$(readlink "$adapter")" + if [[ "$resolved" == "$skills_canonical" ]]; then + pass "~/.claude/skills → ~/.agents/skills (correct target)" + else + fail "~/.claude/skills symlink points to wrong target: $resolved" + fi else - fail "~/.agents/skills/ not found" + fail "~/.claude/skills is not a symlink" fi echo "" -echo "--- idempotency ---" -if HOME="$TEMP_HOME" bash "$REPO_ROOT/scripts/install.sh" > /dev/null 2>&1; then - pass "second run exits zero" +echo "--- skills correctly replaced on second install (no double-nesting) ---" +skill_name="$(basename "$(find "$REPO_ROOT/.agents/skills" -mindepth 1 -maxdepth 1 -type d | head -1)")" +nested="$TEMP_HOME/.agents/skills/$skill_name/$skill_name" +if [[ -d "$nested" ]]; then + fail "$skill_name/$skill_name exists — skill was nested instead of replaced" else - fail "second run failed" + pass "$skill_name not double-nested after second install" +fi + +echo "" +echo "--- user skills preserved after install (merge, not wipe) ---" +TEMP_HOME2="$(mktemp -d)" +trap 'rm -rf "$TEMP_HOME2"' EXIT +# Pre-create a user skill not in the source +mkdir -p "$TEMP_HOME2/.agents/skills/my-custom-skill" +echo "custom" > "$TEMP_HOME2/.agents/skills/my-custom-skill/SKILL.md" +HOME="$TEMP_HOME2" bash "$REPO_ROOT/scripts/install.sh" > /dev/null +if [[ -f "$TEMP_HOME2/.agents/skills/my-custom-skill/SKILL.md" ]]; then + pass "pre-existing user skill preserved after install" +else + fail "pre-existing user skill was wiped by install" +fi + +echo "" +echo "--- warning emitted when adapter target is a real directory ---" +TEMP_HOME3="$(mktemp -d)" +trap 'rm -rf "$TEMP_HOME3"' EXIT +mkdir -p "$TEMP_HOME3/.claude/skills/some-user-skill" +output="$(HOME="$TEMP_HOME3" bash "$REPO_ROOT/scripts/install.sh" 2>&1)" +if echo "$output" | grep -q "Warning"; then + pass "warning emitted when ~/.claude/skills exists as real directory" +else + fail "no warning when ~/.claude/skills is a real directory" +fi +if [[ -d "$TEMP_HOME3/.claude/skills/some-user-skill" ]]; then + pass "real directory left intact when warning emitted" +else + fail "real directory was destroyed despite warning" +fi + +echo "" +echo "--- idempotency: second run state is correct ---" +HOME="$TEMP_HOME" bash "$REPO_ROOT/scripts/install.sh" > /dev/null 2>&1 \ + && pass "second run exits zero" \ + || fail "second run failed" + +# skill files still match source after second run +while IFS= read -r -d '' src; do + rel="${src#"$REPO_ROOT/.agents/skills/"}" + dest="$TEMP_HOME/.agents/skills/$rel" + if diff -q "$src" "$dest" > /dev/null 2>&1; then + pass "idempotent: skills/$rel correct after second install" + else + fail "idempotent: skills/$rel corrupted after second install" + fi +done < <(find "$REPO_ROOT/.agents/skills" -type f -print0) + +# no double-nesting after second run +while IFS= read -r -d '' skill_dir; do + skill_name="$(basename "$skill_dir")" + nested="$TEMP_HOME/.agents/skills/$skill_name/$skill_name" + if [[ -d "$nested" ]]; then + fail "idempotent: $skill_name double-nested after second install" + else + pass "idempotent: $skill_name not double-nested after second install" + fi +done < <(find "$REPO_ROOT/.agents/skills" -mindepth 1 -maxdepth 1 -type d -print0) + +# symlink still correct after second run +adapter="$TEMP_HOME/.claude/skills" +if [[ -L "$adapter" ]] && [[ "$(readlink "$adapter")" == "$TEMP_HOME/.agents/skills" ]]; then + pass "idempotent: ~/.claude/skills symlink intact after second install" +else + fail "idempotent: ~/.claude/skills symlink broken after second install" fi echo ""