entrygraph 0.1.2__tar.gz → 0.1.3__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.3/.github/workflows/ci.yml +77 -0
  2. {entrygraph-0.1.2 → entrygraph-0.1.3}/PKG-INFO +11 -6
  3. {entrygraph-0.1.2 → entrygraph-0.1.3}/README.md +5 -5
  4. {entrygraph-0.1.2 → entrygraph-0.1.3}/RELEASING.md +13 -13
  5. {entrygraph-0.1.2 → entrygraph-0.1.3}/pyproject.toml +44 -1
  6. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/_version.py +2 -2
  7. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/api.py +71 -45
  8. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/cli/main.py +75 -33
  9. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/cli/render.py +29 -11
  10. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/db/meta.py +2 -2
  11. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/db/models.py +6 -5
  12. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/detect/entrypoints/base.py +2 -3
  13. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/detect/entrypoints/configs.py +31 -10
  14. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/detect/entrypoints/csharp.py +1 -3
  15. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/detect/entrypoints/golang.py +45 -26
  16. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/detect/entrypoints/java.py +53 -35
  17. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/detect/entrypoints/javascript.py +89 -22
  18. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/detect/entrypoints/php.py +19 -12
  19. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/detect/entrypoints/python.py +159 -63
  20. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/detect/entrypoints/ruby.py +42 -24
  21. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/detect/entrypoints/rust.py +27 -16
  22. entrygraph-0.1.3/src/entrygraph/detect/frameworks.py +593 -0
  23. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/detect/manifests.py +13 -9
  24. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/detect/taint.py +2 -2
  25. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/extract/base.py +3 -3
  26. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/extract/csharp.py +28 -48
  27. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/extract/golang.py +33 -21
  28. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/extract/java.py +70 -37
  29. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/extract/javascript.py +151 -55
  30. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/extract/php.py +101 -85
  31. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/extract/python.py +58 -27
  32. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/extract/registry.py +0 -1
  33. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/extract/ruby.py +11 -11
  34. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/extract/rust.py +30 -28
  35. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/fs/hashing.py +1 -5
  36. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/fs/lang.py +1 -2
  37. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/fs/walker.py +27 -8
  38. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/graph/adjacency.py +19 -6
  39. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/graph/cte.py +11 -4
  40. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/pipeline/scanner.py +122 -60
  41. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/pipeline/worker.py +5 -4
  42. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/pipeline/writer.py +3 -3
  43. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/resolve/externals.py +1 -1
  44. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/resolve/hierarchy.py +8 -3
  45. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/resolve/resolver.py +43 -11
  46. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/python/flask_app/app/routes.py +1 -1
  47. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/python/flask_app/cli.py +0 -1
  48. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_api.py +2 -2
  49. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_cli.py +19 -9
  50. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_entrypoint_expansion.py +97 -37
  51. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_entrypoints.py +1 -1
  52. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_extract_csharp.py +7 -8
  53. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_extract_go.py +3 -2
  54. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_extract_java.py +32 -18
  55. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_extract_javascript.py +12 -10
  56. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_extract_php.py +12 -29
  57. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_extract_python.py +7 -8
  58. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_extract_ruby.py +21 -19
  59. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_extract_rust.py +8 -5
  60. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_frameworks.py +16 -7
  61. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_hardening.py +0 -2
  62. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_incremental.py +13 -12
  63. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_indexer.py +29 -14
  64. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_manifests.py +11 -6
  65. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_models.py +85 -27
  66. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_reachability.py +18 -22
  67. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_render.py +1 -2
  68. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_resolver.py +46 -18
  69. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_scoring.py +53 -19
  70. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_taint.py +6 -3
  71. {entrygraph-0.1.2 → entrygraph-0.1.3}/uv.lock +232 -0
  72. entrygraph-0.1.2/.github/workflows/ci.yml +0 -27
  73. entrygraph-0.1.2/src/entrygraph/detect/frameworks.py +0 -369
  74. {entrygraph-0.1.2 → entrygraph-0.1.3}/.github/workflows/release.yml +0 -0
  75. {entrygraph-0.1.2 → entrygraph-0.1.3}/.gitignore +0 -0
  76. {entrygraph-0.1.2 → entrygraph-0.1.3}/LICENSE +0 -0
  77. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/__init__.py +0 -0
  78. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/__main__.py +0 -0
  79. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/cli/__init__.py +0 -0
  80. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/data/sinks/csharp.toml +0 -0
  81. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/data/sinks/go.toml +0 -0
  82. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/data/sinks/java.toml +0 -0
  83. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/data/sinks/javascript.toml +0 -0
  84. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/data/sinks/lib_javascript.toml +0 -0
  85. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/data/sinks/lib_python.toml +0 -0
  86. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/data/sinks/php.toml +0 -0
  87. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/data/sinks/python.toml +0 -0
  88. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/data/sinks/ruby.toml +0 -0
  89. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/data/sinks/rust.toml +0 -0
  90. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/db/__init__.py +0 -0
  91. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/db/engine.py +0 -0
  92. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/db/queries.py +0 -0
  93. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/detect/__init__.py +0 -0
  94. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/detect/entrypoints/__init__.py +0 -0
  95. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/errors.py +0 -0
  96. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/extract/__init__.py +0 -0
  97. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/extract/ir.py +0 -0
  98. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/fs/__init__.py +0 -0
  99. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/graph/__init__.py +0 -0
  100. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/graph/scoring.py +0 -0
  101. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/kinds.py +0 -0
  102. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/parsing/__init__.py +0 -0
  103. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/parsing/parsers.py +0 -0
  104. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/parsing/queries.py +0 -0
  105. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/pipeline/__init__.py +0 -0
  106. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/py.typed +0 -0
  107. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/csharp/calls.scm +0 -0
  108. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/csharp/definitions.scm +0 -0
  109. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/csharp/imports.scm +0 -0
  110. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/go/calls.scm +0 -0
  111. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/go/definitions.scm +0 -0
  112. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/go/imports.scm +0 -0
  113. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/java/calls.scm +0 -0
  114. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/java/definitions.scm +0 -0
  115. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/java/imports.scm +0 -0
  116. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/javascript/calls.scm +0 -0
  117. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/javascript/definitions.scm +0 -0
  118. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/javascript/imports.scm +0 -0
  119. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/php/calls.scm +0 -0
  120. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/php/definitions.scm +0 -0
  121. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/php/imports.scm +0 -0
  122. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/python/calls.scm +0 -0
  123. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/python/definitions.scm +0 -0
  124. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/python/imports.scm +0 -0
  125. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/ruby/calls.scm +0 -0
  126. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/ruby/definitions.scm +0 -0
  127. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/ruby/imports.scm +0 -0
  128. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/rust/calls.scm +0 -0
  129. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/rust/definitions.scm +0 -0
  130. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/queries/rust/imports.scm +0 -0
  131. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/resolve/__init__.py +0 -0
  132. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/resolve/symbol_table.py +0 -0
  133. {entrygraph-0.1.2 → entrygraph-0.1.3}/src/entrygraph/results.py +0 -0
  134. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/conftest.py +0 -0
  135. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/csharp/aspnet_app/Controllers/ReportsController.cs +0 -0
  136. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/csharp/aspnet_app/Program.cs +0 -0
  137. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/csharp/aspnet_app/Services/ReportService.cs +0 -0
  138. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/csharp/aspnet_app/app.csproj +0 -0
  139. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/go/gin_app/go.mod +0 -0
  140. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/go/gin_app/main.go +0 -0
  141. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/go/gin_app/service.go +0 -0
  142. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/java/spring_app/pom.xml +0 -0
  143. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/java/spring_app/src/main/java/com/example/Application.java +0 -0
  144. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/java/spring_app/src/main/java/com/example/ReportRunner.java +0 -0
  145. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/java/spring_app/src/main/java/com/example/ReportService.java +0 -0
  146. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/java/spring_app/src/main/java/com/example/UserController.java +0 -0
  147. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/javascript/express_app/package.json +0 -0
  148. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/javascript/express_app/src/routes.js +0 -0
  149. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/javascript/express_app/src/services.js +0 -0
  150. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/php/laravel_app/app/Http/Controllers/ReportController.php +0 -0
  151. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/php/laravel_app/artisan +0 -0
  152. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/php/laravel_app/composer.json +0 -0
  153. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/php/laravel_app/routes/web.php +0 -0
  154. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/python/flask_app/app/__init__.py +0 -0
  155. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/python/flask_app/app/db.py +0 -0
  156. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/python/flask_app/app/services.py +0 -0
  157. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/python/flask_app/requirements.txt +0 -0
  158. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/ruby/sinatra_app/Gemfile +0 -0
  159. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/ruby/sinatra_app/app.rb +0 -0
  160. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/ruby/sinatra_app/services/runner.rb +0 -0
  161. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/rust/axum_app/Cargo.toml +0 -0
  162. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/rust/axum_app/src/handlers.rs +0 -0
  163. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/fixtures/rust/axum_app/src/main.rs +0 -0
  164. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_engine_pragmas.py +0 -0
  165. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_lang.py +0 -0
  166. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_sink_catalog.py +0 -0
  167. {entrygraph-0.1.2 → entrygraph-0.1.3}/tests/test_walker.py +0 -0
