This document provides comprehensive guidelines for AI assistants working on the libmagic-rs project, ensuring consistent, high-quality development practices and project understanding.
@GOTCHAS.md
libmagic-rs is a pure-Rust implementation of libmagic, designed to replace the C-based library with a memory-safe, efficient alternative for file type detection.
- Memory Safety: Pure Rust implementation with no unsafe code (except vetted dependencies)
- Performance: Memory-mapped I/O with zero-copy operations where possible
- Compatibility: Support for common libmagic syntax patterns
- Extensibility: AST-based design for easy addition of new rule types
- No unsafe code except in vetted dependencies (memmap2, byteorder, etc.)
- Bounds checking for all buffer access using
.get()methods - Safe resource management with RAII patterns
- Graceful error handling for malformed inputs
- Safe string operations: Use
strip_prefix()/strip_suffix()instead of direct slicing (&str[n..]) to avoid UTF-8 panics
- All code must pass
cargo clippy -- -D warningswith no exceptions - Preserve all
denyattributes and-D warningsflags - Fix clippy suggestions unless they conflict with project requirements
- Use
cargo fmtfor consistent code formatting
- Use memory-mapped I/O (
memmap2) for efficient file access - Implement zero-copy operations where possible
- Use Aho-Corasick indexing for multi-pattern string searches (planned)
- Cache compiled magic rules for performance (planned)
- Profile with
cargo benchfor performance regressions
- Target >85% test coverage with
cargo llvm-cov - All code changes must include comprehensive tests
- Use
cargo nextestfor faster, more reliable test execution - Include property tests with
proptestfor fuzzing - Benchmark critical path components with
criterion - Verify doc examples with
cargo test --doc- ensure example strings don't accidentally match multiple patterns
The project follows a clear separation of concerns:
Magic File → Parser → AST → Evaluator → Match Results → Output Formatter
↓
Target File → Memory Mapper → File Buffer
// Core data structures in lib.rs
pub struct MagicRule { /* ... */ }
pub enum TypeKind {
Byte { signed: bool },
Short { endian: Endianness, signed: bool },
Long { endian: Endianness, signed: bool },
Quad { endian: Endianness, signed: bool },
String { max_length: Option<usize> },
}
pub enum Operator {
Equal, NotEqual, LessThan, GreaterThan, LessEqual, GreaterEqual,
BitwiseAnd, BitwiseAndMask(u64), BitwiseXor, BitwiseNot, AnyValue,
}
// Parser module structure
parser/
├── mod.rs // Public parser interface
├── ast.rs // AST node definitions
├── grammar/ // Magic file DSL parsing (nom) -- split into focused submodules
│ ├── mod.rs // Top-level parse_magic_rule_line, dispatch
│ ├── numbers.rs // parse_number, parse_unsigned_number
│ ├── value.rs // parse_value (quoted strings, numeric literals)
│ ├── type_suffix.rs // pstring /B/H/L, regex /c/s, search /N suffixes
│ └── tests/ // Grammar test modules
├── types.rs // Type keyword parsing and TypeKind conversion
└── codegen.rs // Serialization for code generation (shared with build.rs)
// Evaluator module structure
evaluator/
├── mod.rs // Public interface: EvaluationContext, RuleMatch, re-exports
├── tests.rs // Unit tests for EvaluationContext and RuleMatch
├── engine/ // Core evaluation engine submodule
│ ├── mod.rs // evaluate_single_rule, evaluate_rules, evaluate_rules_with_config
│ └── tests.rs // Engine unit tests
├── types/ // Type interpretation with endianness (directory module, issue #63)
│ ├── mod.rs // read_typed_value, read_pattern_match, bytes_consumed_with_pattern
│ ├── numeric.rs // byte/short/long/quad readers
│ ├── string.rs // string/pstring readers
│ ├── float.rs // float/double readers
│ ├── date.rs // date/qdate readers and timestamp formatting
│ └── regex.rs // regex/search readers, REGEX_MAX_BYTES cap, thread-local cache
├── strength.rs // Strength modifier application
├── offset/ // Offset resolution submodule
│ ├── mod.rs // Dispatcher (resolve_offset) and re-exports
│ ├── absolute.rs // OffsetError, resolve_absolute_offset
│ ├── indirect.rs // resolve_indirect_offset (fully implemented, issue #37)
│ └── relative.rs // resolve_relative_offset (GNU `file` anchor semantics)
└── operators/ // Operator application submodule
├── mod.rs // Dispatcher (apply_operator, apply_any_value) and re-exports
├── equality.rs // apply_equal, apply_not_equal
├── comparison.rs // compare_values, apply_less_than/greater_than/less_equal/greater_equal
└── bitwise.rs // apply_bitwise_and, apply_bitwise_and_mask, apply_bitwise_xor, apply_bitwise_not- Keep source files under 500-600 lines
- Split larger files into focused modules
- Use clear, descriptive module names
- Avoid using emojis and other non-ASCII characters in code, comments, or documentation, except when the code is handling non-plaintext characters (for example: em dash, en dash, or other non-ASCII symbols).
When implementing case-insensitive string matching:
- Lowercase inputs at ALL entry points (constructors, setters)
- Store normalized values internally
- Document the case-insensitivity in public API docs
// Library errors should be descriptive and actionable
#[derive(Debug, thiserror::Error)]
pub enum MagicError {
#[error("Failed to parse magic file at line {line}: {reason}")]
ParseError { line: usize, reason: String },
#[error("IO error reading file: {0}")]
IoError(#[from] std::io::Error),
#[error("Invalid offset specification: {offset}")]
InvalidOffset { offset: String },
}
// Use Result types consistently
pub fn evaluate_magic_rules(
rules: &[MagicRule],
data: &[u8],
) -> Result<Option<String>, MagicError> {
// Implementation
}- Clippy pedantic lints are active (e.g., prefer
trailing_zeros()over bitwise masks) - Comparison operators share a
compare_values() -> Option<Ordering>helper inoperators/comparison.rs-- new comparison logic goes there, not in individualapply_*functions
See GOTCHAS.md for build script boundaries (S1), enum variant update checklists (S2), parser architecture (S3), numeric type pitfalls (S5), string/pstring encoding (S6), and other non-obvious behaviors.
- Files: snake_case (e.g.,
magic_rule.rs) - Types: PascalCase (e.g.,
MagicRule,TypeKind) - Functions: snake_case (e.g.,
resolve_offset,evaluate_rule) - Constants: SCREAMING_SNAKE_CASE (e.g.,
DEFAULT_BUFFER_SIZE) - Modules: snake_case (e.g.,
evaluator,output)
All commands should be run via mise exec -- to use the project's pinned Rust toolchain.
# Development cycle
cargo check # Fast syntax/type checking
cargo build # Build project
cargo test # Run all tests
cargo clippy # Linting with strict warnings
cargo fmt # Format code
just ci-check # Run complete CI suite locally (pre-commit validation)
# Performance and quality
cargo bench # Run benchmarks
cargo doc # Generate documentation
cargo test --doc # Test documentation examples- Unit Tests: Alongside source files with
#[cfg(test)] - Integration Tests: In
tests/directory with real magic files - Compatibility Tests: Complete test suite from original file project
- Property Tests: Use
proptestfor fuzzing magic rule evaluation - Benchmarks: Critical path performance tests with
criterion - Coverage: Target >85% with
cargo llvm-cov - Test style: Prefer table-driven tests over one-assertion-per-function tests; consolidate related cases into a single test with descriptive failure messages
- Offsets: Absolute, from-end, indirect, and relative specifications (relative offsets
&+N/&-Nare evaluated using GNUfilesemantics -- the previous-match anchor) - Types:
byte,short,long,quad,float,double,string,pstringwith endianness support; unsigned variantsubyte,ushort/ubeshort/uleshort,ulong/ubelong/ulelong,uquad/ubequad/ulequad; float/double endian variantsbefloat/lefloat,bedouble/ledouble; 32-bit date/timestamp typesdate/ldate/bedate/beldate/ledate/leldate; 64-bit date/timestamp typesqdate/qldate/beqdate/beqldate/leqdate/leqldate;pstringis a Pascal string (length-prefixed) with support for 1/2/4-byte length prefixes via/B,/H(2-byte BE),/h(2-byte LE),/L(4-byte BE),/l(4-byte LE) suffixes, and the/Jflag (stored length includes prefix width, JPEG convention) which is combinable with width suffixes (e.g.,pstring/HJ); date values formatted as "Www Mmm DD HH:MM:SS YYYY" matching GNUfileoutput; types are signed by default (libmagic-compatible) - Operators:
=(equal),!=(not equal),<(less than),>(greater than),<=(less equal),>=(greater equal),&(bitwise AND with optional mask),^(bitwise XOR),~(bitwise NOT),x(any value) - Nested Rules: Hierarchical rule evaluation with proper indentation
- String Matching: Exact string matching with null-termination and Pascal string (length-prefixed) support
- Regex type: Binary-safe regex matching via
regex::bytes::Regex. Full flag support:/c(case-insensitive),/s(anchor advances to match-start instead of match-end),/l(scan window is measured in lines instead of bytes). Flags combine in any order (regex/cs,regex/csl,regex/lc). Numeric counts are honored:regex/100scans at most 100 bytes;regex/1lscans at most 1 line. Multi-line regex matching is always on (matching libmagic's unconditionalREG_NEWLINE), so^and$match at line boundaries regardless of/l. Every scan window is capped at 8192 bytes (FILE_REGEX_MAX) regardless of the user's count. - Search type: Bounded literal pattern scan via
memchr::memmem::find;search/Ncaps the scan window toNbytes from the offset. The range is mandatory and stored asNonZeroUsize, so baresearchandsearch/0are parse errors (matching GNUfilemagic(5)). Anchor advance follows GNUfilesemantics (match-end, not window-end) so relative-offset children resolve to the byte immediately after the matched pattern. - Meta-type directives:
default,clear,name <id>,use <id>,indirect, andoffsetare fully implemented.nameblocks are hoisted into aNameTableat load time (parser::name_table::extract_name_table).useinvokes subroutines at the resolved offset viaRuleEnvironmentthreaded throughEvaluationContext::rule_env; subroutine-local absolute offsets resolve relative to the use-site base (tracked viaEvaluationContext::base_offset).defaultfires only when no sibling at the same level has matched;clearresets the per-level sibling-matched flag so a laterdefaultcan fire.indirectre-applies the root rule set at the resolved offset, bounded byEvaluationConfig::max_recursion_depth.offsetreports the resolved file offset asValue::Uint(pos)for format-string rendering. Continuation siblings (recursion_depth > 0) see the parent-level anchor on each iteration rather than chaining -- matching libmagic'sms->c.li[cont_level]model. Top-level siblings still chain (documented in GOTCHAS S3.8). - Printf-style format substitution: Rule messages support
%d,%i,%u,%x,%X,%o,%s,%c, and%%, along with width/padding modifiers (%05d,%-5d) and length modifiers (l,ll,h, etc. -- parsed and ignored). Hex specifiers respect the rule'sTypeKind::bit_width()to mask sign-extended signed reads (so a signed byte carrying-1renders asff, notffffffffffffffff). Implemented insrc/output/format.rs::format_magic_messageand wired intoMagicDatabase::build_result. Unrecognized specifiers pass through literally with adebug!log.
See Development Phases below for the planned roadmap of features not yet implemented (Aho-Corasick multi-pattern optimization and !:mime/!:ext/!:apple directive evaluation).
- 64-bit integer types:
quad/uquad,bequad/ubequad,lequad/ulequadare implemented;qquad(128-bit) is not yet supported stringevaluation reads until first NUL or end-of-buffer;max_length: Some(_)is supported programmatically (via the AST) but libmagic itself has no corresponding surface syntax, so this is not a parity gapstringtype modifier flags are not supported:/B(compact whitespace),/b(compact blanks),/c//C(case-insensitive),/t//T(force text/binary),/w//W(whitespace optional). Onlypstringhas suffix parsing today.pstringsupports 1-byte (/B), 2-byte big-endian (/H), 2-byte little-endian (/h), 4-byte big-endian (/L), and 4-byte little-endian (/l) length prefixes, plus the/Jflag (stored length includes prefix width). All flags are combinable (e.g.,pstring/HJ) and fully implemented.
- Parser handles
&,&<decimal>, and&0x<hex>masks across the fullu64range; compound forms like arithmetic expressions in mask position (&(N+M)) or post-mask modifiers are not parsed
- Indirect offsets are fully implemented (parsing + evaluation) with specifiers:
.b/.B(byte),.s/.S(short),.l/.L(long),.q/.Q(quad); lowercase = little-endian, uppercase = big-endian (GNUfilesemantics); pointer types signed by default; adjustment after closing paren:(base.type)+adj - Relative offsets are fully evaluated against the GNU
fileprevious-match anchor: the engine tracksEvaluationContext::last_match_end(), advancing it after each successful match by the bytes consumed (variable-width types include c-string NUL terminators and pstring length prefixes). Top-level relative offsets resolve from anchor 0. Magic-file&+N/&-Nparsing is still TODO -- relative offsets are exercised programmatically through the AST.
- Limited support for special directives (only
!:strengthis parsed) - No support for
!:mime,!:ext,!:appledirectives in evaluation - Meta-type directives (
default,clear,name,use,indirect,offset) are all fully implemented with evaluator dispatch, including printf-style format substitution in message rendering (see "Currently Implemented" above for details).
See issue #52 for the planned enhancement roadmap.
- Memory Mapping: Use
mmapto avoid loading entire files into memory - Zero-Copy: Minimize allocations during rule evaluation
- Aho-Corasick: Use for multi-pattern string searches when beneficial
- Rule Caching: Cache compiled magic rules for repeated use
- Early Exit: Stop evaluation as soon as a definitive match is found
// Example benchmark structure
#[bench]
fn bench_magic_evaluation(b: &mut Bencher) {
let rules = load_magic_rules("tests/fixtures/standard.magic");
let file_data = include_bytes!("../tests/fixtures/sample.bin");
b.iter(|| evaluate_rules(&rules, file_data));
}sample.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV)
{
"filename": "sample.bin",
"matches": [
{
"text": "ELF 64-bit LSB executable",
"offset": 0,
"value": "7f454c46",
"tags": [
"executable",
"elf"
],
"score": 90,
"mime_type": "application/x-executable"
}
],
"metadata": {
"file_size": 8192,
"evaluation_time_ms": 2.3,
"rules_evaluated": 45
}
}Note: Currently implemented types are
Byte,Short,Long,Quad,Float,Double,Date,QDate,String,PString,Regex, andSearch. See "Current Limitations" for the remaining gaps in regex/search flag coverage.
- Extend
TypeKindenum insrc/parser/ast.rs - Add keyword parsing in
src/parser/types.rs(parse_type_keywordandtype_keyword_to_kind) - Add value/operator parsing in
src/parser/grammar/mod.rsif needed - Implement reading logic in
src/evaluator/types.rs - Update
serialize_type_kind()insrc/parser/codegen.rs - Add tests for the new type
- Update documentation
Meta-types sit inside TypeKind::Meta(MetaType) and do not read bytes. Adding a new variant requires:
- Add the variant to
MetaTypeinsrc/parser/ast.rs. Update the three test fixtures that iterateMetaTypevariants:test_meta_type_variants_debug_clone_eq,test_meta_type_serde_roundtrip,test_type_kind_meta_bit_width_is_none(see GOTCHAS S2.11). - Add the keyword tag in
parse_type_keywordand the arm intype_keyword_to_kindinsrc/parser/types.rs, plus thetest_roundtrip_all_keywordsarray. - Update
serialize_type_kind(the innerTypeKind::Meta(meta)arm) insrc/parser/codegen.rs. - Update
arb_type_kindintests/property_tests.rs(prop_oneofbranch forMetaType). - Decide semantics: does the new variant need inline loop-level dispatch in
evaluate_rules(likeUse,Default,Clear,Indirect— each of which mutates the match vector orsibling_matchedflag) or is it a silent no-op via theMeta(_)wildcard arm inevaluate_single_rule_with_anchor? Add the arm accordingly insrc/evaluator/engine/mod.rs. - Add unit tests covering parse round-trip, the evaluator arm, and any new
RuleEnvironmentlookups.
Note: Currently implemented operators are
Equal,NotEqual,LessThan,GreaterThan,LessEqual,GreaterEqual,BitwiseAnd(withBitwiseAndMask),BitwiseXor,BitwiseNot, andAnyValue.
- Extend
Operatorenum insrc/parser/ast.rs - Add parsing logic in
src/parser/grammar/mod.rs - Implement operator logic in
src/evaluator/operators/submodule - Update
serialize_operator()insrc/parser/codegen.rs - Update strength calculation match in
src/evaluator/strength.rs - Update
arb_operator()intests/property_tests.rs - Add tests for the new operator
- Update documentation
- Profile with
cargo benchto identify bottlenecks - Use memory-mapped I/O for file access
- Implement caching for compiled rules
- Use Aho-Corasick for multi-pattern searches
- Minimize allocations in hot paths
Build scripts (build.rs) cannot import the crate being built, which makes them difficult to test. To enable comprehensive testing of build script logic:
- Extract build logic into a library module with
#[cfg(any(test, doc))] - Keep build.rs minimal, calling functions from the testable module
- Write unit tests in the library module to verify all code paths
- Example:
src/build_helpers.rsprovides testable parsing and code generation
This pattern ensures build-time failures (e.g., invalid magic files) are properly tested and produce clear error messages.
- Continue parsing after syntax errors
- Collect all errors for batch reporting
- Provide clear error messages with line numbers
- Graceful degradation
- Skip problematic rules and continue with others
- Maintain evaluation context for nested rules
- Proper resource cleanup
- Clear error messages for file access issues
- Handle truncated and corrupted files safely
- No unsafe code except in vetted dependencies
- Bounds checking for all buffer access
- Safe handling of malformed input
- Fuzzing integration for robustness testing
- Validate magic file syntax before parsing
- Check file size limits and resource usage
- Handle malicious or malformed input gracefully
- Implement timeouts for long-running evaluations
- All public APIs require rustdoc with examples
- Include error conditions and recovery strategies
- Provide usage examples for common patterns
- Document performance characteristics
- Explain complex algorithms and optimizations
- Document magic file syntax support
- Include references to libmagic compatibility
- Explain design decisions and trade-offs
The project uses GitHub Actions CI with Mergify merge protections:
- Formatting:
cargo fmtfor consistent code style - Linting:
cargo clippy -- -D warningsfor best practices - Compilation:
cargo checkandcargo buildfor error detection - Testing:
cargo testandcargo nextest runfor validation - Security:
cargo auditfor vulnerability detection - License Compliance: Verify dependency licenses
- All code must pass clippy with
-D warnings - Test coverage must be >85%
- No compilation warnings or errors
- All tests must pass
- Security audit must pass
- Performance benchmarks must not regress
All pull requests require review before merging. Reviews are performed by maintainers and automated tools (CodeRabbit). Reviewers check for:
- Correctness: Does the code do what it claims? Are edge cases handled?
- Memory safety: No unsafe code blocks (except vetted dependencies). All buffer access must use bounds checking with
.get()methods. No raw pointer arithmetic or transmute operations. - Error handling: Proper use of
Resulttypes, no panics in library code, nounwrap()orexpect()in library code. Usethiserrorfor structured error types. - Tests: New functionality has tests, existing tests still pass, edge cases and error conditions are covered. Property tests with
proptestfor complex data structures. - Performance: No unnecessary allocations in hot paths, no regressions in benchmarks. Memory-mapped I/O used for file access.
- libmagic compatibility: Changes maintain compatibility with libmagic behavior and magic file format. Output format matches GNU
filecommand expectations. - Style: Follows project conventions, passes
cargo fmtandcargo clippy -- -D warnings - Documentation: Public APIs have rustdoc with examples, AGENTS.md updated if architecture changes
CI must pass before merge. Mergify merge protections enforce these checks. Bot PRs from dependabot and dosubot are auto-merged by Mergify when all required CI checks pass. Bot PRs from release-plz are auto-merged by Mergify when their required DCO check passes (they are exempt from full CI in .mergify.yml). Human PRs are merged manually by maintainers.
- Phase: Early development (MVP)
- Focus: Core parser and evaluator implementation
- Priority: Memory safety and basic functionality
- Next Steps: Enhanced features and performance optimization
memmap2: Memory-mapped file I/Obyteorder: Endianness handlingnom: Parser combinatorsserde: Serializationclap: CLI argument parsingregex: Binary-safe pattern matching viaregex::bytes::RegexforTypeKind::Regexevaluationmemchr: SIMD-accelerated literal pattern search, used forTypeKind::Searchaho-corasick: Multi-pattern search (planned, not yet added)
- MVP (v0.1.0) - shipped: Basic parsing and evaluation with byte/short/long/quad/string types, equality and bitwise AND operators, built-in rules for 10 common formats
- Enhanced Features (v0.2.0) - shipped: Comparison operators (
>,<,<=,>=), bitwise XOR/NOT, indirect and relative offset evaluation, strength-based rule ordering - Advanced Types (v0.3.0) - shipped: float/double/date/qdate/pstring types, regex, search, evaluator submodule split
- v0.4.0 - shipped: parse warnings, JSON metadata, improved errors, cargo-dist release pipeline
- v0.5.x (current) - in flight: TOCTOU/search-path hardening, regex compile cache,
EvaluationConfignon_exhaustive,MagicRule::newvalidator, thread-local regex cache - v0.6.0:
Valuepattern refactor (eliminate pattern-as-literal overloading),MagicDatabase::builder(),Directiveextension point for!:mime/!:ext/!:apple - v1.0.0: Stable API, complete documentation, 95%+ compatibility with GNU file, Aho-Corasick multi-pattern optimization, cargo-fuzz harness, complete
#[non_exhaustive]coverage
- Keep modules focused and cohesive
- Use clear, descriptive names
- Minimize coupling between modules
- Maximize cohesion within modules
- Use
Result<T, E>patterns consistently - Avoid panics in library code
- Provide actionable error messages
- Implement graceful degradation
- Write tests alongside implementation
- Include edge cases and error conditions
- Use property-based testing for complex logic
- Benchmark performance-critical code
- Document public APIs thoroughly
- Include usage examples
- Explain design decisions
- Keep documentation up-to-date
- Compilation errors: Check for missing dependencies and syntax issues
- Test failures: Verify test logic and expected behavior
- Performance issues: Profile with
cargo benchand optimize hot paths - Memory issues: Check for bounds violations and resource leaks
- Use
cargo test -- --nocapturefor test output - Enable debug logging with
RUST_LOG=debug - Use
cargo clippyto catch potential issues - Profile with
cargo benchfor performance analysis
This guide ensures consistent, high-quality development practices for the libmagic-rs project while maintaining focus on memory safety, performance, and compatibility.
- Mergify auto-merges dependabot/dosubot PRs when full CI passes; release-plz PRs when DCO passes (exempt from full CI)
- Human PRs are merged manually -- Mergify only provides merge protections for those
.mergify.ymlconfigures auto-merge rules and merge protectionscargo deny checkusesdeny.toml(default) -- do not specify a custom config path.github/workflows/release.ymlis auto-generated by cargo-dist -- do not modify manually- All
.rsfiles must have copyright and SPDX headers (see any source file for format) Cargo.lockandmise.lockare committed for reproducible builds -- do not gitignore- In justfile recipes, never wrap
justin{{ mise_exec }}-- it's redundant - Changelog:
just changelog,just changelog-version <tag>,just changelog-unreleased - Security contact: support@evilbitlabs.io (matches PGP key in SECURITY.md)
docs/solutions/— documented solutions to past problems, organized by category (logic-errors/, integration-issues/, security-issues/, developer-experience/) with YAML frontmatter (tags,severity,components). Relevant when implementing or debugging in documented areas.
This project has the OSSF Best Practices passing badge. Maintain these standards:
- Sign off commits with
git commit -s(DCO enforced by GitHub App) - Pass CI (clippy, fmt, tests, CodeQL, cargo audit) before merge
- Include tests for new functionality -- this is policy, not optional
- Be reviewed (human or CodeRabbit) for correctness, safety, and style
- Not introduce
unsafecode,unwrap()/expect()in library code, or panics
- Have human-readable release notes via git-cliff (not raw git log)
- Use unique SemVer identifiers (
vX.Y.Ztags) - Be built reproducibly (pinned toolchain, committed lock files, cargo-dist)
- Vulnerabilities go through private reporting (GitHub advisories or support@evilbitlabs.io), never public issues
cargo auditandcargo denyrun daily in CI -- fix findings promptly- Medium+ severity vulnerabilities: we aim to release a fix within 90 days of confirmation (see SECURITY.md for canonical policy)
unsafe_code = "forbid"is enforced project-wide via workspace lints inCargo.toml-- this is a hardening mechanism, not a suggestiondocs/src/security-assurance.mdmust be updated when new attack surface is introduced
- Public APIs require rustdoc with examples
- CONTRIBUTING.md documents code review criteria, test policy, DCO, and governance
- SECURITY.md documents vulnerability reporting with scope, safe harbor, and PGP key
- AGENTS.md must accurately reflect implemented features (not aspirational)
docs/src/release-verification.mddocuments artifact signing for users
@.tessl/RULES.md follow the instructions