feat: deploy skills pipeline with provider adapter pattern
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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-name>/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-name>/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/<name>/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/<topic>.md` and are read on-demand.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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/<name>/` 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/<name>/provider-manifest.sh`; `install.sh` discovers and wires these up automatically.
|
||||
|
||||
### Architectural decisions
|
||||
|
||||
|
||||
@@ -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/<name>/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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
9
docs/adr/0007-provider-adapters-as-symlinks.md
Normal file
9
docs/adr/0007-provider-adapters-as-symlinks.md
Normal file
@@ -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/<name>/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.
|
||||
6
providers/claude-code/provider-manifest.sh
Normal file
6
providers/claude-code/provider-manifest.sh
Normal file
@@ -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")
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 ""
|
||||
|
||||
Reference in New Issue
Block a user