trailmark 0.1.3__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. {trailmark-0.1.3 → trailmark-0.2.2}/AGENTS.md +25 -13
  2. {trailmark-0.1.3 → trailmark-0.2.2}/PKG-INFO +172 -16
  3. trailmark-0.2.2/README.md +448 -0
  4. trailmark-0.2.2/docs/entrypoint-patterns.md +924 -0
  5. {trailmark-0.1.3 → trailmark-0.2.2}/pyproject.toml +1 -1
  6. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/__init__.py +1 -1
  7. trailmark-0.2.2/src/trailmark/analysis/diff.py +280 -0
  8. trailmark-0.2.2/src/trailmark/analysis/entrypoints.py +1398 -0
  9. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/cli.py +139 -3
  10. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/models/__init__.py +0 -4
  11. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/models/annotations.py +0 -23
  12. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/_common.py +63 -31
  13. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/c/parser.py +0 -5
  14. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/cairo/parser.py +0 -5
  15. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/cpp/parser.py +5 -5
  16. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/csharp/parser.py +3 -6
  17. trailmark-0.2.2/src/trailmark/parsers/dart/__init__.py +5 -0
  18. trailmark-0.2.2/src/trailmark/parsers/dart/parser.py +498 -0
  19. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/erlang/parser.py +0 -5
  20. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/go/parser.py +0 -9
  21. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/haskell/parser.py +0 -5
  22. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/java/parser.py +0 -5
  23. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/javascript/parser.py +2 -11
  24. trailmark-0.2.2/src/trailmark/parsers/kotlin/__init__.py +5 -0
  25. trailmark-0.2.2/src/trailmark/parsers/kotlin/parser.py +315 -0
  26. trailmark-0.2.2/src/trailmark/parsers/objc/__init__.py +5 -0
  27. trailmark-0.2.2/src/trailmark/parsers/objc/parser.py +456 -0
  28. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/php/parser.py +0 -5
  29. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/python/parser.py +0 -5
  30. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/rust/parser.py +0 -5
  31. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/solidity/parser.py +0 -5
  32. trailmark-0.2.2/src/trailmark/parsers/swift/__init__.py +5 -0
  33. trailmark-0.2.2/src/trailmark/parsers/swift/parser.py +317 -0
  34. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/typescript/parser.py +0 -13
  35. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/query/api.py +251 -3
  36. trailmark-0.2.2/tests/test_cli_parser.py +262 -0
  37. trailmark-0.2.2/tests/test_common_parser.py +154 -0
  38. trailmark-0.2.2/tests/test_dart_parser.py +95 -0
  39. trailmark-0.2.2/tests/test_detector_tags.py +295 -0
  40. trailmark-0.2.2/tests/test_diff.py +520 -0
  41. trailmark-0.2.2/tests/test_entrypoints.py +985 -0
  42. trailmark-0.2.2/tests/test_hidden_apis.py +125 -0
  43. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_javascript_parser.py +13 -0
  44. trailmark-0.2.2/tests/test_kotlin_parser.py +113 -0
  45. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_models.py +0 -15
  46. trailmark-0.2.2/tests/test_objc_parser.py +88 -0
  47. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_parser_edge_cases.py +4 -9
  48. trailmark-0.2.2/tests/test_polyglot.py +130 -0
  49. trailmark-0.2.2/tests/test_readme.py +305 -0
  50. trailmark-0.2.2/tests/test_swift_parser.py +95 -0
  51. {trailmark-0.1.3 → trailmark-0.2.2}/uv.lock +109 -157
  52. trailmark-0.1.3/README.md +0 -292
  53. {trailmark-0.1.3 → trailmark-0.2.2}/.github/dependabot.yml +0 -0
  54. {trailmark-0.1.3 → trailmark-0.2.2}/.github/workflows/ci.yml +0 -0
  55. {trailmark-0.1.3 → trailmark-0.2.2}/.github/workflows/mutation.yml +0 -0
  56. {trailmark-0.1.3 → trailmark-0.2.2}/.github/workflows/publish.yml +0 -0
  57. {trailmark-0.1.3 → trailmark-0.2.2}/.github/workflows/semgrep.yml +0 -0
  58. {trailmark-0.1.3 → trailmark-0.2.2}/.github/workflows/zizmor.yml +0 -0
  59. {trailmark-0.1.3 → trailmark-0.2.2}/.gitignore +0 -0
  60. {trailmark-0.1.3 → trailmark-0.2.2}/CONTRIBUTING.md +0 -0
  61. {trailmark-0.1.3 → trailmark-0.2.2}/LICENSE +0 -0
  62. {trailmark-0.1.3 → trailmark-0.2.2}/NOTICE +0 -0
  63. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/analysis/__init__.py +0 -0
  64. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/analysis/augment.py +0 -0
  65. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/analysis/preanalysis.py +0 -0
  66. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/diagram.py +0 -0
  67. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/models/edges.py +0 -0
  68. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/models/graph.py +0 -0
  69. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/models/nodes.py +0 -0
  70. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/__init__.py +0 -0
  71. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/base.py +0 -0
  72. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/c/__init__.py +0 -0
  73. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/cairo/__init__.py +0 -0
  74. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/circom/__init__.py +0 -0
  75. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/circom/parser.py +0 -0
  76. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/cpp/__init__.py +0 -0
  77. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/csharp/__init__.py +0 -0
  78. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/erlang/__init__.py +0 -0
  79. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/go/__init__.py +0 -0
  80. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/haskell/__init__.py +0 -0
  81. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/java/__init__.py +0 -0
  82. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/javascript/__init__.py +0 -0
  83. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/masm/__init__.py +0 -0
  84. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/masm/parser.py +0 -0
  85. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/php/__init__.py +0 -0
  86. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/python/__init__.py +0 -0
  87. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/ruby/__init__.py +0 -0
  88. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/ruby/parser.py +0 -0
  89. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/rust/__init__.py +0 -0
  90. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/solidity/__init__.py +0 -0
  91. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/parsers/typescript/__init__.py +0 -0
  92. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/py.typed +0 -0
  93. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/query/__init__.py +0 -0
  94. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/storage/__init__.py +0 -0
  95. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/storage/graph_store.py +0 -0
  96. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/__init__.py +0 -0
  97. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/circom/LICENSE +0 -0
  98. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/circom/__init__.py +0 -0
  99. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/circom/binding.c +0 -0
  100. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/circom/grammar.js +0 -0
  101. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/circom/src/parser.c +0 -0
  102. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/circom/src/tree_sitter/parser.h +0 -0
  103. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/masm/LICENSE +0 -0
  104. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/masm/__init__.py +0 -0
  105. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/masm/binding.c +0 -0
  106. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/masm/grammar.js +0 -0
  107. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/masm/src/parser.c +0 -0
  108. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/masm/src/tree_sitter/alloc.h +0 -0
  109. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/masm/src/tree_sitter/array.h +0 -0
  110. {trailmark-0.1.3 → trailmark-0.2.2}/src/trailmark/tree_sitter_custom/masm/src/tree_sitter/parser.h +0 -0
  111. {trailmark-0.1.3 → trailmark-0.2.2}/tests/__init__.py +0 -0
  112. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_augment.py +0 -0
  113. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_c_parser.py +0 -0
  114. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_cairo_parser.py +0 -0
  115. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_circom_grammar.py +0 -0
  116. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_circom_parser.py +0 -0
  117. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_cli.py +0 -0
  118. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_cpp_parser.py +0 -0
  119. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_csharp_parser.py +0 -0
  120. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_diagram_script.py +0 -0
  121. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_erlang_parser.py +0 -0
  122. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_go_parser.py +0 -0
  123. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_haskell_parser.py +0 -0
  124. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_java_parser.py +0 -0
  125. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_masm_parser.py +0 -0
  126. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_php_parser.py +0 -0
  127. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_preanalysis.py +0 -0
  128. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_property_based.py +0 -0
  129. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_python_parser.py +0 -0
  130. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_query.py +0 -0
  131. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_query_edge_cases.py +0 -0
  132. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_ruby_parser.py +0 -0
  133. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_rust_parser.py +0 -0
  134. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_solidity_parser.py +0 -0
  135. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_storage.py +0 -0
  136. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_storage_edge_cases.py +0 -0
  137. {trailmark-0.1.3 → trailmark-0.2.2}/tests/test_typescript_parser.py +0 -0
