
Construir e consultar uma representação de banco de dados em grafo do código-fonte
# Trailmark
[](https://github.com/trailofbits/trailmark/actions/workflows/ci.yml)
[](https://github.com/trailofbits/trailmark/actions/workflows/mutation.yml)
Analisa o código-fonte em grafos consultáveis de funções, classes, chamadas e anotações semânticas para análise de segurança.
O Trailmark usa [tree-sitter](https://tree-sitter.github.io/) para análise de AST independente de linguagem e [rustworkx](https://www.rustworkx.org/) para travessia de grafos de alto desempenho. A visão de longo prazo é combinar este grafo com testes de mutação e fuzzing guiado por cobertura para identificar lacunas entre suposições e cobertura de testes que sejam alcançáveis a partir da entrada do usuário.
## Como Funciona
O Trailmark opera em três fases: **análise**, **indexação** e **consulta**.
```mermaid
flowchart TD
A["Source Files"] --> B["tree-sitter Parser"]
B --> C["CodeGraph (nodes + edges)"]
C --> D["rustworkx GraphStore"]
D --> E["QueryEngine"]
E --> F["JSON / Summary / Hotspots"]
classDef src fill:#007bff26,stroke:#007bff,color:#007bff
classDef parse fill:#28a74526,stroke:#28a745,color:#28a745
classDef data fill:#6f42c126,stroke:#6f42c1,color:#6f42c1
classDef query fill:#ffc10726,stroke:#e6a817,color:#e6a817
class A src
class B parse
class C,D data
class E,F query
```
### 1. Análise
Um analisador específico de linguagem percorre o diretório, analisa cada arquivo em uma AST do tree-sitter e extrai:
- **Nós** — funções, métodos, classes, structs, interfaces, traits, enums, módulos, namespaces
- **Arestas** — chamadas, herança, implementação, contenção, importações
- **Metadados** — anotações de tipo, complexidade ciclomática, ramificações, docstrings, tipos de exceção
### Linguagens Suportadas
| Linguagem | Extensões | Construções principais |
| --- | --- | --- |
| Python | `.py` | funções, classes, métodos |
| JavaScript | `.js`, `.jsx`, `.mjs`, `.cjs` | funções, classes, arrow functions |
| TypeScript | `.ts`, `.tsx` | funções, classes, interfaces, enums |
| PHP | `.php` | funções, classes, interfaces, traits |
| Ruby | `.rb` | métodos, classes, módulos |
| C | `.c`, `.h` | funções, structs, enums |
| C++ | `.cpp`, `.hpp`, `.cc`, `.hh`, `.cxx`, `.hxx` | funções, classes, structs, namespaces |
| C# | `.cs` | métodos, classes, interfaces, structs, enums, namespaces |
| Java | `.java` | métodos, classes, interfaces, enums |
| Go | `.go` | funções, métodos, structs, interfaces |
| Rust | `.rs` | funções, structs, traits, enums, blocos impl, blocos `verus!` |
| Solidity | `.sol` | contratos, interfaces, bibliotecas, funções, modificadores, structs, enums |
| Cairo | `.cairo` | funções, traits, structs, enums, blocos impl, contratos StarkNet |
| Circom | `.circom` | templates, funções, sinais, componentes |
| Haskell | `.hs` | funções, tipos de dados, classes de tipo, instâncias |
| Erlang | `.erl` | funções, records, behaviours, módulos |
| Miden Assembly | `.masm` | procedimentos, entrypoints, constantes, invocações |
| Swift | `.swift` | funções, classes, structs, enums, protocolos, extensões |
| Objective-C | `.m`, `.mm`, `.h` | funções C, classes, métodos (nomenclatura baseada em seletores) |
| Kotlin | `.kt`, `.kts` | funções, classes, interfaces, data classes, objetos, métodos |
| Dart | `.dart` | funções, classes, classes abstratas, métodos, construtores |
| Move | `.move` | módulos, funções, importações, chamadas diretas |
| Tact | `.tact` | contratos, structs, receivers, funções |
| Func | `.fc`, `.func` | funções, includes, chamadas diretas |
| Sway | `.sw` | interfaces ABI, structs, métodos impl, funções |
| Rego | `.rego` | pacotes, importações, regras de política, chamadas de regras |
| Proto | `.proto` | serviços, RPCs, mensagens, campos, enums |
| Thrift | `.thrift` | serviços, funções, structs, campos, enums |
| GraphQL | `.graphql`, `.gql` | tipos de objeto, operações raiz, campos, enums |
| SQL | `.sql` | schemas, tabelas, views, funções, procedimentos |
O parser Rust extrai funções comuns, `spec` e `proof` com corpos
de blocos `verus! { ... }`. A gramática Verus atualmente suporta apenas
blocos delimitados por chaves; `verus!(...)` e `verus![...]` são ignorados com um aviso.
Módulos inline mantêm IDs qualificados (por exemplo, `file.inner:function`), e
métodos mantêm seu impl ou trait proprietário. Cada bloco é analisado independentemente;
erros de sintaxe produzem um aviso com o arquivo e a localização do bloco.
Invocações qualificadas como `vstd::prelude::verus!` também são suportadas.
Funções auxiliares locais mantêm sua função envolvente em seus IDs (por exemplo,
`file:outer.inner`). Chamadas a essas funções auxiliares e importações relativas como
`use super::*` resolvem para declarações presentes no arquivo.
Nomes locais repetidos em blocos diferentes recebem um sufixo de deslocamento de bytes para manter
seus nós distintos, e as chamadas respeitam o escopo do bloco.
Structs, enums, traits e módulos inline locais também mantêm a função envolvente:
por exemplo, `file:outer.Guard.drop` e `file:outer.local.helper`.
Seus corpos têm chamadas e complexidade separadas. Chamadas explícitas a tipos e módulos
locais conhecidos resolvem dentro do arquivo. Chamadas simples buscam escopos de funções e módulos;
itens associados usam caminhos como `Type::method`. Chamadas com ponto resolvem
apenas quando o receptor nomeia um struct ou enum conhecido. Outros receptores de valor,
incluindo `self.method()`, permanecem não resolvidos. Chamadas implícitas de destrutor e inferência
de tipo de receptor não são modeladas. Essas regras se aplicam ao Rust comum, bem como a
blocos Verus. Editar código antes de uma declaração com sufixo de deslocamento altera
seu ID, então `trailmark diff` pode reportar essa declaração como removida e adicionada.
O Trailmark não expande macros arbitrárias. Invocações Verus literais dentro de
macros opacas ou corpos de funções são ignoradas com um aviso. Definições de
`macro_rules!` não invocadas não são expandidas. Assinaturas de funções sem corpos
e chamadas em cláusulas `requires`/`ensures` não são extraídas.
```mermaid
flowchart TD
subgraph "Per-File Parsing"
F["Source file"] --> TS["tree-sitter AST"]
TS --> EX["Extract nodes"]
TS --> EC["Extract call edges"]
TS --> EB["Count branches"]
TS --> ET["Resolve types"]
end
EX --> CG["CodeGraph"]
EC --> CG
EB --> CG
ET --> CG
classDef src fill:#007bff26,stroke:#007bff,color:#007bff
classDef parse fill:#28a74526,stroke:#28a745,color:#28a745
classDef extract fill:#ffc10726,stroke:#e6a817,color:#e6a817
classDef data fill:#6f42c126,stroke:#6f42c1,color:#6f42c1
class F src
class TS parse
class EX,EC,EB,ET extract
class CG data
```
Os IDs de nó seguem o esquema `module:function`, `module:Class` ou `module:Class.method` para uma consulta inequívoca. A análise de diretórios resolve chamadas entre arquivos sem qualificação quando existe uma definição única; chamadas entre arquivos ambíguas são mantidas em seu alvo original de melhor esforço e marcadas como `uncertain`. A confiança das arestas é marcada como `certain` (chamadas diretas, `self.method()`), `inferred` (acesso a atributos em objetos que não são self) ou `uncertain` (despacho dinâmico ou resolução ambígua).
### 2. Índice
O `GraphStore` carrega o `CodeGraph` em um `PyDiGraph` do rustworkx e constrói mapeamentos bidirecionais de ID/índice para travessia rápida.
### 3. Consulta
O `QueryEngine` fornece uma API de alto nível sobre o grafo indexado:
| Método | Descrição |
| --- | --- |
| `callers_of(name)` | Chamadores diretos do alvo nomeado |
| `callees_of(name)` | Chamados diretos da origem nomeada |
| `ancestors_of(name)` | Toda função que pode alcançar transitivamente o alvo (fatia ascendente) |
| `reachable_from(name)` | Toda função alcançável transitivamente a partir da origem |
| `paths_between(src, dst)` | Todos os caminhos de chamada simples entre dois nós |
| `connect_subgraphs(source, target)` | Caminhos que conectam dois subgrafos nomeados |
| `entrypoint_paths_to(name)` | Caminhos de qualquer entrypoint detectado até o alvo |
| `attack_surface()` | Entrypoints marcados com nível de confiança, valor do ativo e atributos do parser quando presentes |
| `complexity_hotspots(n)` | Funções com complexidade ciclomática ≥ n |
| `functions_that_raise(exc)` | Funções cuja lista de exceções detectada pelo parser inclui `exc` |
| `generic_parameters(name)` | Parâmetros de tipo genérico declarados por um nó |
| `type_references(name)` | Referências de tipo de parâmetro, retorno, exceção e limite genérico |
| `annotate(name, kind, description, source)` | Adiciona uma anotação semântica a um nó |
| `annotations_of(name, kind=None)` | Obtém anotações de um nó, opcionalmente filtradas por tipo |
| `nodes_with_annotation(kind)` | Todo nó marcado com o tipo de anotação fornecido |
| `clear_annotations(name, kind=None)` | Remove anotações de um nó |
| `diff_against(other)` | Diff estrutural do grafo deste engine vs. outro |
| `preanalysis()` | Executa as passagens de pré-análise integradas e armazena anotações/subgrafos |
| `augment_sarif(path)` | Mescla descobertas SARIF no grafo |
| `augment_weaudit(path)` | Mescla descobertas do weAudit no grafo |
| `augment_binary(path)` | Mescla um arquivo JSON de grafo de análise binária externo |
| `findings(kind=None)` | Retorna nós que carregam anotações no estilo de descobertas |
| `subgraph(name)` | Retorna os nós em um subgrafo nomeado |
| `subgraph_edges(name)` | Retorna as arestas induzidas dentro de um subgrafo nomeado |
| `subgraph_names()` | Lista todos os subgrafos nomeados atualmente no grafo |
| `summary()` | Contagens de nós, contagens de arestas, dependências |
| `to_json()` | Exportação completa do grafo |
### Modelo de Dados
```mermaid
classDiagram
class CodeGraph {
language: str
root_path: str
nodes: dict[str, CodeUnit]
edges: list[CodeEdge]
annotations: dict[str, list[Annotation]]
entrypoints: dict[str, EntrypointTag]
dependencies: list[str]
add_annotation(node_id, annotation)
clear_annotations(node_id, kind=None)
merge(other)
}
class CodeUnit {
id: str
name: str
kind: NodeKind
location: SourceLocation
parameters: tuple[Parameter]
return_type: TypeRef
exception_types: tuple[TypeRef]
cyclomatic_complexity: int
branches: tuple[BranchInfo]
docstring: str
}
class CodeEdge {
source_id: str
target_id: str
kind: EdgeKind
confidence: EdgeConfidence
}
class Annotation {
kind: AnnotationKind
description: str
source: str
}
class EntrypointTag {
kind: EntrypointKind
trust_level: TrustLevel
description: str
asset_value: AssetValue
}
CodeGraph "1" *-- "*" CodeUnit
CodeGraph "1" *-- "*" CodeEdge
CodeGraph "1" *-- "*" Annotation
CodeGraph "1" *-- "*" EntrypointTag
```
**Tipos de nó:** `function`, `method`, `class`, `module`, `struct`, `interface`, `trait`, `enum`, `namespace`, `contract`, `library`, `template`, `proxy`
**Origens de nó:** `source`, `proxy`, `binary`, `synthetic`
**Tipos de aresta:** `calls`, `inherits`, `implements`, `contains`, `imports`, `resolves_to`, `type_uses`, `specializes`, `corresponds_to`
**Confiança da aresta:** `certain`, `inferred`, `uncertain`
Chamadas não resolvidas são materializadas como nós proxy, como
`proxy.unresolved:<raw-symbol>`, para que os resultados de travessia possam mostrar onde a análise de código-fonte perdeu a resolução, em vez de descartar silenciosamente essa aresta. Análises de diretório, incluindo análises de linguagem única, também aplicam links de repositório e materializam arestas `TYPE_USES`. O suporte à análise binária importa grafos de chamadas JSON externos; o Trailmark não desmonta executáveis por conta própria.
### Grafo de Exemplo
Dado este código Python:
```python
class Auth:
def verify(self, token: str) -> bool:
return self._check_sig(token)
def _check_sig(self, token: str) -> bool:
...
def handle_request(req: Request) -> Response:
auth = Auth()
if auth.verify(req.token):
return process(req)
return deny()
```
Trailmark produz um grafo como:
```mermaid
graph TD
HR["handle_request"] -->|calls| AV["Auth.verify"]
HR -->|calls| P["process"]
HR -->|calls| D["deny"]
AV -->|calls| CS["Auth._check_sig"]
A["Auth"] -->|contains| AV
A -->|contains| CS
classDef fn fill:#007bff26,stroke:#007bff,color:#007bff
classDef cls fill:#6f42c126,stroke:#6f42c1,color:#6f42c1
class HR,P,D fn
class A,AV,CS cls
```
## Instalação
Os exemplos abaixo acompanham o branch de desenvolvimento atual. Para o pacote
publicado mais recente, instale a partir do PyPI. Para o conjunto exato de recursos descrito
aqui, instale a partir de um checkout e execute os comandos via `uv run`.
```bash
# Latest published release
uv pip install trailmark
# Current checkout / development branch
uv sync --all-groups
```
Requer Python ≥ 3.12.
O Trailmark requer `tree-sitter-language-pack` 1.22.1 ou mais recente para a maioria das gramáticas.
Caches de gramáticas pertencentes ao root são suportados quando não são graváveis por grupo ou por outros.
As versões atuais usam o armazenamento de certificados da plataforma para downloads de gramáticas. Em
ambientes com inspeção TLS ou offline, pré-popule o cache do pacote com
`python -c "import tree_sitter_language_pack as p; p.download_all()"` em uma
plataforma compatível e, em seguida, copie o diretório de cache `tree-sitter-language-pack` resultante
para a máquina de destino. `HTTPS_PROXY` também é respeitado. A gramática SQL
é distribuída como a dependência do wheel `tree-sitter-sql` e não usa esse cache.
As gramáticas Circom, Miden Assembly e Verus incluídas são compiladas no primeiro uso.
Isso requer um compilador C (`cc`), cabeçalhos de desenvolvimento do Python e acesso de escrita
aos diretórios de gramáticas instalados. Arquivos Rust sem blocos Verus não
disparam compilação. Se a gramática Verus não puder ser compilada ou carregada, o Trailmark
registra um aviso, ignora os blocos Verus e continua analisando Rust comum.
O cache de binding do Verus inclui o conteúdo do código-fonte compilado, a ABI do Python,
a plataforma e as opções do compilador. Entradas alteradas selecionam um novo binding; compilações
concluídas são publicadas atomicamente. Falhas do compilador incluem o status de saída e
stderr no aviso.
Checkouts do Git mantêm a definição da gramática Verus e geram seu parser C durante
as compilações com o Tree-sitter CLI 0.27.1. Antes de `uv sync` ou `uv build` em um checkout,
execute `npm ci` com os scripts de instalação do npm habilitados; o Node.js é necessário para a geração.
O parser e os cabeçalhos gerados são ignorados pelo git. Wheels e distribuições de código-fonte
incluem esses fontes gerados, portanto a instalação de pacotes publicados não requer nem
Node.js nem o Tree-sitter CLI.
Instalações editáveis via `uv` reexecutam o hook de build para restaurar arquivos gerados ausentes;
entradas inalteradas reutilizam a saída existente.
## Uso
```bash
# Report the installed version
trailmark --version # or: trailmark -V
trailmark version # subcommand form
# Full JSON graph (Python, the default)
trailmark analyze path/to/project
# Analyze a different language
trailmark analyze --language rust path/to/project
trailmark analyze --language javascript path/to/project
# Polyglot: auto-detect and merge every supported language found in the
# tree, or pass an explicit comma-separated list.
trailmark analyze --language auto path/to/project
trailmark analyze --language python,rust,solidity path/to/project
# Summary statistics
trailmark analyze --summary path/to/project
# Complexity hotspots (threshold >= 10)
trailmark analyze --complexity 10 path/to/project
# Augment the graph with external findings (SARIF from static analyzers,
# weAudit findings from the VS Code extension). Each --sarif / --weaudit
# flag is repeatable. Add --json to print the augmented graph.
trailmark augment --sarif results.sarif path/to/project
trailmark augment --weaudit findings.json path/to/project
trailmark augment --sarif a.sarif --sarif b.sarif --json path/to/project
# List detected entrypoints (attack surface). Uses heuristic detection
# (main() functions, pyproject.toml [project.scripts]) plus an optional
# override file at .trailmark/entrypoints.toml (see below).
trailmark entrypoints path/to/project
trailmark entrypoints --json path/to/project
# Structural diff between two code graphs. Accepts directory paths or
# git refs (branches, tags, commits). Surfaces added/removed nodes,
# call-edge changes, and — most usefully — attack-surface changes.
trailmark diff before/ after/
trailmark diff --repo . main HEAD # compare git refs
trailmark diff --json before/ after/ # machine-readable output
# Generate a Mermaid diagram from the code graph. --type is required; the
# choices are call-graph, class-hierarchy, module-deps, containment,
# complexity, and data-flow. Use --focus to scope large graphs.
trailmark diagram --target path/to/project --type call-graph
trailmark diagram -t path/to/project -T call-graph -f parse_file --depth 3
trailmark diagram -t path/to/project -T complexity --threshold 5 --direction LR
```
### Detecção de entrypoints
O Trailmark popula automaticamente `graph.entrypoints` para que `attack_surface()`, a propagação de taint e a travessia de fronteiras de privilégio tenham dados com que trabalhar. A detecção é executada em quatro camadas, cada uma sobrepondo a anterior:
1. **Heurística genérica de `main`.** Qualquer função chamada `main` em qualquer linguagem. Marcada como `user_input` / `trusted_internal` / `low`.
2. **Varredura ciente de frameworks.** Padrões de decoradores, atributos e visibilidade por linguagem — consulte a tabela abaixo.
3. **`pyproject.toml [project.scripts]`.** Alvos de CLI explícitos recebem uma classificação de confiança/ativo aprimorada.
4. **Arquivo de substituição local ao repositório.** Entrypoints curados manualmente em `.trailmark/entrypoints.toml` sempre prevalecem.
Cobertura de frameworks:
| Linguagem | Frameworks detectados |
| --- | --- |
| Python | Flask, FastAPI, aiohttp, Click, Typer, Celery |
| JavaScript / TypeScript | NestJS, Next.js (App Router + Pages API), AWS Lambda |
| Java | Spring MVC / WebFlux, JAX-RS, listeners Kafka, servlets |
| C# | ASP.NET Core, Azure Functions |
| PHP | Atributos `#[Route]` do Symfony + anotações legadas |
| Rust | actix-web, rocket, exportações FFI (`#[no_mangle]`, `pub extern "C"`), atributos async-main |
| Solidity | Visibilidade `external` / `public` |
| Cairo / StarkNet | `#[external]`, `#[view]`, `#[l1_handler]`, `#[constructor]` |
| Circom | Declarações `component main` |
| Miden Assembly | Diretivas `export.<name>` |
| Haskell | `main ::` / `main =` de nível superior |
| Erlang | Funções listadas em `-export([...])` |
| Swift | Atributo de aplicação `@main` |
| Objective-C | Seletores de ciclo de vida do `UIApplicationDelegate` (por exemplo, `application:openURL:options:`) |
| Kotlin | Anotações Spring MVC / WebFlux (compartilhadas com Java), métodos de ciclo de vida de componentes Android (`onCreate`, `onReceive`, `onBind`, ...) |
| Dart | Marcadores nativos chamáveis `@pragma('vm:entry-point')` |
| Go | Registros da stdlib `http.HandleFunc` / `http.Handle`, registros de handlers no estilo gin/chi/echo `<router>.GET/POST/...` |
| Ruby | Ações de controllers Rails (classes que herdam de `ApplicationController` / `ActionController::*`), métodos `perform` de workers Sidekiq |
| C / C++ | Linkagem `extern "C"`, `__attribute__((visibility("default")))`, `__declspec(dllexport)` |
Para qualquer coisa que as heurísticas não detectem, declare entrypoints explicitamente em `.trailmark/entrypoints.toml` na raiz do projeto. O arquivo suporta tanto entradas de nó único quanto entradas baseadas em regras:
```toml
# Single-node entry
[[entrypoint]]
node = "my_module:handle_request" # node id, or "module.path:function"
kind = "api" # user_input | api | database | file_system | third_party
trust = "untrusted_external" # untrusted_external | semi_trusted_external | trusted_internal
asset_value = "high" # high | medium | low
description = "HTTP POST /auth"
# Rule: every PHP script under public_html/ is a web-exposed entrypoint.
[[entrypoint]]
file_glob = "public_html/**/*.php"
kind = "user_input"
trust = "untrusted_external"
asset_value = "high"
description = "Web-exposed PHP script"
# Rule: any function that takes a PSR-7 request object.
[[entrypoint]]
param_type = "ServerRequestInterface"
kind = "api"
trust = "untrusted_external"
asset_value = "high"
description = "PSR-7 HTTP handler"
# Rule: functions named `handle_*`.
[[entrypoint]]
name_regex = "^handle_"
kind = "api"
trust = "untrusted_external"
# Rule: conditions compose with AND — web.py files AND name starts with handle_.
[[entrypoint]]
file_glob = "public/*.py"
name_regex = "^handle_"
kind = "api"
trust = "untrusted_external"
```
Entradas posteriores substituem as anteriores quando duas regras marcam o mesmo nó, portanto coloque regras amplas primeiro e correções específicas depois.
Consulte [docs/entrypoint-patterns.md](https://github.com/trailofbits/trailmark/blob/main/docs/entrypoint-patterns.md) para a referência completa, incluindo frameworks ainda não implementados (Express / Koa / Fastify, Laravel, Cobra, axum, warp, clap e outros) com padrões prontos para grep que os contribuidores podem usar para adicionar novos detetores.
A deteção de Solidity usa metadados do parser em vez de regexes de assinatura. Declarações de interface são excluídas, e overrides derivados anotam as implementações base correspondentes com `solidity_overridden_by` em vez de remover o entrypoint base. Funções concretas `public` e `external` permanecem entrypoints, incluindo funções `view` e `pure`; os seus atributos `solidity_visibility`, `solidity_mutability` e de override são devolvidos por `attack_surface()` para que os chamadores possam distinguir exposição apenas de leitura e filtrar implementações base substituídas. `attack_surface()` inclui atributos de entrypoint específicos do parser quando estão associados ao nó do grafo subjacente.
### Ligações entre linguagens e externas
A análise polyglot funde grafos de linguagens, mas muitas relações RPC, FFI, subprocessos e host/contrato não são visíveis na sintaxe do código-fonte. Declare estas relações de forma determinística em `.trailmark/links.toml`:
```toml
[[link]]
source = "backend:submit"
target = "contract:Verifier.verify"
kind = "calls" # defaults to calls
confidence = "certain" # defaults to inferred
description = "JSON-RPC eth_call"
[[link]]
source = "backend:notify"
target = "payments-webhook"
target_external = true # required because target is unresolved
```
Referências podem ser IDs de nó exatos ou nomes/sufixos únicos. Referências ambíguas, endpoints internos desconhecidos, valores de enum inválidos e TOML malformado geram `ValueError`. Definir `source_external = true` ou `target_external = true` permite explicitamente esse endpoint não resolvido e cria um nó proxy. Este arquivo é uma interface de configuração pública estável.
### Limitações da análise
- `entrypoint_paths_to()` relata alcançabilidade no grafo de chamadas, não fluxo de dados controlado pelo atacante. Use resultados de taint da pré-análise como um sinal separado e aproximado; o Trailmark ainda não realiza análise de taint interprocedural.
- O TypeScript resolve chamadas diretas e receptores diretos atribuídos com `new ConcreteClass()`. O despacho por interface através de manifests, nomes de propriedades computados, contêineres de injeção de dependência e outros mecanismos dinâmicos permanece como melhor esforço.
- O suporte a SQL é orientado a PostgreSQL e extrai schemas, tabelas, views, funções, procedimentos e dependências de rotinas/views. Não é um validador completo de dialetos SQL nem um analisador semântico de consultas.
### API programática
```python
from trailmark.parse import parse_directory, parse_file
from trailmark.query.api import QueryEngine
# Parse-only API: get the raw CodeGraph without building GraphStore/QueryEngine.
graph = parse_file("path/to/file.py")
graph = parse_directory("path/to/project", language="auto")
# Single-language (default) or auto-detect + merge across all languages
engine = QueryEngine.from_directory("path/to/project")
engine = QueryEngine.from_directory("path/to/project", language="auto")
engine = QueryEngine.from_directory("path/to/project", language="python,rust")
# Direct neighbors
engine.callers_of("handle_request")
engine.callees_of("handle_request")
# Transitive slicing — who could reach this sink, or what could it reach?
engine.ancestors_of("Auth._check_sig")
engine.reachable_from("handle_request")
# Attack-surface paths from any detected entrypoint
engine.entrypoint_paths_to("Auth._check_sig")
# All call paths between two nodes
engine.paths_between("handle_request", "Auth._check_sig")
# Functions with cyclomatic complexity >= 10
engine.complexity_hotspots(10)
# What functions can raise a given exception? (uses parser-detected
# exception_types; no runtime tracing required)
engine.functions_that_raise("PermissionError")
# Add and query semantic annotations
from trailmark.models.annotations import AnnotationKind
engine.annotate(
"handle_request",
AnnotationKind.ASSUMPTION,
"Caller has already authenticated the session token",
source="llm",
)
engine.annotations_of("handle_request")
engine.nodes_with_annotation(AnnotationKind.FINDING)
# Diff against an earlier snapshot of the same codebase
before = QueryEngine.from_directory("before/")
diff = engine.diff_against(before)
# diff contains: summary_delta, nodes {added/removed/modified},
# edges {added/removed}, entrypoints {added/removed/modified}
# Run the built-in audit-oriented preanalysis passes
engine.preanalysis()
engine.findings()
engine.subgraph_names()
# Programmatic augmentation hooks for external tooling
engine.augment_sarif("results.sarif")
engine.augment_weaudit("findings.json")
```
`NodeKind.SCHEMA`, `TABLE`, `VIEW` e `PROCEDURE` são aditivos na v0.5.0;
consumidores que fazem correspondência exaustiva de valores de enum devem adicionar casos para eles.
## Desenvolvimento
```bash
# Install package and dev dependencies
npm ci
uv sync --all-groups
# Lint and format
uv run ruff check --fix
uv run ruff format
# Type check
uv run --locked ty check
# Tests
uv run pytest -q tests/
# Mutation testing with pytest-gremlins
uv run python -m tests.run_mutations --reviewed --workers=4
uv run python -m tests.mutation_gate
# Full repository mutation campaign
uv run python -m tests.run_mutations --workers=4
```
Os relatórios de mutação são gravados em `coverage/gremlins/`. Os pull requests mutam os arquivos Rust/Verus nomeados em `tests/mutation_baseline.json` e fazem o gate de funções novas e alteradas ali. Campanhas completas do repositório são executadas em pushes para a main e por meio do acionamento manual do workflow de mutação. Funções legadas inalteradas são identificadas por hashes de origem e entram no gate automaticamente quando editadas. O gate rejeita sobreviventes não revisados, testes/resultados ausentes, erros e timeouts. Exceções exigem um motivo e uma impressão digital da função e da mutação; alterar essa função exige revisar suas exceções novamente.
O runner fixa o pytest-gremlins 1.9.0 e usa subprocessos reais do pytest para cada mutante, incluindo fixtures e testes parametrizados. Cada mutante mantém a suíte de testes completa selecionada: os testes unitários de componente são executados primeiro, seguidos pelos testes restantes, parando na primeira falha. Mutantes de helpers compartilhados mantêm testes de todos os idiomas. Os testes permanecem em seus arquivos existentes; `tests/mutation_order.py` define a ordenação, e componentes desconhecidos mantêm a ordem de coleta. O runner ignora a pré-verificação de cobertura redundante, verifica a linha de base limpa e cada ordenação distinta de testes instrumentados, e preserva os metadados do pacote e os IDs exatos de parâmetros ao carregar código mutado.
Essas medidas de compatibilidade tratam de problemas do runner e de seleção upstream; seus testes de controle devem passar antes de atualizar o plugin.
As atualizações de snapshot (`TRAILMARK_UPDATE_SNAPSHOTS=1`) ficam desativadas durante o CI e as execuções de mutação. Para focar uma campanha local, passe `--targets` com caminhos de origem separados por vírgula, seguidos pelos arquivos de teste relevantes. `--workers=4` já habilita a execução paralela. Use `--test-order=collection` para comparar com a ordem original do pytest.
## Licença
Apache-2.0