{"id":50953457,"url":"https://github.com/trailofbits/trailmark","last_synced_at":"2026-06-27T11:02:33.390Z","repository":{"id":348079271,"uuid":"1171786009","full_name":"trailofbits/trailmark","owner":"trailofbits","description":"Build and query a graph database representation of source code","archived":false,"fork":false,"pushed_at":"2026-06-16T22:24:03.000Z","size":915,"stargazers_count":425,"open_issues_count":4,"forks_count":24,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-06-18T08:34:49.543Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/trailofbits.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-03T15:55:28.000Z","updated_at":"2026-06-18T05:57:00.000Z","dependencies_parsed_at":null,"dependency_job_id":"f749710d-ab0d-479a-b612-13d076c6740e","html_url":"https://github.com/trailofbits/trailmark","commit_stats":null,"previous_names":["trailofbits/trailmark"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/trailofbits/trailmark","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trailofbits%2Ftrailmark","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trailofbits%2Ftrailmark/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trailofbits%2Ftrailmark/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trailofbits%2Ftrailmark/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/trailofbits","download_url":"https://codeload.github.com/trailofbits/trailmark/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trailofbits%2Ftrailmark/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34850575,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-27T02:00:06.362Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-18T04:00:31.781Z","updated_at":"2026-06-27T11:02:33.384Z","avatar_url":"https://github.com/trailofbits.png","language":"C","funding_links":[],"categories":["AI \u0026 Agentic Development"],"sub_categories":["Tools"],"readme":"# Trailmark\n\n[![CI](https://github.com/trailofbits/trailmark/actions/workflows/ci.yml/badge.svg)](https://github.com/trailofbits/trailmark/actions/workflows/ci.yml)\n[![Mutation Testing](https://github.com/trailofbits/trailmark/actions/workflows/mutation.yml/badge.svg)](https://github.com/trailofbits/trailmark/actions/workflows/mutation.yml)\n\nParse source code into queryable graphs of functions, classes, calls, and semantic annotations for security analysis.\n\nTrailmark uses [tree-sitter](https://tree-sitter.github.io/) for language-agnostic AST parsing and [rustworkx](https://www.rustworkx.org/) for high-performance graph traversal. The long-term vision is to combine this graph with mutation testing and coverage-guided fuzzing to identify gaps between assumptions and test coverage that are reachable from user input.\n\n## How It Works\n\nTrailmark operates in three phases: **parse**, **index**, and **query**.\n\n```mermaid\nflowchart TD\n    A[\"Source Files\"] --\u003e B[\"tree-sitter Parser\"]\n    B --\u003e C[\"CodeGraph (nodes + edges)\"]\n    C --\u003e D[\"rustworkx GraphStore\"]\n    D --\u003e E[\"QueryEngine\"]\n    E --\u003e F[\"JSON / Summary / Hotspots\"]\n\n    classDef src fill:#007bff26,stroke:#007bff,color:#007bff\n    classDef parse fill:#28a74526,stroke:#28a745,color:#28a745\n    classDef data fill:#6f42c126,stroke:#6f42c1,color:#6f42c1\n    classDef query fill:#ffc10726,stroke:#e6a817,color:#e6a817\n\n    class A src\n    class B parse\n    class C,D data\n    class E,F query\n```\n\n### 1. Parse\n\nA language-specific parser walks the directory, parses each file into a tree-sitter AST, and extracts:\n\n- **Nodes** \u0026mdash; functions, methods, classes, structs, interfaces, traits, enums, modules, namespaces\n- **Edges** \u0026mdash; calls, inheritance, implementation, containment, imports\n- **Metadata** \u0026mdash; type annotations, cyclomatic complexity, branches, docstrings, exception types\n\n### Supported Languages\n\n| Language | Extensions | Key constructs |\n| --- | --- | --- |\n| Python | `.py` | functions, classes, methods |\n| JavaScript | `.js`, `.jsx`, `.mjs`, `.cjs` | functions, classes, arrow functions |\n| TypeScript | `.ts`, `.tsx` | functions, classes, interfaces, enums |\n| PHP | `.php` | functions, classes, interfaces, traits |\n| Ruby | `.rb` | methods, classes, modules |\n| C | `.c`, `.h` | functions, structs, enums |\n| C++ | `.cpp`, `.hpp`, `.cc`, `.hh`, `.cxx`, `.hxx` | functions, classes, structs, namespaces |\n| C# | `.cs` | methods, classes, interfaces, structs, enums, namespaces |\n| Java | `.java` | methods, classes, interfaces, enums |\n| Go | `.go` | functions, methods, structs, interfaces |\n| Rust | `.rs` | functions, structs, traits, enums, impl blocks |\n| Solidity | `.sol` | contracts, interfaces, libraries, functions, modifiers, structs, enums |\n| Cairo | `.cairo` | functions, traits, structs, enums, impl blocks, StarkNet contracts |\n| Circom | `.circom` | templates, functions, signals, components |\n| Haskell | `.hs` | functions, data types, type classes, instances |\n| Erlang | `.erl` | functions, records, behaviours, modules |\n| Miden Assembly | `.masm` | procedures, entrypoints, constants, invocations |\n| Swift | `.swift` | functions, classes, structs, enums, protocols, extensions |\n| Objective-C | `.m`, `.mm`, `.h` | C functions, classes, methods (selector-based naming) |\n| Kotlin | `.kt`, `.kts` | functions, classes, interfaces, data classes, objects, methods |\n| Dart | `.dart` | functions, classes, abstract classes, methods, constructors |\n| Move | `.move` | modules, functions, imports, direct calls |\n| Tact | `.tact` | contracts, structs, receivers, functions |\n| Func | `.fc`, `.func` | functions, includes, direct calls |\n| Sway | `.sw` | ABI interfaces, structs, impl methods, functions |\n| Rego | `.rego` | packages, imports, policy rules, rule calls |\n| Proto | `.proto` | services, RPCs, messages, fields, enums |\n| Thrift | `.thrift` | services, functions, structs, fields, enums |\n| GraphQL | `.graphql`, `.gql` | object types, root operations, fields, enums |\n\n```mermaid\nflowchart TD\n    subgraph \"Per-File Parsing\"\n        F[\"Source file\"] --\u003e TS[\"tree-sitter AST\"]\n        TS --\u003e EX[\"Extract nodes\"]\n        TS --\u003e EC[\"Extract call edges\"]\n        TS --\u003e EB[\"Count branches\"]\n        TS --\u003e ET[\"Resolve types\"]\n    end\n\n    EX --\u003e CG[\"CodeGraph\"]\n    EC --\u003e CG\n    EB --\u003e CG\n    ET --\u003e CG\n\n    classDef src fill:#007bff26,stroke:#007bff,color:#007bff\n    classDef parse fill:#28a74526,stroke:#28a745,color:#28a745\n    classDef extract fill:#ffc10726,stroke:#e6a817,color:#e6a817\n    classDef data fill:#6f42c126,stroke:#6f42c1,color:#6f42c1\n\n    class F src\n    class TS parse\n    class EX,EC,EB,ET extract\n    class CG data\n```\n\nNode IDs follow the scheme `module:function`, `module:Class`, or `module:Class.method` for unambiguous lookup. Directory parsing resolves bare cross-file calls when a unique definition exists; ambiguous cross-file calls are left at their original best-effort target and marked `uncertain`. Edge confidence is tagged as `certain` (direct calls, `self.method()`), `inferred` (attribute access on non-self objects), or `uncertain` (dynamic dispatch or ambiguous resolution).\n\n### 2. Index\n\nThe `GraphStore` loads the `CodeGraph` into a rustworkx `PyDiGraph` and builds bidirectional ID/index mappings for fast traversal.\n\n### 3. Query\n\nThe `QueryEngine` provides a high-level API over the indexed graph:\n\n| Method | Description |\n| --- | --- |\n| `callers_of(name)` | Direct callers of the named target |\n| `callees_of(name)` | Direct callees of the named source |\n| `ancestors_of(name)` | Every function that can transitively reach the target (upward slice) |\n| `reachable_from(name)` | Every function transitively reachable from the source |\n| `paths_between(src, dst)` | All simple call paths between two nodes |\n| `connect_subgraphs(source, target)` | Paths connecting two named subgraphs |\n| `entrypoint_paths_to(name)` | Paths from any detected entrypoint to the target |\n| `attack_surface()` | Entrypoints tagged with trust level and asset value |\n| `complexity_hotspots(n)` | Functions with cyclomatic complexity \u0026ge; n |\n| `functions_that_raise(exc)` | Functions whose parser-detected exception list includes `exc` |\n| `generic_parameters(name)` | Generic type parameters declared by a node |\n| `type_references(name)` | Parameter, return, exception, and generic-bound type references |\n| `annotate(name, kind, description, source)` | Add a semantic annotation to a node |\n| `annotations_of(name, kind=None)` | Get annotations for a node, optionally filtered by kind |\n| `nodes_with_annotation(kind)` | Every node tagged with the given annotation kind |\n| `clear_annotations(name, kind=None)` | Remove annotations from a node |\n| `diff_against(other)` | Structural diff of this engine's graph vs. another |\n| `preanalysis()` | Run the built-in pre-analysis passes and store annotations/subgraphs |\n| `augment_sarif(path)` | Merge SARIF findings into the graph |\n| `augment_weaudit(path)` | Merge weAudit findings into the graph |\n| `augment_binary(path)` | Merge an external binary-analysis graph JSON file |\n| `findings(kind=None)` | Return nodes carrying finding-style annotations |\n| `subgraph(name)` | Return the nodes in a named subgraph |\n| `subgraph_edges(name)` | Return induced edges inside a named subgraph |\n| `subgraph_names()` | List every named subgraph currently on the graph |\n| `summary()` | Node counts, edge counts, dependencies |\n| `to_json()` | Full graph export |\n\n### Data Model\n\n```mermaid\nclassDiagram\n    class CodeGraph {\n        language: str\n        root_path: str\n        nodes: dict[str, CodeUnit]\n        edges: list[CodeEdge]\n        annotations: dict[str, list[Annotation]]\n        entrypoints: dict[str, EntrypointTag]\n        dependencies: list[str]\n        add_annotation(node_id, annotation)\n        clear_annotations(node_id, kind=None)\n        merge(other)\n    }\n\n    class CodeUnit {\n        id: str\n        name: str\n        kind: NodeKind\n        location: SourceLocation\n        parameters: tuple[Parameter]\n        return_type: TypeRef\n        exception_types: tuple[TypeRef]\n        cyclomatic_complexity: int\n        branches: tuple[BranchInfo]\n        docstring: str\n    }\n\n    class CodeEdge {\n        source_id: str\n        target_id: str\n        kind: EdgeKind\n        confidence: EdgeConfidence\n    }\n\n    class Annotation {\n        kind: AnnotationKind\n        description: str\n        source: str\n    }\n\n    class EntrypointTag {\n        kind: EntrypointKind\n        trust_level: TrustLevel\n        description: str\n        asset_value: AssetValue\n    }\n\n    CodeGraph \"1\" *-- \"*\" CodeUnit\n    CodeGraph \"1\" *-- \"*\" CodeEdge\n    CodeGraph \"1\" *-- \"*\" Annotation\n    CodeGraph \"1\" *-- \"*\" EntrypointTag\n```\n\n**Node kinds:** `function`, `method`, `class`, `module`, `struct`, `interface`, `trait`, `enum`, `namespace`, `contract`, `library`, `template`, `proxy`\n\n**Node origins:** `source`, `proxy`, `binary`, `synthetic`\n\n**Edge kinds:** `calls`, `inherits`, `implements`, `contains`, `imports`, `resolves_to`, `type_uses`, `specializes`, `corresponds_to`\n\n**Edge confidence:** `certain`, `inferred`, `uncertain`\n\nUnresolved calls are materialized as proxy nodes such as\n`proxy.unresolved:\u003craw-symbol\u003e` so traversal results can show where source\nanalysis lost resolution instead of silently dropping that edge. Binary\nanalysis support imports external JSON call graphs; Trailmark does not\ndisassemble executables itself.\n\n### Example Graph\n\nGiven this Python code:\n\n```python\nclass Auth:\n    def verify(self, token: str) -\u003e bool:\n        return self._check_sig(token)\n\n    def _check_sig(self, token: str) -\u003e bool:\n        ...\n\ndef handle_request(req: Request) -\u003e Response:\n    auth = Auth()\n    if auth.verify(req.token):\n        return process(req)\n    return deny()\n```\n\nTrailmark produces a graph like:\n\n```mermaid\ngraph TD\n    HR[\"handle_request\"] --\u003e|calls| AV[\"Auth.verify\"]\n    HR --\u003e|calls| P[\"process\"]\n    HR --\u003e|calls| D[\"deny\"]\n    AV --\u003e|calls| CS[\"Auth._check_sig\"]\n    A[\"Auth\"] --\u003e|contains| AV\n    A --\u003e|contains| CS\n\n    classDef fn fill:#007bff26,stroke:#007bff,color:#007bff\n    classDef cls fill:#6f42c126,stroke:#6f42c1,color:#6f42c1\n\n    class HR,P,D fn\n    class A,AV,CS cls\n```\n\n## Installation\n\nThe examples below track the current development branch. For the latest\npublished package, install from PyPI. For the exact feature set described\nhere, install from a checkout and run commands via `uv run`.\n\n```bash\n# Latest published release\nuv pip install trailmark\n\n# Current checkout / development branch\nuv sync --all-groups\n```\n\nRequires Python \u0026ge; 3.12.\n\n## Usage\n\n```bash\n# Report the installed version\ntrailmark --version     # or: trailmark -V\ntrailmark version       # subcommand form\n\n# Full JSON graph (Python, the default)\ntrailmark analyze path/to/project\n\n# Analyze a different language\ntrailmark analyze --language rust path/to/project\ntrailmark analyze --language javascript path/to/project\n\n# Polyglot: auto-detect and merge every supported language found in the\n# tree, or pass an explicit comma-separated list.\ntrailmark analyze --language auto path/to/project\ntrailmark analyze --language python,rust,solidity path/to/project\n\n# Summary statistics\ntrailmark analyze --summary path/to/project\n\n# Complexity hotspots (threshold \u003e= 10)\ntrailmark analyze --complexity 10 path/to/project\n\n# Augment the graph with external findings (SARIF from static analyzers,\n# weAudit findings from the VS Code extension). Each --sarif / --weaudit\n# flag is repeatable. Add --json to print the augmented graph.\ntrailmark augment --sarif results.sarif path/to/project\ntrailmark augment --weaudit findings.json path/to/project\ntrailmark augment --sarif a.sarif --sarif b.sarif --json path/to/project\n\n# List detected entrypoints (attack surface). Uses heuristic detection\n# (main() functions, pyproject.toml [project.scripts]) plus an optional\n# override file at .trailmark/entrypoints.toml (see below).\ntrailmark entrypoints path/to/project\ntrailmark entrypoints --json path/to/project\n\n# Structural diff between two code graphs. Accepts directory paths or\n# git refs (branches, tags, commits). Surfaces added/removed nodes,\n# call-edge changes, and — most usefully — attack-surface changes.\ntrailmark diff before/ after/\ntrailmark diff --repo . main HEAD          # compare git refs\ntrailmark diff --json before/ after/        # machine-readable output\n\n# Generate a Mermaid diagram from the code graph. --type is required; the\n# choices are call-graph, class-hierarchy, module-deps, containment,\n# complexity, and data-flow. Use --focus to scope large graphs.\ntrailmark diagram --target path/to/project --type call-graph\ntrailmark diagram -t path/to/project -T call-graph -f parse_file --depth 3\ntrailmark diagram -t path/to/project -T complexity --threshold 5 --direction LR\n```\n\n### Entrypoint detection\n\nTrailmark automatically populates `graph.entrypoints` so `attack_surface()`, taint propagation, and privilege-boundary crossing have data to work with. Detection runs in four layers, each overriding the last:\n\n1. **Generic `main` heuristic.** Any function named `main` in any language. Tagged `user_input` / `trusted_internal` / `low`.\n2. **Framework-aware scan.** Decorator, attribute, and visibility patterns per language — see the table below.\n3. **`pyproject.toml [project.scripts]`.** Explicit CLI targets get an upgraded trust/asset classification.\n4. **Repo-local override file.** Hand-curated entrypoints in `.trailmark/entrypoints.toml` always win.\n\nFramework coverage:\n\n| Language | Frameworks detected |\n| --- | --- |\n| Python | Flask, FastAPI, aiohttp, Click, Typer, Celery |\n| JavaScript / TypeScript | NestJS, Next.js (App Router + Pages API), AWS Lambda |\n| Java | Spring MVC / WebFlux, JAX-RS, Kafka listeners, servlets |\n| C# | ASP.NET Core, Azure Functions |\n| PHP | Symfony `#[Route]` attributes + legacy annotations |\n| Rust | actix-web, rocket, FFI exports (`#[no_mangle]`, `pub extern \"C\"`), async-main attributes |\n| Solidity | `external` / `public` visibility |\n| Cairo / StarkNet | `#[external]`, `#[view]`, `#[l1_handler]`, `#[constructor]` |\n| Circom | `component main` declarations |\n| Miden Assembly | `export.\u003cname\u003e` directives |\n| Haskell | top-level `main ::` / `main =` |\n| Erlang | functions listed in `-export([...])` |\n| Swift | `@main` app attribute |\n| Objective-C | `UIApplicationDelegate` lifecycle selectors (e.g. `application:openURL:options:`) |\n| Kotlin | Spring MVC / WebFlux annotations (shared with Java), Android component lifecycle methods (`onCreate`, `onReceive`, `onBind`, ...) |\n| Dart | `@pragma('vm:entry-point')` native-callable markers |\n| Go | `http.HandleFunc` / `http.Handle` stdlib registrations, gin/chi/echo-style `\u003crouter\u003e.GET/POST/...` handler registrations |\n| Ruby | Rails controller actions (classes inheriting `ApplicationController` / `ActionController::*`), Sidekiq worker `perform` methods |\n| C / C++ | `extern \"C\"` linkage, `__attribute__((visibility(\"default\")))`, `__declspec(dllexport)` |\n\nFor anything the heuristics miss, declare entrypoints explicitly in `.trailmark/entrypoints.toml` at the project root. The file supports both single-node and rule-based entries:\n\n```toml\n# Single-node entry\n[[entrypoint]]\nnode = \"my_module:handle_request\"  # node id, or \"module.path:function\"\nkind = \"api\"                       # user_input | api | database | file_system | third_party\ntrust = \"untrusted_external\"       # untrusted_external | semi_trusted_external | trusted_internal\nasset_value = \"high\"               # high | medium | low\ndescription = \"HTTP POST /auth\"\n\n# Rule: every PHP script under public_html/ is a web-exposed entrypoint.\n[[entrypoint]]\nfile_glob = \"public_html/**/*.php\"\nkind = \"user_input\"\ntrust = \"untrusted_external\"\nasset_value = \"high\"\ndescription = \"Web-exposed PHP script\"\n\n# Rule: any function that takes a PSR-7 request object.\n[[entrypoint]]\nparam_type = \"ServerRequestInterface\"\nkind = \"api\"\ntrust = \"untrusted_external\"\nasset_value = \"high\"\ndescription = \"PSR-7 HTTP handler\"\n\n# Rule: functions named `handle_*`.\n[[entrypoint]]\nname_regex = \"^handle_\"\nkind = \"api\"\ntrust = \"untrusted_external\"\n\n# Rule: conditions compose with AND — web.py files AND name starts with handle_.\n[[entrypoint]]\nfile_glob = \"public/*.py\"\nname_regex = \"^handle_\"\nkind = \"api\"\ntrust = \"untrusted_external\"\n```\n\nLater entries override earlier ones when two rules tag the same node, so place broad rules first and specific corrections after.\n\nSee [docs/entrypoint-patterns.md](docs/entrypoint-patterns.md) for the full reference, including frameworks not yet implemented (Express / Koa / Fastify, Laravel, Cobra, axum, warp, clap, and others) with grep-ready patterns contributors can use to add new detectors.\n\n### Programmatic API\n\n```python\nfrom trailmark.parse import parse_directory, parse_file\nfrom trailmark.query.api import QueryEngine\n\n# Parse-only API: get the raw CodeGraph without building GraphStore/QueryEngine.\ngraph = parse_file(\"path/to/file.py\")\ngraph = parse_directory(\"path/to/project\", language=\"auto\")\n\n# Single-language (default) or auto-detect + merge across all languages\nengine = QueryEngine.from_directory(\"path/to/project\")\nengine = QueryEngine.from_directory(\"path/to/project\", language=\"auto\")\nengine = QueryEngine.from_directory(\"path/to/project\", language=\"python,rust\")\n\n# Direct neighbors\nengine.callers_of(\"handle_request\")\nengine.callees_of(\"handle_request\")\n\n# Transitive slicing — who could reach this sink, or what could it reach?\nengine.ancestors_of(\"Auth._check_sig\")\nengine.reachable_from(\"handle_request\")\n\n# Attack-surface paths from any detected entrypoint\nengine.entrypoint_paths_to(\"Auth._check_sig\")\n\n# All call paths between two nodes\nengine.paths_between(\"handle_request\", \"Auth._check_sig\")\n\n# Functions with cyclomatic complexity \u003e= 10\nengine.complexity_hotspots(10)\n\n# What functions can raise a given exception? (uses parser-detected\n# exception_types; no runtime tracing required)\nengine.functions_that_raise(\"PermissionError\")\n\n# Add and query semantic annotations\nfrom trailmark.models.annotations import AnnotationKind\n\nengine.annotate(\n    \"handle_request\",\n    AnnotationKind.ASSUMPTION,\n    \"Caller has already authenticated the session token\",\n    source=\"llm\",\n)\nengine.annotations_of(\"handle_request\")\nengine.nodes_with_annotation(AnnotationKind.FINDING)\n\n# Diff against an earlier snapshot of the same codebase\nbefore = QueryEngine.from_directory(\"before/\")\ndiff = engine.diff_against(before)\n# diff contains: summary_delta, nodes {added/removed/modified},\n# edges {added/removed}, entrypoints {added/removed/modified}\n\n# Run the built-in audit-oriented preanalysis passes\nengine.preanalysis()\nengine.findings()\nengine.subgraph_names()\n\n# Programmatic augmentation hooks for external tooling\nengine.augment_sarif(\"results.sarif\")\nengine.augment_weaudit(\"findings.json\")\n```\n\n## Development\n\n```bash\n# Install package and dev dependencies\nuv sync --all-groups\n\n# Lint and format\nuv run ruff check --fix\nuv run ruff format\n\n# Type check\nuv tool install ty \u0026\u0026 ty check\n\n# Tests\nuv run pytest -q tests/\n\n# Mutation testing (on macOS, set this env var to avoid rustworkx fork segfaults)\nOBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES uv run mutmut run\n```\n\n## License\n\nApache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrailofbits%2Ftrailmark","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftrailofbits%2Ftrailmark","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrailofbits%2Ftrailmark/lists"}