Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .claude/skills

This file was deleted.

29 changes: 29 additions & 0 deletions .github/workflows/skills.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Skills

on:
push:
pull_request:

jobs:
verify:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- name: Install loadout
run: cargo install --git https://github.com/pentaxis93/loadout.git --tag v0.4.0 loadout

- name: Verify skills manifest
run: ./scripts/skills_verify.sh

- name: Smoke test loadout pilot
run: ./tests/loadout_pilot_smoke.sh

- name: Smoke test gitignore rules
run: ./tests/gitignore_skill_targets_smoke.sh

- name: Run workspace tests
run: cargo test --workspace
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
/target
/.agents/skills
/.claude/skills
/.opencode/skills
23 changes: 23 additions & 0 deletions .loadout/agentd.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[sources]
skills = ["../skills"]

[target_aliases.claude_code]
global = "~/.claude/skills"
project = ".claude/skills"

[target_aliases.opencode]
global = "~/.config/opencode/skills"
project = ".opencode/skills"

[target_aliases.codex]
global = "~/.agents/skills"
project = ".agents/skills"

[global]
targets = []
skills = ["ground", "bdd", "land", "issue-craft", "planning"]

[projects.".."]
inherit = true
skills = []
targets = ["claude_code", "opencode", "codex"]
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ agentd uses a workspace-and-plugin shape: the `agentd` binary crate composes foc
This workflow is always in effect for contributions to this repository and is not optional.

### Ground Before Designing
For any new module, API surface, protocol, or data structure, define what capability must exist when the change is complete before inspecting existing implementation patterns. State required outcomes first, then derive constraints from what must be true for those outcomes to hold. Separate actual constraints from inherited assumptions and challenge assumptions unless they are verified by requirements, interfaces, or tests. Compare against existing approaches only after a need-first design exists. Reference: `.agents/skills/ground/SKILL.md`.
For any new module, API surface, protocol, or data structure, define what capability must exist when the change is complete before inspecting existing implementation patterns. State required outcomes first, then derive constraints from what must be true for those outcomes to hold. Separate actual constraints from inherited assumptions and challenge assumptions unless they are verified by requirements, interfaces, or tests. Compare against existing approaches only after a need-first design exists. Reference: `skills/ground/SKILL.md`.

### BDD First
Every change must follow this sequence: behavioral spec -> test -> implementation -> verification. Define done as observable behavior before coding. Write or update tests that fail without the change and pass when behavior is correct. Implement only what is necessary to satisfy the behavioral contract. No PR is complete without behavioral coverage for the change.
Expand Down
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.PHONY: skills-sync skills-verify

skills-sync:
./scripts/skills_sync.sh

skills-verify:
./scripts/skills_verify.sh
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,21 @@ Coming soon.

## Developer Tooling: Skills

agentd supports project-level skills with two layers:

- Shared public skills tracked in this repository at `.agents/skills/`.
- Optional personal skills overlay installed locally at project level and kept untracked.

See `docs/personal-skill-overlay.md` for the personal overlay workflow, collision policy, and git-clean verification steps.
agentd pilots `loadout` for project-level skill discovery.

- Project skills are tracked in this repository at `skills/` and used as the
sole default `loadout` source for project setup.
- Skill vendoring is manifest-driven via `skills.manifest.toml`.
- Sync vendored skills from upstream: `make skills-sync`
- Verify manifest + config coherence: `make skills-verify`
- `loadout` installs enabled skills into tool discovery directories:
- `.agents/skills/` (Codex)
- `.claude/skills/` (Claude Code)
- `.opencode/skills/` (OpenCode)
- Project pilot config lives at `.loadout/agentd.toml` and is intended to be used with:
- `LOADOUT_CONFIG=$PWD/.loadout/agentd.toml loadout install`

See `docs/personal-skill-overlay.md` for personal overlay layering and rollback to manual links.

## License

