feat: add per-repo skill deployment pattern
- Add bin/use-skills.sh helper with use_skill and load_skills_from_manifest - Add .skills manifest pattern for declarative skill configuration - Fix ai-skills.nix: remove broken npm plugin code, update skill list - Add update-opencode, web-search, web-research to flake.nix availableSkills - Add RFC and documentation for team adoption 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
0e098ba0bb
commit
e921fd96df
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
# Use bd merge for beads JSONL files
|
||||||
|
.beads/issues.jsonl merge=beads
|
||||||
68
bin/use-skills.sh
Executable file
68
bin/use-skills.sh
Executable file
|
|
@ -0,0 +1,68 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Helper for per-repo skill deployment via direnv
|
||||||
|
# Source this from your .envrc or copy the functions
|
||||||
|
#
|
||||||
|
# Usage in .envrc:
|
||||||
|
# source /path/to/skills/bin/use-skills.sh
|
||||||
|
# use_skills worklog web-search
|
||||||
|
#
|
||||||
|
# Or without sourcing (copy-paste into .envrc):
|
||||||
|
# SKILLS_REPO="git+ssh://git@forgejo.example/dan/skills.git"
|
||||||
|
# use_skill() { ... }
|
||||||
|
# use_skill worklog
|
||||||
|
|
||||||
|
# Default repo - uses local git, override with SKILLS_REPO for remote
|
||||||
|
# Local: git+file:///home/dan/proj/skills (default, works offline)
|
||||||
|
# Network: git+http://192.168.1.108:3000/dan/skills.git
|
||||||
|
SKILLS_REPO="${SKILLS_REPO:-git+file://$HOME/proj/skills}"
|
||||||
|
|
||||||
|
# Install a single skill via nix build + symlink
|
||||||
|
use_skill() {
|
||||||
|
local skill="$1"
|
||||||
|
local out
|
||||||
|
|
||||||
|
out=$(nix build --print-out-paths --no-link "${SKILLS_REPO}#${skill}" 2>/dev/null)
|
||||||
|
if [[ -z "$out" ]]; then
|
||||||
|
echo "use_skill: failed to build ${skill}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Claude Code
|
||||||
|
if [[ -d .claude ]] || [[ -n "${SKILLS_CLAUDE:-}" ]]; then
|
||||||
|
mkdir -p .claude/skills
|
||||||
|
ln -sfn "$out" ".claude/skills/${skill}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# OpenCode
|
||||||
|
if [[ -d .opencode ]] || [[ -n "${SKILLS_OPENCODE:-}" ]]; then
|
||||||
|
mkdir -p .opencode/skills
|
||||||
|
ln -sfn "$out" ".opencode/skills/${skill}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "use_skill: ${skill} -> ${out}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install multiple skills
|
||||||
|
use_skills() {
|
||||||
|
# Ensure at least one target exists
|
||||||
|
mkdir -p .claude/skills .opencode/skills
|
||||||
|
|
||||||
|
for skill in "$@"; do
|
||||||
|
use_skill "$skill"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load skills from .skills manifest file
|
||||||
|
load_skills_from_manifest() {
|
||||||
|
[[ ! -f .skills ]] && return 0
|
||||||
|
mkdir -p .claude/skills .opencode/skills
|
||||||
|
|
||||||
|
while IFS= read -r skill || [[ -n "$skill" ]]; do
|
||||||
|
# Skip empty lines and comments
|
||||||
|
[[ -z "$skill" || "$skill" =~ ^[[:space:]]*# ]] && continue
|
||||||
|
# Strip inline comments and whitespace
|
||||||
|
skill="${skill%%#*}"
|
||||||
|
skill="${skill// /}"
|
||||||
|
[[ -n "$skill" ]] && use_skill "$skill"
|
||||||
|
done < .skills
|
||||||
|
}
|
||||||
144
docs/PER-REPO-SKILLS.md
Normal file
144
docs/PER-REPO-SKILLS.md
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
# Per-Repo Skill Deployment
|
||||||
|
|
||||||
|
Deploy selected skills to individual projects using direnv + Nix.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Each project can declare which skills it needs. When team members enter the directory (via direnv), skills are symlinked from the Nix store into `.claude/skills/` and `.opencode/skills/`.
|
||||||
|
|
||||||
|
```
|
||||||
|
teammate clones repo
|
||||||
|
↓
|
||||||
|
direnv allow
|
||||||
|
↓
|
||||||
|
nix builds skills (cached)
|
||||||
|
↓
|
||||||
|
symlinks created in .claude/skills/
|
||||||
|
↓
|
||||||
|
Claude Code sees project-local skills
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Add to `.envrc`
|
||||||
|
|
||||||
|
**Option A: Source the helper** (if skills repo is accessible)
|
||||||
|
```bash
|
||||||
|
source ~/proj/skills/bin/use-skills.sh
|
||||||
|
use_skills worklog web-search
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B: Self-contained** (copy-paste, no external dependency)
|
||||||
|
```bash
|
||||||
|
# AI Agent Skills
|
||||||
|
SKILLS_REPO="git+file://$HOME/proj/skills" # or git+http://... for remote
|
||||||
|
|
||||||
|
use_skill() {
|
||||||
|
local skill="$1"
|
||||||
|
local out
|
||||||
|
out=$(nix build --print-out-paths --no-link "${SKILLS_REPO}#${skill}" 2>/dev/null)
|
||||||
|
if [[ -n "$out" ]]; then
|
||||||
|
mkdir -p .claude/skills .opencode/skills
|
||||||
|
ln -sfn "$out" ".claude/skills/${skill}"
|
||||||
|
ln -sfn "$out" ".opencode/skills/${skill}"
|
||||||
|
echo "use_skill: ${skill}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
use_skill worklog
|
||||||
|
use_skill web-search
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Add to `.gitignore`
|
||||||
|
|
||||||
|
```
|
||||||
|
.claude/skills/
|
||||||
|
.opencode/skills/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Done
|
||||||
|
|
||||||
|
Team members clone and run `direnv allow`. Skills appear.
|
||||||
|
|
||||||
|
## Available Skills
|
||||||
|
|
||||||
|
Run `nix flake show git+ssh://git@forgejo.delmore.io/dan/skills.git` to see all available skills.
|
||||||
|
|
||||||
|
Current list:
|
||||||
|
- `worklog` - Create org-mode worklogs
|
||||||
|
- `web-search` - Search the web via Claude
|
||||||
|
- `web-research` - Deep research with multiple backends
|
||||||
|
- `update-opencode` - Update OpenCode via Nix
|
||||||
|
- `update-spec-kit` - Update spec-kit ecosystem
|
||||||
|
- `niri-window-capture` - Capture window screenshots
|
||||||
|
- `screenshot-latest` - Find latest screenshots
|
||||||
|
- `tufte-press` - Generate study card JSON
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **direnv** triggers on directory entry
|
||||||
|
2. **nix build** fetches/builds the skill package (cached locally)
|
||||||
|
3. **symlink** points `.claude/skills/<name>` to `/nix/store/xxx-ai-skill-<name>`
|
||||||
|
4. **Claude Code** reads skills from `.claude/skills/` when in that directory
|
||||||
|
|
||||||
|
Skills are always fetched from the latest commit on the skills repo. Nix caches builds locally, so subsequent loads are fast.
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
### Different skills per project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Project A - .envrc
|
||||||
|
use_skill worklog
|
||||||
|
use_skill web-search
|
||||||
|
|
||||||
|
# Project B - .envrc
|
||||||
|
use_skill worklog
|
||||||
|
use_skill tufte-press
|
||||||
|
```
|
||||||
|
|
||||||
|
### Claude Code only (no OpenCode)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SKILLS_REPO="git+file://$HOME/proj/skills"
|
||||||
|
mkdir -p .claude/skills
|
||||||
|
for skill in worklog web-search; do
|
||||||
|
ln -sfn $(nix build --print-out-paths --no-link "${SKILLS_REPO}#${skill}") ".claude/skills/${skill}"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pin to specific version
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SKILLS_REPO="git+file://$HOME/proj/skills?rev=abc123def" # pin to commit
|
||||||
|
use_skill worklog # uses pinned revision
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Skills not appearing
|
||||||
|
|
||||||
|
1. Check `direnv allow` was run
|
||||||
|
2. Check `.claude/skills/` exists and has symlinks
|
||||||
|
3. Restart Claude Code (it loads skills at startup)
|
||||||
|
|
||||||
|
### nix build fails
|
||||||
|
|
||||||
|
1. Check network access to skills repo
|
||||||
|
2. Check `nix flake show $SKILLS_REPO` works
|
||||||
|
3. Check skill name is correct
|
||||||
|
|
||||||
|
### Symlinks broken after nix-collect-garbage
|
||||||
|
|
||||||
|
Re-run `direnv reload` to rebuild and re-link.
|
||||||
|
|
||||||
|
## Comparison with Global Skills
|
||||||
|
|
||||||
|
| Aspect | Global (~/.claude/skills/) | Per-Repo (.claude/skills/) |
|
||||||
|
|--------|---------------------------|---------------------------|
|
||||||
|
| Scope | All projects | Single project |
|
||||||
|
| Deployment | Nix Home Manager | direnv + nix build |
|
||||||
|
| Team sharing | Via dotfiles | Via project .envrc |
|
||||||
|
| Selection | User-wide | Per-project |
|
||||||
|
|
||||||
|
Use global for personal defaults. Use per-repo for project-specific or team-shared skills.
|
||||||
234
docs/RFC-SKILLS-MANIFEST.md
Normal file
234
docs/RFC-SKILLS-MANIFEST.md
Normal file
|
|
@ -0,0 +1,234 @@
|
||||||
|
# RFC: .skills Manifest Pattern
|
||||||
|
|
||||||
|
**Status**: Draft
|
||||||
|
**Created**: 2024-11-30
|
||||||
|
**Author**: dan + claude
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
A lightweight pattern for per-repo skill deployment where projects declare desired skills in a `.skills` manifest file. This enables:
|
||||||
|
- Team members get skills on clone + `direnv allow`
|
||||||
|
- Agents can query installed skills without loading full docs
|
||||||
|
- Agents can install skills by editing the manifest
|
||||||
|
- Central skills repo remains single source of truth
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
### Current State
|
||||||
|
- Skills live in central `skills` repo
|
||||||
|
- Global deployment via Nix Home Manager to `~/.claude/skills/`
|
||||||
|
- Per-repo deployment undocumented and manual
|
||||||
|
|
||||||
|
### Problems
|
||||||
|
1. **No per-repo differentiation** - all projects get same global skills
|
||||||
|
2. **Agent context bloat** - loading full skill docs is expensive
|
||||||
|
3. **No standard pattern** - each project reinvents .envrc setup
|
||||||
|
4. **Agent can't help** - no standard way for agent to install skills
|
||||||
|
|
||||||
|
### Goals
|
||||||
|
1. Projects declare which skills they need
|
||||||
|
2. Team members get skills automatically (via direnv)
|
||||||
|
3. Agents can answer "what skills do we have?"
|
||||||
|
4. Agents can add skills with minimal context
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
### The `.skills` Manifest
|
||||||
|
|
||||||
|
A simple text file in project root:
|
||||||
|
|
||||||
|
```
|
||||||
|
# .skills - AI agent skills for this project
|
||||||
|
# Run `direnv reload` after editing
|
||||||
|
|
||||||
|
worklog
|
||||||
|
web-search
|
||||||
|
```
|
||||||
|
|
||||||
|
**Format**:
|
||||||
|
- One skill name per line
|
||||||
|
- Lines starting with `#` are comments
|
||||||
|
- Empty lines ignored
|
||||||
|
- Skill names match flake package names
|
||||||
|
|
||||||
|
### The `.envrc` Integration
|
||||||
|
|
||||||
|
Projects add to their `.envrc`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# AI Agent Skills
|
||||||
|
if [[ -f .skills ]]; then
|
||||||
|
SKILLS_REPO="${SKILLS_REPO:-git+file://$HOME/proj/skills}"
|
||||||
|
mkdir -p .claude/skills .opencode/skills
|
||||||
|
while IFS= read -r skill || [[ -n "$skill" ]]; do
|
||||||
|
[[ -z "$skill" || "$skill" =~ ^[[:space:]]*# ]] && continue
|
||||||
|
skill="${skill%%#*}" # strip inline comments
|
||||||
|
skill="${skill// /}" # strip whitespace
|
||||||
|
out=$(nix build --print-out-paths --no-link "${SKILLS_REPO}#${skill}" 2>/dev/null)
|
||||||
|
if [[ -n "$out" ]]; then
|
||||||
|
ln -sfn "$out" ".claude/skills/${skill}"
|
||||||
|
ln -sfn "$out" ".opencode/skills/${skill}"
|
||||||
|
fi
|
||||||
|
done < .skills
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
Or source the helper:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
if [[ -f .skills ]]; then
|
||||||
|
source ~/proj/skills/bin/use-skills.sh
|
||||||
|
load_skills_from_manifest
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
### The `.gitignore` Entries
|
||||||
|
|
||||||
|
```
|
||||||
|
.claude/skills/
|
||||||
|
.opencode/skills/
|
||||||
|
```
|
||||||
|
|
||||||
|
The manifest (`.skills`) IS committed. The symlinks are not.
|
||||||
|
|
||||||
|
### Agent Context Blurb
|
||||||
|
|
||||||
|
Add to project's `CLAUDE.md` or `AGENTS.md`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Skills
|
||||||
|
|
||||||
|
This project uses AI agent skills. Skills are declared in `.skills` and installed via direnv.
|
||||||
|
|
||||||
|
**Installed**: See `.skills`
|
||||||
|
**Available**: worklog, web-search, web-research, update-opencode, update-spec-kit, niri-window-capture, screenshot-latest, tufte-press
|
||||||
|
**To add**: Edit `.skills`, then run `direnv reload`
|
||||||
|
**Docs**: See ~/proj/skills for full skill documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
This gives the agent enough context (~4 lines) to:
|
||||||
|
- Answer "what skills do we have?" → read `.skills`
|
||||||
|
- Add a skill → edit `.skills`, tell user to `direnv reload`
|
||||||
|
- Know where to find full docs if needed
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### Initial Setup (one-time per project)
|
||||||
|
|
||||||
|
1. Create `.skills` with desired skills
|
||||||
|
2. Add skills loader to `.envrc`
|
||||||
|
3. Add `.claude/skills/` to `.gitignore`
|
||||||
|
4. Add brief context to project's CLAUDE.md
|
||||||
|
5. Commit
|
||||||
|
|
||||||
|
### Team Member Onboarding
|
||||||
|
|
||||||
|
1. Clone repo
|
||||||
|
2. Run `direnv allow`
|
||||||
|
3. Skills are installed automatically
|
||||||
|
|
||||||
|
### Adding a Skill
|
||||||
|
|
||||||
|
**Human workflow**:
|
||||||
|
1. Edit `.skills`, add skill name
|
||||||
|
2. Run `direnv reload`
|
||||||
|
|
||||||
|
**Agent-assisted workflow**:
|
||||||
|
1. Human: "add the worklog skill"
|
||||||
|
2. Agent: edits `.skills`, adds `worklog`
|
||||||
|
3. Agent: "Added worklog to .skills. Run `direnv reload` to install."
|
||||||
|
|
||||||
|
### Querying Skills
|
||||||
|
|
||||||
|
**"What skills do we have?"**
|
||||||
|
- Agent reads `.skills` file
|
||||||
|
- Or runs `ls .claude/skills/`
|
||||||
|
|
||||||
|
**"What skills are available?"**
|
||||||
|
- Agent references the list in CLAUDE.md context
|
||||||
|
- Or runs `nix flake show $SKILLS_REPO`
|
||||||
|
|
||||||
|
## File Locations
|
||||||
|
|
||||||
|
```
|
||||||
|
project/
|
||||||
|
├── .skills # Manifest (committed)
|
||||||
|
├── .envrc # Loads skills from manifest (committed)
|
||||||
|
├── .gitignore # Ignores .claude/skills/ (committed)
|
||||||
|
├── CLAUDE.md # Agent context blurb (committed)
|
||||||
|
├── .claude/
|
||||||
|
│ └── skills/ # Symlinks to nix store (NOT committed)
|
||||||
|
│ ├── worklog -> /nix/store/xxx
|
||||||
|
│ └── web-search -> /nix/store/yyy
|
||||||
|
└── .opencode/
|
||||||
|
└── skills/ # Same symlinks (NOT committed)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Helper Script
|
||||||
|
|
||||||
|
`skills/bin/use-skills.sh` provides:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Load skills from .skills manifest
|
||||||
|
load_skills_from_manifest() {
|
||||||
|
[[ ! -f .skills ]] && return
|
||||||
|
mkdir -p .claude/skills .opencode/skills
|
||||||
|
while IFS= read -r skill || [[ -n "$skill" ]]; do
|
||||||
|
[[ -z "$skill" || "$skill" =~ ^[[:space:]]*# ]] && continue
|
||||||
|
use_skill "${skill%%#*}"
|
||||||
|
done < .skills
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration
|
||||||
|
|
||||||
|
### Existing Projects
|
||||||
|
|
||||||
|
1. Survey current `.envrc` files for skill-related setup
|
||||||
|
2. Extract skill list to `.skills`
|
||||||
|
3. Replace custom logic with standard pattern
|
||||||
|
4. Add CLAUDE.md context blurb
|
||||||
|
|
||||||
|
### New Projects
|
||||||
|
|
||||||
|
Use the template:
|
||||||
|
```bash
|
||||||
|
cp ~/proj/skills/templates/skills-envrc-snippet.sh .envrc.skills
|
||||||
|
cat .envrc.skills >> .envrc
|
||||||
|
echo "worklog" > .skills
|
||||||
|
echo ".claude/skills/" >> .gitignore
|
||||||
|
```
|
||||||
|
|
||||||
|
## Alternatives Considered
|
||||||
|
|
||||||
|
### Full flake.nix in each project
|
||||||
|
- Heavier than needed
|
||||||
|
- Not all projects want/have flake.nix
|
||||||
|
- Decided: .envrc + nix build is lighter
|
||||||
|
|
||||||
|
### Central config mapping repos to skills
|
||||||
|
- Single view of all projects
|
||||||
|
- But adds indirection and sync complexity
|
||||||
|
- Decided: per-repo manifest is simpler
|
||||||
|
|
||||||
|
### Agent edits .envrc directly
|
||||||
|
- Fragile - .envrc has other content
|
||||||
|
- Decided: separate .skills file is cleaner for agent editing
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
1. **Should we validate skill names?** Currently fails silently if skill doesn't exist.
|
||||||
|
2. **Should we support skill versions?** e.g., `worklog@v1.2.0`
|
||||||
|
3. **Should there be a `skills` CLI?** e.g., `skills add worklog`, `skills list`
|
||||||
|
|
||||||
|
## Implementation Checklist
|
||||||
|
|
||||||
|
- [x] Create `bin/use-skills.sh` helper
|
||||||
|
- [x] Create `docs/PER-REPO-SKILLS.md` documentation
|
||||||
|
- [x] Create this RFC
|
||||||
|
- [ ] Update `bin/use-skills.sh` with `load_skills_from_manifest`
|
||||||
|
- [ ] Create template `.skills` file
|
||||||
|
- [ ] Create template CLAUDE.md blurb
|
||||||
|
- [ ] Survey existing projects for migration
|
||||||
|
- [ ] Migrate pilot project
|
||||||
|
- [ ] Update AGENTS.md with pattern reference
|
||||||
133
docs/RFC-TEAM-SKILLS-ADOPTION.md
Normal file
133
docs/RFC-TEAM-SKILLS-ADOPTION.md
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
# RFC: Per-Project AI Agent Skills
|
||||||
|
|
||||||
|
**Status**: Proposal
|
||||||
|
**Date**: 2025-11-30
|
||||||
|
**Audience**: All project teams using Claude Code or OpenCode
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
A standard pattern for projects to declare which AI agent skills they need. Team members automatically get the right skills when they clone and enter the project directory.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Currently, AI agent skills are deployed globally to everyone's machine. This means:
|
||||||
|
- All projects get all skills, whether they need them or not
|
||||||
|
- No way to share project-specific skills with teammates
|
||||||
|
- No visibility into what skills a project uses
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Projects declare their skills in a `.skills` file. When you enter the directory (via direnv), the skills are automatically installed.
|
||||||
|
|
||||||
|
```
|
||||||
|
# .skills
|
||||||
|
worklog
|
||||||
|
web-search
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it. Clone the repo, run `direnv allow`, and you have the skills.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
```
|
||||||
|
You clone a repo with .skills file
|
||||||
|
↓
|
||||||
|
Run `direnv allow`
|
||||||
|
↓
|
||||||
|
direnv reads .skills, builds each skill via Nix
|
||||||
|
↓
|
||||||
|
Skills symlinked to .claude/skills/ and .opencode/skills/
|
||||||
|
↓
|
||||||
|
Claude Code / OpenCode sees the skills
|
||||||
|
```
|
||||||
|
|
||||||
|
Skills come from the central skills repo (`~/proj/skills`), so everyone gets the same version.
|
||||||
|
|
||||||
|
## Available Skills
|
||||||
|
|
||||||
|
| Skill | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `worklog` | Create org-mode worklogs documenting sessions |
|
||||||
|
| `web-search` | Search the web via Claude subprocess |
|
||||||
|
| `web-research` | Deep research with multiple backends |
|
||||||
|
| `update-opencode` | Update OpenCode version via Nix |
|
||||||
|
| `niri-window-capture` | Capture window screenshots (security-sensitive) |
|
||||||
|
| `tufte-press` | Generate Tufte-style study cards |
|
||||||
|
|
||||||
|
## Adopting This Pattern
|
||||||
|
|
||||||
|
### 1. Create `.skills` file
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "worklog" > .skills
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Update `.envrc`
|
||||||
|
|
||||||
|
Add to your project's `.envrc`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# AI Agent Skills
|
||||||
|
if [[ -f .skills ]]; then
|
||||||
|
source ~/proj/skills/bin/use-skills.sh
|
||||||
|
load_skills_from_manifest
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Update `.gitignore`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo ".claude/skills/" >> .gitignore
|
||||||
|
echo ".opencode/skills/" >> .gitignore
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. (Optional) Add agent context
|
||||||
|
|
||||||
|
Add to your `AGENTS.md` or `CLAUDE.md`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Skills
|
||||||
|
|
||||||
|
This project uses AI agent skills declared in `.skills`.
|
||||||
|
- **Installed**: See `.skills`
|
||||||
|
- **To add**: Edit `.skills`, run `direnv reload`
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Commit and share
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .skills .envrc .gitignore
|
||||||
|
git commit -m "Add AI agent skills support"
|
||||||
|
```
|
||||||
|
|
||||||
|
Teammates will get the skills when they `direnv allow`.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
**Q: Do I need Nix?**
|
||||||
|
A: Yes. Skills are built via Nix for reproducibility.
|
||||||
|
|
||||||
|
**Q: What if I'm offline?**
|
||||||
|
A: Works fine. Skills build from the local `~/proj/skills` repo.
|
||||||
|
|
||||||
|
**Q: Can I pin a specific version?**
|
||||||
|
A: Yes. Use `SKILLS_REPO="git+file://$HOME/proj/skills?rev=abc123"` in your `.envrc`.
|
||||||
|
|
||||||
|
**Q: What if a skill doesn't exist?**
|
||||||
|
A: The build will fail silently for that skill. Check the name matches a skill in the repo.
|
||||||
|
|
||||||
|
**Q: How do I see what skills are installed?**
|
||||||
|
A: `ls .claude/skills/` or `cat .skills`
|
||||||
|
|
||||||
|
**Q: How do I add a skill after setup?**
|
||||||
|
A: Edit `.skills`, then run `direnv reload`.
|
||||||
|
|
||||||
|
## Future Improvements
|
||||||
|
|
||||||
|
1. **Global direnv helper** - Add `load_project_skills` to `~/.config/direnv/direnvrc` so `.envrc` only needs one line
|
||||||
|
2. **CLI tool** - `skills init` to set up a project, `skills add <name>` to add skills
|
||||||
|
3. **Validation** - Warn if a skill name doesn't exist
|
||||||
|
|
||||||
|
## Questions / Feedback
|
||||||
|
|
||||||
|
Open an issue in the skills repo or ask in the team channel.
|
||||||
211
docs/worklogs/2025-11-30-per-repo-skill-deployment-design.org
Normal file
211
docs/worklogs/2025-11-30-per-repo-skill-deployment-design.org
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
#+TITLE: Per-Repo Skill Deployment: Design and RFC
|
||||||
|
#+DATE: 2025-11-30
|
||||||
|
#+KEYWORDS: skills, per-repo, direnv, nix, deployment, rfc, beads, manifest
|
||||||
|
#+COMMITS: 0 (uncommitted work)
|
||||||
|
#+COMPRESSION_STATUS: uncompressed
|
||||||
|
|
||||||
|
* Session Summary
|
||||||
|
** Date: 2025-11-30 (Sunday)
|
||||||
|
** Focus Area: Designing and documenting per-repo skill deployment pattern
|
||||||
|
|
||||||
|
* Accomplishments
|
||||||
|
- [X] Explored skills repository structure and identified 9 outstanding issues
|
||||||
|
- [X] Populated beads issue tracker with all identified work items
|
||||||
|
- [X] Set up dependency relationships between design decisions and documentation tasks
|
||||||
|
- [X] Fixed ai-skills.nix module - removed broken opencode-skills npm plugin code
|
||||||
|
- [X] Updated flake.nix to include all 8 skills (was missing update-opencode, web-search, web-research)
|
||||||
|
- [X] Designed per-repo skill deployment pattern using direnv + nix build
|
||||||
|
- [X] Created bin/use-skills.sh helper script with use_skill, use_skills, load_skills_from_manifest
|
||||||
|
- [X] Created docs/PER-REPO-SKILLS.md with quick start documentation
|
||||||
|
- [X] Created docs/RFC-SKILLS-MANIFEST.md with full pattern specification
|
||||||
|
- [X] Introduced .skills manifest file concept for agent-friendly skill configuration
|
||||||
|
- [ ] Survey existing repos for .envrc patterns (next step)
|
||||||
|
- [ ] Migrate pilot project to new pattern
|
||||||
|
|
||||||
|
* Key Decisions
|
||||||
|
|
||||||
|
** Decision 1: Per-repo skills via direnv + nix build (not devShell)
|
||||||
|
- Context: User has Nix but wanted something lighter than full flake.nix in each consumer project
|
||||||
|
- Options considered:
|
||||||
|
1. Home Manager module - writes to global ~/.claude/skills/, no per-repo differentiation
|
||||||
|
2. devShell symlinks - requires flake.nix in every consumer project
|
||||||
|
3. direnv + nix build - lighter, just add to .envrc
|
||||||
|
- Rationale: direnv already used in many repos, nix build caches builds, no new files needed
|
||||||
|
- Impact: Each project can specify its own skill set via .envrc or .skills file
|
||||||
|
|
||||||
|
** Decision 2: .skills manifest file for agent editing
|
||||||
|
- Context: Agent needs to be able to add/query skills without bloating context
|
||||||
|
- Options considered:
|
||||||
|
1. Agent edits .envrc directly - fragile, .envrc has other content
|
||||||
|
2. Central config mapping repos to skills - indirection, sync complexity
|
||||||
|
3. Separate .skills file - clean for agent editing, .envrc reads it
|
||||||
|
- Rationale: .skills is simple text (one skill per line), easy for agent to read/modify
|
||||||
|
- Impact: Agent can answer "what skills do we have?" by reading .skills, can add skills by appending
|
||||||
|
|
||||||
|
** Decision 3: Keep both Claude Code and OpenCode support
|
||||||
|
- Context: Asked whether to simplify to Claude Code only
|
||||||
|
- Decision: Keep dual-agent support
|
||||||
|
- Rationale: Infrastructure already supports both, minimal extra complexity
|
||||||
|
- Impact: Skills symlinked to both .claude/skills/ and .opencode/skills/
|
||||||
|
|
||||||
|
** Decision 4: Always latest from skills repo (no version pinning)
|
||||||
|
- Context: User preference for simplicity over reproducibility here
|
||||||
|
- Decision: Default to latest, optional rev pinning if needed
|
||||||
|
- Impact: nix build fetches latest, caches locally for performance
|
||||||
|
|
||||||
|
* Problems & Solutions
|
||||||
|
| Problem | Solution | Learning |
|
||||||
|
|---------|----------|----------|
|
||||||
|
| ai-skills.nix had broken fetchFromNpm with empty sha256 | Removed entire opencode-skills npm plugin section | Keep modules simple, remove dead code |
|
||||||
|
| availableSkills list in flake.nix was stale | Added update-opencode, web-search, web-research | Need process to keep lists in sync |
|
||||||
|
| bd dep syntax confusion (from vs to) | bd dep add A B means "A depends on B" not "A blocks B" | Read help output carefully |
|
||||||
|
| Home Manager module writes to global paths, not per-repo | Per-repo uses .claude/skills/ in project, not global | Different deployment targets, different mechanisms |
|
||||||
|
|
||||||
|
* Technical Details
|
||||||
|
|
||||||
|
** Code Changes
|
||||||
|
- Total files modified: 2 existing + 3 new
|
||||||
|
- Key files changed:
|
||||||
|
- `modules/ai-skills.nix` - Removed 60 lines of broken npm plugin code, updated skill list in docs
|
||||||
|
- `flake.nix` - Added 3 skills to availableSkills list
|
||||||
|
- New files created:
|
||||||
|
- `bin/use-skills.sh` - Helper script with use_skill, use_skills, load_skills_from_manifest functions
|
||||||
|
- `docs/PER-REPO-SKILLS.md` - Quick start documentation for per-repo deployment
|
||||||
|
- `docs/RFC-SKILLS-MANIFEST.md` - Full RFC documenting the .skills manifest pattern
|
||||||
|
|
||||||
|
** Commands Used
|
||||||
|
#+begin_src bash
|
||||||
|
# Beads workflow
|
||||||
|
bd ready # See available work
|
||||||
|
bd create --title="..." --type=task --description="..."
|
||||||
|
bd update <id> --status=in_progress
|
||||||
|
bd close <id> --reason="..."
|
||||||
|
bd dep add <from> <to> # from depends on to
|
||||||
|
bd list
|
||||||
|
bd stats
|
||||||
|
|
||||||
|
# Nix validation
|
||||||
|
nix flake check # Verified all packages build
|
||||||
|
|
||||||
|
# Skill deployment pattern
|
||||||
|
nix build --print-out-paths --no-link "git+ssh://...#worklog"
|
||||||
|
ln -sfn "$out" ".claude/skills/worklog"
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Architecture Notes
|
||||||
|
- Skills repo flake exports individual packages per skill
|
||||||
|
- Consumer projects don't need flake.nix, just .envrc with nix build calls
|
||||||
|
- Symlinks point to /nix/store paths - Claude Code follows them fine
|
||||||
|
- .skills manifest is committed, .claude/skills/ symlinks are gitignored
|
||||||
|
- Brief context blurb in project CLAUDE.md tells agent about skills (~4 lines)
|
||||||
|
|
||||||
|
* Process and Workflow
|
||||||
|
|
||||||
|
** What Worked Well
|
||||||
|
- Beads for tracking issues - good visibility into work items
|
||||||
|
- Plan mode for thinking through architecture before coding
|
||||||
|
- Iterative questioning to clarify user requirements (Nix available? Source of truth?)
|
||||||
|
- "Ultrathink" prompt to go deeper on design questions
|
||||||
|
|
||||||
|
** What Was Challenging
|
||||||
|
- Initial confusion about what "per-repo" meant (global HM module vs project-local)
|
||||||
|
- Getting beads dependency direction right (had to remove and re-add)
|
||||||
|
- Balancing thoroughness with "don't overengineer" guidance
|
||||||
|
|
||||||
|
* Learning and Insights
|
||||||
|
|
||||||
|
** Technical Insights
|
||||||
|
- Claude Code loads skills from both ~/.claude/skills/ (global) AND .claude/skills/ (project-local)
|
||||||
|
- Project-local skills add to or shadow global - good for per-repo customization
|
||||||
|
- nix build --print-out-paths gives store path without building if cached
|
||||||
|
- direnv + nix build is lighter than devShell for simple cases
|
||||||
|
|
||||||
|
** Process Insights
|
||||||
|
- "Ultrathink" is a good trigger for deeper analysis
|
||||||
|
- Questioning assumptions early ("do you need flake.nix in every project?") saves design churn
|
||||||
|
- Agent-friendly design (like .skills manifest) is worth thinking about explicitly
|
||||||
|
|
||||||
|
** Architectural Insights
|
||||||
|
- Separation of concerns: manifest (.skills) vs loader (.envrc) vs runtime (symlinks)
|
||||||
|
- Agent context efficiency: brief blurb in CLAUDE.md, not full skill docs
|
||||||
|
- The .skills file serves multiple purposes: human config, agent query, agent edit
|
||||||
|
|
||||||
|
* Context for Future Work
|
||||||
|
|
||||||
|
** Open Questions
|
||||||
|
- Should we validate skill names? Currently fails silently if skill doesn't exist
|
||||||
|
- Should we support skill versions? e.g., `worklog@v1.2.0`
|
||||||
|
- Should there be a `skills` CLI? e.g., `skills add worklog`, `skills list`
|
||||||
|
- How to handle skills that need secrets (like web-research with KAGI_API_KEY)?
|
||||||
|
|
||||||
|
** Next Steps
|
||||||
|
- Survey existing repos for .envrc patterns
|
||||||
|
- Migrate pilot project to new .skills pattern
|
||||||
|
- Clean up beads.left.jsonl merge artifact
|
||||||
|
- Commit and push this session's work
|
||||||
|
|
||||||
|
** Related Work
|
||||||
|
- Previous: [[file:2025-11-09-nix-flake-module-development-opencode-skills-integration.org][Nix Flake Module Development]] - original HM module design
|
||||||
|
- Previous: [[file:2025-11-22-create-web-search-skill.org][Create Web Search Skill]] - skill that motivated this work
|
||||||
|
- Related: docs/CROSS-REPO-SKILL-COLLABORATION.md - Nix flake input pattern (for HM deployments)
|
||||||
|
- Created: docs/RFC-SKILLS-MANIFEST.md - this session's main artifact
|
||||||
|
|
||||||
|
* Raw Notes
|
||||||
|
|
||||||
|
Beads status at session end:
|
||||||
|
- 4 closed: skills-3o7 (nix fix), skills-pu4 (cleanup), skills-cnc (helper), skills-39g (RFC)
|
||||||
|
- 7 open: README updates, design decisions, code review items
|
||||||
|
|
||||||
|
Key insight: The question "how does an agent help configure skills?" led to the .skills manifest design. Agent-friendly != human-friendly, need both.
|
||||||
|
|
||||||
|
The pattern is intentionally minimal:
|
||||||
|
1. .skills file lists skill names
|
||||||
|
2. .envrc reads .skills and symlinks
|
||||||
|
3. CLAUDE.md tells agent about available skills
|
||||||
|
4. Agent can read/edit .skills, tell user to direnv reload
|
||||||
|
|
||||||
|
* Session Continuation: Pilot Migration and Flow Design
|
||||||
|
|
||||||
|
** Pilot Migration: orch
|
||||||
|
Successfully migrated orch project as pilot for .skills pattern:
|
||||||
|
- Created `.skills` manifest with `worklog`
|
||||||
|
- Updated `.envrc` to source helper and call `load_skills_from_manifest`
|
||||||
|
- Updated `.gitignore` to exclude `.claude/skills/` and `.opencode/skills/`
|
||||||
|
- Added Skills section to `AGENTS.md`
|
||||||
|
|
||||||
|
** Technical Fix: Local Nix Build
|
||||||
|
- Original default URL `git+ssh://git@forgejo.delmore.io/dan/skills.git` didn't resolve
|
||||||
|
- Changed to `git+file://$HOME/proj/skills` for offline/local development
|
||||||
|
- `path:` scheme failed due to `.beads/bd.sock` (unsupported file type)
|
||||||
|
- `git+file://` only includes git-tracked files, works correctly
|
||||||
|
|
||||||
|
** Verified Working
|
||||||
|
#+begin_src
|
||||||
|
orch/.claude/skills/worklog -> /nix/store/...-ai-skill-worklog
|
||||||
|
orch/.opencode/skills/worklog -> /nix/store/...-ai-skill-worklog
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
** Dotfiles Analysis
|
||||||
|
Explored how dotfiles manages Claude Code:
|
||||||
|
- `home/claude.nix` deploys 4 skills via Home Manager
|
||||||
|
- `~/.claude/settings.json` has `bd prime` hooks (global, not Nix-managed)
|
||||||
|
- Drift: dotfiles has 4 skills, skills repo has 8
|
||||||
|
- Global skills come from dotfiles copy, per-repo from skills repo directly
|
||||||
|
|
||||||
|
** Flow Design Discussion
|
||||||
|
Identified need to simplify per-repo flow. Current requires 4 file changes.
|
||||||
|
Options discussed:
|
||||||
|
1. Global direnv helper (add `load_project_skills` to direnvrc)
|
||||||
|
2. CLI tool (`skills init`, `skills add`)
|
||||||
|
3. Convention over configuration (auto-load if `.skills` exists)
|
||||||
|
|
||||||
|
Direction: Simplify with global direnv helper + team-facing RFC.
|
||||||
|
|
||||||
|
* Session Metrics
|
||||||
|
- Commits made: 0 (uncommitted)
|
||||||
|
- Files touched: 7+ (skills repo + orch pilot)
|
||||||
|
- Lines added/removed: ~500 lines new content
|
||||||
|
- New documentation: RFC-SKILLS-MANIFEST.md, PER-REPO-SKILLS.md
|
||||||
|
- Beads created: 9 issues
|
||||||
|
- Beads closed: 4 issues
|
||||||
|
- Pilot project: orch migrated successfully
|
||||||
|
|
@ -18,6 +18,9 @@
|
||||||
"tufte-press"
|
"tufte-press"
|
||||||
"worklog"
|
"worklog"
|
||||||
"update-spec-kit"
|
"update-spec-kit"
|
||||||
|
"update-opencode"
|
||||||
|
"web-search"
|
||||||
|
"web-research"
|
||||||
];
|
];
|
||||||
in
|
in
|
||||||
flake-utils.lib.eachDefaultSystem
|
flake-utils.lib.eachDefaultSystem
|
||||||
|
|
|
||||||
|
|
@ -4,22 +4,6 @@ with lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.services.ai-skills;
|
cfg = config.services.ai-skills;
|
||||||
|
|
||||||
# Helper to install opencode-skills npm package
|
|
||||||
opencodeSkillsPlugin = pkgs.buildNpmPackage rec {
|
|
||||||
pname = "opencode-skills";
|
|
||||||
version = "0.1.0";
|
|
||||||
|
|
||||||
src = pkgs.fetchFromNpm {
|
|
||||||
name = pname;
|
|
||||||
version = version;
|
|
||||||
sha256 = ""; # TODO: Get actual hash
|
|
||||||
};
|
|
||||||
|
|
||||||
# Alternative: install from npm directly at runtime
|
|
||||||
# This is a placeholder - actual implementation would fetch from npm
|
|
||||||
};
|
|
||||||
|
|
||||||
in {
|
in {
|
||||||
options.services.ai-skills = {
|
options.services.ai-skills = {
|
||||||
enable = mkEnableOption "AI agent skills for Claude Code and OpenCode";
|
enable = mkEnableOption "AI agent skills for Claude Code and OpenCode";
|
||||||
|
|
@ -34,8 +18,11 @@ in {
|
||||||
- tufte-press: Generate study card JSON
|
- tufte-press: Generate study card JSON
|
||||||
- worklog: Create org-mode worklogs
|
- worklog: Create org-mode worklogs
|
||||||
- update-spec-kit: Update spec-kit ecosystem
|
- update-spec-kit: Update spec-kit ecosystem
|
||||||
|
- update-opencode: Update OpenCode via Nix
|
||||||
|
- web-search: Search the web via Claude
|
||||||
|
- web-research: Deep web research with multiple backends
|
||||||
'';
|
'';
|
||||||
example = [ "worklog" "screenshot-latest" ];
|
example = [ "worklog" "web-search" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
skillsPath = mkOption {
|
skillsPath = mkOption {
|
||||||
|
|
@ -55,12 +42,6 @@ in {
|
||||||
default = true;
|
default = true;
|
||||||
description = "Deploy skills to OpenCode (~/.config/opencode/skills/)";
|
description = "Deploy skills to OpenCode (~/.config/opencode/skills/)";
|
||||||
};
|
};
|
||||||
|
|
||||||
installOpencodePlugin = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = true;
|
|
||||||
description = "Install opencode-skills npm plugin";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
|
|
@ -91,43 +72,6 @@ in {
|
||||||
}) cfg.skills
|
}) cfg.skills
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
|
|
||||||
# OpenCode plugin installation
|
|
||||||
(mkIf (cfg.enableOpenCode && cfg.installOpencodePlugin) {
|
|
||||||
".config/opencode/package.json" = {
|
|
||||||
text = builtins.toJSON {
|
|
||||||
dependencies = {
|
|
||||||
"@opencode-ai/plugin" = "1.0.44";
|
|
||||||
"opencode-skills" = "^0.1.0";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})
|
|
||||||
];
|
];
|
||||||
|
|
||||||
# Ensure opencode-skills plugin is in config
|
|
||||||
home.activation.opencodeSkillsPlugin = mkIf (cfg.enableOpenCode && cfg.installOpencodePlugin) (
|
|
||||||
lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
|
||||||
# Install npm dependencies for OpenCode
|
|
||||||
if [ -f "$HOME/.config/opencode/package.json" ]; then
|
|
||||||
cd "$HOME/.config/opencode"
|
|
||||||
if command -v bun &> /dev/null; then
|
|
||||||
${pkgs.bun}/bin/bun install
|
|
||||||
elif command -v npm &> /dev/null; then
|
|
||||||
${pkgs.nodejs}/bin/npm install
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ensure plugin is enabled in config
|
|
||||||
CONFIG_FILE="$HOME/.config/opencode/config.json"
|
|
||||||
if [ -f "$CONFIG_FILE" ]; then
|
|
||||||
# Check if plugin array includes opencode-skills
|
|
||||||
if ! ${pkgs.jq}/bin/jq -e '.plugin | index("opencode-skills")' "$CONFIG_FILE" &> /dev/null; then
|
|
||||||
echo "Warning: opencode-skills plugin not in config.json plugin array"
|
|
||||||
echo "Add it manually: { \"plugin\": [\"opencode-skills\"] }"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
''
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue