This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Rules Promptfoo Gazelle

Gazelle extension for offline Promptfoo validation tests

rules_promptfoo_gazelle generates required, offline promptfoo_validate_test targets for Promptfoo configurations below an evals/ directory. It never creates an evals subpackage and never generates live promptfoo_test targets.

Add rules_promptfoo as a normal dependency and this generator as a development dependency:

bazel_dep(name = "rules_promptfoo", version = "<VERSION>")
bazel_dep(
    name = "rules_promptfoo_gazelle",
    version = "<VERSION>",
    dev_dependency = True,
)

For one-pass skill package creation, also add rules_skill as a normal dependency and rules_skill_gazelle as a development dependency:

bazel_dep(name = "rules_skill", version = "<VERSION>")
bazel_dep(
    name = "rules_skill_gazelle",
    version = "<VERSION>",
    dev_dependency = True,
)

Add both public language targets to the repository’s custom Gazelle binary:

load("@gazelle//:def.bzl", "gazelle_binary")

gazelle_binary(
    name = "gazelle_binary",
    languages = [
        "@rules_promptfoo_gazelle//gazelle",
        "@rules_skill_gazelle//gazelle",
    ],
)

With both extensions installed, a fresh directory containing SKILL.md and a conventional Promptfoo configuration receives both skill_library and promptfoo_validate_test targets in one Gazelle run; it does not need an existing BUILD file.

The extension recognizes these conventional configuration paths:

evals/promptfooconfig.yaml
evals/promptfooconfig.<variant>.yaml
evals/promptfooconfig.yml
evals/promptfooconfig.<variant>.json

The default file produces eval_config_test; a variant produces eval_<sanitized_variant>_config_test. Variants are lowercased, and runs of characters other than ASCII letters and digits become underscores. Colliding sanitized names receive a stable path-derived __<8 lowercase hex digits> suffix. The double underscore cannot be produced by the sanitizer, so a variant cannot claim a collision target’s namespace.

When a non-root containing package has a SKILL.md, each ordinary validation target stages that package’s conventional :skill target. Repository-root skills are unsupported by rules_skill, so a root-package SKILL.md does not infer that label. The exact no_skill variant omits skills so it remains a control. A generic Promptfoo package without SKILL.md receives no inferred skill label. Every non-configuration regular file under evals/, including README.md and test cases, is added to data in sorted order. Directories containing BUILD, BUILD.bazel, or an additional filename configured as valid in Gazelle are package boundaries and are not traversed.

The extension reconciles config, data, and skills when a discovered configuration matches a conventionally named validation target. Adding or editing eval files therefore updates those attributes on the next run. Removing or renaming a configuration does not delete the old target: a name shape is not reliable proof that Gazelle created a rule, so stale targets must be removed explicitly. Gazelle’s # keep semantics remain available at the rule, attribute, and individual list-item levels for intentional overrides. Other attributes, including args, env, tags, and visibility, remain manual and are preserved while the generated target exists. Existing manual or live promptfoo_test rules are also left intact. The extension does not follow a symlink used as the package’s evals directory, because it could escape the repository. Such a symlink, or another unexpected filesystem scan error including an error while checking a candidate package boundary, is a non-destructive no-op rather than a signal to remove targets. Before scanning, it also resolves the repository root and package directory and refuses a package whose resolved path escapes the workspace, including a package reached through Gazelle’s symlink-following support. The extension manages the shared load for both Promptfoo rule symbols and uses Bzlmod’s apparent repository name.

The public @rules_promptfoo_gazelle//gazelle:gazelle_promptfoo binary contains only this extension and can update a nested workspace without initializing unrelated language plugins from its parent repository.