@@ -5,9 +5,10 @@ Development context for AI agents working on this codebase.
5
5
  ## Project Overview
6
6
 
7
7
  Trailmark parses source code into directed graphs of functions, classes,
8
- calls, and semantic metadata for security analysis. It supports 16
9
- languages via tree-sitter (plus a bundled Circom grammar) and uses
10
- rustworkx for graph traversal.
8
+ calls, and semantic metadata for security analysis. It supports more than
9
+ twenty language frontends via tree-sitter-language-pack plus bundled
10
+ custom grammars, and uses rustworkx for graph traversal. Treat
11
+ `README.md` as the authoritative public feature list.
11
12
 
12
13
  ## Architecture
13
14
 
@@ -19,17 +20,22 @@ CodeGraph (data) -> GraphStore (indexed storage) -> QueryEngine (facade)
19
20
  - **GraphStore** wraps CodeGraph in a rustworkx PyDiGraph. Validates
20
21
  node existence. Returns model objects.
21
22
  - **QueryEngine** resolves names to node IDs, delegates to GraphStore,
22
- returns plain dicts for JSON serialization.
23
+ and returns JSON-serializable data structures for user-facing APIs.
23
24
 
24
25
  ## Key Conventions
25
26
 
26
- - All public methods return `False` or `[]` for missing nodes. Never raise.
27
- - QueryEngine returns dicts; GraphStore returns model objects.
27
+ - Query/read methods should return `False`, `[]`, or empty sets for
28
+ missing nodes where practical. Factory methods and invalid-input paths
29
+ may raise `ValueError` or exit via the CLI.
30
+ - QueryEngine returns JSON-serializable data structures; GraphStore
31
+ returns model objects and node IDs.
28
32
  - Node IDs follow `module:function`, `module:Class`, `module:Class.method`.
