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
This commit is contained in:
m3tm3re
2026-01-11 13:06:32 +01:00
parent 3e20c82603
commit d53031bfc9
4 changed files with 335 additions and 120 deletions

178
AGENTS.md
View File

@@ -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 <id> # View issue details
bd update <id> --status in_progress # Claim work
bd close <id> # 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/<skill-name>
# Check skill triggers by reading SKILL.md frontmatter
grep -A5 "^description:" skill/<skill-name>/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

View File

@@ -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/<name>`
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/<name>`
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

151
scripts/test-skill.sh Executable file
View File

@@ -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 <skill> # 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

View File

@@ -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 <skill_directory>")
sys.exit(1)
valid, message = validate_skill(sys.argv[1])
print(message)
sys.exit(0 if valid else 1)