Skip to content
Draft
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
42 changes: 42 additions & 0 deletions docs/usage/ai.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# AI-Assisted Development

Plugboard ships with tooling to help AI coding agents understand how to build models using the framework. The `plugboard ai` command group provides utilities for setting up AI-assisted development workflows.

## Initialising a project

The `plugboard ai init` command creates an `AGENTS.md` file in your project directory. This file gives AI coding agents the context they need to help you build Plugboard models — covering how to create components, assemble processes, use events, and follow best practices.

`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.

### Usage

To create an `AGENTS.md` file in the current working directory:

```bash
plugboard ai init
```

To create the file in a specific directory:

```bash
plugboard ai init /path/to/project
```

!!! note
The command will not overwrite an existing `AGENTS.md` file. If one already exists in the target directory, the command exits with an error.

### What's in the file?

The generated `AGENTS.md` covers:

- **Planning a model** — how to break a problem down into components, inputs, outputs, and data flows.
- **Implementing components** — using built-in library components and creating custom ones by subclassing [`Component`][plugboard.component.Component].
- **Assembling a process** — connecting components together and running a [`LocalProcess`][plugboard.process.LocalProcess].
- **Event-driven models** — defining custom [`Event`][plugboard.events.Event] types, emitting events, and writing event handlers.
- **Exporting models** — saving process definitions to YAML and running them via the CLI.

The file is intended to be committed to version control alongside your project code so that any AI agent working in the repository has immediate access to Plugboard conventions.

### Customising

After generating the file you can edit it freely to add project-specific instructions — for example, domain context, coding standards, or pointers to your own components and data sources.
4 changes: 2 additions & 2 deletions examples/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Ask questions if anything is not clear about the business logic or you require a

Always check whether the functionality you need is already available in the library components in `plugboard.library`. For example, try to use:
- `FileReader` and `FileWriter` for reading/writing data from CSV or parquet files.
- `SQLReader` and `SQLReader` for reading/writing data from SQL databases.
- `SQLReader` and `SQLWriter` for reading/writing data from SQL databases.
- `LLMChat` for interacting with standard LLMs, e.g. OpenAI, Gemini, etc.

**Using Built-in Components**
Expand All @@ -51,7 +51,7 @@ data_loader = FileReader(name="input_data", path="input.csv", field_names=["x",

**Creating Custom Components**

New components should inherit from `plugboard.componen.Component`. Add logging messages where it would be helpful by using the bound logger `self._logger`.
New components should inherit from `plugboard.component.Component`. Add logging messages where it would be helpful by using the bound logger `self._logger`.

```python
import typing as _t
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ nav:
- Event-driven models: examples/tutorials/event-driven-models.md
- Tuning a process: examples/tutorials/tuning-a-process.md
- Configuration: usage/configuration.md
- AI-Assisted Development: usage/ai.md
- Topics: usage/topics.md
- Demos:
- Fundamentals:
Expand Down
2 changes: 2 additions & 0 deletions plugboard/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import typer

from plugboard import __version__
from plugboard.cli.ai import app as ai_app
from plugboard.cli.process import app as process_app
from plugboard.cli.server import app as server_app
from plugboard.cli.version import app as version_app
Expand All @@ -14,6 +15,7 @@
help=f"[bold]Plugboard CLI[/bold]\n\nVersion {__version__}",
pretty_exceptions_show_locals=False,
)
app.add_typer(ai_app, name="ai")
app.add_typer(process_app, name="process")
app.add_typer(server_app, name="server")
app.add_typer(version_app, name="version")
1 change: 1 addition & 0 deletions plugboard/cli/ai/AGENTS.md
41 changes: 41 additions & 0 deletions plugboard/cli/ai/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Plugboard AI CLI."""

from pathlib import Path
import shutil

from rich import print
from rich.console import Console
import typer


app = typer.Typer(
rich_markup_mode="rich", no_args_is_help=True, pretty_exceptions_show_locals=False
)
stderr = Console(stderr=True)

_AGENTS_MD = Path(__file__).parent / "AGENTS.md"


@app.command()
def init(
directory: Path = typer.Argument(
default=None,
help="Target directory for the AGENTS.md file. Defaults to the current working directory.",
exists=True,
file_okay=False,
dir_okay=True,
resolve_path=True,
),
) -> None:
"""Initialise a project with an AGENTS.md file for AI-assisted development."""
if directory is None:
directory = Path.cwd()

target = directory / "AGENTS.md"

if target.exists():
stderr.print("[red]AGENTS.md already exists[/red] in the target directory.")
raise typer.Exit(1)

shutil.copy2(_AGENTS_MD, target)
print(f"[green]Created[/green] {target}")
36 changes: 36 additions & 0 deletions tests/unit/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,42 @@ def test_cli_process_diagram() -> None:
assert "flowchart" in result.stdout


def test_cli_ai_init(tmp_path: Path) -> None:
"""Tests the ai init command creates AGENTS.md."""
result = runner.invoke(app, ["ai", "init", str(tmp_path)])
assert result.exit_code == 0
assert "Created" in result.stdout
# File must exist with expected content
agents_md = tmp_path / "AGENTS.md"
assert agents_md.exists()
content = agents_md.read_text()
assert "Plugboard" in content


def test_cli_ai_init_already_exists(tmp_path: Path) -> None:
"""Tests the ai init command fails when AGENTS.md already exists."""
(tmp_path / "AGENTS.md").write_text("existing content")
result = runner.invoke(app, ["ai", "init", str(tmp_path)])
assert result.exit_code == 1
# Error is printed to stderr which typer captures in output
assert "already exists" in result.output


def test_cli_ai_init_default_directory() -> None:
"""Tests the ai init command uses current directory by default."""
with tempfile.TemporaryDirectory() as tmpdir:
original_cwd = Path.cwd()
try:
import os

os.chdir(tmpdir)
result = runner.invoke(app, ["ai", "init"])
assert result.exit_code == 0
assert (Path(tmpdir) / "AGENTS.md").exists()
finally:
os.chdir(original_cwd)


def test_cli_server_discover(test_project_dir: Path) -> None:
"""Tests the server discover command."""
with respx.mock:
Expand Down
Loading