Expand Down
98 changes: 67 additions & 31 deletions docs/personal-skill-overlay.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,100 @@
# Personal Skills Overlay

How to combine shared public skills with personal developer skills at project scope.
How to layer personal skills on top of project skills using `loadout`.

## Goal

- Keep shared project skills committed with the repository.
- Allow each developer to add personal skills locally at project level.
- Keep personal skill content and identity details out of committed files.
- Keep shared project skills committed at `skills/`.
- Allow each developer to layer private personal skills locally.
- Keep personal skill names, URLs, and paths out of committed files.

## Directory Contract
## Project Pilot Contract

- Public skills (tracked): `.agents/skills/<public-skill>/SKILL.md`
- Personal overlay (local only): `.agents/skills/<personal-skill>/`
- Canonical tracked source in this repo: `skills/<skill>/SKILL.md`
- Skills are curated through `skills.manifest.toml` and synchronized with
`scripts/skills_sync.sh`.
- Project loadout config: `.loadout/agentd.toml`
- Project default source is self-contained in this repo: `../skills`
- Project default enabled skills: `ground`, `bdd`, `land`, `issue-craft`,
`planning`
- Loadout-managed project targets:
- `.agents/skills` (Codex)
- `.claude/skills` (Claude Code)
- `.opencode/skills` (OpenCode)

## Installation Pattern (Personal Overlay)
Install project skills:

1. Store personal skills in a personal location (for example a personal repo clone).
2. Install them into this project as symlinks under `.agents/skills/`.
3. Add personal overlay paths to local-only excludes in `.git/info/exclude`.
```bash
LOADOUT_CONFIG="$PWD/.loadout/agentd.toml" loadout install
```

Example local installation pattern:
## Personal Overlay Pattern (Local Only)

