Skip to content

Commit 8e1acf8

Browse files
authored
Add style-aware AI skill installation
1 parent 7db0256 commit 8e1acf8

9 files changed

Lines changed: 95 additions & 28 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ The fastest way to get started is to let your AI coding assistant build your fir
7676
plugboard ai init
7777
```
7878

79-
This copies a context file (`AGENTS.md`) and a `skills/` directory into your project so your AI tool has Plugboard-specific guidance for structuring models, keeping component arguments YAML-friendly, exporting configs, generating diagrams, running scenarios, and setting up tuning.
79+
This copies a context file (`AGENTS.md`) and installs Agent Skills into `.agents/skills/` by default (or `.github/skills/` / `.claude/skills/` with `--style`) so your AI tool has Plugboard-specific guidance for structuring models, keeping component arguments YAML-friendly, exporting configs, generating diagrams, running scenarios, and setting up tuning.
8080

8181
Once initialised, simply open your AI tool of choice (GitHub Copilot, Cursor, Claude, etc.) and describe the model you want to build. For example:
8282

docs/usage/ai.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Plugboard ships with tooling to help AI coding agents understand how to build mo
44

55
## Initialising a project
66

7-
The `plugboard ai init` command creates an `AGENTS.md` file and a `skills/` directory in your project directory. Together they give AI coding agents the context they need to help you build, export, visualise, run, and tune Plugboard models.
7+
The `plugboard ai init` command creates an `AGENTS.md` file and installs Agent Skills in your project directory. Together they give AI coding agents the context they need to help you build, export, visualise, run, and tune Plugboard models.
88

99
`AGENTS.md` is a convention used by AI coding tools (such as [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview), [Codex](https://openai.com/index/codex/), and [Gemini CLI](https://github.com/google-gemini/gemini-cli)) to discover project-specific instructions automatically.
1010

@@ -22,8 +22,14 @@ To create the file in a specific directory:
2222
plugboard ai init /path/to/project
2323
```
2424

25+
To choose where the skills are installed:
26+
27+
```bash
28+
plugboard ai init --style github
29+
```
30+
2531
!!! note
26-
The command will not overwrite an existing `AGENTS.md` file or `skills/` directory. If either already exists in the target directory, the command exits with an error.
32+
The command will not overwrite an existing `AGENTS.md` file or the selected skills directory. If either already exists in the target directory, the command exits with an error.
2733

2834
### What's installed?
2935

@@ -37,7 +43,13 @@ The generated `AGENTS.md` covers:
3743
- **Event-driven models** — defining custom [`Event`][plugboard.events.Event] types, emitting events, and writing event handlers.
3844
- **Exporting models** — saving process definitions to YAML and running them via the CLI.
3945

40-
The generated `skills/` directory includes reusable task guides for:
46+
The generated skills use the Agent Skills `SKILL.md` format with frontmatter, and are installed into one of these directories:
47+
48+
- `.agents/skills/` for `--style agents` (default)
49+
- `.github/skills/` for `--style github`
50+
- `.claude/skills/` for `--style claude`
51+
52+
The generated skills include reusable task guides for:
4153

4254
- creating a YAML config from a model defined in Python
4355
- generating a process diagram with `plugboard process diagram`

plugboard/cli/ai/AGENTS.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,18 @@ Use this sequence when helping a user plan, implement, and run a Plugboard model
3232

3333
## Skill files
3434

35-
Additional task-specific instructions are available in `./skills/`. When a user asks for one of these tasks, read the matching `SKILLS.md` file and follow it:
35+
Additional task-specific instructions are available in a style-specific skills directory. Depending on the setup, look under one of:
3636

37-
- `skills/create-yaml-config/SKILLS.md`
38-
- `skills/process-diagram/SKILLS.md`
39-
- `skills/run-process-scenario/SKILLS.md`
40-
- `skills/configure-tune/SKILLS.md`
37+
- `./.agents/skills/`
38+
- `./.github/skills/`
39+
- `./.claude/skills/`
40+
41+
When a user asks for one of these tasks, read the matching `SKILL.md` file and follow it:
42+
43+
- `create-yaml-config/SKILL.md`
44+
- `process-diagram/SKILL.md`
45+
- `run-process-scenario/SKILL.md`
46+
- `configure-tune/SKILL.md`
4147

4248
## Planning a model
4349

plugboard/cli/ai/__init__.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from pathlib import Path
44
import shutil
5+
import typing as _t
56

67
from rich import print
78
from rich.console import Console
@@ -15,28 +16,45 @@
1516

1617
_AGENTS_MD = Path(__file__).parent / "AGENTS.md"
1718
_SKILLS_DIR = Path(__file__).parent / "skills"
19+
_STYLE_SKILLS_DIRS: dict[str, Path] = {
20+
"agents": Path(".agents/skills"),
21+
"github": Path(".github/skills"),
22+
"claude": Path(".claude/skills"),
23+
}
1824

1925

2026
@app.command()
2127
def init(
2228
directory: Path = typer.Argument(
2329
default=None,
2430
help=(
25-
"Target directory for the AGENTS.md file and skills directory. "
31+
"Target directory for the AGENTS.md file and style-specific skills directory. "
2632
"Defaults to the current working directory."
2733
),
2834
exists=True,
2935
file_okay=False,
3036
dir_okay=True,
3137
resolve_path=True,
3238
),
39+
style: _t.Annotated[
40+
_t.Literal["claude", "github", "agents"],
41+
typer.Option(
42+
"--style",
43+
help=(
44+
"Skill installation style. "
45+
"Options: 'agents' for .agents/skills, "
46+
"'github' for .github/skills, "
47+
"'claude' for .claude/skills."
48+
),
49+
),
50+
] = "agents",
3351
) -> None:
3452
"""Initialise a project with Plugboard AI guidance files."""
3553
if directory is None:
3654
directory = Path.cwd()
3755

3856
agents_target = directory / "AGENTS.md"
39-
skills_target = directory / "skills"
57+
skills_target = directory / _STYLE_SKILLS_DIRS[style]
4058
existing_paths = [path.name for path in (agents_target, skills_target) if path.exists()]
4159

4260
if existing_paths:

plugboard/cli/ai/skills/configure-tune/SKILLS.md renamed to plugboard/cli/ai/skills/configure-tune/SKILL.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
# Skill: Add a `tune` section to a Plugboard YAML config
1+
---
2+
name: configure-tune
3+
description: Add and shape a tune section in a Plugboard YAML configuration.
4+
---
5+
6+
# Add a `tune` section to a Plugboard YAML config
27

38
## Use this skill when
49

plugboard/cli/ai/skills/create-yaml-config/SKILLS.md renamed to plugboard/cli/ai/skills/create-yaml-config/SKILL.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
# Skill: Create a YAML config from a Python-defined Plugboard model
1+
---
2+
name: create-yaml-config
3+
description: Create a YAML configuration from a Plugboard process defined in Python code.
4+
---
5+
6+
# Create a YAML config from a Python-defined Plugboard model
27

38
## Use this skill when
49

plugboard/cli/ai/skills/process-diagram/SKILLS.md renamed to plugboard/cli/ai/skills/process-diagram/SKILL.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
# Skill: Create a diagram from a Plugboard process
1+
---
2+
name: process-diagram
3+
description: Generate a Mermaid diagram from a Plugboard YAML process configuration.
4+
---
5+
6+
# Create a diagram from a Plugboard process
27

38
## Use this skill when
49

@@ -11,7 +16,7 @@ Create a diagram from a Plugboard process using the CLI.
1116

1217
## Instructions
1318

14-
1. Work from a YAML config whenever possible. If the process only exists in Python, first create a YAML config using the `skills/create-yaml-config/SKILLS.md` guidance.
19+
1. Work from a YAML config whenever possible. If the process only exists in Python, first use the `create-yaml-config` skill at `../create-yaml-config/SKILL.md`.
1520
2. Confirm which YAML config should be used. If the user wants the diagram saved to a file, plan to capture the CLI output.
1621
3. Run:
1722

plugboard/cli/ai/skills/run-process-scenario/SKILLS.md renamed to plugboard/cli/ai/skills/run-process-scenario/SKILL.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
# Skill: Run a Plugboard process for a specific scenario
1+
---
2+
name: run-process-scenario
3+
description: Update a Plugboard YAML config for a requested scenario and run it with the CLI.
4+
---
5+
6+
# Run a Plugboard process for a specific scenario
27

38
## Use this skill when
49