29
33
  - Edge confidence: `certain`, `inferred`, `uncertain`.
30
34
  - Annotation sources: `"llm"`, `"docstring"`, `"manual"`.
31
35
  - Frozen dataclasses for immutable data. `CodeGraph` is mutable.
32
36
  - No relative (`..`) imports. Use `from trailmark.*`.
37
+ - Update `README.md` and `tests/test_readme.py` when documented behavior
38
+ changes.
33
39
 
34
40
  ## File Layout
35
41
 
@@ -46,6 +52,7 @@ src/trailmark/
46
52
  python/ # One subpackage per language
47
53
  javascript/
48
54
  ...
55
+ analysis/ # Higher-level passes: entrypoints, diff, augment, preanalysis
49
56
  storage/
50
57
  graph_store.py # GraphStore: rustworkx-backed indexed storage
51
58
  query/
@@ -57,10 +64,11 @@ tests/ # pytest test suite
57
64
  ## Running Checks
58
65
 
59
66
  ```bash
60
- uv run ruff check --fix src/ tests/
61
- uv run ruff format src/ tests/
62
- uv run ty check
63
- pytest -q
67
+ uv sync --all-groups
68
+ uv run ruff check src/ tests/
69
+ uv run ruff format --check src/ tests/
70
+ uv tool install ty && ty check
71
+ uv run pytest -q tests/
64
72
  ```
