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