@@ -11,7 +16,7 @@ Create or update a YAML config for the requested scenario, then run it with `plu
1116

1217
## Instructions
1318

14-
1. Make sure a YAML config exists for the model. If the model only exists in Python, create the YAML first using the `skills/create-yaml-config/SKILLS.md` guidance.
19+
1. Make sure a YAML config exists for the model. If the model only exists in Python, create the YAML first by using the `create-yaml-config` skill at `../create-yaml-config/SKILL.md`.
1520
2. Ask for any missing scenario inputs before running anything.
1621
3. Update the YAML config with the exact parameter values the user requested.
1722
4. Preserve a clear component structure that matches the real-world model. Do not collapse multiple entities into one component just to make the config shorter.

tests/unit/test_cli.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -149,22 +149,33 @@ def test_cli_process_validate_invalid() -> None:
149149

150150

151151
def test_cli_ai_init(tmp_path: Path) -> None:
152-
"""Tests the ai init command creates AGENTS.md and skills."""
152+
"""Tests the ai init command creates AGENTS.md and agent-style skills."""
153153
result = runner.invoke(app, ["ai", "init", str(tmp_path)])
154154
assert result.exit_code == 0
155155
assert "Created" in result.stdout
156156
agents_md = tmp_path / "AGENTS.md"
157-
skills_dir = tmp_path / "skills"
158-
skill_files = sorted(skills_dir.glob("*/SKILLS.md"))
157+
skills_dir = tmp_path / ".agents" / "skills"
158+
skill_files = sorted(skills_dir.glob("*/SKILL.md"))
159159
assert agents_md.exists()
160160
assert len(skill_files) == 4
161161
assert "serialisable" in agents_md.read_text()
162-
assert "process.dump" in (skills_dir / "create-yaml-config" / "SKILLS.md").read_text()
163-
process_diagram_skill = skills_dir / "process-diagram" / "SKILLS.md"
164-
run_process_skill = skills_dir / "run-process-scenario" / "SKILLS.md"
162+
yaml_skill = skills_dir / "create-yaml-config" / "SKILL.md"
163+
process_diagram_skill = skills_dir / "process-diagram" / "SKILL.md"
164+
run_process_skill = skills_dir / "run-process-scenario" / "SKILL.md"
165+
tune_skill = skills_dir / "configure-tune" / "SKILL.md"
166+
assert yaml_skill.read_text().startswith("---\nname: create-yaml-config\n")
167+
assert "process.dump" in yaml_skill.read_text()
165168
assert "plugboard process diagram" in process_diagram_skill.read_text()
166169
assert "plugboard process run" in run_process_skill.read_text()
167-
assert "`tune` section" in (skills_dir / "configure-tune" / "SKILLS.md").read_text()
170+
assert "`tune` section" in tune_skill.read_text()
171+
172+
173+
def test_cli_ai_init_github_style(tmp_path: Path) -> None:
174+
"""Tests the ai init command creates GitHub-style skills when requested."""
175+
result = runner.invoke(app, ["ai", "init", "--style", "github", str(tmp_path)])
176+
assert result.exit_code == 0
177+
assert (tmp_path / "AGENTS.md").exists()
178+
assert (tmp_path / ".github" / "skills" / "process-diagram" / "SKILL.md").exists()
168179

169180

170181
def test_cli_ai_init_already_exists(tmp_path: Path) -> None:
@@ -176,8 +187,8 @@ def test_cli_ai_init_already_exists(tmp_path: Path) -> None:
176187

177188

178189
def test_cli_ai_init_fails_when_skills_directory_exists(tmp_path: Path) -> None:
179-
"""Tests the ai init command fails when the skills directory already exists."""
180-
(tmp_path / "skills").mkdir()
190+
"""Tests the ai init command fails when the selected skills directory already exists."""
191+
(tmp_path / ".agents" / "skills").mkdir(parents=True)
181192
result = runner.invoke(app, ["ai", "init", str(tmp_path)])
182193
assert result.exit_code == 1
183194
assert "skills" in result.output
@@ -194,7 +205,7 @@ def test_cli_ai_init_default_directory() -> None:
194205
result = runner.invoke(app, ["ai", "init"])
195206
assert result.exit_code == 0
196207
assert (Path(tmpdir) / "AGENTS.md").exists()
197-
assert (Path(tmpdir) / "skills" / "process-diagram" / "SKILLS.md").exists()
208+
assert (Path(tmpdir) / ".agents" / "skills" / "process-diagram" / "SKILL.md").exists()
198209
finally:
199210
os.chdir(original_cwd)
200211

@@ -208,7 +219,7 @@ def test_cli_ai_agents_template_is_packaged_file() -> None:
208219

209220
def test_cli_ai_skills_templates_are_packaged_files() -> None:
210221
"""Tests the skills templates are real package files rather than symlinks."""
211-
skill_files = sorted(_SKILLS_DIR.glob("*/SKILLS.md"))
222+
skill_files = sorted(_SKILLS_DIR.glob("*/SKILL.md"))
212223
assert skill_files
213224
for skill_file in skill_files:
214225
assert skill_file.exists()

0 commit comments

Comments
 (0)