65
73
 
66
74
  ## Mutation Testing
@@ -85,6 +93,10 @@ This is not needed on Linux/CI (Ubuntu).
85
93
  - Follow the three-layer pattern: add to CodeGraph first, then GraphStore
86
94
  (with validation), then QueryEngine (with name resolution and dict
87
95
  conversion).
88
- - Add tests at each layer: `test_models.py`, `test_storage.py`,
89
- `test_query.py`.
90
- - Update `README.md` if adding user-facing API.
96
+ - Analysis passes live under `trailmark.analysis` and should usually
97
+ persist results as annotations or named subgraphs rather than
98
+ bypassing the graph model.
99
+ - Add tests at each relevant layer: `test_models.py`, `test_storage.py`,
100
+ `test_query.py`, plus focused parser/analysis/doc regression tests.
101
+ - Update `README.md` and the matching regression tests if adding or
102
+ changing user-facing API.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trailmark
3
- Version: 0.1.3
3
+ Version: 0.2.2
4
4
  Summary: Parse source code into a queryable graph of functions, classes, calls, and semantic annotations
5
5
  Project-URL: Homepage, https://github.com/trailofbits/trailmark
6
6
  Project-URL: Repository, https://github.com/trailofbits/trailmark
@@ -60,7 +60,7 @@ A language-specific parser walks the directory, parses each file into a tree-sit
60
60
  | Language | Extensions | Key constructs |
61
61
  | --- | --- | --- |
62
62
  | Python | `.py` | functions, classes, methods |
63
- | JavaScript | `.js`, `.jsx` | functions, classes, arrow functions |
63
+ | JavaScript | `.js`, `.jsx`, `.mjs`, `.cjs` | functions, classes, arrow functions |
64
64
  | TypeScript | `.ts`, `.tsx` | functions, classes, interfaces, enums |
65
65
  | PHP | `.php` | functions, classes, interfaces, traits |
66
66
  | Ruby | `.rb` | methods, classes, modules |
@@ -76,6 +76,10 @@ A language-specific parser walks the directory, parses each file into a tree-sit
76
76
  | Haskell | `.hs` | functions, data types, type classes, instances |
77
77
  | Erlang | `.erl` | functions, records, behaviours, modules |
78
78
  | Miden Assembly | `.masm` | procedures, entrypoints, constants, invocations |
79
+ | Swift | `.swift` | functions, classes, structs, enums, protocols, extensions |
80
+ | Objective-C | `.m`, `.mm`, `.h` | C functions, classes, methods (selector-based naming) |
81
+ | Kotlin | `.kt`, `.kts` | functions, classes, interfaces, data classes, objects, methods |
82
+ | Dart | `.dart` | functions, classes, abstract classes, methods, constructors |
79
83
 
