Write a rule¶
A rule is a folder <family>/<slug>/: a rule.toml, a README.md, what runs, and fixtures/.
botica rules new writes the folder, with TODO where the rule needs its text, and
botica rules conform checks it against its fixtures.
botica rules new structure/no-foo --kind ast-grep --language python --standard CES-63
# the folder of a new rule (ast-grep, judgment or external) in catalog/rules
botica rules new structure/no-blobs --kind extension --runtime python --language python \
--catalog .botica/rules/acme # a rule of a catalog of a project: id acme/no-blobs, code ACMESTRUC001
botica rules new structure/dep-hygiene --kind external --language python --tool deptry \
--docs https://deptry.com/ --config prek.toml # an external rule and its fragment config/prek.toml
botica rules new structure/no-exec --kind ast-grep --language python --from judge-it
# a rule with the text of the judgment rule judge-it, added to its covered-by
botica rules conform catalog/rules # each rule over its fixtures, with and without each optimisation;
# .botica/rules/<name> for a catalog of a project (it has a catalog.toml);
# the projects of the fixtures go to .botica/conformance, which ignores itself
botica rules test --judge abide [test-through-interface]
# abide judges the fixtures of each judgment rule; compared with expected.json
The kind of a rule¶
| Kind | What it is | Where |
|---|---|---|
ast-grep |
a pattern over the syntax tree of one file, in YAML | any catalog |
extension |
a program that answers the protocol of botica (Extensions) | a catalog of a project |
external |
another tool enforces the rule through its configuration | any catalog |
judgment |
a question that a judge answers on the code | any catalog |
native |
Rust inside botica | the built-in catalog |
A pattern rule is the first choice. A judgment rule is for what no deterministic check can
express, even with configuration from the repository, and says why in why-not-deterministic
(ADR 0005).
A pattern rule¶
botica rules new writes this rule.toml, a preview rule that is off by default with the next
code of its family:
id = "acme/no-eval"
code = "ACMESTRUC002"
summary = "TODO: what the rule asks, in one line"
status = "preview"
scope = "file"
needs = "syntax"
run-on = ["cli", "save", "type"]
default-enabled = false
default-severity = "warning"
[[implementations]]
kind = "ast-grep"
languages = ["python"]
file = "python.yml"
run-on says where the rule runs: cli in botica lint, save and type in the editor. The
pattern is in python.yml; in a catalog of a project its id is the full id:
id: acme/no-eval
language: python
severity: warning
message: "eval() runs text as code: parse the text with ast.literal_eval or json.loads instead"
rule:
pattern: eval($$$ARGS)
The message says the rule and its fix, and names no other rule. The README has the sections of
its template in their order (Directive, Why, Do, Don't, Suppression for this kind), and
the catalog does not load a README whose sections do not match its kind.
Fixtures¶
fixtures/valid/ holds files on which the rule finds nothing, and fixtures/invalid/ files on
which it finds something. fixtures/expected.json lists what the invalid files raise: the path
from invalid/, the severity, the range and the message of each finding. The output of
botica lint in that folder has that form; write it, then read it:
cd .botica/rules/acme/structure/no-eval/fixtures/invalid
botica lint --preview --select acme/no-eval --output-format json . > ../expected.json
botica rules conform copies each side of the fixtures into a project of its own under
.botica/conformance, runs the rule with and without each optimisation of the engine, and compares
the findings with expected.json. It exits with 1 when a rule finds something on a valid fixture,
raises something else on the invalid ones, or has no fixture on a side. A rule that fails its
fixtures is changed, or its fixtures are, with a reason; the expectation is not rewritten to match
the output.
Promote a rule¶
A new rule is status = "preview" and runs only with --preview and when it is selected.
botica rules eval <id> <path> runs it on real code to review its false positives. stable and
default-enabled = true are a separate change.
External rules¶
Another tool enforces an external rule (kind = "external") through its configuration. The rule
names the tool, the address of its documentation and, in config/<file>.toml of its folder,
the fragment of configuration that goes into <file>.toml at the root of the project;
external-config reports a fragment that is missing.
The built-in catalog has no external rule; a catalog of a project declares them.
Judgment rules¶
A judgment rule has one implementation of kind judgment, with a question, its criteria and
why-not-deterministic:
[[implementations]]
kind = "judgment"
languages = ["python"]
when = "edit"
why-not-deterministic = "Whether a test reaches into the internals of the unit under test needs the code of both."
[implementations.question]
type = "boolean"
instructions = "Does a test read a private attribute of the unit under test?"
criteria = { true = "a test reaches into the internals", false = "the tests call the public interface" }
botica lint never runs it. botica rules export --format agents-md writes the active judgment
rules into AGENTS.md, and --format abide into .abide/rubric.json, the rubric of
abide. abide reads true as the violation: the
question asks whether the file breaks the rule. botica lint --judged <file> reads what abide
reports and applies the configuration and the comments to it.
botica rules test --judge abide <slug> calibrates a rule. It lays out the fixtures in
.botica/judge/<slug>, a git repository with a rubric that holds the rule alone, runs
abide audit --json, and compares each verdict with the band and the range of probability that
fixtures/expected.json gives the fixture. abide judges through the API of TypeSafe. With
TYPESAFE_AI_BASE_URL it sends its calls to another server with the same API instead, such as the
server of llama.cpp or a model run on its own. botica rules test --judge abide runs abide with
the environment as it is.
A rule of the built-in catalog¶
A rule of catalog/rules/ names the standard it implements in standard, and a pull request that
adds one touches that rule and one standard: scripts/check_rule_change.py fails one that touches
more. The rule botica-rule-author of the skills of this repository describes the same steps for
an agent.