entrygraph 0.1.4__tar.gz → 0.1.5__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 (166) hide show
  1. {entrygraph-0.1.4 → entrygraph-0.1.5}/PKG-INFO +1 -1
  2. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/_version.py +2 -2
  3. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/detect/entrypoints/python.py +25 -14
  4. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/detect/frameworks.py +10 -0
  5. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/detect/manifests.py +3 -1
  6. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/graph/scoring.py +1 -1
  7. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/pipeline/scanner.py +3 -2
  8. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/resolve/resolver.py +4 -1
  9. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_entrypoint_expansion.py +11 -0
  10. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_frameworks.py +12 -0
  11. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_manifests.py +11 -0
  12. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_resolver.py +32 -0
  13. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_scoring.py +9 -0
  14. {entrygraph-0.1.4 → entrygraph-0.1.5}/.github/workflows/ci.yml +0 -0
  15. {entrygraph-0.1.4 → entrygraph-0.1.5}/.github/workflows/release.yml +0 -0
  16. {entrygraph-0.1.4 → entrygraph-0.1.5}/.gitignore +0 -0
  17. {entrygraph-0.1.4 → entrygraph-0.1.5}/LICENSE +0 -0
  18. {entrygraph-0.1.4 → entrygraph-0.1.5}/README.md +0 -0
  19. {entrygraph-0.1.4 → entrygraph-0.1.5}/RELEASING.md +0 -0
  20. {entrygraph-0.1.4 → entrygraph-0.1.5}/pyproject.toml +0 -0
  21. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/__init__.py +0 -0
  22. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/__main__.py +0 -0
  23. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/api.py +0 -0
  24. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/cli/__init__.py +0 -0
  25. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/cli/main.py +0 -0
  26. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/cli/render.py +0 -0
  27. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/data/sinks/csharp.toml +0 -0
  28. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/data/sinks/go.toml +0 -0
  29. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/data/sinks/java.toml +0 -0
  30. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/data/sinks/javascript.toml +0 -0
  31. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/data/sinks/lib_javascript.toml +0 -0
  32. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/data/sinks/lib_python.toml +0 -0
  33. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/data/sinks/php.toml +0 -0
  34. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/data/sinks/python.toml +0 -0
  35. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/data/sinks/ruby.toml +0 -0
  36. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/data/sinks/rust.toml +0 -0
  37. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/db/__init__.py +0 -0
  38. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/db/engine.py +0 -0
  39. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/db/meta.py +0 -0
  40. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/db/models.py +0 -0
  41. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/db/queries.py +0 -0
  42. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/detect/__init__.py +0 -0
  43. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/detect/entrypoints/__init__.py +0 -0
  44. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/detect/entrypoints/base.py +0 -0
  45. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/detect/entrypoints/configs.py +0 -0
  46. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/detect/entrypoints/csharp.py +0 -0
  47. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/detect/entrypoints/golang.py +0 -0
  48. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/detect/entrypoints/java.py +0 -0
  49. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/detect/entrypoints/javascript.py +0 -0
  50. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/detect/entrypoints/php.py +0 -0
  51. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/detect/entrypoints/ruby.py +0 -0
  52. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/detect/entrypoints/rust.py +0 -0
  53. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/detect/taint.py +0 -0
  54. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/errors.py +0 -0
  55. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/extract/__init__.py +0 -0
  56. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/extract/base.py +0 -0
  57. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/extract/csharp.py +0 -0
  58. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/extract/golang.py +0 -0
  59. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/extract/ir.py +0 -0
  60. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/extract/java.py +0 -0
  61. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/extract/javascript.py +0 -0
  62. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/extract/php.py +0 -0
  63. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/extract/python.py +0 -0
  64. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/extract/registry.py +0 -0
  65. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/extract/ruby.py +0 -0
  66. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/extract/rust.py +0 -0
  67. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/fs/__init__.py +0 -0
  68. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/fs/hashing.py +0 -0
  69. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/fs/lang.py +0 -0
  70. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/fs/walker.py +0 -0
  71. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/graph/__init__.py +0 -0
  72. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/graph/adjacency.py +0 -0
  73. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/graph/cte.py +0 -0
  74. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/kinds.py +0 -0
  75. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/parsing/__init__.py +0 -0
  76. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/parsing/parsers.py +0 -0
  77. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/parsing/queries.py +0 -0
  78. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/pipeline/__init__.py +0 -0
  79. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/pipeline/worker.py +0 -0
  80. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/pipeline/writer.py +0 -0
  81. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/py.typed +0 -0
  82. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/csharp/calls.scm +0 -0
  83. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/csharp/definitions.scm +0 -0
  84. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/csharp/imports.scm +0 -0
  85. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/go/calls.scm +0 -0
  86. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/go/definitions.scm +0 -0
  87. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/go/imports.scm +0 -0
  88. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/java/calls.scm +0 -0
  89. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/java/definitions.scm +0 -0
  90. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/java/imports.scm +0 -0
  91. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/javascript/calls.scm +0 -0
  92. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/javascript/definitions.scm +0 -0
  93. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/javascript/imports.scm +0 -0
  94. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/php/calls.scm +0 -0
  95. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/php/definitions.scm +0 -0
  96. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/php/imports.scm +0 -0
  97. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/python/calls.scm +0 -0
  98. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/python/definitions.scm +0 -0
  99. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/python/imports.scm +0 -0
  100. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/ruby/calls.scm +0 -0
  101. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/ruby/definitions.scm +0 -0
  102. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/ruby/imports.scm +0 -0
  103. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/rust/calls.scm +0 -0
  104. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/rust/definitions.scm +0 -0
  105. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/queries/rust/imports.scm +0 -0
  106. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/resolve/__init__.py +0 -0
  107. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/resolve/externals.py +0 -0
  108. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/resolve/hierarchy.py +0 -0
  109. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/resolve/symbol_table.py +0 -0
  110. {entrygraph-0.1.4 → entrygraph-0.1.5}/src/entrygraph/results.py +0 -0
  111. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/conftest.py +0 -0
  112. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/csharp/aspnet_app/Controllers/ReportsController.cs +0 -0
  113. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/csharp/aspnet_app/Program.cs +0 -0
  114. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/csharp/aspnet_app/Services/ReportService.cs +0 -0
  115. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/csharp/aspnet_app/app.csproj +0 -0
  116. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/go/gin_app/go.mod +0 -0
  117. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/go/gin_app/main.go +0 -0
  118. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/go/gin_app/service.go +0 -0
  119. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/java/spring_app/pom.xml +0 -0
  120. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/java/spring_app/src/main/java/com/example/Application.java +0 -0
  121. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/java/spring_app/src/main/java/com/example/ReportRunner.java +0 -0
  122. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/java/spring_app/src/main/java/com/example/ReportService.java +0 -0
  123. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/java/spring_app/src/main/java/com/example/UserController.java +0 -0
  124. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/javascript/express_app/package.json +0 -0
  125. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/javascript/express_app/src/routes.js +0 -0
  126. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/javascript/express_app/src/services.js +0 -0
  127. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/php/laravel_app/app/Http/Controllers/ReportController.php +0 -0
  128. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/php/laravel_app/artisan +0 -0
  129. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/php/laravel_app/composer.json +0 -0
  130. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/php/laravel_app/routes/web.php +0 -0
  131. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/python/flask_app/app/__init__.py +0 -0
  132. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/python/flask_app/app/db.py +0 -0
  133. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/python/flask_app/app/routes.py +0 -0
  134. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/python/flask_app/app/services.py +0 -0
  135. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/python/flask_app/cli.py +0 -0
  136. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/python/flask_app/requirements.txt +0 -0
  137. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/ruby/sinatra_app/Gemfile +0 -0
  138. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/ruby/sinatra_app/app.rb +0 -0
  139. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/ruby/sinatra_app/services/runner.rb +0 -0
  140. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/rust/axum_app/Cargo.toml +0 -0
  141. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/rust/axum_app/src/handlers.rs +0 -0
  142. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/fixtures/rust/axum_app/src/main.rs +0 -0
  143. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_api.py +0 -0
  144. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_cli.py +0 -0
  145. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_engine_pragmas.py +0 -0
  146. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_entrypoints.py +0 -0
  147. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_extract_csharp.py +0 -0
  148. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_extract_go.py +0 -0
  149. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_extract_java.py +0 -0
  150. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_extract_javascript.py +0 -0
  151. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_extract_php.py +0 -0
  152. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_extract_python.py +0 -0
  153. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_extract_ruby.py +0 -0
  154. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_extract_rust.py +0 -0
  155. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_hardening.py +0 -0
  156. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_incremental.py +0 -0
  157. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_indexer.py +0 -0
  158. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_lang.py +0 -0
  159. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_models.py +0 -0
  160. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_pool.py +0 -0
  161. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_reachability.py +0 -0
  162. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_render.py +0 -0
  163. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_sink_catalog.py +0 -0
  164. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_taint.py +0 -0
  165. {entrygraph-0.1.4 → entrygraph-0.1.5}/tests/test_walker.py +0 -0
  166. {entrygraph-0.1.4 → entrygraph-0.1.5}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: entrygraph