80
84
  ```mermaid
81
85
  flowchart TD
@@ -114,15 +118,27 @@ The `GraphStore` loads the `CodeGraph` into a rustworkx `PyDiGraph` and builds b
114
118
  The `QueryEngine` provides a high-level API over the indexed graph:
115
119
 
116
120
  | Method | Description |
117
- |---|---|
118
- | `callers_of(name)` | All functions that call the named target |
119
- | `callees_of(name)` | All functions called by the named source |
121
+ | --- | --- |
122
+ | `callers_of(name)` | Direct callers of the named target |
123
+ | `callees_of(name)` | Direct callees of the named source |
124
+ | `ancestors_of(name)` | Every function that can transitively reach the target (upward slice) |
125
+ | `reachable_from(name)` | Every function transitively reachable from the source |
120
126
  | `paths_between(src, dst)` | All simple call paths between two nodes |
127
+ | `entrypoint_paths_to(name)` | Paths from any detected entrypoint to the target |
121
128
  | `attack_surface()` | Entrypoints tagged with trust level and asset value |
122
129
  | `complexity_hotspots(n)` | Functions with cyclomatic complexity ≥ n |
123
- | `annotate(name, kind, desc, source)` | Add a semantic annotation to a node |
130
+ | `functions_that_raise(exc)` | Functions whose parser-detected exception list includes `exc` |
131
+ | `annotate(name, kind, description, source)` | Add a semantic annotation to a node |
124
132
  | `annotations_of(name, kind=None)` | Get annotations for a node, optionally filtered by kind |
133
+ | `nodes_with_annotation(kind)` | Every node tagged with the given annotation kind |
125
134
  | `clear_annotations(name, kind=None)` | Remove annotations from a node |
135
+ | `diff_against(other)` | Structural diff of this engine's graph vs. another |
136
+ | `preanalysis()` | Run the built-in pre-analysis passes and store annotations/subgraphs |
137
+ | `augment_sarif(path)` | Merge SARIF findings into the graph |
138
+ | `augment_weaudit(path)` | Merge weAudit findings into the graph |
139
+ | `findings(kind=None)` | Return nodes carrying finding-style annotations |
140
+ | `subgraph(name)` | Return the nodes in a named subgraph |
141
+ | `subgraph_names()` | List every named subgraph currently on the graph |
126
142
  | `summary()` | Node counts, edge counts, dependencies |
127
143
  | `to_json()` | Full graph export |
128
144
 
@@ -227,15 +243,27 @@ graph TD
227
243
 
228
244
  ## Installation
229
245
 
246
+ The examples below track the current development branch. For the latest
247
+ published package, install from PyPI. For the exact feature set described
248
+ here, install from a checkout and run commands via `uv run`.
249
+
230
250
  ```bash
251
+ # Latest published release
231
252
  uv pip install trailmark
253
+
254
+ # Current checkout / development branch
255
+ uv sync --all-groups
232
256
  ```
233
257
 
234
- Requires Python ≥ 3.13.
258
+ Requires Python ≥ 3.12.
235
259
 
236
260
  ## Usage
237
261
 
238
262
  ```bash
263
+ # Report the installed version
264
+ trailmark --version # or: trailmark -V
265
+ trailmark version # subcommand form
266
+
239
267
  # Full JSON graph (Python, the default)
240
268
  trailmark analyze path/to/project
241
269
 
@@ -243,33 +271,148 @@ trailmark analyze path/to/project
243
271
  trailmark analyze --language rust path/to/project
244
272
  trailmark analyze --language javascript path/to/project
245
273
 
274
+ # Polyglot: auto-detect and merge every supported language found in the
275
+ # tree, or pass an explicit comma-separated list.
276
+ trailmark analyze --language auto path/to/project
277
+ trailmark analyze --language python,rust,solidity path/to/project
278
+
246
279
  # Summary statistics
247
280
  trailmark analyze --summary path/to/project
248
281
 
249
282
  # Complexity hotspots (threshold >= 10)
250
283
  trailmark analyze --complexity 10 path/to/project