@@ -0,0 +1,77 @@
1
+ name: CI
2
+
3
+ # One job per quality gate. main is covered by release.yml's own test gate, so
4
+ # it's excluded here to avoid duplicate runs.
5
+ on:
6
+ pull_request: {}
7
+ push:
8
+ branches-ignore: [main]
9
+
10
+ concurrency:
11
+ group: ci-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ format:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: astral-sh/setup-uv@v5
20
+ with:
21
+ python-version: "3.13"
22
+ - name: ruff format --check
23
+ run: uv run --extra dev ruff format --check .
24
+
25
+ lint:
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ - uses: astral-sh/setup-uv@v5
30
+ with:
31
+ python-version: "3.13"
32
+ - name: ruff check
33
+ run: uv run --extra dev ruff check .
34
+
35
+ typecheck:
36
+ runs-on: ubuntu-latest
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+ - uses: astral-sh/setup-uv@v5
40
+ with:
41
+ python-version: "3.13"
42
+ - name: mypy
43
+ run: uv run --extra dev mypy
44
+
45
+ test:
46
+ runs-on: ubuntu-latest
47
+ strategy:
48
+ fail-fast: false
49
+ matrix:
50
+ python-version: ["3.13", "3.14"]
51
+ steps:
52
+ - uses: actions/checkout@v4
53
+ - uses: astral-sh/setup-uv@v5
54
+ with:
55
+ python-version: ${{ matrix.python-version }}
56
+ - name: pytest + coverage
57
+ run: uv run --extra dev pytest -q --cov=entrygraph --cov-report=term-missing
58
+
59
+ security:
60
+ runs-on: ubuntu-latest
61
+ steps:
62
+ - uses: actions/checkout@v4
63
+ - uses: astral-sh/setup-uv@v5
64
+ with:
65
+ python-version: "3.13"
66
+ - name: bandit
67
+ run: uv run --extra dev bandit -c pyproject.toml -r src/entrygraph -q
68
+
69
+ mdformat:
70
+ runs-on: ubuntu-latest
71
+ steps:
72
+ - uses: actions/checkout@v4
73
+ - uses: astral-sh/setup-uv@v5
74
+ with:
75
+ python-version: "3.13"
76
+ - name: mdformat --check
77
+ run: uv run --extra dev mdformat --check $(git ls-files '*.md')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: entrygraph
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Language-agnostic code graph: query symbols, entrypoints, and source-to-sink call paths from a SQLite index
5
5
  Project-URL: Repository, https://github.com/brettbergin/entrygraph
