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:
178
AGENTS.md
178
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
|
├── context/ # User profiles and preferences
|
||||||
├── command/ # Custom command definitions
|
├── command/ # Custom command definitions
|
||||||
├── skill/ # Opencode Agent Skills (8 skills)
|
├── 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/
|
│ ├── mem0-memory/
|
||||||
│ ├── research/
|
│ ├── research/
|
||||||
│ └── knowledge-management/
|
│ └── knowledge-management/
|
||||||
├── .beads/ # Issue tracking database
|
├── scripts/ # Repository-level utility scripts
|
||||||
└── AGENTS.md # This file
|
└── 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
|
## Skill Development
|
||||||
|
|
||||||
@@ -235,37 +214,43 @@ items:
|
|||||||
|
|
||||||
## Nix Flake Integration
|
## 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
|
```nix
|
||||||
# In your flake.nix or home.nix
|
agents = {
|
||||||
xdg.configFile."opencode" = {
|
url = "git+https://code.m3ta.dev/m3tam3re/AGENTS";
|
||||||
source = /path/to/AGENTS;
|
flake = false; # Pure files, not a Nix flake
|
||||||
recursive = true;
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
This deploys:
|
### Deployment Mapping
|
||||||
- `agent/` → `~/.config/opencode/agent/`
|
|
||||||
- `skill/` → `~/.config/opencode/skill/`
|
|
||||||
- `context/` → `~/.config/opencode/context/`
|
|
||||||
- `command/` → `~/.config/opencode/command/`
|
|
||||||
|
|
||||||
### 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
|
## 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.
|
**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
|
## Testing Skills
|
||||||
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
|
|
||||||
|
|
||||||
### 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
|
### Method 1: XDG_CONFIG_HOME Override (Recommended)
|
||||||
- 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
|
|
||||||
|
|
||||||
## Common Operations
|
Test skills by pointing opencode to this repository directly:
|
||||||
|
|
||||||
### Test a Skill Locally
|
|
||||||
|
|
||||||
```bash
|
```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>
|
python3 skill/skill-creator/scripts/quick_validate.py skill/<skill-name>
|
||||||
|
|
||||||
# Check skill triggers by reading SKILL.md frontmatter
|
# Validate all skills
|
||||||
grep -A5 "^description:" skill/<skill-name>/SKILL.md
|
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
|
### Create New Skill
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -340,11 +362,10 @@ Edit `context/profile.md` to update:
|
|||||||
|
|
||||||
### Modify Agent Behavior
|
### Modify Agent Behavior
|
||||||
|
|
||||||
Edit `agent/chiron.md` to adjust:
|
Edit `agent/agents.json` to adjust agent definitions, and `prompts/*.txt` for system prompts:
|
||||||
- Skill routing logic
|
- `agent/agents.json` - Agent names, models, permissions
|
||||||
- Communication protocols
|
- `prompts/chiron.txt` - Chiron (Plan Mode) system prompt
|
||||||
- Daily rhythm support
|
- `prompts/chiron-forge.txt` - Chiron-Forge (Worker Mode) system prompt
|
||||||
- Operating principles
|
|
||||||
|
|
||||||
## Reference Documentation
|
## Reference Documentation
|
||||||
|
|
||||||
@@ -352,8 +373,7 @@ Edit `agent/chiron.md` to adjust:
|
|||||||
**Workflow patterns**: `skill/skill-creator/references/workflows.md`
|
**Workflow patterns**: `skill/skill-creator/references/workflows.md`
|
||||||
**Output patterns**: `skill/skill-creator/references/output-patterns.md`
|
**Output patterns**: `skill/skill-creator/references/output-patterns.md`
|
||||||
**User profile**: `context/profile.md`
|
**User profile**: `context/profile.md`
|
||||||
**Agent config**: `agent/chiron.md`
|
**Agent config**: `agent/agents.json`
|
||||||
**Beads docs**: `.beads/README.md`
|
|
||||||
|
|
||||||
## Notes for AI Agents
|
## Notes for AI Agents
|
||||||
|
|
||||||
|
|||||||
74
README.md
74
README.md
@@ -17,8 +17,8 @@ This repository serves as a **personal AI operating system** - a collection of s
|
|||||||
|
|
||||||
```
|
```
|
||||||
.
|
.
|
||||||
├── agent/ # AI agent configurations
|
├── agent/ # Agent definitions (agents.json)
|
||||||
│ └── chiron.md # Personal assistant agent
|
├── prompts/ # Agent system prompts (chiron.txt, chiron-forge.txt)
|
||||||
├── context/ # User profiles and preferences
|
├── context/ # User profiles and preferences
|
||||||
│ └── profile.md # Work style, PARA areas, preferences
|
│ └── profile.md # Work style, PARA areas, preferences
|
||||||
├── command/ # Custom command definitions
|
├── 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
|
│ ├── mem0-memory/ # Persistent memory
|
||||||
│ ├── research/ # Investigation workflows
|
│ ├── research/ # Investigation workflows
|
||||||
│ └── knowledge-management/ # Note capture & organization
|
│ └── knowledge-management/ # Note capture & organization
|
||||||
|
├── scripts/ # Repository utility scripts
|
||||||
|
│ └── test-skill.sh # Test skills without deploying
|
||||||
├── .beads/ # Issue tracking database
|
├── .beads/ # Issue tracking database
|
||||||
├── AGENTS.md # Developer documentation
|
├── AGENTS.md # Developer documentation
|
||||||
└── README.md # This file
|
└── 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)
|
#### 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
|
```nix
|
||||||
# In your flake.nix or home.nix
|
# In your flake.nix
|
||||||
{
|
inputs.agents = {
|
||||||
inputs.agents = {
|
url = "git+https://code.m3ta.dev/m3tam3re/AGENTS";
|
||||||
url = "github:yourusername/AGENTS"; # Update with your repo
|
flake = false; # Pure files, not a Nix flake
|
||||||
flake = false;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
# In your home-manager configuration
|
# In your home-manager module (e.g., opencode.nix)
|
||||||
xdg.configFile."opencode" = {
|
xdg.configFile = {
|
||||||
source = inputs.agents;
|
"opencode/skill".source = "${inputs.agents}/skill";
|
||||||
recursive = true;
|
"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:
|
Rebuild your system:
|
||||||
@@ -73,6 +79,8 @@ Rebuild your system:
|
|||||||
home-manager switch
|
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
|
#### Option 2: Manual Installation
|
||||||
|
|
||||||
Clone and symlink:
|
Clone and symlink:
|
||||||
@@ -134,9 +142,18 @@ compatibility: opencode
|
|||||||
python3 skill/skill-creator/scripts/quick_validate.py skill/my-skill-name
|
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
|
## 📚 Available Skills
|
||||||
|
|
||||||
@@ -155,7 +172,7 @@ Open Opencode and trigger your skill naturally in conversation. The skill will l
|
|||||||
|
|
||||||
### Chiron - Personal Assistant
|
### 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:
|
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
|
- Skill routing based on user intent
|
||||||
- Integration with productivity tools (Anytype, ntfy, n8n)
|
- 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
|
**Triggers**: Personal productivity requests, task management, reviews, planning
|
||||||
|
|
||||||
## 🛠️ Development Workflow
|
## 🛠️ Development Workflow
|
||||||
@@ -184,9 +205,9 @@ bd sync # Sync with git
|
|||||||
|
|
||||||
Before committing:
|
Before committing:
|
||||||
|
|
||||||
1. **Validate skills**: `python3 skill/skill-creator/scripts/quick_validate.py skill/<name>`
|
1. **Validate skills**: `./scripts/test-skill.sh --validate` or `python3 skill/skill-creator/scripts/quick_validate.py skill/<name>`
|
||||||
2. **Check formatting**: Ensure YAML frontmatter is valid
|
2. **Test locally**: `./scripts/test-skill.sh --run` to launch opencode with dev skills
|
||||||
3. **Test locally**: Use the skill in Opencode to verify behavior
|
3. **Check formatting**: Ensure YAML frontmatter is valid
|
||||||
4. **Update docs**: Keep README and AGENTS.md in sync
|
4. **Update docs**: Keep README and AGENTS.md in sync
|
||||||
|
|
||||||
### Session Completion
|
### Session Completion
|
||||||
@@ -232,11 +253,12 @@ See `AGENTS.md` for complete developer documentation.
|
|||||||
|
|
||||||
### Modify Agent Behavior
|
### Modify Agent Behavior
|
||||||
|
|
||||||
Edit `agent/chiron.md` to customize:
|
Edit `agent/agents.json` for agent definitions and `prompts/*.txt` for system prompts:
|
||||||
- Skill routing logic
|
- `agent/agents.json` - Agent names, models, permissions
|
||||||
- Communication style
|
- `prompts/chiron.txt` - Chiron (Plan Mode) system prompt
|
||||||
- Operating principles
|
- `prompts/chiron-forge.txt` - Chiron-Forge (Worker Mode) system prompt
|
||||||
- Integration awareness
|
|
||||||
|
**Note**: Agent changes require `home-manager switch` to take effect (config is embedded, not symlinked).
|
||||||
|
|
||||||
### Update User Context
|
### Update User Context
|
||||||
|
|
||||||
|
|||||||
151
scripts/test-skill.sh
Executable file
151
scripts/test-skill.sh
Executable 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
|
||||||
@@ -9,18 +9,20 @@ import re
|
|||||||
import yaml
|
import yaml
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def validate_skill(skill_path):
|
def validate_skill(skill_path):
|
||||||
"""Basic validation of a skill"""
|
"""Basic validation of a skill"""
|
||||||
skill_path = Path(skill_path)
|
skill_path = Path(skill_path)
|
||||||
|
skill_md = skill_path / "SKILL.md"
|
||||||
|
|
||||||
if not skill_md.exists():
|
if not skill_md.exists():
|
||||||
return False, "SKILL.md not found"
|
return False, "SKILL.md not found"
|
||||||
|
|
||||||
content = skill_md.read_text()
|
content = skill_md.read_text()
|
||||||
if not content.startswith('---'):
|
if not content.startswith("---"):
|
||||||
return False, "No YAML frontmatter found"
|
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:
|
if not match:
|
||||||
return False, "Invalid frontmatter format"
|
return False, "Invalid frontmatter format"
|
||||||
|
|
||||||
@@ -33,7 +35,14 @@ def validate_skill(skill_path):
|
|||||||
except yaml.YAMLError as e:
|
except yaml.YAMLError as e:
|
||||||
return False, f"Invalid YAML in frontmatter: {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
|
unexpected_keys = set(frontmatter.keys()) - ALLOWED_PROPERTIES
|
||||||
if unexpected_keys:
|
if unexpected_keys:
|
||||||
@@ -42,40 +51,53 @@ def validate_skill(skill_path):
|
|||||||
f"Allowed properties are: {', '.join(sorted(ALLOWED_PROPERTIES))}"
|
f"Allowed properties are: {', '.join(sorted(ALLOWED_PROPERTIES))}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if 'name' not in frontmatter:
|
if "name" not in frontmatter:
|
||||||
return False, "Missing 'name' in frontmatter"
|
return False, "Missing 'name' in frontmatter"
|
||||||
if 'description' not in frontmatter:
|
if "description" not in frontmatter:
|
||||||
return False, "Missing 'description' in frontmatter"
|
return False, "Missing 'description' in frontmatter"
|
||||||
|
|
||||||
name = frontmatter.get('name', '')
|
name = frontmatter.get("name", "")
|
||||||
if not isinstance(name, str):
|
if not isinstance(name, str):
|
||||||
return False, f"Name must be a string, got {type(name).__name__}"
|
return False, f"Name must be a string, got {type(name).__name__}"
|
||||||
name = name.strip()
|
name = name.strip()
|
||||||
if name:
|
if name:
|
||||||
if not re.match(r'^[a-z0-9-]+$', 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)"
|
return (
|
||||||
if name.startswith('-') or name.endswith('-') or '--' in name:
|
False,
|
||||||
return False, f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens"
|
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:
|
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):
|
if not isinstance(description, str):
|
||||||
return False, f"Description must be a string, got {type(description).__name__}"
|
return False, f"Description must be a string, got {type(description).__name__}"
|
||||||
description = description.strip()
|
description = description.strip()
|
||||||
if description:
|
if description:
|
||||||
if '<' in description or '>' in description:
|
if "<" in description or ">" in description:
|
||||||
return False, "Description cannot contain angle brackets (< or >)"
|
return False, "Description cannot contain angle brackets (< or >)"
|
||||||
if len(description) > 1024:
|
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!"
|
return True, "Skill is valid!"
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if len(sys.argv) != 2:
|
if len(sys.argv) != 2:
|
||||||
print("Usage: python quick_validate.py <skill_directory>")
|
print("Usage: python quick_validate.py <skill_directory>")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
valid, message = validate_skill(sys.argv[1])
|
valid, message = validate_skill(sys.argv[1])
|
||||||
print(message)
|
print(message)
|
||||||
sys.exit(0 if valid else 1)
|
sys.exit(0 if valid else 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user