entrygraph 0.1.0__tar.gz → 0.1.1__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 (167) hide show
  1. entrygraph-0.1.1/PKG-INFO +348 -0
  2. entrygraph-0.1.1/README.md +301 -0
  3. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/_version.py +2 -2
  4. entrygraph-0.1.0/PKG-INFO +0 -204
  5. entrygraph-0.1.0/README.md +0 -157
  6. {entrygraph-0.1.0 → entrygraph-0.1.1}/.github/workflows/ci.yml +0 -0
  7. {entrygraph-0.1.0 → entrygraph-0.1.1}/.github/workflows/release.yml +0 -0
  8. {entrygraph-0.1.0 → entrygraph-0.1.1}/.gitignore +0 -0
  9. {entrygraph-0.1.0 → entrygraph-0.1.1}/LICENSE +0 -0
  10. {entrygraph-0.1.0 → entrygraph-0.1.1}/RELEASING.md +0 -0
  11. {entrygraph-0.1.0 → entrygraph-0.1.1}/pyproject.toml +0 -0
  12. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/__init__.py +0 -0
  13. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/__main__.py +0 -0
  14. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/api.py +0 -0
  15. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/cli/__init__.py +0 -0
  16. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/cli/main.py +0 -0
  17. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/cli/render.py +0 -0
  18. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/data/sinks/csharp.toml +0 -0
  19. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/data/sinks/go.toml +0 -0
  20. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/data/sinks/java.toml +0 -0
  21. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/data/sinks/javascript.toml +0 -0
  22. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/data/sinks/lib_javascript.toml +0 -0
  23. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/data/sinks/lib_python.toml +0 -0
  24. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/data/sinks/php.toml +0 -0
  25. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/data/sinks/python.toml +0 -0
  26. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/data/sinks/ruby.toml +0 -0
  27. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/data/sinks/rust.toml +0 -0
  28. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/db/__init__.py +0 -0
  29. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/db/engine.py +0 -0
  30. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/db/meta.py +0 -0
  31. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/db/models.py +0 -0
  32. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/db/queries.py +0 -0
  33. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/detect/__init__.py +0 -0
  34. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/detect/entrypoints/__init__.py +0 -0
  35. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/detect/entrypoints/base.py +0 -0
  36. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/detect/entrypoints/configs.py +0 -0
  37. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/detect/entrypoints/csharp.py +0 -0
  38. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/detect/entrypoints/golang.py +0 -0
  39. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/detect/entrypoints/java.py +0 -0
  40. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/detect/entrypoints/javascript.py +0 -0
  41. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/detect/entrypoints/php.py +0 -0
  42. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/detect/entrypoints/python.py +0 -0
  43. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/detect/entrypoints/ruby.py +0 -0
  44. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/detect/entrypoints/rust.py +0 -0
  45. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/detect/frameworks.py +0 -0
  46. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/detect/manifests.py +0 -0
  47. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/detect/taint.py +0 -0
  48. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/errors.py +0 -0
  49. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/extract/__init__.py +0 -0
  50. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/extract/base.py +0 -0
  51. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/extract/csharp.py +0 -0
  52. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/extract/golang.py +0 -0
  53. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/extract/ir.py +0 -0
  54. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/extract/java.py +0 -0
  55. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/extract/javascript.py +0 -0
  56. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/extract/php.py +0 -0
  57. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/extract/python.py +0 -0
  58. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/extract/registry.py +0 -0
  59. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/extract/ruby.py +0 -0
  60. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/extract/rust.py +0 -0
  61. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/fs/__init__.py +0 -0
  62. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/fs/hashing.py +0 -0
  63. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/fs/lang.py +0 -0
  64. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/fs/walker.py +0 -0
  65. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/graph/__init__.py +0 -0
  66. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/graph/adjacency.py +0 -0
  67. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/graph/cte.py +0 -0
  68. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/graph/scoring.py +0 -0
  69. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/kinds.py +0 -0
  70. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/parsing/__init__.py +0 -0
  71. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/parsing/parsers.py +0 -0
  72. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/parsing/queries.py +0 -0
  73. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/pipeline/__init__.py +0 -0
  74. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/pipeline/scanner.py +0 -0
  75. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/pipeline/worker.py +0 -0
  76. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/pipeline/writer.py +0 -0
  77. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/py.typed +0 -0
  78. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/csharp/calls.scm +0 -0
  79. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/csharp/definitions.scm +0 -0
  80. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/csharp/imports.scm +0 -0
  81. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/go/calls.scm +0 -0
  82. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/go/definitions.scm +0 -0
  83. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/go/imports.scm +0 -0
  84. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/java/calls.scm +0 -0
  85. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/java/definitions.scm +0 -0
  86. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/java/imports.scm +0 -0
  87. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/javascript/calls.scm +0 -0
  88. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/javascript/definitions.scm +0 -0
  89. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/javascript/imports.scm +0 -0
  90. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/php/calls.scm +0 -0
  91. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/php/definitions.scm +0 -0
  92. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/php/imports.scm +0 -0
  93. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/python/calls.scm +0 -0
  94. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/python/definitions.scm +0 -0
  95. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/python/imports.scm +0 -0
  96. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/ruby/calls.scm +0 -0
  97. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/ruby/definitions.scm +0 -0
  98. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/ruby/imports.scm +0 -0
  99. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/rust/calls.scm +0 -0
  100. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/rust/definitions.scm +0 -0
  101. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/queries/rust/imports.scm +0 -0
  102. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/resolve/__init__.py +0 -0
  103. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/resolve/externals.py +0 -0
  104. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/resolve/hierarchy.py +0 -0
  105. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/resolve/resolver.py +0 -0
  106. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/resolve/symbol_table.py +0 -0
  107. {entrygraph-0.1.0 → entrygraph-0.1.1}/src/entrygraph/results.py +0 -0
  108. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/conftest.py +0 -0
  109. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/csharp/aspnet_app/Controllers/ReportsController.cs +0 -0
  110. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/csharp/aspnet_app/Program.cs +0 -0
  111. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/csharp/aspnet_app/Services/ReportService.cs +0 -0
  112. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/csharp/aspnet_app/app.csproj +0 -0
  113. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/go/gin_app/go.mod +0 -0
  114. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/go/gin_app/main.go +0 -0
  115. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/go/gin_app/service.go +0 -0
  116. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/java/spring_app/pom.xml +0 -0
  117. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/java/spring_app/src/main/java/com/example/Application.java +0 -0
  118. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/java/spring_app/src/main/java/com/example/ReportRunner.java +0 -0
  119. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/java/spring_app/src/main/java/com/example/ReportService.java +0 -0
  120. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/java/spring_app/src/main/java/com/example/UserController.java +0 -0
  121. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/javascript/express_app/package.json +0 -0
  122. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/javascript/express_app/src/routes.js +0 -0
  123. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/javascript/express_app/src/services.js +0 -0
  124. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/php/laravel_app/app/Http/Controllers/ReportController.php +0 -0
  125. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/php/laravel_app/artisan +0 -0
  126. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/php/laravel_app/composer.json +0 -0
  127. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/php/laravel_app/routes/web.php +0 -0
  128. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/python/flask_app/app/__init__.py +0 -0
  129. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/python/flask_app/app/db.py +0 -0
  130. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/python/flask_app/app/routes.py +0 -0
  131. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/python/flask_app/app/services.py +0 -0
  132. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/python/flask_app/cli.py +0 -0
  133. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/python/flask_app/requirements.txt +0 -0
  134. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/ruby/sinatra_app/Gemfile +0 -0
  135. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/ruby/sinatra_app/app.rb +0 -0
  136. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/ruby/sinatra_app/services/runner.rb +0 -0
  137. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/rust/axum_app/Cargo.toml +0 -0
  138. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/rust/axum_app/src/handlers.rs +0 -0
  139. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/fixtures/rust/axum_app/src/main.rs +0 -0
  140. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_api.py +0 -0
  141. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_cli.py +0 -0
  142. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_engine_pragmas.py +0 -0
  143. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_entrypoint_expansion.py +0 -0
  144. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_entrypoints.py +0 -0
  145. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_extract_csharp.py +0 -0
  146. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_extract_go.py +0 -0
  147. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_extract_java.py +0 -0
  148. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_extract_javascript.py +0 -0
  149. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_extract_php.py +0 -0
  150. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_extract_python.py +0 -0
  151. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_extract_ruby.py +0 -0
  152. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_extract_rust.py +0 -0
  153. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_frameworks.py +0 -0
  154. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_hardening.py +0 -0
  155. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_incremental.py +0 -0
  156. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_indexer.py +0 -0
  157. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_lang.py +0 -0
  158. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_manifests.py +0 -0
  159. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_models.py +0 -0
  160. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_reachability.py +0 -0
  161. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_render.py +0 -0
  162. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_resolver.py +0 -0
  163. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_scoring.py +0 -0
  164. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_sink_catalog.py +0 -0
  165. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_taint.py +0 -0
  166. {entrygraph-0.1.0 → entrygraph-0.1.1}/tests/test_walker.py +0 -0
  167. {entrygraph-0.1.0 → entrygraph-0.1.1}/uv.lock +0 -0
@@ -0,0 +1,348 @@
1
+ Metadata-Version: 2.4
2
+ Name: entrygraph
3
+ Version: 0.1.1
4
+ Summary: Language-agnostic code graph: query symbols, entrypoints, and source-to-sink call paths from a SQLite index
5
+ Project-URL: Repository, https://github.com/brettbergin/entrygraph
6
+ Author-email: Brett Bergin <brettberginbc@yahoo.com>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026 Brett Bergin
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ License-File: LICENSE
29
+ Keywords: call-graph,code-analysis,entrypoints,static-analysis,tree-sitter
30
+ Classifier: Development Status :: 3 - Alpha
31
+ Classifier: Intended Audience :: Developers
32
+ Classifier: Programming Language :: Python :: 3
33
+ Classifier: Programming Language :: Python :: 3.11
34
+ Classifier: Programming Language :: Python :: 3.12
35
+ Classifier: Programming Language :: Python :: 3.13
36
+ Classifier: Topic :: Software Development :: Quality Assurance
37
+ Requires-Python: >=3.11
38
+ Requires-Dist: pathspec<1.0,>=0.12
39
+ Requires-Dist: rich>=13.0
40
+ Requires-Dist: sqlalchemy<3.0,>=2.0.30
41
+ Requires-Dist: tree-sitter-language-pack>=1.10
42
+ Requires-Dist: tree-sitter<0.26,>=0.25
43
+ Provides-Extra: dev
44
+ Requires-Dist: pytest-cov; extra == 'dev'
45
+ Requires-Dist: pytest>=8.0; extra == 'dev'
46
+ Description-Content-Type: text/markdown
47
+
48
+ # entrygraph
49
+
50
+ Query your codebase like a graph. `entrygraph` indexes a repository into a
51
+ SQLite database (through the SQLAlchemy ORM) and answers questions about
52
+ **symbols**, **classes/methods**, **entrypoints** (HTTP routes, CLI commands,
53
+ main functions, tasks, lambda handlers), and **source → sink call-graph
54
+ reachability** ("can any HTTP route reach `subprocess.run`?").
55
+
56
+ Language-agnostic via [tree-sitter](https://tree-sitter.github.io/); first-class
57
+ support for **Python, JavaScript/TypeScript, Go, Java, Ruby, C#, PHP, and
58
+ Rust**, with language *and* framework detection.
59
+
60
+ Reachability is a heuristic taint tier, not just call-edge closure: paths are
61
+ **risk-ranked**, **sanitizers** prune or discount them, **class-hierarchy
62
+ analysis** recovers virtual dispatch, and confidence flags trade recall for
63
+ precision. Entrypoints include decorator/attribute routes, call-based route
64
+ registration, middleware, and config-file handlers (serverless, SAM, Procfile,
65
+ Dockerfile).
66
+
67
+ ## Install
68
+
69
+ ```bash
70
+ pip install entrygraph # or: uv pip install entrygraph
71
+ ```
72
+
73
+ Requires Python ≥ 3.11. Installs the `entrygraph` command (you can also run it as
74
+ `uv run entrygraph …` or `python -m entrygraph …`).
75
+
76
+ ## Quick start (CLI)
77
+
78
+ Index a repo once, then query the resulting `.entrygraph.db` as often as you
79
+ like. Every query command takes `--db PATH` (defaults to discovering
80
+ `.entrygraph.db`) and `--json` for machine-readable output.
81
+
82
+ ### `index` — build the graph
83
+
84
+ Walk the tree and extract symbols, imports, and calls into `.entrygraph.db`.
85
+ Incremental by default (only changed files are reparsed); `--full` rebuilds.
86
+
87
+ ```bash
88
+ entrygraph index .
89
+ ```
90
+
91
+ ```
92
+ ╭───────────────── ✓ indexed acme-api ──────────────────╮
93
+ │ files 5 indexed, 0 skipped, 0 deleted of 5 scanned │
94
+ │ graph 32 symbols 34 edges 5 entrypoints │
95
+ │ db /path/to/acme-api/.entrygraph.db │
96
+ ╰─────────────────────── 0.137s ────────────────────────╯
97
+ ```
98
+
99
+ ### `detect` — languages & frameworks
100
+
101
+ Byte-share per language plus framework detections scored from manifest
102
+ dependencies and code signals.
103
+
104
+ ```bash
105
+ entrygraph detect
106
+ ```
107
+
108
+ ```
109
+ Languages
110
+ ┏━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓
111
+ ┃LANGUAGE ┃ FILES ┃ SHARE ┃
112
+ ┡━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩
113
+ │python │ 5 │ ████████████ 100.0%│
114
+ └─────────┴───────┴────────────────────┘
115
+ Frameworks
116
+ ┏━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
117
+ ┃FRAMEWORK ┃ LANGUAGE ┃ CONFIDENCE ┃
118
+ ┡━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
119
+ │flask │ python │ █████████░ 0.94│
120
+ │click │ python │ █████████░ 0.94│
121
+ └──────────┴──────────┴────────────────┘
122
+ ```
123
+
124
+ ### `entrypoints` — your attack surface
125
+
126
+ Every HTTP route, CLI command, task, lambda, middleware, and `main` — with its
127
+ framework, method, route, and handler symbol. Filter with `--kind`,
128
+ `--framework`, or `--route`.
129
+
130
+ ```bash
131
+ entrypoints --kind http_route # or: --framework flask / --route '/api/*'
132
+ ```
133
+
134
+ ```
135
+ ┏━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┓
136
+ ┃KIND ┃ FRAMEWORK ┃ METHOD ┃ ROUTE ┃ HANDLER ┃
137
+ ┡━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━┩
138
+ │http_route │ flask │ GET │ /users/<user_id> │ app.routes.get_user │
139
+ │http_route │ flask │ GET │ /health │ app.routes.health │
140
+ │http_route │ flask │ GET,POST │ /reports │ app.routes.create_report│
141
+ │cli_command │ click │ │ │ cli.report │
142
+ │main │ │ │ │ cli │
143
+ └────────────┴───────────┴──────────┴──────────────────┴─────────────────────────┘
144
+ 5 entrypoint(s)
145
+ ```
146
+
147
+ ### `symbols`, `callers`, `callees` — search & walk the call graph
148
+
149
+ `symbols` globs on name or qualified name (filter by `--kind`/`--file`);
150
+ `callers`/`callees` walk the call graph (`--depth N`).
151
+
152
+ ```bash
153
+ entrygraph symbols --kind class --name 'Report*'
154
+ entrygraph callers app.services.run_report # who calls it
155
+ entrygraph callees app.services.run_report # what it calls
156
+ ```
157
+
158
+ ```
159
+ ┏━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┓
160
+ ┃KIND ┃ QNAME ┃ FILE ┃ LINE┃
161
+ ┡━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━┩
162
+ │class │ app.services.ReportRunner │ app/services.py │ 10│
163
+ └──────┴───────────────────────────┴─────────────────┴─────┘
164
+ ```
165
+
166
+ ### `paths` — source → sink reachability
167
+
168
+ The security workhorse: *can anything reach a dangerous sink?* Paths are
169
+ **risk-ranked** (highest first) and drawn as call trees — the sink node is
170
+ flagged (`⚑`), each hop shows its resolution confidence
171
+ (`exact`/`import`/`fuzzy`/`unresolved`), and badges mark constant-argument sinks
172
+ and speculative edges.
173
+
174
+ ```bash
175
+ entrygraph paths --source '*' --sink-category command_exec
176
+ ```
177
+
178
+ ```
179
+ 7 path(s) * → category:command_exec
180
+
181
+ [1] ■ risk 0.73 app.services.ReportRunner.render_and_execute
182
+ └── → py:subprocess.run line 22 import ⚑ py.command-exec.subprocess
183
+ [4] ■ risk 0.50 app.services.run_report
184
+ └── → app.services.ReportRunner.start line 27 fuzzy
185
+ └── → app.services.ReportRunner.render_and_execute line 17 exact
186
+ └── → py:subprocess.run line 22 import ⚑ py.command-exec.subprocess
187
+ [5] ■ risk 0.49 app.routes.create_report
188
+ └── → app.services.run_report line 20 import
189
+ └── → app.services.ReportRunner.start line 27 fuzzy
190
+ └── → app.services.ReportRunner.render_and_execute line 17 exact
191
+ └── → py:subprocess.run line 22 import ⚑ py.command-exec.subprocess
192
+ ```
193
+
194
+ - **CI gate**: exits `0` when a path is found, `1` when none —
195
+ `entrygraph paths --source '*' --sink-category command_exec && echo reachable`.
196
+ - **Precision/recall dial**: by default only high-confidence edges are traversed.
197
+ Widen with `--include-unresolved` (wildcard `py:*.execute` sinks + dynamic
198
+ calls) or `--include-fuzzy` (speculative class-hierarchy edges); drop
199
+ neutralized paths with `--prune-sanitized`.
200
+ - Target an exact sink with `--sink py:subprocess.run` instead of a category.
201
+
202
+ ### `stats` & `--json`
203
+
204
+ ```bash
205
+ entrygraph stats
206
+ entrygraph --help # every command and flag
207
+ ```
208
+
209
+ ```
210
+ ╭─────────────── index stats ────────────────╮
211
+ │ ┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ │
212
+ │ ┃metric ┃ value┃ │
213
+ │ ┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ │
214
+ │ │repo_root │ /path/to/acme-api │ │
215
+ │ │files │ 5│ │
216
+ │ │symbols │ 32│ │
217
+ │ │edges │ 34│ │
218
+ │ │entrypoints │ 5│ │
219
+ │ │sink_edges │ 2│ │
220
+ │ └─────────────────┴──────────────────────┘ │
221
+ ╰────────────────────────────────────────────╯
222
+ ```
223
+
224
+ Add `--json` to any query command for machine-readable output (paths include
225
+ `risk_score` and `may_continue`):
226
+
227
+ ```json
228
+ [
229
+ {
230
+ "risk_score": 0.4887,
231
+ "may_continue": false,
232
+ "symbols": [
233
+ "app.routes.create_report", "app.services.run_report",
234
+ "app.services.ReportRunner.start",
235
+ "app.services.ReportRunner.render_and_execute", "py:subprocess.run"
236
+ ],
237
+ "lines": [20, 27, 17, 22]
238
+ }
239
+ ]
240
+ ```
241
+
242
+ > Colored tables, share/confidence bars, and risk-tree highlighting render in a
243
+ > real terminal; piped or `--json` output is plain text.
244
+
245
+ ## Python API
246
+
247
+ ```python
248
+ from entrygraph import CodeGraph
249
+
250
+ # Index a repo (creates <repo>/.entrygraph.db by default)
251
+ graph = CodeGraph.index("/path/to/repo")
252
+
253
+ # ...or open an existing index
254
+ graph = CodeGraph.open("graph.db")
255
+
256
+ # Symbols — glob on name or qualified name, filter by kind or file
257
+ graph.symbols(kind="class", name="User*")
258
+ graph.symbol("app.services.Runner.execute") # exact; raises if missing
259
+
260
+ # Detection
261
+ report = graph.detect()
262
+ report.languages # -> [DetectedLanguage(name="python", percent=96.7, ...), ...]
263
+ report.frameworks # -> [DetectedFramework(name="flask", confidence=0.94, ...), ...]
264
+
265
+ # Entrypoints
266
+ graph.entrypoints(framework="flask")
267
+ graph.entrypoints(kind="http_route", route="/api/*")
268
+
269
+ # Call graph
270
+ graph.callers("app.services.run_report") # who calls it
271
+ graph.callees("app.services.run_report", depth=3) # what it (transitively) calls
272
+ graph.references("app.models.CONST") # inbound edges of any kind
273
+
274
+ # Source -> sink reachability (paths are risk-ranked, highest first)
275
+ paths = graph.paths(source="app.routes.*", sink_category="command_exec")
276
+ for p in paths:
277
+ print(p.risk_score, p.render(), "(+may continue)" if p.may_continue else "")
278
+ # 0.49 app.routes.create_report -> app.services.run_report (line 20)
279
+ # -> ...ReportRunner.render_and_execute (line 17) -> py:subprocess.run (line 22)
280
+
281
+ graph.reachable(source="app.routes.upload", sink="py:subprocess.run") # -> bool
282
+
283
+ # Precision/recall dial. By default only EXACT/IMPORT and unique-name FUZZY
284
+ # edges are traversed. Opt into wider (noisier) traversal:
285
+ graph.paths(source="app.routes.*", sink_category="sql",
286
+ include_unresolved=True) # follow py:*.execute wildcard-sink guesses
287
+ graph.paths(source="app.routes.*", sink_category="command_exec",
288
+ include_fuzzy=True) # follow speculative class-hierarchy (CHA) edges
289
+ graph.paths(source="app.routes.*", sink_category="command_exec",
290
+ prune_sanitized=True) # drop paths neutralized by a shlex.quote etc.
291
+
292
+ # Incremental re-index (only changed/added/deleted files are reparsed)
293
+ graph.refresh()
294
+
295
+ # Escape hatches
296
+ graph.session() # raw SQLAlchemy Session
297
+ graph.sql("SELECT ...") # textual query -> list[dict]
298
+ ```
299
+
300
+ Every result is a frozen, immutable dataclass detached from the DB session, so
301
+ results are safe to hold and trivial to serialize.
302
+
303
+ ## How it works
304
+
305
+ 1. **Walk** — `os.scandir` with hard-pruned junk dirs (`node_modules`, `.venv`,
306
+ …), `.gitignore` rules, and size/binary/minified gates. Every skip is recorded
307
+ with a reason.
308
+ 2. **Extract** — tree-sitter `.scm` queries harvest definitions/imports/calls;
309
+ small per-language "shaper" modules build qualified names, import maps, and
310
+ receiver info. Parsing runs across a process pool for large repos.
311
+ 3. **Resolve** — a two-pass resolver binds references to symbols with a
312
+ confidence level (`exact` / `import` / `fuzzy` / `unresolved`). External
313
+ callees (`subprocess.run`, `child_process.exec`, …) become placeholder nodes
314
+ so sinks are real graph terminals.
315
+ 4. **Detect** — frameworks are scored from manifest dependencies plus code
316
+ signals (noisy-or); entrypoint rules map framework patterns to route/command
317
+ records.
318
+ 5. **Store** — everything persists to SQLite via the SQLAlchemy 2.0 ORM with
319
+ bulk inserts and app-assigned keys. Re-indexing is incremental and
320
+ content-hash driven.
321
+ 6. **Query** — reachability runs over an in-memory adjacency cache (BFS/DFS with
322
+ cycle handling); a recursive-CTE SQL engine is available as a fallback
323
+ (`engine="sql"`).
324
+
325
+ ## Extending
326
+
327
+ - **Custom sinks/sources/sanitizers** — drop an `entrygraph.toml` in the repo
328
+ root with `[[sink]]` / `[[source]]` / `[[sanitizer]]` tables (same schema as
329
+ the built-in `data/sinks/*.toml`), or call
330
+ `entrygraph.detect.taint.register_sink(...)` / `register_sanitizer(...)`. A
331
+ `[[sanitizer]]` with `effect = "neutralizes"` prunes a path for its category;
332
+ `effect = "reduces"` only discounts the risk score. Third-party wrapper
333
+ libraries that reach a sink internally are covered by `data/sinks/lib_*.toml`
334
+ "library summaries" (same schema, with a `library = "..."` tag).
335
+ - **New frameworks / entrypoints** — register a `FrameworkSpec` and an
336
+ `EntrypointRule`; adding a framework is usually a few lines.
337
+ - **New languages** — add a `<lang>/{definitions,imports,calls}.scm` query set
338
+ and a shaper implementing the `LanguageExtractor` protocol.
339
+
340
+ ## Releasing
341
+
342
+ Merging to `main` auto-bumps the patch version (via a git tag) and publishes to
343
+ PyPI through Trusted Publishing — see [RELEASING.md](RELEASING.md). The package
344
+ version is derived from git tags by `hatch-vcs`, so it's never hand-edited.
345
+
346
+ ## License
347
+
348
+ MIT
@@ -0,0 +1,301 @@
1
+ # entrygraph
2
+
3
+ Query your codebase like a graph. `entrygraph` indexes a repository into a
4
+ SQLite database (through the SQLAlchemy ORM) and answers questions about
5
+ **symbols**, **classes/methods**, **entrypoints** (HTTP routes, CLI commands,
6
+ main functions, tasks, lambda handlers), and **source → sink call-graph
7
+ reachability** ("can any HTTP route reach `subprocess.run`?").
8
+
9
+ Language-agnostic via [tree-sitter](https://tree-sitter.github.io/); first-class
10
+ support for **Python, JavaScript/TypeScript, Go, Java, Ruby, C#, PHP, and
11
+ Rust**, with language *and* framework detection.
12
+
13
+ Reachability is a heuristic taint tier, not just call-edge closure: paths are
14
+ **risk-ranked**, **sanitizers** prune or discount them, **class-hierarchy
15
+ analysis** recovers virtual dispatch, and confidence flags trade recall for
16
+ precision. Entrypoints include decorator/attribute routes, call-based route
17
+ registration, middleware, and config-file handlers (serverless, SAM, Procfile,
18
+ Dockerfile).
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install entrygraph # or: uv pip install entrygraph
24
+ ```
25
+
26
+ Requires Python ≥ 3.11. Installs the `entrygraph` command (you can also run it as
27
+ `uv run entrygraph …` or `python -m entrygraph …`).
28
+
29
+ ## Quick start (CLI)
30
+
31
+ Index a repo once, then query the resulting `.entrygraph.db` as often as you
32
+ like. Every query command takes `--db PATH` (defaults to discovering
33
+ `.entrygraph.db`) and `--json` for machine-readable output.
34
+
35
+ ### `index` — build the graph
36
+
37
+ Walk the tree and extract symbols, imports, and calls into `.entrygraph.db`.
38
+ Incremental by default (only changed files are reparsed); `--full` rebuilds.
39
+
40
+ ```bash
41
+ entrygraph index .
42
+ ```
43
+
44
+ ```
45
+ ╭───────────────── ✓ indexed acme-api ──────────────────╮
46
+ │ files 5 indexed, 0 skipped, 0 deleted of 5 scanned │
47
+ │ graph 32 symbols 34 edges 5 entrypoints │
48
+ │ db /path/to/acme-api/.entrygraph.db │
49
+ ╰─────────────────────── 0.137s ────────────────────────╯
50
+ ```
51
+
52
+ ### `detect` — languages & frameworks
53
+
54
+ Byte-share per language plus framework detections scored from manifest
55
+ dependencies and code signals.
56
+
57
+ ```bash
58
+ entrygraph detect
59
+ ```
60
+
61
+ ```
62
+ Languages
63
+ ┏━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓
64
+ ┃LANGUAGE ┃ FILES ┃ SHARE ┃
65
+ ┡━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩
66
+ │python │ 5 │ ████████████ 100.0%│
67
+ └─────────┴───────┴────────────────────┘
68
+ Frameworks
69
+ ┏━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
70
+ ┃FRAMEWORK ┃ LANGUAGE ┃ CONFIDENCE ┃
71
+ ┡━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
72
+ │flask │ python │ █████████░ 0.94│
73
+ │click │ python │ █████████░ 0.94│
74
+ └──────────┴──────────┴────────────────┘
75
+ ```
76
+
77
+ ### `entrypoints` — your attack surface
78
+
79
+ Every HTTP route, CLI command, task, lambda, middleware, and `main` — with its
80
+ framework, method, route, and handler symbol. Filter with `--kind`,
81
+ `--framework`, or `--route`.
82
+
83
+ ```bash
84
+ entrypoints --kind http_route # or: --framework flask / --route '/api/*'
85
+ ```
86
+
87
+ ```
88
+ ┏━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┓
89
+ ┃KIND ┃ FRAMEWORK ┃ METHOD ┃ ROUTE ┃ HANDLER ┃
90
+ ┡━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━┩
91
+ │http_route │ flask │ GET │ /users/<user_id> │ app.routes.get_user │
92
+ │http_route │ flask │ GET │ /health │ app.routes.health │
93
+ │http_route │ flask │ GET,POST │ /reports │ app.routes.create_report│
94
+ │cli_command │ click │ │ │ cli.report │
95
+ │main │ │ │ │ cli │
96
+ └────────────┴───────────┴──────────┴──────────────────┴─────────────────────────┘
97
+ 5 entrypoint(s)
98
+ ```
99
+
100
+ ### `symbols`, `callers`, `callees` — search & walk the call graph
101
+
102
+ `symbols` globs on name or qualified name (filter by `--kind`/`--file`);
103
+ `callers`/`callees` walk the call graph (`--depth N`).
104
+
105
+ ```bash
106
+ entrygraph symbols --kind class --name 'Report*'
107
+ entrygraph callers app.services.run_report # who calls it
108
+ entrygraph callees app.services.run_report # what it calls
109
+ ```
110
+
111
+ ```
112
+ ┏━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━┓
113
+ ┃KIND ┃ QNAME ┃ FILE ┃ LINE┃
114
+ ┡━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━┩
115
+ │class │ app.services.ReportRunner │ app/services.py │ 10│
116
+ └──────┴───────────────────────────┴─────────────────┴─────┘
117
+ ```
118
+
119
+ ### `paths` — source → sink reachability
120
+
121
+ The security workhorse: *can anything reach a dangerous sink?* Paths are
122
+ **risk-ranked** (highest first) and drawn as call trees — the sink node is
123
+ flagged (`⚑`), each hop shows its resolution confidence
124
+ (`exact`/`import`/`fuzzy`/`unresolved`), and badges mark constant-argument sinks
125
+ and speculative edges.
126
+
127
+ ```bash
128
+ entrygraph paths --source '*' --sink-category command_exec
129
+ ```
130
+
131
+ ```
132
+ 7 path(s) * → category:command_exec
133
+
134
+ [1] ■ risk 0.73 app.services.ReportRunner.render_and_execute
135
+ └── → py:subprocess.run line 22 import ⚑ py.command-exec.subprocess
136
+ [4] ■ risk 0.50 app.services.run_report
137
+ └── → app.services.ReportRunner.start line 27 fuzzy
138
+ └── → app.services.ReportRunner.render_and_execute line 17 exact
139
+ └── → py:subprocess.run line 22 import ⚑ py.command-exec.subprocess
140
+ [5] ■ risk 0.49 app.routes.create_report
141
+ └── → app.services.run_report line 20 import
142
+ └── → app.services.ReportRunner.start line 27 fuzzy
143
+ └── → app.services.ReportRunner.render_and_execute line 17 exact
144
+ └── → py:subprocess.run line 22 import ⚑ py.command-exec.subprocess
145
+ ```
146
+
147
+ - **CI gate**: exits `0` when a path is found, `1` when none —
148
+ `entrygraph paths --source '*' --sink-category command_exec && echo reachable`.
149
+ - **Precision/recall dial**: by default only high-confidence edges are traversed.
150
+ Widen with `--include-unresolved` (wildcard `py:*.execute` sinks + dynamic
151
+ calls) or `--include-fuzzy` (speculative class-hierarchy edges); drop
152
+ neutralized paths with `--prune-sanitized`.
153
+ - Target an exact sink with `--sink py:subprocess.run` instead of a category.
154
+
155
+ ### `stats` & `--json`
156
+
157
+ ```bash
158
+ entrygraph stats
159
+ entrygraph --help # every command and flag
160
+ ```
161
+
162
+ ```
163
+ ╭─────────────── index stats ────────────────╮
164
+ │ ┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ │
165
+ │ ┃metric ┃ value┃ │
166
+ │ ┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ │
167
+ │ │repo_root │ /path/to/acme-api │ │
168
+ │ │files │ 5│ │
169
+ │ │symbols │ 32│ │
170
+ │ │edges │ 34│ │
171
+ │ │entrypoints │ 5│ │
172
+ │ │sink_edges │ 2│ │
173
+ │ └─────────────────┴──────────────────────┘ │
174
+ ╰────────────────────────────────────────────╯
175
+ ```
176
+
177
+ Add `--json` to any query command for machine-readable output (paths include
178
+ `risk_score` and `may_continue`):
179
+
180
+ ```json
181
+ [
182
+ {
183
+ "risk_score": 0.4887,
184
+ "may_continue": false,
185
+ "symbols": [
186
+ "app.routes.create_report", "app.services.run_report",
187
+ "app.services.ReportRunner.start",
188
+ "app.services.ReportRunner.render_and_execute", "py:subprocess.run"
189
+ ],
190
+ "lines": [20, 27, 17, 22]
191
+ }
192
+ ]
193
+ ```
194
+
195
+ > Colored tables, share/confidence bars, and risk-tree highlighting render in a
196
+ > real terminal; piped or `--json` output is plain text.
197
+
198
+ ## Python API
199
+
200
+ ```python
201
+ from entrygraph import CodeGraph
202
+
203
+ # Index a repo (creates <repo>/.entrygraph.db by default)
204
+ graph = CodeGraph.index("/path/to/repo")
205
+
206
+ # ...or open an existing index
207
+ graph = CodeGraph.open("graph.db")
208
+
209
+ # Symbols — glob on name or qualified name, filter by kind or file
210
+ graph.symbols(kind="class", name="User*")
211
+ graph.symbol("app.services.Runner.execute") # exact; raises if missing
212
+
213
+ # Detection
214
+ report = graph.detect()
215
+ report.languages # -> [DetectedLanguage(name="python", percent=96.7, ...), ...]
216
+ report.frameworks # -> [DetectedFramework(name="flask", confidence=0.94, ...), ...]
217
+
218
+ # Entrypoints
219
+ graph.entrypoints(framework="flask")
220
+ graph.entrypoints(kind="http_route", route="/api/*")
221
+
222
+ # Call graph
223
+ graph.callers("app.services.run_report") # who calls it
224
+ graph.callees("app.services.run_report", depth=3) # what it (transitively) calls
225
+ graph.references("app.models.CONST") # inbound edges of any kind
226
+
227
+ # Source -> sink reachability (paths are risk-ranked, highest first)
228
+ paths = graph.paths(source="app.routes.*", sink_category="command_exec")
229
+ for p in paths:
230
+ print(p.risk_score, p.render(), "(+may continue)" if p.may_continue else "")
231
+ # 0.49 app.routes.create_report -> app.services.run_report (line 20)
232
+ # -> ...ReportRunner.render_and_execute (line 17) -> py:subprocess.run (line 22)
233
+
234
+ graph.reachable(source="app.routes.upload", sink="py:subprocess.run") # -> bool
235
+
236
+ # Precision/recall dial. By default only EXACT/IMPORT and unique-name FUZZY
237
+ # edges are traversed. Opt into wider (noisier) traversal:
238
+ graph.paths(source="app.routes.*", sink_category="sql",
239
+ include_unresolved=True) # follow py:*.execute wildcard-sink guesses
240
+ graph.paths(source="app.routes.*", sink_category="command_exec",
241
+ include_fuzzy=True) # follow speculative class-hierarchy (CHA) edges
242
+ graph.paths(source="app.routes.*", sink_category="command_exec",
243
+ prune_sanitized=True) # drop paths neutralized by a shlex.quote etc.
244
+
245
+ # Incremental re-index (only changed/added/deleted files are reparsed)
246
+ graph.refresh()
247
+
248
+ # Escape hatches
249
+ graph.session() # raw SQLAlchemy Session
250
+ graph.sql("SELECT ...") # textual query -> list[dict]
251
+ ```
252
+
253
+ Every result is a frozen, immutable dataclass detached from the DB session, so
254
+ results are safe to hold and trivial to serialize.
255
+
256
+ ## How it works
257
+
258
+ 1. **Walk** — `os.scandir` with hard-pruned junk dirs (`node_modules`, `.venv`,
259
+ …), `.gitignore` rules, and size/binary/minified gates. Every skip is recorded
260
+ with a reason.
261
+ 2. **Extract** — tree-sitter `.scm` queries harvest definitions/imports/calls;
262
+ small per-language "shaper" modules build qualified names, import maps, and
263
+ receiver info. Parsing runs across a process pool for large repos.
264
+ 3. **Resolve** — a two-pass resolver binds references to symbols with a
265
+ confidence level (`exact` / `import` / `fuzzy` / `unresolved`). External
266
+ callees (`subprocess.run`, `child_process.exec`, …) become placeholder nodes
267
+ so sinks are real graph terminals.
268
+ 4. **Detect** — frameworks are scored from manifest dependencies plus code
269
+ signals (noisy-or); entrypoint rules map framework patterns to route/command
270
+ records.
271
+ 5. **Store** — everything persists to SQLite via the SQLAlchemy 2.0 ORM with
272
+ bulk inserts and app-assigned keys. Re-indexing is incremental and
273
+ content-hash driven.
274
+ 6. **Query** — reachability runs over an in-memory adjacency cache (BFS/DFS with
275
+ cycle handling); a recursive-CTE SQL engine is available as a fallback
276
+ (`engine="sql"`).
277
+
278
+ ## Extending
279
+
280
+ - **Custom sinks/sources/sanitizers** — drop an `entrygraph.toml` in the repo
281
+ root with `[[sink]]` / `[[source]]` / `[[sanitizer]]` tables (same schema as
282
+ the built-in `data/sinks/*.toml`), or call
283
+ `entrygraph.detect.taint.register_sink(...)` / `register_sanitizer(...)`. A
284
+ `[[sanitizer]]` with `effect = "neutralizes"` prunes a path for its category;
285
+ `effect = "reduces"` only discounts the risk score. Third-party wrapper
286
+ libraries that reach a sink internally are covered by `data/sinks/lib_*.toml`
287
+ "library summaries" (same schema, with a `library = "..."` tag).
288
+ - **New frameworks / entrypoints** — register a `FrameworkSpec` and an
289
+ `EntrypointRule`; adding a framework is usually a few lines.
290
+ - **New languages** — add a `<lang>/{definitions,imports,calls}.scm` query set
291
+ and a shaper implementing the `LanguageExtractor` protocol.
292
+
293
+ ## Releasing
294
+
295
+ Merging to `main` auto-bumps the patch version (via a git tag) and publishes to
296
+ PyPI through Trusted Publishing — see [RELEASING.md](RELEASING.md). The package
297
+ version is derived from git tags by `hatch-vcs`, so it's never hand-edited.
298
+
299
+ ## License
300
+
301
+ MIT
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.1.0'
22
- __version_tuple__ = version_tuple = (0, 1, 0)
21
+ __version__ = version = '0.1.1'
22
+ __version_tuple__ = version_tuple = (0, 1, 1)
23
23
 
24
24
  __commit_id__ = commit_id = None