6
6
  Author-email: Brett Bergin <brettberginbc@yahoo.com>
@@ -40,8 +40,13 @@ Requires-Dist: sqlalchemy<3.0,>=2.0.30
40
40
  Requires-Dist: tree-sitter-language-pack>=1.10
41
41
  Requires-Dist: tree-sitter<0.26,>=0.25
42
42
  Provides-Extra: dev
43
+ Requires-Dist: bandit>=1.7; extra == 'dev'
44
+ Requires-Dist: mdformat-gfm>=0.3; extra == 'dev'
45
+ Requires-Dist: mdformat>=0.7; extra == 'dev'
46
+ Requires-Dist: mypy<2,>=1.11; extra == 'dev'
43
47
  Requires-Dist: pytest-cov; extra == 'dev'
44
48
  Requires-Dist: pytest>=8.0; extra == 'dev'
49
+ Requires-Dist: ruff>=0.15; extra == 'dev'
45
50
  Description-Content-Type: text/markdown
46
51
 
47
52
  # entrygraph
@@ -304,20 +309,20 @@ results are safe to hold and trivial to serialize.
304
309
  1. **Walk** — `os.scandir` with hard-pruned junk dirs (`node_modules`, `.venv`,
305
310
  …), `.gitignore` rules, and size/binary/minified gates. Every skip is recorded
306
311
  with a reason.
307
- 2. **Extract** — tree-sitter `.scm` queries harvest definitions/imports/calls;
312
+ 1. **Extract** — tree-sitter `.scm` queries harvest definitions/imports/calls;
308
313
  small per-language "shaper" modules build qualified names, import maps, and
309
314
  receiver info. Parsing runs across a process pool for large repos.
310
- 3. **Resolve** — a two-pass resolver binds references to symbols with a
315
+ 1. **Resolve** — a two-pass resolver binds references to symbols with a
311
316
  confidence level (`exact` / `import` / `fuzzy` / `unresolved`). External
312
317
  callees (`subprocess.run`, `child_process.exec`, …) become placeholder nodes
313
318
  so sinks are real graph terminals.
314
- 4. **Detect** — frameworks are scored from manifest dependencies plus code
319
+ 1. **Detect** — frameworks are scored from manifest dependencies plus code
315
320
  signals (noisy-or); entrypoint rules map framework patterns to route/command
316
321
  records.
317
- 5. **Store** — everything persists to SQLite via the SQLAlchemy 2.0 ORM with
322
+ 1. **Store** — everything persists to SQLite via the SQLAlchemy 2.0 ORM with
318
323
  bulk inserts and app-assigned keys. Re-indexing is incremental and
319
324
  content-hash driven.
320
- 6. **Query** — reachability runs over an in-memory adjacency cache (BFS/DFS with
325
+ 1. **Query** — reachability runs over an in-memory adjacency cache (BFS/DFS with
321
326
  cycle handling); a recursive-CTE SQL engine is available as a fallback
322
327
  (`engine="sql"`).
323
328
 
@@ -258,20 +258,20 @@ results are safe to hold and trivial to serialize.
258
258
  1. **Walk** — `os.scandir` with hard-pruned junk dirs (`node_modules`, `.venv`,
259
259
  …), `.gitignore` rules, and size/binary/minified gates. Every skip is recorded
260
260
  with a reason.
261
- 2. **Extract** — tree-sitter `.scm` queries harvest definitions/imports/calls;
261
+ 1. **Extract** — tree-sitter `.scm` queries harvest definitions/imports/calls;
262
262
  small per-language "shaper" modules build qualified names, import maps, and
263
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
264
+ 1. **Resolve** — a two-pass resolver binds references to symbols with a
265
265
  confidence level (`exact` / `import` / `fuzzy` / `unresolved`). External
266
266
  callees (`subprocess.run`, `child_process.exec`, …) become placeholder nodes
267
267
  so sinks are real graph terminals.
268
- 4. **Detect** — frameworks are scored from manifest dependencies plus code
268
+ 1. **Detect** — frameworks are scored from manifest dependencies plus code
269
269
  signals (noisy-or); entrypoint rules map framework patterns to route/command
270
270
  records.
271
- 5. **Store** — everything persists to SQLite via the SQLAlchemy 2.0 ORM with
271
+ 1. **Store** — everything persists to SQLite via the SQLAlchemy 2.0 ORM with
272
272
  bulk inserts and app-assigned keys. Re-indexing is incremental and
273
273
  content-hash driven.
274
- 6. **Query** — reachability runs over an in-memory adjacency cache (BFS/DFS with
274
+ 1. **Query** — reachability runs over an in-memory adjacency cache (BFS/DFS with
275
275
  cycle handling); a recursive-CTE SQL engine is available as a fallback
276
276
  (`engine="sql"`).
277
277
 
@@ -7,12 +7,12 @@ PyPI — no manual version edits, no tokens.
7
7
  ## How it works
8
8
 
9
9
  1. A merge lands on `main` → [`.github/workflows/release.yml`](.github/workflows/release.yml) runs.
10
- 2. The test suite must pass (gate).
11
- 3. The workflow finds the latest `vX.Y.Z` tag and computes the next **patch**
10
+ 1. The test suite must pass (gate).
11
+ 1. The workflow finds the latest `vX.Y.Z` tag and computes the next **patch**
12
12
  version (`v0.1.4` → `v0.1.5`). With no tags yet, the first release is `v0.1.0`.
13
- 4. It creates and pushes that tag. [`hatch-vcs`](https://github.com/ofek/hatch-vcs)
13
+ 1. It creates and pushes that tag. [`hatch-vcs`](https://github.com/ofek/hatch-vcs)
14
14
  derives the package version from the tag, so nothing is committed back to `main`.
15
- 5. `uv build` produces the sdist + wheel, which are published to PyPI via
15
+ 1. `uv build` produces the sdist + wheel, which are published to PyPI via
16
16
  **Trusted Publishing (OIDC)** and attached to a GitHub Release.
17
17
 
18
18
  Pull requests are tested separately by [`.github/workflows/ci.yml`](.github/workflows/ci.yml)
@@ -26,15 +26,15 @@ This lets GitHub Actions publish without an API token. Because the project
26
26
  doesn't exist on PyPI yet, add it as a **pending** publisher:
27
27
 
28
28
  1. Log in to PyPI → <https://pypi.org/manage/account/publishing/>.
29
- 2. Under "Add a new pending publisher", enter:
30
- | Field | Value |
31
- | --- | --- |
32
- | PyPI Project Name | `entrygraph` |
33
- | Owner | `brettbergin` |
34
- | Repository name | `entrygraph` |
35
- | Workflow name | `release.yml` |
36
- | Environment name | *(leave blank)* |
37
- 3. Save. The first successful run creates the project and binds the publisher.
29
+ 1. Under "Add a new pending publisher", enter:
30
+ | Field | Value |
31
+ | ----------------- | --------------- |
32
+ | PyPI Project Name | `entrygraph` |
33
+ | Owner | `brettbergin` |
34
+ | Repository name | `entrygraph` |
35
+ | Workflow name | `release.yml` |
36
+ | Environment name | *(leave blank)* |
37
+ 1. Save. The first successful run creates the project and binds the publisher.
38
38
 
39
39
  > The PyPI project name `entrygraph` must be available (or already owned by you).
40
40
  > If it's taken, rename the project in `pyproject.toml` before the first release.
@@ -28,7 +28,17 @@ dependencies = [
28
28
  ]
29
29
 
30
30
  [project.optional-dependencies]
31
- dev = ["pytest>=8.0", "pytest-cov"]
31
+ dev = [
32
+ "pytest>=8.0",
33
+ "pytest-cov",
34
+ "ruff>=0.15",
35
+ # mypy 2.x needs pathspec>=1.0 (imports pathspec.patterns.gitignore), which
36
+ # conflicts with our runtime pin pathspec<1.0; 1.x is compatible.
37
+ "mypy>=1.11,<2",
38
+ "bandit>=1.7",
39
+ "mdformat>=0.7",
40
+ "mdformat-gfm>=0.3",
41
+ ]
32
42
 
33
43
  [project.scripts]
34
44
  entrygraph = "entrygraph.cli.main:main"
@@ -54,3 +64,36 @@ packages = ["src/entrygraph"]
54
64
  [tool.pytest.ini_options]
55
65
  testpaths = ["tests"]
56
66
  markers = ["slow: performance guardrail tests (deselect with '-m \"not slow\"')"]
67
+
68
+ [tool.ruff]
69
+ line-length = 100
70
+ target-version = "py313"
71
+ extend-exclude = ["src/entrygraph/_version.py"]
72
+
73
+ [tool.ruff.lint]
74
+ # pyflakes, pycodestyle, isort, pyupgrade, bugbear, comprehensions, simplify
75
+ select = ["F", "E", "W", "I", "UP", "B", "C4", "SIM"]
76
+ ignore = [
77
+ "SIM105", # contextlib.suppress over try/except/pass — try/except is clearer here
78
+ "B905", # zip() without strict= — fine for equal-length paired iterables
79
+ ]
80
+
81
+ [tool.ruff.lint.per-file-ignores]
82
+ "tests/*" = ["B011", "E402", "SIM"] # tests: allow assert False, late imports, verbose forms
83
+ # B023: the DFS helper closes over loop-scoped stack/on_path but is invoked
84
+ # synchronously within the same iteration, so the late-binding concern is moot.
85
+ "src/entrygraph/graph/adjacency.py" = ["B023"]
86
+
87
+ [tool.mypy]
88
+ python_version = "3.13"
89
+ mypy_path = "src"
90
+ packages = ["entrygraph"]
91
+ ignore_missing_imports = true # tree-sitter, sqlalchemy internals, etc. ship partial stubs
92
+ exclude = ["_version\\.py$"]
93
+
94
+ [tool.bandit]
95
+ # Scan library code only; tests/fixtures contain deliberately vulnerable sample apps.
96
+ exclude_dirs = ["tests", "src/entrygraph/_version.py"]
97
+ # B314/B405: ElementTree parses local project manifests (pom.xml/.csproj) the user is
98
+ # already indexing — not attacker-controlled network XML, so XXE risk is not applicable.
99
+ skips = ["B314", "B405"]
@@ -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.2'
22
- __version_tuple__ = version_tuple = (0, 1, 2)
21
+ __version__ = version = '0.1.3'
22
+ __version_tuple__ = version_tuple = (0, 1, 3)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -8,8 +8,8 @@ frozen dataclasses; the Session never escapes (except via the explicit
8
8
  from __future__ import annotations
9
9
 
10
10
  import json
11
+ from collections.abc import Iterator
11
12
  from pathlib import Path
12
- from typing import Iterator
13
13
 
14
14
  from sqlalchemy import Engine, func, select, text
15
15
  from sqlalchemy.orm import Session
@@ -23,7 +23,7 @@ from entrygraph.errors import (
23
23
  RepositoryNotIndexedError,
24
24
  SymbolNotFoundError,
25
25
  )
26
- from entrygraph.graph.adjacency import AdjacencyCache, Hop
26
+ from entrygraph.graph.adjacency import AdjacencyCache
27
27
  from entrygraph.graph.scoring import is_constant_args, score_path
28
28
  from entrygraph.kinds import Confidence
29
29
  from entrygraph.results import (
@@ -42,7 +42,7 @@ from entrygraph.results import (
42
42
 
43
43
  DEFAULT_DB_NAME = ".entrygraph.db"
44
44
 
45
- SourceSpec = "str | Symbol | Entrypoint | list[str | Symbol | Entrypoint]"
45
+ type SourceSpec = str | Symbol | Entrypoint | list[str | Symbol | Entrypoint]
46
46
 
47
47
 
48
48
  def _traversal_params(
@@ -76,11 +76,12 @@ class CodeGraph:
76
76
  self._engine = engine
77
77
  self._session_factory = make_session_factory(engine)
78
78
  self._adjacency: dict[tuple[frozenset[str], int], AdjacencyCache] = {}
79
+ self._last_index_stats: IndexStats | None = None
79
80
 
80
81
  # ---------------- construction ----------------
81
82
 
82
83
  @classmethod
83
- def index(cls, root: str | Path, db: str | Path | None = None) -> "CodeGraph":
84
+ def index(cls, root: str | Path, db: str | Path | None = None) -> CodeGraph:
84
85
  """Index (or fully re-index) a repository and return an open graph."""
85
86
  from entrygraph.pipeline.scanner import index_repository
86
87
 
@@ -92,7 +93,7 @@ class CodeGraph:
92
93
  return graph
93
94
 
94
95
  @classmethod
95
- def open(cls, db: str | Path) -> "CodeGraph":
96
+ def open(cls, db: str | Path) -> CodeGraph:
96
97
  db_path = Path(db)
97
98
  if not db_path.exists():
98
99
  raise DatabaseNotFoundError(f"no index database at {db_path}")
@@ -103,7 +104,7 @@ class CodeGraph:
103
104
  def close(self) -> None:
104
105
  self._engine.dispose()
105
106
 
106
- def __enter__(self) -> "CodeGraph":
107
+ def __enter__(self) -> CodeGraph:
107
108
  return self
108
109
 
109
110
  def __exit__(self, *exc) -> None:
@@ -124,8 +125,14 @@ class CodeGraph:
124
125
  ) -> list[Symbol]:
125
126
  with self._session_factory() as session:
126
127
  return q.select_symbols(
127
- session, kind=kind, name=name, qname=qname, file=file,
128
- include_external=include_external, limit=limit, offset=offset,
128
+ session,
129
+ kind=kind,
130
+ name=name,
131
+ qname=qname,
132
+ file=file,
133
+ include_external=include_external,
134
+ limit=limit,
135
+ offset=offset,
129
136
  )
130
137
 
131
138
  def symbol(self, qname: str) -> Symbol:
@@ -152,9 +159,13 @@ class CodeGraph:
152
159
 
153
160
  def detect(self) -> DetectionReport:
154
161
  with self._session_factory() as session:
155
- rows = session.execute(
156
- select(models.Detection).order_by(models.Detection.confidence.desc())
157
- ).scalars().all()
162
+ rows = (
163
+ session.execute(
164
+ select(models.Detection).order_by(models.Detection.confidence.desc())
165
+ )
166
+ .scalars()
167
+ .all()
168
+ )
158
169
  languages = []
159
170
  frameworks = []
160
171
  for row in rows:
@@ -177,7 +188,7 @@ class CodeGraph:
177
188
  evidence=tuple(evidence.get("signals", [])),
178
189
  )
179
190
  )
180
- languages.sort(key=lambda l: l.byte_count, reverse=True)
191
+ languages.sort(key=lambda lang: lang.byte_count, reverse=True)
181
192
  return DetectionReport(languages=tuple(languages), frameworks=tuple(frameworks))
182
193
 
183
194
  # ---------------- entrypoints ----------------
@@ -197,23 +208,27 @@ class CodeGraph:
197
208
 
198
209
  # ---------------- traversal ----------------
199
210
 
200
- def callers(self, target: "SourceSpec", *, depth: int = 1,
201
- edge_kinds: tuple[str, ...] = ("calls",)) -> list[Symbol]:
211
+ def callers(
212
+ self, target: SourceSpec, *, depth: int = 1, edge_kinds: tuple[str, ...] = ("calls",)
213
+ ) -> list[Symbol]:
202
214
  return self._neighbors(target, depth, "in", edge_kinds)
203
215
 
204
- def callees(self, target: "SourceSpec", *, depth: int = 1,
205
- edge_kinds: tuple[str, ...] = ("calls",)) -> list[Symbol]:
216
+ def callees(
217
+ self, target: SourceSpec, *, depth: int = 1, edge_kinds: tuple[str, ...] = ("calls",)
218
+ ) -> list[Symbol]:
206
219
  return self._neighbors(target, depth, "out", edge_kinds)
207
220
 
208
- def references(self, target: "SourceSpec") -> list[Edge]:
221
+ def references(self, target: SourceSpec) -> list[Edge]:
209
222
  """All inbound edges (any kind) to the matching symbols."""
210
223
  with self._session_factory() as session:
211
224
  ids = self._spec_to_ids(session, target)
212
225
  if not ids:
213
226
  return []
214
- rows = session.execute(
215
- select(models.Edge).where(models.Edge.dst_symbol_id.in_(ids))
216
- ).scalars().all()
227
+ rows = (
228
+ session.execute(select(models.Edge).where(models.Edge.dst_symbol_id.in_(ids)))
229
+ .scalars()
230
+ .all()
231
+ )
217
232
  src_map = q.symbols_by_ids(session, {r.src_symbol_id for r in rows})
218
233
  return [
219
234
  Edge(
@@ -236,8 +251,8 @@ class CodeGraph:
236
251
  def paths(
237
252
  self,
238
253
  *,
239
- source: "SourceSpec",
240
- sink: "SourceSpec | None" = None,
254
+ source: SourceSpec,
255
+ sink: SourceSpec | None = None,
241
256
  sink_category: str | None = None,
242
257
  max_depth: int = 25,
243
258
  max_paths: int = 10,
@@ -282,15 +297,16 @@ class CodeGraph:
282
297
  for path in raw_paths
283
298
  ]
284
299
  results = [cp for cp in built if not (prune_sanitized and _fully_sanitized(cp))]
285
- results.sort(key=lambda p: (-(p.risk_score or 0.0), len(p.symbols),
286
- [s.id for s in p.symbols]))
300
+ results.sort(
301
+ key=lambda p: (-(p.risk_score or 0.0), len(p.symbols), [s.id for s in p.symbols])
302
+ )
287
303
  return results
288
304
 
289
305
  def reachable(
290
306
  self,
291
307
  *,
292
- source: "SourceSpec",
293
- sink: "SourceSpec | None" = None,
308
+ source: SourceSpec,
309
+ sink: SourceSpec | None = None,
294
310
  sink_category: str | None = None,
295
311
  max_depth: int = 25,
296
312
  edge_kinds: tuple[str, ...] = ("calls",),
@@ -320,8 +336,7 @@ class CodeGraph:
320
336
  repo = session.execute(select(models.Repository)).scalars().first()
321
337
  if repo is None:
322
338
  raise RepositoryNotIndexedError("database has no indexed repository")
323
- stats = index_repository(repo.root_path, self._engine, incremental=True,
324
- paranoid=paranoid)
339
+ stats = index_repository(repo.root_path, self._engine, incremental=True, paranoid=paranoid)
325
340
  self._adjacency.clear()
326
341
  return stats
327
342
 
@@ -341,9 +356,7 @@ class CodeGraph:
341
356
  symbols=count(select(func.count(models.Symbol.id))),
342
357
  edges=count(select(func.count(models.Edge.id))),
343
358
  resolved_edges=count(
344
- select(func.count(models.Edge.id)).where(
345
- models.Edge.dst_symbol_id.is_not(None)
346
- )
359
+ select(func.count(models.Edge.id)).where(models.Edge.dst_symbol_id.is_not(None))
347
360
  ),
348
361
  entrypoints=count(select(func.count(models.Entrypoint.id))),
349
362
  sink_edges=count(
@@ -366,8 +379,14 @@ class CodeGraph:
366
379
  gen = session.execute(select(models.Repository.index_generation)).scalar()
367
380
  return gen or 0
368
381
 
369
- def _traverser(self, session: Session, engine: str, edge_kinds: tuple[str, ...],
370
- min_confidence: int, include_cha: bool = True):
382
+ def _traverser(
383
+ self,
384
+ session: Session,
385
+ engine: str,
386
+ edge_kinds: tuple[str, ...],
387
+ min_confidence: int,
388
+ include_cha: bool = True,
389
+ ):
371
390
  if engine == "sql":
372
391
  from entrygraph.graph.cte import CteEngine
373
392
 
@@ -376,8 +395,13 @@ class CodeGraph:
376
395
  raise ValueError(f"unknown reachability engine {engine!r} (use 'memory' or 'sql')")
377
396
  return self._cache(session, edge_kinds, min_confidence, include_cha)
378
397
 
379
- def _cache(self, session: Session, edge_kinds: tuple[str, ...],
380
- min_confidence: int, include_cha: bool = True) -> AdjacencyCache:
398
+ def _cache(
399
+ self,
400
+ session: Session,
401
+ edge_kinds: tuple[str, ...],
402
+ min_confidence: int,
403
+ include_cha: bool = True,
404
+ ) -> AdjacencyCache:
381
405
  kinds = frozenset(edge_kinds)
382
406
  generation = self._generation(session)
383
407
  key = (kinds | {f"minconf:{min_confidence}", f"cha:{include_cha}"}, generation)
@@ -417,11 +441,12 @@ class CodeGraph:
417
441
  models.Edge.dst_symbol_id.is_not(None),
418
442
  )
419
443
  ).scalars()
420
- ids |= set(rows)
444
+ ids |= {r for r in rows if r is not None}
421
445
  return ids
422
446
 
423
- def _neighbors(self, target, depth: int, direction: str,
424
- edge_kinds: tuple[str, ...]) -> list[Symbol]:
447
+ def _neighbors(
448
+ self, target, depth: int, direction: str, edge_kinds: tuple[str, ...]
449
+ ) -> list[Symbol]:
425
450
  with self._session_factory() as session:
426
451
  ids = self._spec_to_ids(session, target)
427
452
  if not ids:
@@ -438,13 +463,13 @@ class CodeGraph:
438
463
  return registry_for_repo(repo.root_path if repo else None)
439
464
 
440
465
  @staticmethod
441
- def _edge_rows(session: Session, raw_paths) -> dict[int, "models.Edge"]:
466
+ def _edge_rows(session: Session, raw_paths) -> dict[int, models.Edge]:
442
467
  edge_ids = {hop.edge_id for path in raw_paths for _, hop in path if hop and hop.edge_id}
443
468
  if not edge_ids:
444
469
  return {}
445
- rows = session.execute(
446
- select(models.Edge).where(models.Edge.id.in_(edge_ids))
447
- ).scalars().all()
470
+ rows = (
471
+ session.execute(select(models.Edge).where(models.Edge.id.in_(edge_ids))).scalars().all()
472
+ )
448
473
  return {r.id: r for r in rows}
449
474
 
450
475
  @staticmethod
@@ -522,15 +547,16 @@ class CodeGraph:
522
547
 
523
548
  risk = score_path(
524
549
  hop_confidences=[h.confidence for h in hops],
525
- hop_vias=[(rows[i].via if rows[i] else h.via) for i, h in enumerate(hops)],
550
+ hop_vias=[(row.via if row is not None else h.via) for row, h in zip(rows, hops)],
526
551
  sink_severity=registry.severity_of(sink_id),
527
552
  sanitized_effect=sanitized_effect,
528
553
  constant_args=terminal_const,
529
554
  source_tainted=path[0][0] in tainted_sources,
530
555
  )
531
556
  may_continue = any(node in excluded_nodes for node, _ in path)
532
- return CallPath(symbols=symbols, edges=tuple(path_edges),
533
- risk_score=risk, may_continue=may_continue)
557
+ return CallPath(
558
+ symbols=symbols, edges=tuple(path_edges), risk_score=risk, may_continue=may_continue
559
+ )
534
560
 
535
561
  @staticmethod
536
562
  def _path_sanitizers(symbols, rows, registry, sink_category):