rtl_buddy — AI Agent Guide
Role
This repo is the source-of-truth implementation of the rtl_buddy CLI.
Key Files
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.pyowns CLI wiring, global options, and command dispatch.RootConfigselects platform, builder, verible, and regression config fromroot_config.yaml.TestRunnerdrives PRE, COMPILE, SIM, and POST with early-stop support.VlogSimcaptures the suite cwd once, but both compile and sim now run from per-test workspaces underartefacts/<sanitized-test>/; repeated runs useartefacts/<sanitized-test>/run-0001/, whiletest.log,test.err, andtest.randseedin the suite directory remain latest-run symlinks.- Compile-side generated files such as
run.f,compile.log, builder outputs, and relativebuilder-simvpaths are resolved from the per-test artefact root, not from the suite directory. VlogFilelisthandles.fparsing and transformations. It resolves model entries from the realmodels.yamllocation, resolves testbench entries from the suite cwd, and writes paths relative to the directory containing the generatedrun.f.- Nested raw coverage paths such as
artefacts/<test>/run-0001/coverage.datmust preserve the suite-root hint during LCOV/CoverviewSF: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:
# 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 forrtl_buddy.logand the console. Machine-oriented fields are not visible. - Machine mode (
--machine):rtl_buddy.logis written as JSON Lines with the event name, all fields, and the human message. Console output is plain text.
Adding new events
- Choose a dotted event name following the existing convention (e.g.
compile.start,sim.timeout,suite_config.load_failed). - Call
log_event(logger, logging.<LEVEL>, "your.event", field1=val1, ...). - If the event is logged at WARNING or above, add a dedicated
caseentry in_human_message(). Users see these messages directly, so they must be clear and actionable. - 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, thenraise FatalRtlBuddyError(...). The top-levelrun()catches these and exits with code 2. - Per-test filelist failures: log at
logging.ERROR, thenraise FilelistError(...).TestRunnercatches these and records aFilelistFailResults. - Sweep/preproc script failures: return an error string from
pre()or_expand_tests_with_sweep(). The caller records aSetupFailResultsand continues. - Do not use
logger.critical()— the oldExitHandlerabort 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:
- If any CLI command, flag, or help text changed: run
python scripts/gen_cli_reference.pyand commit the updateddocs/reference/cli.mdin the same PR. The file is committed to the repo and must stay in sync — CI will catch drift viapython scripts/gen_cli_reference.py --check. - If you add or edit a docs page: ensure it has a non-empty
description:YAML frontmatter field. CI enforces this viapython scripts/check_docs_frontmatter.py --check. Seedocs/CONTRIBUTING.mdfor the required format. - Update any downstream agent docs if command behavior, YAML schema, version expectations, or validation notes changed.
- 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.mdis 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 https://rtl-buddy.github.io/rtl_buddy/, not restate it. - Agent-facing local docs access goes through
rtl-buddy docs .... The wheel shipsdocs/**/*.mddirectly (via a symlink atsrc/rtl_buddy/docs) so docs are always in sync with the installed version. - Any edit to
SKILL.mdtakes effect for users only after they re-runrtl-buddy skill install.rtl-buddy skill statussurfaces stale installs via the.rtl_buddy_skill_versionmarker. src/rtl_buddy/skill/gitignore_snippet.txtis printed by project-level installs and byrtl-buddy skill print-gitignore.- Files in
src/rtl_buddy/skill/are included in the wheel automatically via hatchling'spackages = ["src/rtl_buddy"]. Adding new files to the skill dir requires no extra config. Thedocs/directory ships viaforce-includeinpyproject.tomland 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<root>/.claude/skills/rtl_buddy/and<root>/.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/<suite>/ 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:
- The workflow computes the next
vMAJOR.MINOR.PATCHtag, creates it, and pushes it. - A GitHub release is created (not marked pre-release).
- The wheel is built (hatch-vcs derives the version from the tag) and published to PyPI.
- Docs are deployed to
gh-pagesunder the matchingv{major}alias;latestis 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:
- Run the Release workflow on your feature branch with the desired bump type and the Mark as pre-release checkbox enabled.
- The workflow appends
rcNto the computed base tag (PEP 440). Ifv2.3.0rc1already exists, the next isv2.3.0rc2. - A GitHub release is created and marked pre-release.
- 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. - Docs are not published — the
latestalias 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-pagesbranch. - A
GH_PAGES_TOKENsecret is required because pushes made with the defaultGITHUB_TOKENdo not reliably trigger downstream docs publishing from automation-created tags. - Update and tag any downstream integrations that track this repo after a stable release.