Architecture and contributing¶
Ryni is a single Rust package with a library and a CLI. The public CLI and JSON contracts are documented; the Rust API is currently internal-facing and may change as the engine evolves.
Analysis boundaries¶
source.rsowns immutable source snapshots, line indexes and byte-range validation.document.rsextracts Markdown links/headings and skill frontmatter once per document. It preserves the original YAML slice and offsets, including BOM/CRLF.registry.rsdefines rule identities, scope, stability, defaults, explanations and fix availability. Documentation tables are generated from this registry.settings.rsvalidates configuration and packs, then resolves effective settings.discovery.rsfinds supported files and reports discovery failures.rules/contains document checks and a small project index for skill names.filesystem.rssupplies target observations, allowing tests or editors to provide their own filesystem view. Its cache lives for one scan only.lib.rsorchestrates checking and offersanalyzefor supplied source snapshots.diagnostic.rsandreport.rscontain structured findings and scan results.output.rsrenders those findings for humans;main.rsowns CLI behavior.fix.rsvalidates and applies proposed edits; rules never write files.
Metadata semantic findings remain file-level when the YAML parser does not provide an exact field range. Syntax errors use parser offsets when valid. Ryni never underlines a guessed field position. Every source range is a half-open UTF-8 byte range; displayed and JSON positions use one-based Unicode scalar columns, not terminal cell widths or LSP UTF-16 offsets.
The project index retains only skill names and source snapshots needed for
cross-file findings. Document rules receive a ProjectContext with settings and
filesystem access. They do not discover files or load configuration.
Adding or changing a rule¶
Add its stable ID and metadata to registry.rs, implement the check under
rules/, and connect it to the document or project phase. New conventions begin
in preview and require explicit selection. Add positive, negative, boundary and
false-positive examples to the fixture or integration tests. Keep prerequisite
parse failures separate from dependent semantic findings.
Rule IDs are configuration and JSON interfaces: do not silently rename or reuse them. Promote preview behavior only after reviewing fixtures and corpus results. Changes to stable defaults or findings require a changelog entry and an explicit compatibility decision. The first local pack schema is version 1; incompatible format changes require a new schema version. Preview behavior may change between releases, so pin the Ryni binary as well as pack contents in CI.
Regenerate rule tables after registry changes:
cargo build --locked
python3 scripts/generate_rules.py
python3 scripts/generate_rules.py --check
Regression checks¶
cargo fmt --check
cargo test --locked
cargo clippy --all-targets --locked -- -D warnings
python3 -m unittest discover -s benchmarks -p 'test_*.py'
Tests combine rule fixtures, CLI integration, a terminal snapshot, and generated Unicode/malformed-input cases. Properties cover valid ranges, deterministic findings and rendering without panics. File-system tests compare cached, uncached, sequential and parallel runs, including target creation and deletion between scans. Fix tests cover opt-in, stale sources, overlap rejection and idempotence.
When CLI output changes intentionally, update tests/snapshots/local-link.txt
and review the diff. Never accept a changed snapshot or corpus baseline solely
to make tests pass.
The optional repository benchmark lives under benchmarks/ and checks pinned
real projects. Schema 2 baselines compare structured diagnostics independently of
terminal presentation. A baseline describes observed behavior, not proof of
correctness; minimize confirmed bugs into focused regression cases.
CI runs Rust checks on Linux, macOS and Windows, checks the declared Rust 1.88 minimum, tests benchmark helpers, verifies generated rule tables and checks Rust documentation. The full corpus remains opt-in to keep ordinary CI bounded.
Performance¶
Use ryni check . --timings to print discovery, reading, parsing, rule and project
phase times plus target lookup/cache counts to stderr. With --threads N, file
phase totals represent summed work across threads, not elapsed wall time. Rendering
is outside these phase totals; corpus wall times include startup and output.
Sequential checking is the default; --threads N opts into a local thread pool.
Findings are sorted by path, source offset, rule ID and message after aggregation.
--no-cache disables the target cache for comparison. No cache survives a scan:
creating or deleting a target is observed by the next invocation, even when the
linking document is unchanged. Files changing during a scan do not provide an
atomic repository snapshot.
Profile representative workloads before changing defaults. A persistent result cache would need to track external target dependencies, including missing targets; source-file modification time alone is insufficient.