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)