From d53031bfc95f3fb430217db73384263b799b8515 Mon Sep 17 00:00:00 2001 From: m3tm3re Date: Sun, 11 Jan 2026 13:06:32 +0100 Subject: [PATCH] Add skill testing workflow and clarify Nix integration - Add scripts/test-skill.sh for testing skills without deploying - Update AGENTS.md with accurate Nix integration details - Explain agent config nuance (embedded vs symlinked) - Fix quick_validate.py missing skill_md variable - Update README.md to match documentation changes --- AGENTS.md | 178 ++++++++++-------- README.md | 74 +++++--- scripts/test-skill.sh | 151 +++++++++++++++ skill/skill-creator/scripts/quick_validate.py | 52 +++-- 4 files changed, 335 insertions(+), 120 deletions(-) create mode 100755 scripts/test-skill.sh diff --git a/AGENTS.md b/AGENTS.md index 912d731..682fd77 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,7 +29,8 @@ This repository serves as a foundation for any Opencode-compatible skill or agen ``` . -├── agent/ # AI agent configurations (chiron.md) +├── agent/ # Agent definitions (agents.json) +├── prompts/ # Agent system prompts (chiron.txt, chiron-forge.txt) ├── context/ # User profiles and preferences ├── command/ # Custom command definitions ├── skill/ # Opencode Agent Skills (8 skills) @@ -41,32 +42,10 @@ This repository serves as a foundation for any Opencode-compatible skill or agen │ ├── mem0-memory/ │ ├── research/ │ └── knowledge-management/ -├── .beads/ # Issue tracking database +├── scripts/ # Repository-level utility scripts └── AGENTS.md # This file ``` -## Issue Tracking with Beads - -This project uses **bd** (beads) for AI-native issue tracking. - -### Quick Reference - -```bash -bd ready # Find available work -bd show # View issue details -bd update --status in_progress # Claim work -bd close # Complete work -bd sync # Sync with git -bd list # View all issues -bd create "title" # Create new issue -``` - -### Beads Workflow Integration - -- Issues live in `.beads/` directory (git-tracked) -- Auto-syncs with commits -- CLI-first design for AI agents -- No web UI needed ## Skill Development @@ -235,37 +214,43 @@ items: ## Nix Flake Integration -This repository is designed to be consumed by a Nix flake via home-manager. +This repository is the central source for all Opencode configuration, consumed as a **non-flake input** by your NixOS configuration. -### Expected Deployment Pattern +### Integration Reference +**NixOS config location**: `~/p/NIX/nixos-config/home/features/coding/opencode.nix` + +**Flake input definition** (in `flake.nix`): ```nix -# In your flake.nix or home.nix -xdg.configFile."opencode" = { - source = /path/to/AGENTS; - recursive = true; +agents = { + url = "git+https://code.m3ta.dev/m3tam3re/AGENTS"; + flake = false; # Pure files, not a Nix flake }; ``` -This deploys: -- `agent/` → `~/.config/opencode/agent/` -- `skill/` → `~/.config/opencode/skill/` -- `context/` → `~/.config/opencode/context/` -- `command/` → `~/.config/opencode/command/` +### Deployment Mapping -### Best Practices for Nix +| Source | Deployed To | Method | +|--------|-------------|--------| +| `skill/` | `~/.config/opencode/skill/` | xdg.configFile (symlink) | +| `context/` | `~/.config/opencode/context/` | xdg.configFile (symlink) | +| `command/` | `~/.config/opencode/command/` | xdg.configFile (symlink) | +| `prompts/` | `~/.config/opencode/prompts/` | xdg.configFile (symlink) | +| `agent/agents.json` | `programs.opencode.settings.agent` | **Embedded into config.json** | + +### Important: Agent Configuration Nuance + +The `agent/` directory is **NOT** deployed as files to `~/.config/opencode/agent/`. Instead, `agents.json` is read at Nix evaluation time and embedded directly into the opencode `config.json` via: + +```nix +programs.opencode.settings.agent = builtins.fromJSON (builtins.readFile "${inputs.agents}/agent/agents.json"); +``` + +**Implications**: +- Agent changes require `home-manager switch` to take effect +- Skills, context, and commands are symlinked (changes visible immediately after rebuild) +- The `prompts/` directory is referenced by `agents.json` via `{file:./prompts/chiron.txt}` syntax -1. **Keep original structure** - Don't rename directories; Opencode expects this layout -2. **Use recursive = true** - Required for directory deployment -3. **Exclude .git and .beads** - These are development artifacts: - ```nix - xdg.configFile."opencode" = { - source = /path/to/AGENTS; - recursive = true; - # Or filter with lib.cleanSource - }; - ``` -4. **No absolute paths in configs** - All references should be relative ## Quality Gates @@ -282,41 +267,78 @@ Before committing changes, verify: **When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds. -### MANDATORY WORKFLOW -1. **File issues for remaining work** - Create beads issues for anything that needs follow-up -2. **Run quality gates** - Validate modified skills, check file structure -3. **Update issue status** - Close finished work, update in-progress items -4. **PUSH TO REMOTE** - This is MANDATORY: - ```bash - git pull --rebase - bd sync - git push - git status # MUST show "up to date with origin" - ``` -5. **Clean up** - Clear stashes, prune remote branches -6. **Verify** - All changes committed AND pushed -7. **Hand off** - Provide context for next session +## Testing Skills -### CRITICAL RULES +Since this repo deploys via Nix/home-manager, changes require a rebuild to appear in `~/.config/opencode/`. Use these methods to test skills during development. -- Work is NOT complete until `git push` succeeds -- NEVER stop before pushing - that leaves work stranded locally -- NEVER say "ready to push when you are" - YOU must push -- If push fails, resolve and retry until it succeeds +### Method 1: XDG_CONFIG_HOME Override (Recommended) -## Common Operations - -### Test a Skill Locally +Test skills by pointing opencode to this repository directly: ```bash -# Validate structure +# From the AGENTS repository root +cd ~/p/AI/AGENTS + +# List skills loaded from this repo (not the deployed ones) +XDG_CONFIG_HOME=. opencode debug skill + +# Run an interactive session with development skills +XDG_CONFIG_HOME=. opencode + +# Or use the convenience script +./scripts/test-skill.sh # List all development skills +./scripts/test-skill.sh task-management # Validate specific skill +./scripts/test-skill.sh --run # Launch interactive session +``` + +**Note**: The convenience script creates a temporary directory with proper symlinks since opencode expects `$XDG_CONFIG_HOME/opencode/skill/` structure. + +### Method 2: Project-Local Skills + +For quick iteration on a single skill, use `.opencode/skill/` in any project: + +```bash +cd /path/to/any/project +mkdir -p .opencode/skill/ + +# Symlink the skill you're developing +ln -s ~/p/AI/AGENTS/skill/my-skill .opencode/skill/ + +# Skills in .opencode/skill/ are auto-discovered alongside global skills +opencode debug skill +``` + +### Method 3: Validation Only + +Validate skill structure without running opencode: + +```bash +# Validate a single skill python3 skill/skill-creator/scripts/quick_validate.py skill/ -# Check skill triggers by reading SKILL.md frontmatter -grep -A5 "^description:" skill//SKILL.md +# Validate all skills +for dir in skill/*/; do + python3 skill/skill-creator/scripts/quick_validate.py "$dir" +done ``` +### Verification Commands + +```bash +# List all loaded skills (shows name, description, location) +opencode debug skill + +# Show resolved configuration +opencode debug config + +# Show where opencode looks for files +opencode debug paths +``` + + +## Common Operations + ### Create New Skill ```bash @@ -340,11 +362,10 @@ Edit `context/profile.md` to update: ### Modify Agent Behavior -Edit `agent/chiron.md` to adjust: -- Skill routing logic -- Communication protocols -- Daily rhythm support -- Operating principles +Edit `agent/agents.json` to adjust agent definitions, and `prompts/*.txt` for system prompts: +- `agent/agents.json` - Agent names, models, permissions +- `prompts/chiron.txt` - Chiron (Plan Mode) system prompt +- `prompts/chiron-forge.txt` - Chiron-Forge (Worker Mode) system prompt ## Reference Documentation @@ -352,8 +373,7 @@ Edit `agent/chiron.md` to adjust: **Workflow patterns**: `skill/skill-creator/references/workflows.md` **Output patterns**: `skill/skill-creator/references/output-patterns.md` **User profile**: `context/profile.md` -**Agent config**: `agent/chiron.md` -**Beads docs**: `.beads/README.md` +**Agent config**: `agent/agents.json` ## Notes for AI Agents diff --git a/README.md b/README.md index 59eb703..338ca4c 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ This repository serves as a **personal AI operating system** - a collection of s ``` . -├── agent/ # AI agent configurations -│ └── chiron.md # Personal assistant agent +├── agent/ # Agent definitions (agents.json) +├── prompts/ # Agent system prompts (chiron.txt, chiron-forge.txt) ├── context/ # User profiles and preferences │ └── profile.md # Work style, PARA areas, preferences ├── command/ # Custom command definitions @@ -32,6 +32,8 @@ This repository serves as a **personal AI operating system** - a collection of s │ ├── mem0-memory/ # Persistent memory │ ├── research/ # Investigation workflows │ └── knowledge-management/ # Note capture & organization +├── scripts/ # Repository utility scripts +│ └── test-skill.sh # Test skills without deploying ├── .beads/ # Issue tracking database ├── AGENTS.md # Developer documentation └── README.md # This file @@ -50,22 +52,26 @@ This repository serves as a **personal AI operating system** - a collection of s #### Option 1: Nix Flake (Recommended) -Add to your Nix flake configuration: +This repository is consumed as a **non-flake input** by your NixOS configuration: ```nix -# In your flake.nix or home.nix -{ - inputs.agents = { - url = "github:yourusername/AGENTS"; # Update with your repo - flake = false; - }; +# In your flake.nix +inputs.agents = { + url = "git+https://code.m3ta.dev/m3tam3re/AGENTS"; + flake = false; # Pure files, not a Nix flake +}; - # In your home-manager configuration - xdg.configFile."opencode" = { - source = inputs.agents; - recursive = true; - }; -} +# In your home-manager module (e.g., opencode.nix) +xdg.configFile = { + "opencode/skill".source = "${inputs.agents}/skill"; + "opencode/context".source = "${inputs.agents}/context"; + "opencode/command".source = "${inputs.agents}/command"; + "opencode/prompts".source = "${inputs.agents}/prompts"; +}; + +# Agent config is embedded into config.json, not deployed as files +programs.opencode.settings.agent = builtins.fromJSON + (builtins.readFile "${inputs.agents}/agent/agents.json"); ``` Rebuild your system: @@ -73,6 +79,8 @@ Rebuild your system: home-manager switch ``` +**Note**: The `agent/` directory is NOT deployed as files. Instead, `agents.json` is read at Nix evaluation time and embedded into the opencode `config.json`. + #### Option 2: Manual Installation Clone and symlink: @@ -134,9 +142,18 @@ compatibility: opencode python3 skill/skill-creator/scripts/quick_validate.py skill/my-skill-name ``` -### 4. Test in Opencode +### 4. Test the Skill -Open Opencode and trigger your skill naturally in conversation. The skill will load based on the `description` field in the frontmatter. +Test your skill without deploying via home-manager: + +```bash +# Use the test script to validate and list skills +./scripts/test-skill.sh my-skill-name # Validate specific skill +./scripts/test-skill.sh --list # List all dev skills +./scripts/test-skill.sh --run # Launch opencode with dev skills +``` + +The test script creates a temporary config directory with symlinks to this repo's skills, allowing you to test changes before committing. ## 📚 Available Skills @@ -155,7 +172,7 @@ Open Opencode and trigger your skill naturally in conversation. The skill will l ### Chiron - Personal Assistant -**Location**: `agent/chiron.md` +**Configuration**: `agent/agents.json` + `prompts/chiron.txt` Chiron is a personal AI assistant focused on productivity and task management. Named after the wise centaur from Greek mythology, Chiron provides: @@ -164,6 +181,10 @@ Chiron is a personal AI assistant focused on productivity and task management. N - Skill routing based on user intent - Integration with productivity tools (Anytype, ntfy, n8n) +**Modes**: +- **Chiron** (Plan Mode) - Read-only analysis and planning (`prompts/chiron.txt`) +- **Chiron-Forge** (Worker Mode) - Full write access with safety prompts (`prompts/chiron-forge.txt`) + **Triggers**: Personal productivity requests, task management, reviews, planning ## 🛠️ Development Workflow @@ -184,9 +205,9 @@ bd sync # Sync with git Before committing: -1. **Validate skills**: `python3 skill/skill-creator/scripts/quick_validate.py skill/` -2. **Check formatting**: Ensure YAML frontmatter is valid -3. **Test locally**: Use the skill in Opencode to verify behavior +1. **Validate skills**: `./scripts/test-skill.sh --validate` or `python3 skill/skill-creator/scripts/quick_validate.py skill/` +2. **Test locally**: `./scripts/test-skill.sh --run` to launch opencode with dev skills +3. **Check formatting**: Ensure YAML frontmatter is valid 4. **Update docs**: Keep README and AGENTS.md in sync ### Session Completion @@ -232,11 +253,12 @@ See `AGENTS.md` for complete developer documentation. ### Modify Agent Behavior -Edit `agent/chiron.md` to customize: -- Skill routing logic -- Communication style -- Operating principles -- Integration awareness +Edit `agent/agents.json` for agent definitions and `prompts/*.txt` for system prompts: +- `agent/agents.json` - Agent names, models, permissions +- `prompts/chiron.txt` - Chiron (Plan Mode) system prompt +- `prompts/chiron-forge.txt` - Chiron-Forge (Worker Mode) system prompt + +**Note**: Agent changes require `home-manager switch` to take effect (config is embedded, not symlinked). ### Update User Context diff --git a/scripts/test-skill.sh b/scripts/test-skill.sh new file mode 100755 index 0000000..09dadf6 --- /dev/null +++ b/scripts/test-skill.sh @@ -0,0 +1,151 @@ +#!/usr/bin/env bash +# +# Test skills by launching opencode with this repo's config +# +# Usage: +# ./scripts/test-skill.sh # List all development skills +# ./scripts/test-skill.sh # Validate specific skill +# ./scripts/test-skill.sh --run # Launch interactive opencode session +# +# This script creates a temporary XDG_CONFIG_HOME with symlinks to this +# repository's skill/, context/, command/, and prompts/ directories, +# allowing you to test skill changes before deploying via home-manager. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(dirname "$SCRIPT_DIR")" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +setup_test_config() { + local tmp_base="${TMPDIR:-/tmp}/opencode-test-$$" + local tmp_config="$tmp_base/opencode" + + mkdir -p "$tmp_config" + ln -sf "$REPO_ROOT/skill" "$tmp_config/skill" + ln -sf "$REPO_ROOT/context" "$tmp_config/context" + ln -sf "$REPO_ROOT/command" "$tmp_config/command" + ln -sf "$REPO_ROOT/prompts" "$tmp_config/prompts" + + echo "$tmp_base" +} + +cleanup_test_config() { + local tmp_base="$1" + rm -rf "$tmp_base" +} + +usage() { + echo "Usage: $0 [OPTIONS] [SKILL_NAME]" + echo "" + echo "Test Opencode skills from this repository without deploying." + echo "" + echo "Options:" + echo " --run Launch interactive opencode session with dev skills" + echo " --list List all skills (default if no args)" + echo " --validate Validate all skills" + echo " --help Show this help message" + echo "" + echo "Arguments:" + echo " SKILL_NAME Validate a specific skill by name" + echo "" + echo "Examples:" + echo " $0 # List all development skills" + echo " $0 task-management # Validate task-management skill" + echo " $0 --validate # Validate all skills" + echo " $0 --run # Launch interactive session" +} + +list_skills() { + local tmp_base + tmp_base=$(setup_test_config) + trap "cleanup_test_config '$tmp_base'" EXIT + + echo -e "${YELLOW}Skills in development (from $REPO_ROOT):${NC}" + echo "" + XDG_CONFIG_HOME="$tmp_base" opencode debug skill +} + +validate_skill() { + local skill_name="$1" + local skill_path="$REPO_ROOT/skill/$skill_name" + + if [[ ! -d "$skill_path" ]]; then + echo -e "${RED}❌ Skill not found: $skill_name${NC}" + echo "Available skills:" + ls -1 "$REPO_ROOT/skill/" + exit 1 + fi + + echo -e "${YELLOW}Validating skill: $skill_name${NC}" + if python3 "$REPO_ROOT/skill/skill-creator/scripts/quick_validate.py" "$skill_path"; then + echo -e "${GREEN}✅ Skill '$skill_name' is valid${NC}" + else + echo -e "${RED}❌ Skill '$skill_name' has validation errors${NC}" + exit 1 + fi +} + +validate_all() { + echo -e "${YELLOW}Validating all skills...${NC}" + echo "" + + local failed=0 + for skill_dir in "$REPO_ROOT/skill/"*/; do + local skill_name=$(basename "$skill_dir") + echo -n " $skill_name: " + if python3 "$REPO_ROOT/skill/skill-creator/scripts/quick_validate.py" "$skill_dir" > /dev/null 2>&1; then + echo -e "${GREEN}✅${NC}" + else + echo -e "${RED}❌${NC}" + python3 "$REPO_ROOT/skill/skill-creator/scripts/quick_validate.py" "$skill_dir" 2>&1 | sed 's/^/ /' + ((failed++)) || true + fi + done + + echo "" + if [[ $failed -eq 0 ]]; then + echo -e "${GREEN}All skills valid!${NC}" + else + echo -e "${RED}$failed skill(s) failed validation${NC}" + exit 1 + fi +} + +run_opencode() { + local tmp_base + tmp_base=$(setup_test_config) + trap "cleanup_test_config '$tmp_base'" EXIT + + echo -e "${YELLOW}Launching opencode with development skills...${NC}" + echo -e "Config path: ${GREEN}$tmp_base/opencode${NC}" + echo "" + XDG_CONFIG_HOME="$tmp_base" opencode +} + +# Main +case "${1:-}" in + --help|-h) + usage + exit 0 + ;; + --run) + run_opencode + ;; + --list) + list_skills + ;; + --validate) + validate_all + ;; + "") + list_skills + ;; + *) + validate_skill "$1" + ;; +esac diff --git a/skill/skill-creator/scripts/quick_validate.py b/skill/skill-creator/scripts/quick_validate.py index 4dbda29..970c860 100755 --- a/skill/skill-creator/scripts/quick_validate.py +++ b/skill/skill-creator/scripts/quick_validate.py @@ -9,18 +9,20 @@ import re import yaml from pathlib import Path + def validate_skill(skill_path): """Basic validation of a skill""" skill_path = Path(skill_path) + skill_md = skill_path / "SKILL.md" if not skill_md.exists(): return False, "SKILL.md not found" content = skill_md.read_text() - if not content.startswith('---'): + if not content.startswith("---"): return False, "No YAML frontmatter found" - match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL) + match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL) if not match: return False, "Invalid frontmatter format" @@ -33,7 +35,14 @@ def validate_skill(skill_path): except yaml.YAMLError as e: return False, f"Invalid YAML in frontmatter: {e}" - ALLOWED_PROPERTIES = {'name', 'description', 'license', 'allowed-tools', 'metadata', 'compatibility'} + ALLOWED_PROPERTIES = { + "name", + "description", + "license", + "allowed-tools", + "metadata", + "compatibility", + } unexpected_keys = set(frontmatter.keys()) - ALLOWED_PROPERTIES if unexpected_keys: @@ -42,40 +51,53 @@ def validate_skill(skill_path): f"Allowed properties are: {', '.join(sorted(ALLOWED_PROPERTIES))}" ) - if 'name' not in frontmatter: + if "name" not in frontmatter: return False, "Missing 'name' in frontmatter" - if 'description' not in frontmatter: + if "description" not in frontmatter: return False, "Missing 'description' in frontmatter" - name = frontmatter.get('name', '') + name = frontmatter.get("name", "") if not isinstance(name, str): return False, f"Name must be a string, got {type(name).__name__}" name = name.strip() if name: - if not re.match(r'^[a-z0-9-]+$', name): - return False, f"Name '{name}' should be hyphen-case (lowercase letters, digits, and hyphens only)" - if name.startswith('-') or name.endswith('-') or '--' in name: - return False, f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens" + if not re.match(r"^[a-z0-9-]+$", name): + return ( + False, + f"Name '{name}' should be hyphen-case (lowercase letters, digits, and hyphens only)", + ) + if name.startswith("-") or name.endswith("-") or "--" in name: + return ( + False, + f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens", + ) if len(name) > 64: - return False, f"Name is too long ({len(name)} characters). Maximum is 64 characters." + return ( + False, + f"Name is too long ({len(name)} characters). Maximum is 64 characters.", + ) - description = frontmatter.get('description', '') + description = frontmatter.get("description", "") if not isinstance(description, str): return False, f"Description must be a string, got {type(description).__name__}" description = description.strip() if description: - if '<' in description or '>' in description: + if "<" in description or ">" in description: return False, "Description cannot contain angle brackets (< or >)" if len(description) > 1024: - return False, f"Description is too long ({len(description)} characters). Maximum is 1024 characters." + return ( + False, + f"Description is too long ({len(description)} characters). Maximum is 1024 characters.", + ) return True, "Skill is valid!" + if __name__ == "__main__": if len(sys.argv) != 2: print("Usage: python quick_validate.py ") sys.exit(1) - + valid, message = validate_skill(sys.argv[1]) print(message) sys.exit(0 if valid else 1)