284
+
285
+ # Augment the graph with external findings (SARIF from static analyzers,
286
+ # weAudit findings from the VS Code extension). Each --sarif / --weaudit
287
+ # flag is repeatable. Add --json to print the augmented graph.
288
+ trailmark augment --sarif results.sarif path/to/project
289
+ trailmark augment --weaudit findings.json path/to/project
290
+ trailmark augment --sarif a.sarif --sarif b.sarif --json path/to/project
291
+
292
+ # List detected entrypoints (attack surface). Uses heuristic detection
293
+ # (main() functions, pyproject.toml [project.scripts]) plus an optional
294
+ # override file at .trailmark/entrypoints.toml (see below).
295
+ trailmark entrypoints path/to/project
296
+ trailmark entrypoints --json path/to/project
297
+
298
+ # Structural diff between two code graphs. Accepts directory paths or
299
+ # git refs (branches, tags, commits). Surfaces added/removed nodes,
300
+ # call-edge changes, and — most usefully — attack-surface changes.
301
+ trailmark diff before/ after/
302
+ trailmark diff --repo . main HEAD # compare git refs
303
+ trailmark diff --json before/ after/ # machine-readable output
304
+ ```
305
+
306
+ ### Entrypoint detection
307
+
308
+ Trailmark 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:
309
+
310
+ 1. **Generic `main` heuristic.** Any function named `main` in any language. Tagged `user_input` / `trusted_internal` / `low`.
311
+ 2. **Framework-aware scan.** Decorator, attribute, and visibility patterns per language — see the table below.
312
+ 3. **`pyproject.toml [project.scripts]`.** Explicit CLI targets get an upgraded trust/asset classification.
313
+ 4. **Repo-local override file.** Hand-curated entrypoints in `.trailmark/entrypoints.toml` always win.
314
+
315
+ Framework coverage:
316
+
317
+ | Language | Frameworks detected |
318
+ | --- | --- |
319
+ | Python | Flask, FastAPI, aiohttp, Click, Typer, Celery |
320
+ | JavaScript / TypeScript | NestJS, Next.js (App Router + Pages API), AWS Lambda |
321
+ | Java | Spring MVC / WebFlux, JAX-RS, Kafka listeners, servlets |
322
+ | C# | ASP.NET Core, Azure Functions |
323
+ | PHP | Symfony `#[Route]` attributes + legacy annotations |
324
+ | Rust | actix-web, rocket, FFI exports (`#[no_mangle]`, `pub extern "C"`), async-main attributes |
325
+ | Solidity | `external` / `public` visibility |
326
+ | Cairo / StarkNet | `#[external]`, `#[view]`, `#[l1_handler]`, `#[constructor]` |
327
+ | Circom | `component main` declarations |
328
+ | Miden Assembly | `export.<name>` directives |
329
+ | Haskell | top-level `main ::` / `main =` |
330
+ | Erlang | functions listed in `-export([...])` |
331
+ | Swift | `@main` app attribute |
332
+ | Objective-C | `UIApplicationDelegate` lifecycle selectors (e.g. `application:openURL:options:`) |
333
+ | Kotlin | Spring MVC / WebFlux annotations (shared with Java), Android component lifecycle methods (`onCreate`, `onReceive`, `onBind`, ...) |
334
+ | Dart | `@pragma('vm:entry-point')` native-callable markers |
335
+ | Go | `http.HandleFunc` / `http.Handle` stdlib registrations, gin/chi/echo-style `<router>.GET/POST/...` handler registrations |
336
+ | Ruby | Rails controller actions (classes inheriting `ApplicationController` / `ActionController::*`), Sidekiq worker `perform` methods |
337
+ | C / C++ | `extern "C"` linkage, `__attribute__((visibility("default")))`, `__declspec(dllexport)` |
338
+
339
+ For 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:
340
+
341
+ ```toml
342
+ # Single-node entry
343
+ [[entrypoint]]
344
+ node = "my_module:handle_request" # node id, or "module.path:function"
345
+ kind = "api" # user_input | api | database | file_system | third_party
346
+ trust = "untrusted_external" # untrusted_external | semi_trusted_external | trusted_internal
347
+ asset_value = "high" # high | medium | low
348
+ description = "HTTP POST /auth"
349
+
350
+ # Rule: every PHP script under public_html/ is a web-exposed entrypoint.
351
+ [[entrypoint]]
352
+ file_glob = "public_html/**/*.php"
353
+ kind = "user_input"
354
+ trust = "untrusted_external"
355
+ asset_value = "high"
356
+ description = "Web-exposed PHP script"
357
+
358
+ # Rule: any function that takes a PSR-7 request object.
359
+ [[entrypoint]]
360
+ param_type = "ServerRequestInterface"
361
+ kind = "api"
362
+ trust = "untrusted_external"
363
+ asset_value = "high"
364
+ description = "PSR-7 HTTP handler"
365
+
366
+ # Rule: functions named `handle_*`.
367
+ [[entrypoint]]
368
+ name_regex = "^handle_"
369
+ kind = "api"
370
+ trust = "untrusted_external"
371
+
372
+ # Rule: conditions compose with AND — web.py files AND name starts with handle_.
373
+ [[entrypoint]]
374
+ file_glob = "public/*.py"
375
+ name_regex = "^handle_"
376
+ kind = "api"
377
+ trust = "untrusted_external"
251
378
  ```