3
- Version: 0.1.4
3
+ Version: 0.1.5
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>
@@ -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.4'
22
- __version_tuple__ = version_tuple = (0, 1, 4)
21
+ __version__ = version = '0.1.5'
22
+ __version_tuple__ = version_tuple = (0, 1, 5)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -7,6 +7,7 @@ import re
7
7
 
8
8
  from entrygraph.detect.entrypoints.base import (
9
9
  EntrypointRule,
10
+ Matcher,
10
11
  first_string_arg,
11
12
  identifier_args,
12
13
  methods_kwarg,
@@ -157,20 +158,30 @@ def _django_urls(x: FileExtraction) -> list[EntrypointHint]:
157
158
  return hints
158
159
 
159
160
 
160
- def _click_commands(x: FileExtraction) -> list[EntrypointHint]:
161
- hints = []
162
- for symbol, decorator in _decorated(x):
163
- if _CLICK_CMD.match(decorator):
164
- hints.append(
165
- EntrypointHint(
166
- rule_id="python.click.command",
167
- kind=EntrypointKind.CLI_COMMAND,
168
- handler_qualified_name=symbol.qualified_name,
169
- name=first_string_arg(decorator) or symbol.name,
170
- framework="click",
161
+ def _click_like(framework: str, rule_id: str) -> Matcher:
162
+ """click and typer share decorator shape (`@app.command`); only the
163
+ reported framework/rule_id differ, so bind them per rule."""
164
+
165
+ def _match(x: FileExtraction) -> list[EntrypointHint]:
166
+ hints = []
167
+ for symbol, decorator in _decorated(x):
168
+ if _CLICK_CMD.match(decorator):
169
+ hints.append(
170
+ EntrypointHint(
171
+ rule_id=rule_id,
172
+ kind=EntrypointKind.CLI_COMMAND,
173
+ handler_qualified_name=symbol.qualified_name,
174
+ name=first_string_arg(decorator) or symbol.name,
175
+ framework=framework,
176
+ )
171
177
  )
172
- )
173
- return hints
178
+ return hints
179
+
180
+ return _match
181
+
182
+
183
+ _click_commands = _click_like("click", "python.click.command")
184
+ _typer_commands = _click_like("typer", "python.typer.command")
174
185
 
175
186
 
176
187
  def _celery_tasks(x: FileExtraction) -> list[EntrypointHint]:
@@ -363,7 +374,7 @@ register(
363
374
  )
364
375
  register(
365
376
  EntrypointRule(
366
- "python.typer.command", "python", "typer", EntrypointKind.CLI_COMMAND, _click_commands
377
+ "python.typer.command", "python", "typer", EntrypointKind.CLI_COMMAND, _typer_commands
367
378
  )
368
379
  )
369
380
  register(
@@ -104,6 +104,16 @@ FRAMEWORKS: list[FrameworkSpec] = [
104
104
  ),
105
105
  ),
106
106
  # JS/TS, Go, Java, Ruby specs land with their extractors (M8)
107
+ FrameworkSpec(
108
+ "aws-lambda-js",
109
+ "javascript",
110
+ (
111
+ FrameworkSignal("file_presence", "serverless.y*ml", 0.5),
112
+ FrameworkSignal("file_presence", "template.y*ml", 0.3),
113
+ FrameworkSignal("manifest_dep", "@types/aws-lambda", 0.6),
114
+ FrameworkSignal("manifest_dep", "aws-lambda", 0.5),
115
+ ),
116
+ ),
107
117
  FrameworkSpec(
108
118
  "express",
109
119
  "javascript",
@@ -223,9 +223,11 @@ def parse_manifests(root: str | Path) -> ManifestDeps:
223
223
  if not manifest.is_file():
224
224
  continue
225
225
  if any(
226
- part in ("node_modules", "vendor", ".venv", "venv", "target", "packages")
226
+ part in ("node_modules", "vendor", ".venv", "venv", "target")
227
227
  for part in manifest.parts
228
228
  ):
229
+ # note: "packages" is intentionally NOT excluded — it is the
230
+ # standard JS workspace layout (packages/<name>/package.json).
229
231
  continue
230
232
  try:
231
233
  text = manifest.read_text(encoding="utf-8", errors="replace")
@@ -33,7 +33,7 @@ _LENGTH_DECAY = 0.97
33
33
  _CONST_TOKEN = re.compile(
34
34
  r"""^(
35
35
  \s | , | = | \( | \) | \[ | \] | \{ | \} | : |
36
- '[^']*' | "[^"]*" | `[^`]*` |
36
+ '[^']*' | "[^"]*" | `(?:[^`$]|\$(?!\{))*` | # backtick: no ${...} interpolation
37
37
  \d[\d_.eExXaAbBcCdDfF]* |
38
38
  True|False|None|null|true|false|nil |
39
39
  [A-Za-z_]\w*\s*= # kwarg name before '='
@@ -305,12 +305,13 @@ def _write_files(
305
305
  for path, fid in session.execute(select(File.path, File.id).where(File.repo_id == repo.id)):
306
306
  file_id_by_path[path] = fid
307
307
 
308
- reindexed = {w.path for w in diff.to_index} | {
308
+ to_index_paths = {w.path for w in diff.to_index}
309
+ reindexed = to_index_paths | {
309
310
  w.path for w in walked if w.skip_reason and w.path not in file_id_by_path
310
311
  }
311
312
  new_rows = []
312
313
  for wf in walked:
313
- if wf.path in file_id_by_path and wf.path not in {w.path for w in diff.to_index}:
314
+ if wf.path in file_id_by_path and wf.path not in to_index_paths:
314
315
  continue # unchanged; row already present
315
316
  if wf.path not in reindexed and not wf.skip_reason:
316
317
  continue
@@ -71,6 +71,7 @@ class FileResolver:
71
71
  self.module_symbol_id = module_symbol_id
72
72
  self.table = table
73
73
  self.externals = externals
74
+ self.is_package = is_package
74
75
  self.prefix = LANG_PREFIX.get(extraction.language, extraction.language)
75
76
  self.import_map, self.wildcard_modules = build_import_map(extraction, is_package)
76
77
 
@@ -122,7 +123,9 @@ class FileResolver:
122
123
  seen: set[str] = set()
123
124
  for imp in self.x.imports:
124
125
  module = (
125
- expand_relative(imp, self.x.module_path, False) if imp.is_relative else imp.module
126
+ expand_relative(imp, self.x.module_path, self.is_package)
127
+ if imp.is_relative
128
+ else imp.module
126
129
  )
127
130
  if not module or module in seen:
128
131
  continue
@@ -160,6 +160,17 @@ def test_lambda_js_handler_rule():
160
160
  assert hints and hints[0].kind is EntrypointKind.LAMBDA_HANDLER
161
161
 
162
162
 
163
+ def test_typer_command_reports_typer_not_click():
164
+ # typer reuses click's decorator shape; the hint must carry framework/rule_id
165
+ # for typer, not be mislabeled as click.
166
+ sym = _sym("serve", "cli.serve")
167
+ sym.decorators = ["@app.command()"]
168
+ hints = _run("python.typer.command", _extraction([sym]), frameworks=frozenset({"typer"}))
169
+ assert hints
170
+ assert hints[0].framework == "typer"
171
+ assert hints[0].rule_id == "python.typer.command"
172
+
173
+
163
174
  def test_sidekiq_worker_rule():
164
175
  from entrygraph.detect.entrypoints import rules_for
165
176
 
@@ -33,6 +33,18 @@ def test_language_gating():
33
33
  assert not any(d.name == "rails" for d in detected)
34
34
 
35
35
 
36
+ def test_aws_lambda_js_detected_from_serverless_config():
37
+ # the javascript.aws-lambda.handler entrypoint rule is gated on this spec;
38
+ # regression: the spec was missing, so the rule could never fire.
39
+ detected = detect_frameworks(
40
+ ManifestDeps(javascript={"@types/aws-lambda"}),
41
+ import_signals=set(),
42
+ file_paths=["serverless.yml"],
43
+ languages_present={"javascript"},
44
+ )
45
+ assert any(d.name == "aws-lambda-js" for d in detected)
46
+
47
+
36
48
  def test_typescript_counts_for_js_frameworks():
37
49
  manifests = ManifestDeps(javascript={"express"})
38
50
  detected = detect_frameworks(
@@ -78,6 +78,17 @@ def test_parse_manifests_fixture():
78
78
  assert "requirements.txt" in deps.sources
79
79
 
80
80
 
81
+ def test_parse_manifests_reads_js_workspace_packages(tmp_path: Path):
82
+ # `packages/<name>/package.json` is the standard JS workspace layout and must
83
+ # not be excluded (regression: "packages" was in the skip list).
84
+ pkg = tmp_path / "packages" / "api"
85
+ pkg.mkdir(parents=True)
86
+ (pkg / "package.json").write_text('{"dependencies": {"express": "^4"}}')
87
+ deps = parse_manifests(tmp_path)
88
+ assert "express" in deps.javascript
89
+ assert "packages/api/package.json" in deps.sources
90
+
91
+
81
92
  # ---------------- C1: C#, PHP, Rust manifests ----------------
82
93
  from entrygraph.detect.manifests import ( # noqa: E402
83
94
  parse_cargo_toml,
@@ -57,6 +57,38 @@ def test_import_based_project_resolution():
57
57
  assert call.confidence is Confidence.IMPORT
58
58
 
59
59
 
60
+ def test_relative_import_from_package_init_resolves_submodule():
61
+ # `from .sub import thing` in the package's own __init__.py (module_path
62
+ # "app", is_package=True) must resolve to the project module "app.sub".
63
+ # Regression: is_package was hardcoded False, dropping "app" and yielding a
64
+ # bogus ".sub" external placeholder.
65
+ table = make_table()
66
+ table.add_module("app.sub", 20)
67
+ x = FileExtraction(
68
+ path="app/__init__.py",
69
+ language="python",
70
+ module_path="app",
71
+ parse_ok=True,
72
+ error_count=0,
73
+ imports=[
74
+ RawImport(
75
+ module="sub",
76
+ imported_name="thing",
77
+ alias="thing",
78
+ span=SPAN,
79
+ is_relative=True,
80
+ relative_level=1,
81
+ )
82
+ ],
83
+ )
84
+ externals = ExternalRegistry(iter(range(100, 200)).__next__)
85
+ resolver = FileResolver(x, 1, table, externals, is_package=True)
86
+ edges = resolver.resolve()
87
+ imp = next(e for e in edges if e.kind is EdgeKind.IMPORTS)
88
+ assert imp.dst_qname == "app.sub"
89
+ assert imp.dst_symbol_id == 20
90
+
91
+
60
92
  def test_external_import_creates_placeholder():
61
93
  table = make_table()
62
94
  imports = [RawImport(module="subprocess", imported_name=None, alias="sub", span=SPAN)]
@@ -17,6 +17,15 @@ def test_is_constant_args():
17
17
  assert not is_constant_args("(a, b, c, d, e, f, ...")
18
18
 
19
19
 
20
+ def test_js_template_literal_interpolation_is_not_constant():
21
+ # a plain backtick template with no interpolation is a literal
22
+ assert is_constant_args("(`ls -la`)")
23
+ # `${...}` carries a variable into the sink -> the archetypal JS command
24
+ # injection must NOT be scored as constant/safe
25
+ assert not is_constant_args("(`ls ${req.query.dir}`)")
26
+ assert not is_constant_args("(`${userInput}`)")
27
+
28
+
20
29
  def test_score_path_ordering_by_severity():
21
30
  common = {
22
31
  "hop_confidences": [int(Confidence.EXACT)],
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes