# rtl_buddy — AI Agent Guide ## Role This repo is the source-of-truth implementation of the `rtl_buddy` CLI. ## Key Files ```text src/rtl_buddy/ ├── __main__.py # package entry point ├── rtl_buddy.py # Typer CLI and top-level command flow ├── skill_install.py # `rtl-buddy skill ...` subcommands ├── skill/ # bundled agent skill (shipped in the wheel) ├── logging_utils.py # log_event(), setup_logging(), console helpers ├── errors.py # FatalRtlBuddyError, FilelistError, SetupScriptError ├── seed_mode.py # seed handling enum ├── config/ │ ├── root.py # discover_project_root(), RootConfig │ ├── model.py # ModelConfig (models.yaml) │ ├── test.py # TestConfig / TestConfigFile (tests.yaml) │ ├── spec.py # SpecConfig / SpecBlock / SpecCoverageItem (specs.yaml) │ └── ... # platform, rtl, verible, coverage, coverview, reg ├── runner/test_runner.py # PRE -> COMP -> SIM -> POST execution └── tools/ ├── spec_trace.py # discover_spec_configs, build_coverage_map, etc. └── ... # filelist, sim, postproc, verible wrappers ``` ## Development Rules - Keep changes targeted. Avoid broad refactors unless the task requires them. - Preserve CLI behavior unless intentionally changing it. - Treat YAML config classes and runtime behavior as part of the public interface for downstream RTL projects. - When adding or changing behavior, update downstream docs and validation assets after validating the implementation. ## Implementation Notes - `rtl_buddy.py` owns CLI wiring, global options, and command dispatch. - `RootConfig` selects platform, builder, verible, and regression config from `root_config.yaml`. - `TestRunner` drives PRE, COMPILE, SIM, and POST with early-stop support. - `VlogSim` captures the suite cwd once, but both compile and sim now run from per-test workspaces under `artefacts//`; repeated runs use `artefacts//run-0001/`, while `test.log`, `test.err`, and `test.randseed` in the suite directory remain latest-run symlinks. - Compile-side generated files such as `run.f`, `compile.log`, builder outputs, and relative `builder-simv` paths are resolved from the per-test artefact root, not from the suite directory. - `VlogFilelist` handles `.f` parsing and transformations. It resolves model entries from the real `models.yaml` location, resolves testbench entries from the suite cwd, and writes paths relative to the directory containing the generated `run.f`. - Nested raw coverage paths such as `artefacts//run-0001/coverage.dat` must preserve the suite-root hint during LCOV/Coverview `SF:` rewriting. When updating coverage path logic, make sure duplicate basenames still resolve against the originating suite root instead of falling back to repo-wide basename matching. - Hook scripts (`sweep`, `preproc`, `postproc`) are executed dynamically and should be treated as compatibility-sensitive APIs. ## Validation Typical checks: ```bash # from a project root that has `rtl_buddy` installed ./venv/bin/python -m rtl_buddy regression -c regression.yaml ./venv/bin/python -m rtl_buddy filelist test_module -c design/example_block/src/models.yaml ./venv/bin/python -m rtl_buddy verible syntax design/example_block/src/test_module.sv ./venv/bin/python -m rtl_buddy --machine docs list ./venv/bin/python -m rtl_buddy --machine docs show agents # from a suite directory cd design/example_block/verif ../../../venv/bin/python -m rtl_buddy test basic ``` If validating the dev checkout directly, install this repo into the target venv and confirm with `./venv/bin/python -m rtl_buddy --version`. ## Logging Practices All runtime logging goes through `log_event()` in `src/rtl_buddy/logging_utils.py`. Do not use `logger.info(f"...")` directly — use `log_event(logger, level, "event.name", key=value, ...)` so that both human and machine modes produce correct output. ### How it works - **Human mode (default)**: `_human_message()` converts each event into a readable sentence for `rtl_buddy.log` and the console. Machine-oriented fields are not visible. - **Machine mode (`--machine`)**: `rtl_buddy.log` is written as JSON Lines with the event name, all fields, and the human message. Console output is plain text. ### Adding new events 1. Choose a dotted event name following the existing convention (e.g. `compile.start`, `sim.timeout`, `suite_config.load_failed`). 2. Call `log_event(logger, logging., "your.event", field1=val1, ...)`. 3. If the event is logged at **WARNING or above**, add a dedicated `case` entry in `_human_message()`. Users see these messages directly, so they must be clear and actionable. 4. DEBUG and INFO events may rely on the wildcard fallback formatter, which converts `"foo.bar"` to `"foo bar"` and appends select fields. ### Error handling - Fatal config/environment errors: log at `logging.ERROR`, then `raise FatalRtlBuddyError(...)`. The top-level `run()` catches these and exits with code 2. - Per-test filelist failures: log at `logging.ERROR`, then `raise FilelistError(...)`. `TestRunner` catches these and records a `FilelistFailResults`. - Sweep/preproc script failures: return an error string from `pre()` or `_expand_tests_with_sweep()`. The caller records a `SetupFailResults` and continues. - Do not use `logger.critical()` — the old `ExitHandler` abort pattern has been removed. ### Console output - Use `emit_console_text()` for direct user-facing output (e.g. git status banner, regression directory). - Use `render_summary()` for result tables — it writes a Rich table to the console and plain text to the log file. - Use `task_status()` for spinners on long-running phases (compile, sim). Falls back to plain text on non-interactive terminals. ## Required Follow-Through After meaningful `rtl_buddy` changes: 1. **If any CLI command, flag, or help text changed**: run `python scripts/gen_cli_reference.py` and commit the updated `docs/reference/cli.md` in the same PR. The file is committed to the repo and must stay in sync — CI will catch drift via `python scripts/gen_cli_reference.py --check`. 2. **If you add or edit a docs page**: ensure it has a non-empty `description:` YAML frontmatter field. CI enforces this via `python scripts/check_docs_frontmatter.py --check`. See `docs/CONTRIBUTING.md` for the required format. 3. Update any downstream agent docs if command behavior, YAML schema, version expectations, or validation notes changed. 4. Update downstream integrations to the intended commit as needed. ## Skill Distribution The rtl_buddy agent skill ships inside this wheel at `src/rtl_buddy/skill/` and is materialized by `rtl-buddy skill install`. There is no separate skill repo — the legacy `rtl-buddy-codex-skill` repo is deprecated. Dev-only audit skills live under `.claude/skills/` in this repo and are not distributed. ### Rules when editing skill content - `src/rtl_buddy/skill/SKILL.md` is the single source consumed by both Claude Code (at `.claude/skills/rtl_buddy/`) and Codex (at `.agents/skills/rtl_buddy/` for project scope, `~/.codex/skills/rtl_buddy/` for user scope). - Keep `SKILL.md` ≤60 lines and agent-specific. Anything covered by the docs site should cite , not restate it. - Agent-facing local docs access goes through `rtl-buddy docs ...`. The wheel ships `docs/**/*.md` directly (via a symlink at `src/rtl_buddy/docs`) so docs are always in sync with the installed version. - Any edit to `SKILL.md` takes effect for users only after they re-run `rtl-buddy skill install`. `rtl-buddy skill status` surfaces stale installs via the `.rtl_buddy_skill_version` marker. - `src/rtl_buddy/skill/gitignore_snippet.txt` is printed by project-level installs and by `rtl-buddy skill print-gitignore`. - Files in `src/rtl_buddy/skill/` are included in the wheel automatically via hatchling's `packages = ["src/rtl_buddy"]`. Adding new files to the skill dir requires no extra config. The `docs/` directory ships via `force-include` in `pyproject.toml` and is excluded from package discovery to avoid double-inclusion. ### Install scope policy - **Default is user-level** (`~/.claude/skills/rtl_buddy/`, `~/.codex/skills/rtl_buddy/`). This is deliberate: the skill is workflow-pattern guidance that changes rarely across rtl_buddy versions, and a single copy per machine nudges users to keep rtl_buddy versions aligned across projects. - `--project` (or `--root PATH`) opts into project-level install at `/.claude/skills/rtl_buddy/` and `/.agents/skills/rtl_buddy/`. Claude Code's project-level precedence means a project-level copy overrides the user-level one when both exist — this is the escape hatch for projects that pin a divergent rtl_buddy major. - Do not flip the default to project-level without rediscussion; the precedence model makes user-level-plus-project-override the clean path. ### Project root discovery `config.root.discover_project_root()` is the single shared entry point for locating the project root. It walks up from `cwd` for `root_config.yaml`, then for `.git/`. Pass `fallback_cwd=True` to return `cwd` silently when neither is found (used by the spec commands); the default raises `FatalRtlBuddyError`. This handles agents invoking from `verif//` subdirs — `Path.cwd()` alone would be wrong. ## Release Workflow Releases are triggered by merging a PR to `main` with a `version/` label, or via `workflow_dispatch`. ### Stable release Apply one of `version/patch`, `version/minor`, or `version/major` to the PR. On merge: 1. The workflow computes the next `vMAJOR.MINOR.PATCH` tag, creates it, and pushes it. 2. A GitHub release is created (not marked pre-release). 3. The wheel is built (hatch-vcs derives the version from the tag) and published to PyPI. 4. Docs are deployed to `gh-pages` under the matching `v{major}` alias; `latest` is updated if this is the highest major. ### Pre-release Pre-releases are cut from a **feature branch** via `workflow_dispatch` — never by merging to `main`. Merging to `main` with a `version/` label always produces a stable release. To cut a pre-release: 1. Run the Release workflow on your feature branch with the desired bump type and the **Mark as pre-release** checkbox enabled. 2. The workflow appends `rcN` to the computed base tag (PEP 440). If `v2.3.0rc1` already exists, the next is `v2.3.0rc2`. 3. A GitHub release is created and marked **pre-release**. 4. The wheel is published to PyPI as a pre-release version (e.g. `2.3.0rc1`). Unqualified version ranges (`>=2.2.0`) will not resolve to it. 5. Docs are **not** published — the `latest` alias is not updated. The version is computed from the latest stable tag at dispatch time. If `main` advances and releases the same bump tier before your branch merges, the next RC will shift to the following version — that is expected and acceptable. ### Infrastructure notes - GitHub Pages must be configured to publish from the `gh-pages` branch. - A `GH_PAGES_TOKEN` secret is required because pushes made with the default `GITHUB_TOKEN` do not reliably trigger downstream docs publishing from automation-created tags. - Update and tag any downstream integrations that track this repo after a stable release.