252
379
 
380
+ Later entries override earlier ones when two rules tag the same node, so place broad rules first and specific corrections after.
381
+
382
+ See [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.
383
+
253
384
  ### Programmatic API
254
385
 
255
386
  ```python
256
387
  from trailmark.query.api import QueryEngine
257
388
 
389
+ # Single-language (default) or auto-detect + merge across all languages
258
390
  engine = QueryEngine.from_directory("path/to/project")
391
+ engine = QueryEngine.from_directory("path/to/project", language="auto")
392
+ engine = QueryEngine.from_directory("path/to/project", language="python,rust")
259
393
 
260
- # Who calls this function?
394
+ # Direct neighbors
261
395
  engine.callers_of("handle_request")
262
-
263
- # What does this function call?
264
396
  engine.callees_of("handle_request")
265
397
 
266
- # Call paths from entrypoint to sensitive function
398
+ # Transitive slicing — who could reach this sink, or what could it reach?
399
+ engine.ancestors_of("Auth._check_sig")
400
+ engine.reachable_from("handle_request")
401
+
402
+ # Attack-surface paths from any detected entrypoint
403
+ engine.entrypoint_paths_to("Auth._check_sig")
404
+
405
+ # All call paths between two nodes
267
406
  engine.paths_between("handle_request", "Auth._check_sig")
268
407
 
269
408
  # Functions with cyclomatic complexity >= 10
270
409
  engine.complexity_hotspots(10)
271
410
 
272
- # Add a semantic annotation
411
+ # What functions can raise a given exception? (uses parser-detected
412
+ # exception_types; no runtime tracing required)
413
+ engine.functions_that_raise("PermissionError")
414
+
415
+ # Add and query semantic annotations
273
416
  from trailmark.models.annotations import AnnotationKind
274
417
 
275
418
  engine.annotate(
@@ -278,10 +421,23 @@ engine.annotate(
278
421
  "Caller has already authenticated the session token",
279
422
  source="llm",
280
423
  )
281
-
282
- # Retrieve annotations
283
424
  engine.annotations_of("handle_request")
284
- engine.annotations_of("handle_request", kind=AnnotationKind.ASSUMPTION)
425
+ engine.nodes_with_annotation(AnnotationKind.FINDING)
426
+
427
+ # Diff against an earlier snapshot of the same codebase
428
+ before = QueryEngine.from_directory("before/")
429
+ diff = engine.diff_against(before)
430
+ # diff contains: summary_delta, nodes {added/removed/modified},
431
+ # edges {added/removed}, entrypoints {added/removed/modified}
432
+
433
+ # Run the built-in audit-oriented preanalysis passes
434
+ engine.preanalysis()
435
+ engine.findings()
436
+ engine.subgraph_names()
437
+
438
+ # Programmatic augmentation hooks for external tooling
439
+ engine.augment_sarif("results.sarif")
440
+ engine.augment_weaudit("findings.json")
285
441
  ```
286
442
 
287
443
  ## Development
@@ -298,7 +454,7 @@ uv run ruff format
298
454
  uv tool install ty && ty check
299
455
 
300
456
  # Tests
301
- uv run pytest -q
457
+ uv run pytest -q tests/
302
458
 
303
459
  # Mutation testing (on macOS, set this env var to avoid rustworkx fork segfaults)
304
460
  OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES uv run mutmut run