# Best Practices: Skills as Single Source of Truth **Philosophy**: This repository is the canonical source for all AI agent skills. All deployments consume via Nix flake inputs. ## Core Principles ### 1. Skills Repo is Authoritative **What this means**: - All skill development happens in `dan/skills` repository - No permanent local copies in consumer projects - Changes must be committed here to be deployed **Why**: - Clear ownership and maintenance responsibility - Version control provides audit trail - Reproducible deployments across all environments - Prevents drift and divergence ### 2. Modern Nix with Flakes **What this means**: - Use `flake.nix` for all configuration - Consumers add skills repo as flake input - Leverage flake.lock for reproducibility - Use NixOS modules for deployment **Why**: - Declarative configuration - Version pinning with flake.lock - Reproducible builds - Composable modules ### 3. Temporary Local Copies Only During Development **What this means**: - OK to copy skill to consumer for testing during initial development - Must switch to flake input once skill is stable - Duration: Hours to 1-2 days max, not weeks - Always transition to production pattern (flake input) **Why**: - Fast iteration during development - Clean production deployments - Prevents forgetting to sync changes - Clear distinction between dev and prod patterns ## Workflow Patterns ### Adding a New Skill **Development phase** (1-2 days): ```bash # 1. Create skill in skills repo cd ~/proj/skills mkdir -p skills/new-skill/{scripts,examples,templates} # ... create SKILL.md, README.md, etc ... # 2. Commit early (even if WIP) git add skills/new-skill/ git commit -m "WIP: Add new-skill (initial structure)" # 3. Optional: Copy to consumer for rapid testing cp -r skills/new-skill ~/proj/ops-dev/skills/ # Test, iterate, modify in place # 4. When working, sync back to skills repo cp -r ~/proj/ops-dev/skills/new-skill skills/ git add skills/new-skill/ git commit -m "Complete new-skill implementation" ``` **Production phase** (permanent): ```bash # 5. Add to flake packages (if not already) # Edit flake.nix, add to packages output # 6. Consumer switches to flake input cd ~/proj/ops-dev # Edit flake.nix: # services.ai-skills.selectedSkills = [ "new-skill" ]; nix flake lock --update-input skills # Deploy to VM # 7. Remove local copy rm -rf ~/proj/ops-dev/skills/new-skill git add -A git commit -m "Switch new-skill to flake input" ``` **Key point**: Don't skip step 6-7. Leaving skills as local copies defeats the purpose. ### Updating an Existing Skill **Always in skills repo**: ```bash # 1. Make changes in skills repo cd ~/proj/skills # Edit skills/tufte-press/SKILL.md or scripts # 2. Commit with clear message git commit -am "tufte-press: Add example for concurrent equations" # 3. Push to remote (Forgejo) git push origin master # 4. Consumers update when ready cd ~/proj/ops-dev nix flake lock --update-input skills # Deploy to VM ``` **Never**: - ❌ Make changes in consumer's local copy - ❌ Keep local modifications "just for us" - ❌ Fork the skill for project-specific tweaks **Instead**: - ✅ Make changes in skills repo - ✅ If change is project-specific, use configuration options - ✅ If broadly useful, add to skills repo for everyone ### Testing Changes Before Committing **Option A: Test in skills repo directly**: ```bash cd ~/proj/skills # Make changes vim skills/worklog/scripts/suggest-filename.sh # Test locally (if skill has test scripts) ./skills/worklog/scripts/suggest-filename.sh test-input.org # Commit when working git commit -am "worklog: Fix filename suggestion for long titles" ``` **Option B: Test in consumer with local override**: ```bash # Temporarily point to local path for testing cd ~/proj/ops-dev # Edit flake.nix: # inputs.skills.url = "path:/home/dan/proj/skills"; nix flake lock --update-input skills # Deploy to VM and test # When working, revert to git URL # Edit flake.nix: # inputs.skills.url = "git+http://192.168.1.108:3000/dan/skills.git"; nix flake lock --update-input skills ``` **Option C: Use a branch**: ```bash cd ~/proj/skills # Create feature branch git checkout -b feature/worklog-improvements # Make changes, commit git commit -am "worklog: Add weekly summary aggregation" git push origin feature/worklog-improvements # Consumer tests the branch cd ~/proj/ops-dev # Edit flake.nix: # inputs.skills.url = "git+http://192.168.1.108:3000/dan/skills.git?ref=feature/worklog-improvements"; nix flake lock --update-input skills # Test on VM # When stable, merge to master cd ~/proj/skills git checkout master git merge feature/worklog-improvements git push origin master # Consumer switches back to master cd ~/proj/ops-dev # Edit flake.nix: back to master URL nix flake lock --update-input skills ``` ## Anti-Patterns (Don't Do This) ### ❌ Permanent Local Copies **Wrong**: ```bash # Consumer repo permanently has skills/ directory ops-dev/ skills/ tufte-press/ # Local copy worklog/ # Local copy ``` **Why wrong**: - Must manually sync changes - Risk of divergence - Unclear which version is deployed - Defeats purpose of single source of truth **Right**: ```nix # Consumer repo uses flake input inputs.skills.url = "git+http://192.168.1.108:3000/dan/skills.git"; services.ai-skills.selectedSkills = [ "tufte-press" "worklog" ]; ``` ### ❌ Project-Specific Forks **Wrong**: ```bash # Creating modified copy for one project ops-dev/skills/tufte-press-custom/ # Modified version just for ops-dev ``` **Why wrong**: - Others can't benefit from improvements - Maintenance burden (must merge upstream changes) - Creates fragmentation **Right**: - Add configuration options to skill - Make skill flexible via environment variables or config files - Keep one version that works for everyone ### ❌ Manual File Copying **Wrong**: ```bash # Regularly doing this scp skills/new-feature.sh root@vm:/etc/opencode/skills/worklog/scripts/ ``` **Why wrong**: - Bypasses version control - Not reproducible - No audit trail - Breaks on next rebuild **Right**: ```bash # Commit to skills repo, update flake cd ~/proj/skills git commit -am "worklog: Add new feature" git push cd ~/proj/ops-dev nix flake lock --update-input skills # Deploy via nixos-rebuild ``` ### ❌ Long-Lived Development Branches **Wrong**: ```bash # Branch exists for weeks/months git checkout -b dan/experimental-features # ... months pass ... # Never merged, consumers stuck on old version ``` **Why wrong**: - Others can't use improvements - Becomes hard to merge later - Defeats shared repository purpose **Right**: - Short-lived feature branches (days, not weeks) - Merge to master frequently - Use feature flags if needed for WIP features - Or commit to master with "WIP" markers in docs ## Version Control Practices ### Commit Messages **Good examples**: ``` tufte-press: Add support for margin figure citations worklog: Fix filename generation for dates with slashes screenshot-latest: Improve error handling when no screenshots found niri-window-capture: Add security audit logging ``` **Pattern**: `: ` **Why**: Clear which skill changed, easy to scan git log ### When to Commit **Commit frequently**: - ✅ After adding new script - ✅ After fixing bug - ✅ After updating documentation - ✅ After testing confirms it works **Don't wait for**: - ❌ "Perfect" state (commit early, improve later) - ❌ All skills to be updated (commit per-skill changes separately) - ❌ "Big batch" of changes (many small commits better) ### Branching Strategy **Simple approach**: - `master` branch is always deployable - Feature branches for significant changes - Merge to master when working - Delete branch after merge **Example**: ```bash # New skill git checkout -b add-sql-formatter-skill # ... work ... git push origin add-sql-formatter-skill # ... test ... git checkout master git merge add-sql-formatter-skill git push origin master git branch -d add-sql-formatter-skill ``` ## Deployment Best Practices ### Pinning Versions **Default: Track latest**: ```nix inputs.skills.url = "git+http://192.168.1.108:3000/dan/skills.git"; # flake.lock tracks specific commit # Update with: nix flake lock --update-input skills ``` **When to pin**: Production systems that need stability ```nix inputs.skills.url = "git+http://192.168.1.108:3000/dan/skills.git?rev=abc123def"; # Locked to specific commit, won't change until manually updated ``` ### Update Cadence **Development VMs**: Update frequently ```bash # Daily or when skills change nix flake lock --update-input skills ``` **Production VMs**: Update intentionally ```bash # Weekly or after testing in dev nix flake lock --update-input skills # Test thoroughly before deploying ``` ### Rollback Strategy **If skill breaks**: ```bash # Option 1: Remove from selectedSkills temporarily services.ai-skills.selectedSkills = [ "tufte-press" # "worklog" # Disabled due to bug ]; # Option 2: Pin to older commit cd ~/proj/ops-dev nix flake lock --override-input skills git+http://.../skills.git?rev= # Option 3: Fix in skills repo and update cd ~/proj/skills # Fix bug git commit -am "worklog: Fix critical bug" git push cd ~/proj/ops-dev nix flake lock --update-input skills ``` ## Multi-User Coordination ### Communication **Before making breaking changes**: 1. Announce in team chat/IRC 2. Create issue in Forgejo 3. Use feature branch for testing 4. Get feedback before merging **After making significant changes**: 1. Update CHANGELOG in skill directory 2. Notify consumers 3. Document migration steps if needed ### Handling Conflicts **If two people modify same skill**: 1. Coordinate via git (branches, PRs) 2. Use git merge/rebase to combine changes 3. Test combined changes before deploying 4. Modern git handles this well ### Shared vs Personal Skills **Shared skills** (in skills repo): - General-purpose capabilities - Useful to multiple people/projects - Maintained collaboratively - Examples: tufte-press, worklog, screenshot-latest **Personal skills** (project-local): - Truly project-specific - Not useful to others - Rapid iteration needed - Can live in `.opencode/command/` or `.claude/skills/` locally **Default**: If in doubt, put in skills repo. Easy to share is better than hidden. ## Summary Checklist **When developing new skill**: - [x] Create in skills repo (even if WIP) - [x] Commit frequently - [x] OK to copy to consumer for testing (temporarily) - [x] Switch to flake input when stable (within 1-2 days) - [x] Remove local copy after switching **When updating existing skill**: - [x] Make changes in skills repo only - [x] Commit with clear message - [x] Push to remote - [x] Consumers update via flake lock - [x] Never modify in consumer's local copy **When deploying**: - [x] Use flake input (Model 1) - [x] Import ai-skills module - [x] Select skills via configuration - [x] Update with `nix flake lock --update-input skills` - [x] No manual file copying **Red flags**: - [ ] Permanent `skills/` directory in consumer repo - [ ] Manual `scp` of skill files - [ ] "Custom" versions of skills - [ ] Long-lived local modifications - [ ] Forgetting to sync changes back **Good signs**: - [x] All skills in skills repo - [x] Consumers use flake inputs - [x] Clear git history - [x] Frequent small commits - [x] Fast update cycle (commit → push → update → deploy) --- **The goal**: Make skills as easy to use and update as possible, while maintaining single source of truth and version control.