```bash
PROJECT_ROOT=/path/to/agentd
PERSONAL_ROOT=/path/to/personal-skills
1. Store personal skills in a private location (for example a personal repo
clone).
2. Add that location to your local loadout config `sources.skills` before the
project source so first match wins.
3. Enable personal skill names in `global.skills` or project skills for your
local config.

Example local config snippet:

mkdir -p "$PROJECT_ROOT/.agents/skills"
ln -sfn "$PERSONAL_ROOT/skills/my-personal-skill" \
"$PROJECT_ROOT/.agents/skills/my-personal-skill"
printf '%s\n' '.agents/skills/my-personal-skill' >> "$PROJECT_ROOT/.git/info/exclude"
```toml
[sources]
skills = [
"/path/to/personal-skills",
"/path/to/agentd/skills",
]
```

Use an idempotent local installer script if you have many personal skills.
Do not commit personal config edits. Keep them in your user-scoped
`~/.config/loadout/loadout.toml` or in an untracked local copy.

## Collision Policy

- Personal overlay names may intentionally collide with tracked public skill directory names.
- On collision, the personal overlay skill overrides the project skill for that developer's local environment.
- Use this for personal adaptation of shared workflows without changing team-wide defaults.
- Name collisions are allowed by source ordering.
- First matching source wins; this enables personal override of a shared skill.
- Shared defaults remain unchanged for other developers.

## Git Hygiene Policy

- Do not add personal overlay patterns to `.gitignore`.
- Do not commit personal skill names, URLs, or local paths.
- Use `.git/info/exclude` (or equivalent local-only ignore) for personal overlay entries.
- Do not commit personal source paths or private skill names.
- Keep personal local setup out of tracked files.
- Generated target directories are ignored by this repo.

## Verification Checklist

Run after installing or updating personal overlays:
Run after personal overlay changes:

```bash
./scripts/skills_verify.sh
LOADOUT_CONFIG="$PWD/.loadout/agentd.toml" loadout list
LOADOUT_CONFIG="$PWD/.loadout/agentd.toml" loadout check
git status --porcelain
```

Expected: no personal overlay paths appear in output.
Expected:

Optional link verification:
- `skills_verify.sh` confirms manifest/loadout/skills directory coherence.
- `loadout list` resolves skills from expected source paths.
- `loadout check` reports no blocking errors.
- `git status --porcelain` contains no personal path leakage.

## Rollback To Manual Alias Flow

If pilot behavior fails, use manual links:

```bash
find .agents/skills -maxdepth 1 -type l -print
mkdir -p .agents .claude .opencode
ln -sfn ../skills .agents/skills
ln -sfn ../.agents/skills .claude/skills
ln -sfn ../.agents/skills .opencode/skills
```

Expected: only intentionally installed personal symlinks are listed.
This restores manual project-level discovery paths for Codex, Claude Code, and
OpenCode.

When using normal pilot operation, do not hand-manage symlinks inside these
target directories; let `loadout install` and `loadout clean` own them.
These target roots are ignored by git in both directory and symlink forms to
keep local setup changes out of repository status.
37 changes: 37 additions & 0 deletions docs/skills-loadout-pilot-spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Skills Loadout Pilot Spec

Behavioral specification for issue #14: use `loadout` to manage project-level
skill discovery for Codex, Claude Code, and OpenCode in `agentd`.

## Scenario 1: Fresh project install exposes identical project skills

Given a clone of `agentd` with `skills/` populated
And no sibling skill repositories present
And `LOADOUT_CONFIG` set to `.loadout/agentd.toml`
When `loadout install` runs in the project root
Then `.agents/skills`, `.claude/skills`, and `.opencode/skills` each contain
the same enabled skill names
And each entry is a symlink to the canonical source under `skills/`.

## Scenario 2: Config change propagates across all project targets

Given an installed project from Scenario 1
When the enabled skill set in `.loadout/agentd.toml` changes
And `loadout clean` then `loadout install` runs again
Then all project target directories reflect the same updated skill set
And no target has stale managed links.

## Scenario 3: Skill behavior is unchanged

Given an existing tracked skill in `skills/`
When `loadout validate` runs
Then frontmatter and markdown content validation results match pre-pilot
expectations
And only discovery plumbing has changed.

## Scenario 4: Manual rollback remains available

Given loadout pilot setup is removed or disabled
When the documented manual symlink commands are applied
Then Codex and Claude Code discover project skills through manual links
And the rollback path is deterministic and documented.
52 changes: 52 additions & 0 deletions scripts/skills_sync.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
MANIFEST="$ROOT/skills.manifest.toml"
SOURCE_ROOT="${1:-${AGENTS_SKILLS_ROOT:-$ROOT/../agents/skills}}"

if [[ ! -f "$MANIFEST" ]]; then
echo "missing manifest: $MANIFEST"
exit 1
fi

if [[ ! -d "$SOURCE_ROOT" ]]; then
echo "missing source root: $SOURCE_ROOT"
echo "set AGENTS_SKILLS_ROOT or pass path as first arg"
exit 1
fi

TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT

mapfile -t ENTRIES < <(
awk -F'"' '
/^name = / {name=$2}
/^source_path = / {print name "\t" $2}
' "$MANIFEST"
)

if [[ "${#ENTRIES[@]}" -eq 0 ]]; then
echo "no skill entries found in manifest"
exit 1
fi

for row in "${ENTRIES[@]}"; do
name="${row%%$'\t'*}"
rel="${row#*$'\t'}"
src="$SOURCE_ROOT/$rel"
dst="$TMP/$name"

if [[ ! -d "$src" ]]; then
echo "missing skill source: $src"
exit 1
fi

rsync -a --exclude '__pycache__' "$src/" "$dst/"
done

rm -rf "$ROOT/skills"
mkdir -p "$ROOT/skills"
rsync -a "$TMP/" "$ROOT/skills/"

echo "synced skills from $SOURCE_ROOT to $ROOT/skills"
Loading