cg-code-graph 0.20.0__tar.gz → 0.21.0__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 (375) hide show
  1. {cg_code_graph-0.20.0/cg_code_graph.egg-info → cg_code_graph-0.21.0}/PKG-INFO +2 -1
  2. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/README.md +1 -0
  3. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/__init__.py +1 -1
  4. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/blindspots.py +107 -0
  5. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/cli.py +49 -0
  6. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/config.py +42 -2
  7. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/core/model.py +1 -0
  8. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/core/plugin.py +3 -0
  9. cg_code_graph-0.21.0/cg_code_graph/core/redact.py +158 -0
  10. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/coverage.py +49 -14
  11. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/external.py +18 -4
  12. cg_code_graph-0.21.0/cg_code_graph/guard_facts.py +440 -0
  13. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/hooks.py +2 -1
  14. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/indexer.py +33 -2
  15. cg_code_graph-0.21.0/cg_code_graph/insecure_transport.py +513 -0
  16. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/link.py +96 -2
  17. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/local_sockets.py +4 -2
  18. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/mcp_server.py +30 -1
  19. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/payload.py +5 -3
  20. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/django/plugin.py +16 -1
  21. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/express/plugin.py +13 -1
  22. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/laravel/plugin.py +4 -2
  23. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/laravel/values.py +86 -24
  24. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/php/inline_guards.py +19 -0
  25. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/ts/extractor/extract.mjs +222 -10
  26. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/ts/extractor/fw.mjs +18 -1
  27. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/ts/plugin.py +4 -2
  28. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/express.yaml +6 -0
  29. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/query.py +8 -2
  30. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/routes.py +87 -3
  31. cg_code_graph-0.21.0/cg_code_graph/sdk_systems.py +2701 -0
  32. cg_code_graph-0.21.0/cg_code_graph/ssrf_input.py +854 -0
  33. cg_code_graph-0.21.0/cg_code_graph/surface.py +990 -0
  34. cg_code_graph-0.21.0/cg_code_graph/webhook_subs.py +557 -0
  35. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/webhooks.py +126 -11
  36. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0/cg_code_graph.egg-info}/PKG-INFO +2 -1
  37. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph.egg-info/SOURCES.txt +17 -0
  38. cg_code_graph-0.21.0/tests/test_echo_wrapper.py +99 -0
  39. cg_code_graph-0.21.0/tests/test_fp_batch.py +293 -0
  40. cg_code_graph-0.21.0/tests/test_redact_salt.py +97 -0
  41. cg_code_graph-0.21.0/tests/test_sdk_external_infra.py +279 -0
  42. cg_code_graph-0.21.0/tests/test_sdk_external_mail.py +167 -0
  43. cg_code_graph-0.21.0/tests/test_ssrf.py +424 -0
  44. cg_code_graph-0.21.0/tests/test_surface.py +703 -0
  45. cg_code_graph-0.21.0/tests/test_surface_transport.py +530 -0
  46. cg_code_graph-0.21.0/tests/test_ts_node_http.py +100 -0
  47. cg_code_graph-0.21.0/tests/test_unguarded.py +204 -0
  48. cg_code_graph-0.21.0/tests/test_webhooks_152b.py +194 -0
  49. cg_code_graph-0.20.0/cg_code_graph/sdk_systems.py +0 -963
  50. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/LICENSE +0 -0
  51. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/MANIFEST.in +0 -0
  52. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/__main__.py +0 -0
  53. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/affected.py +0 -0
  54. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/agent_rules.py +0 -0
  55. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/agents.py +0 -0
  56. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/aitools.py +0 -0
  57. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/android_ipc.py +0 -0
  58. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/apple_ipc.py +0 -0
  59. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/apps.py +0 -0
  60. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/bridges.py +0 -0
  61. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/brokers.py +0 -0
  62. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/concepts.py +0 -0
  63. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/core/__init__.py +0 -0
  64. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/core/cache.py +0 -0
  65. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/core/detect.py +0 -0
  66. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/core/env.py +0 -0
  67. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/core/extractors.py +0 -0
  68. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/core/fsutil.py +0 -0
  69. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/core/generated.py +0 -0
  70. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/core/paths.py +0 -0
  71. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/core/store.py +0 -0
  72. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/core/syntax_errors.py +0 -0
  73. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/dart_isolates.py +0 -0
  74. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/doctor.py +0 -0
  75. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/explore.py +0 -0
  76. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/graphql.py +0 -0
  77. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/jobs.py +0 -0
  78. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/lint_async.py +0 -0
  79. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/local_ipc.py +0 -0
  80. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/mcp_install.py +0 -0
  81. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/parity.py +0 -0
  82. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/parity_structure.py +0 -0
  83. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plans.py +0 -0
  84. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/platform_scan.py +0 -0
  85. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/platforms.py +0 -0
  86. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/__init__.py +0 -0
  87. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/astro/__init__.py +0 -0
  88. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/astro/content.py +0 -0
  89. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/astro/plugin.py +0 -0
  90. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/cfamily/__init__.py +0 -0
  91. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/cfamily/plugin.py +0 -0
  92. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/cfamily/syntax.py +0 -0
  93. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/dart/__init__.py +0 -0
  94. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/dart/bridges.py +0 -0
  95. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/dart/extractor/bin/extract.dart +0 -0
  96. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/dart/extractor/pubspec.lock +0 -0
  97. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/dart/extractor/pubspec.yaml +0 -0
  98. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/dart/http.py +0 -0
  99. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/dart/models.py +0 -0
  100. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/dart/plugin.py +0 -0
  101. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/dart/program.py +0 -0
  102. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/django/__init__.py +0 -0
  103. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/django/extras.py +0 -0
  104. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/django/models.py +0 -0
  105. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/django/schemas.py +0 -0
  106. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/django/shapes.py +0 -0
  107. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/django/urls.py +0 -0
  108. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/express/__init__.py +0 -0
  109. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/flutter/__init__.py +0 -0
  110. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/flutter/plugin.py +0 -0
  111. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/java/__init__.py +0 -0
  112. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/java/exact.py +0 -0
  113. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/java/plugin.py +0 -0
  114. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/jvm/__init__.py +0 -0
  115. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/jvm/interop.py +0 -0
  116. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/jvm/spring.py +0 -0
  117. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/kotlin/__init__.py +0 -0
  118. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/kotlin/exact.py +0 -0
  119. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/kotlin/plugin.py +0 -0
  120. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/kotlin/reparse.py +0 -0
  121. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/laravel/__init__.py +0 -0
  122. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/laravel/broadcast.py +0 -0
  123. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/laravel/tests.py +0 -0
  124. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/native/__init__.py +0 -0
  125. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/native/gates.py +0 -0
  126. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/native/runner.py +0 -0
  127. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/native/scipread.py +0 -0
  128. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/native/ts.py +0 -0
  129. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/nest/__init__.py +0 -0
  130. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/nest/plugin.py +0 -0
  131. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/nextjs/__init__.py +0 -0
  132. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/nextjs/plugin.py +0 -0
  133. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/nuxt/__init__.py +0 -0
  134. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/nuxt/plugin.py +0 -0
  135. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/php/__init__.py +0 -0
  136. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/php/external.py +0 -0
  137. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/php/extractor/composer.json +0 -0
  138. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/php/extractor/composer.lock +0 -0
  139. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/php/extractor/extract.php +0 -0
  140. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/php/gating.py +0 -0
  141. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/php/http_client.py +0 -0
  142. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/php/plugin.py +0 -0
  143. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/php/strings.py +0 -0
  144. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/python/__init__.py +0 -0
  145. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/python/aitools.py +0 -0
  146. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/python/external.py +0 -0
  147. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/python/fields.py +0 -0
  148. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/python/plugin.py +0 -0
  149. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/python/refs.py +0 -0
  150. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/python/roots.py +0 -0
  151. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/python/socketio.py +0 -0
  152. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/python/subproc.py +0 -0
  153. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/python/tests.py +0 -0
  154. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/python/values.py +0 -0
  155. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/pyweb/__init__.py +0 -0
  156. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/pyweb/plugin.py +0 -0
  157. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/pyweb/values.py +0 -0
  158. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/rust/__init__.py +0 -0
  159. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/rust/cargo.py +0 -0
  160. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/rust/plugin.py +0 -0
  161. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/rust/syntax.py +0 -0
  162. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/scip/__init__.py +0 -0
  163. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/scip/importer.py +0 -0
  164. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/scip/scip.proto +0 -0
  165. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/scip/scip_pb2.py +0 -0
  166. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/stubs/__init__.py +0 -0
  167. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/stubs/plugins.py +0 -0
  168. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/swift/__init__.py +0 -0
  169. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/swift/baseurl.py +0 -0
  170. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/swift/exact.py +0 -0
  171. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/swift/indexstore.py +0 -0
  172. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/swift/packages.py +0 -0
  173. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/swift/plugin.py +0 -0
  174. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/ts/__init__.py +0 -0
  175. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/ts/baseurl.py +0 -0
  176. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/ts/extractor/package-lock.json +0 -0
  177. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/ts/extractor/package.json +0 -0
  178. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/ts/extractor/rr.mjs +0 -0
  179. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/ts/nav.py +0 -0
  180. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/ts/react_router.py +0 -0
  181. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/tsweb/__init__.py +0 -0
  182. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/tsweb/common.py +0 -0
  183. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/plugins/tsweb/data.py +0 -0
  184. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/__init__.py +0 -0
  185. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/c_cpp.yaml +0 -0
  186. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/common.yaml +0 -0
  187. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/dart.yaml +0 -0
  188. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/django-ninja.yaml +0 -0
  189. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/django.yaml +0 -0
  190. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/djangorestframework.yaml +0 -0
  191. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/java.yaml +0 -0
  192. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/kotlin.yaml +0 -0
  193. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/laravel.yaml +0 -0
  194. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/nest.yaml +0 -0
  195. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/nextjs.yaml +0 -0
  196. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/nuxt.yaml +0 -0
  197. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/php.yaml +0 -0
  198. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/python.yaml +0 -0
  199. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/react-router.yaml +0 -0
  200. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/rust.yaml +0 -0
  201. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/spring.yaml +0 -0
  202. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/swift.yaml +0 -0
  203. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/presets/typescript.yaml +0 -0
  204. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/process_runs.py +0 -0
  205. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/protocols/__init__.py +0 -0
  206. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/protocols/builtin.py +0 -0
  207. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/protocols/matchers.py +0 -0
  208. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/protocols/view.py +0 -0
  209. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/realtime.py +0 -0
  210. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/realtime_events.py +0 -0
  211. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/realtime_native.py +0 -0
  212. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/realtime_ws.py +0 -0
  213. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/roundtrip.py +0 -0
  214. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/rpc.py +0 -0
  215. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/sockets.py +0 -0
  216. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/starters.py +0 -0
  217. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/tests_index.py +0 -0
  218. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/udp_apps.py +0 -0
  219. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/viz/__init__.py +0 -0
  220. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/viz/graph.py +0 -0
  221. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/viz/server.py +0 -0
  222. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/viz/static/app.css +0 -0
  223. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/viz/static/app.js +0 -0
  224. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/viz/static/index.html +0 -0
  225. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/viz/static/layered.js +0 -0
  226. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/viz/static/vendor/VERSIONS.txt +0 -0
  227. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/viz/static/vendor/cose-base.js +0 -0
  228. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/viz/static/vendor/cytoscape-fcose.js +0 -0
  229. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/viz/static/vendor/cytoscape.min.js +0 -0
  230. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/viz/static/vendor/layout-base.js +0 -0
  231. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/viz/tools/package-lock.json +0 -0
  232. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/viz/tools/package.json +0 -0
  233. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/viz/tools/shoot.mjs +0 -0
  234. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/watch.py +0 -0
  235. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph/xcode.py +0 -0
  236. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph.egg-info/dependency_links.txt +0 -0
  237. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph.egg-info/entry_points.txt +0 -0
  238. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph.egg-info/requires.txt +0 -0
  239. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/cg_code_graph.egg-info/top_level.txt +0 -0
  240. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/pyproject.toml +0 -0
  241. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/setup.cfg +0 -0
  242. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_affected.py +0 -0
  243. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_agents_install.py +0 -0
  244. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_agents_md.py +0 -0
  245. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_ai_tools.py +0 -0
  246. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_android_ipc.py +0 -0
  247. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_apple_ipc.py +0 -0
  248. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_astro.py +0 -0
  249. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_astro_content.py +0 -0
  250. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_astro_server.py +0 -0
  251. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_bridges.py +0 -0
  252. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_bridges_pigeon_monorepo.py +0 -0
  253. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_brokers.py +0 -0
  254. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_cache_clean.py +0 -0
  255. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_cache_invalidation.py +0 -0
  256. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_cache_migration.py +0 -0
  257. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_channels_tests.py +0 -0
  258. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_cli_output.py +0 -0
  259. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_completeness.py +0 -0
  260. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_config_extras.py +0 -0
  261. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_coverage.py +0 -0
  262. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_coverage_ts_hint.py +0 -0
  263. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_dart_isolates.py +0 -0
  264. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_dispatch_interfaces.py +0 -0
  265. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_django_flutter.py +0 -0
  266. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_docs_languages.py +0 -0
  267. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_docs_links.py +0 -0
  268. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_echo_ref.py +0 -0
  269. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_elysia.py +0 -0
  270. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_enum_constants.py +0 -0
  271. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_env.py +0 -0
  272. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_explore.py +0 -0
  273. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_external.py +0 -0
  274. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_gating.py +0 -0
  275. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_generated.py +0 -0
  276. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_graphql.py +0 -0
  277. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_hooks.py +0 -0
  278. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_impact_overrides.py +0 -0
  279. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_index_isolation.py +0 -0
  280. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_inherited_overrides.py +0 -0
  281. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_inline_guards.py +0 -0
  282. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_java.py +0 -0
  283. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_java_exact.py +0 -0
  284. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_jobs.py +0 -0
  285. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_js_refs.py +0 -0
  286. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_js_runtime.py +0 -0
  287. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_kotlin.py +0 -0
  288. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_kotlin_binding.py +0 -0
  289. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_kotlin_exact.py +0 -0
  290. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_kotlin_fields.py +0 -0
  291. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_kotlin_mixed.py +0 -0
  292. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_kotlin_nav_consts.py +0 -0
  293. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_kotlin_props.py +0 -0
  294. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_laravel_determinism.py +0 -0
  295. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_laravel_resources.py +0 -0
  296. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_lint_async.py +0 -0
  297. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_local_ipc.py +0 -0
  298. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_local_sockets.py +0 -0
  299. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_mcp.py +0 -0
  300. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_mcp_index.py +0 -0
  301. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_mcp_install.py +0 -0
  302. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_native_cfamily.py +0 -0
  303. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_native_concurrency.py +0 -0
  304. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_native_rust.py +0 -0
  305. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_nuxt_import_dirs.py +0 -0
  306. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_nuxt_rootsrc.py +0 -0
  307. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_packaging.py +0 -0
  308. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_parity.py +0 -0
  309. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_parity_structure.py +0 -0
  310. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_php_http.py +0 -0
  311. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_php_props.py +0 -0
  312. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_plain_js.py +0 -0
  313. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_plans.py +0 -0
  314. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_platform_gaps.py +0 -0
  315. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_platform_type_variants.py +0 -0
  316. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_platforms.py +0 -0
  317. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_platforms_apple.py +0 -0
  318. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_presets_config.py +0 -0
  319. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_process_ipc.py +0 -0
  320. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_process_runs.py +0 -0
  321. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_protocols.py +0 -0
  322. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_python_app_tests_module.py +0 -0
  323. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_python_collection_chains.py +0 -0
  324. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_python_compat.py +0 -0
  325. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_python_entry_refs.py +0 -0
  326. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_python_fields.py +0 -0
  327. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_python_infer_cycles.py +0 -0
  328. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_python_roots.py +0 -0
  329. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_python_subprocess_tests.py +0 -0
  330. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_python_tests.py +0 -0
  331. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_python_web_hosts.py +0 -0
  332. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_python_web_routes.py +0 -0
  333. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_python_web_routes_more.py +0 -0
  334. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_react_router.py +0 -0
  335. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_realtime.py +0 -0
  336. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_realtime_native.py +0 -0
  337. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_request_keys.py +0 -0
  338. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_roundtrip.py +0 -0
  339. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_route_tables.py +0 -0
  340. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_routes_search_explain.py +0 -0
  341. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_rpc.py +0 -0
  342. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_scip_health.py +0 -0
  343. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_sdk_external.py +0 -0
  344. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_snippet.py +0 -0
  345. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_sockets.py +0 -0
  346. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_spring.py +0 -0
  347. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_starters_budget.py +0 -0
  348. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_swift.py +0 -0
  349. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_swift_binding.py +0 -0
  350. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_swift_exact.py +0 -0
  351. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_swift_facts.py +0 -0
  352. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_swift_fields.py +0 -0
  353. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_swift_modules.py +0 -0
  354. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_swift_more.py +0 -0
  355. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_swift_nav_values.py +0 -0
  356. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_swift_parse_errors.py +0 -0
  357. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_swift_props.py +0 -0
  358. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_swift_sdk.py +0 -0
  359. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_swift_testing.py +0 -0
  360. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_swift_values.py +0 -0
  361. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_tests_scope.py +0 -0
  362. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_ts_fields.py +0 -0
  363. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_ts_fields_js_vue.py +0 -0
  364. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_ts_file_specs.py +0 -0
  365. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_ts_frameworks.py +0 -0
  366. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_ts_nuxt.py +0 -0
  367. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_ts_src_tsconfig.py +0 -0
  368. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_values.py +0 -0
  369. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_viz.py +0 -0
  370. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_vue_nav.py +0 -0
  371. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_watch.py +0 -0
  372. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_webhooks.py +0 -0
  373. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_webhooks_152.py +0 -0
  374. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_websockets.py +0 -0
  375. {cg_code_graph-0.20.0 → cg_code_graph-0.21.0}/tests/test_workspace_link.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cg-code-graph
3
- Version: 0.20.0
3
+ Version: 0.21.0
4
4
  Summary: cg: a code graph of routes, calls, data access and platform boundaries for agents and humans (CLI + MCP server)
5
5
  Author: cyberchronos00
6
6
  License-Expression: MIT
@@ -90,6 +90,7 @@ Go and others: import a SCIP index with `cg index --scip FILE`. Run `cg doctor`
90
90
  - `cg affected --base main`: the tests and entry points a change reaches (`--quiet` feeds your test runner).
91
91
  - Across repos: `cg link` matches client HTTP calls (frontends and Laravel / Guzzle backends) to server routes, and checks request keys (Laravel FormRequest, Elysia schemas, Nuxt / TypeScript bodies).
92
92
  - Route guards: `cg routes --writes --unguarded` lists routes that write with no auth guard. Laravel checks inside the action count as inline guards unless `--strict`.
93
+ - Security views: `cg surface` lists the inbound / outbound attack surface and findings (hardcoded credentials, plaintext protocols, unverified webhooks, unguarded handlers, listeners on all interfaces, disabled TLS / SSH verification, plaintext gRPC, exposed IPC, outbound URLs built from request input) as text, JSON or SARIF, with `--fail-on` for CI.
93
94
  - MCP server (`cg-mcp`) so agents query the graph instead of grepping.
94
95
  - Visual view (`cg serve`) and plan checks (`cg plan check`) for planned changes.
95
96
 
@@ -63,6 +63,7 @@ Go and others: import a SCIP index with `cg index --scip FILE`. Run `cg doctor`
63
63
  - `cg affected --base main`: the tests and entry points a change reaches (`--quiet` feeds your test runner).
64
64
  - Across repos: `cg link` matches client HTTP calls (frontends and Laravel / Guzzle backends) to server routes, and checks request keys (Laravel FormRequest, Elysia schemas, Nuxt / TypeScript bodies).
65
65
  - Route guards: `cg routes --writes --unguarded` lists routes that write with no auth guard. Laravel checks inside the action count as inline guards unless `--strict`.
66
+ - Security views: `cg surface` lists the inbound / outbound attack surface and findings (hardcoded credentials, plaintext protocols, unverified webhooks, unguarded handlers, listeners on all interfaces, disabled TLS / SSH verification, plaintext gRPC, exposed IPC, outbound URLs built from request input) as text, JSON or SARIF, with `--fail-on` for CI.
66
67
  - MCP server (`cg-mcp`) so agents query the graph instead of grepping.
67
68
  - Visual view (`cg serve`) and plan checks (`cg plan check`) for planned changes.
68
69
 
@@ -1,2 +1,2 @@
1
1
  """code-graph: deterministic code knowledge graph (beta)."""
2
- __version__ = "0.20.0"
2
+ __version__ = "0.21.0"
@@ -35,6 +35,8 @@ KINDS = {
35
35
  "Nuxt imports.dirs entry that is not a literal path or glob"),
36
36
  "vue_unresolved_navigation": ("route", "typescript",
37
37
  "Nuxt / Vue / Astro navigation target that matches no page or several pages"),
38
+ "ts_unrecognised_http_client": ("endpoint", "typescript",
39
+ "outbound HTTP call through an imported client (http, https, undici, got, node-fetch, superagent, ...) that made no endpoint"),
38
40
  }
39
41
 
40
42
 
@@ -415,6 +417,110 @@ def nuxt_unevaluable_import_dirs(root: Path) -> dict | None:
415
417
  return _finding("nuxt_unevaluable_import_dirs", hits)
416
418
 
417
419
 
420
+ # ------------------------------------------------------------------------------------------- TS HTTP clients
421
+
422
+ HTTP_CLIENT_MODS = ("http", "https", "undici", "got", "node-fetch", "superagent", "needle", "phin", "request")
423
+ _HTTP_VERBS = {"request", "get", "post", "put", "patch", "delete", "del", "head", "options", "fetch", "stream"}
424
+ _HTTP_ONLY = {"request", "get"} # node:http / node:https: createServer, Agent, ... are not client calls
425
+ _HTTP_MOD_ALT = "|".join(re.escape(m) for m in HTTP_CLIENT_MODS)
426
+ _IMPORT_DEFAULT = re.compile(r"""import\s+(?:type\s+)?(\w+)\s*(?:,\s*\{([^}]*)\}\s*)?from\s*['"](?:node:)?(%s)['"]""" % _HTTP_MOD_ALT)
427
+ _IMPORT_NS = re.compile(r"""import\s+\*\s+as\s+(\w+)\s+from\s*['"](?:node:)?(%s)['"]""" % _HTTP_MOD_ALT)
428
+ _IMPORT_NAMED = re.compile(r"""import\s+(?:type\s+)?\{([^}]*)\}\s*from\s*['"](?:node:)?(%s)['"]""" % _HTTP_MOD_ALT)
429
+ _REQ_BIND = re.compile(r"""(?:const|let|var)\s+(\w+)\s*=\s*(?:await\s+)?require\(\s*['"](?:node:)?(%s)['"]\s*\)""" % _HTTP_MOD_ALT)
430
+ _REQ_DESTR = re.compile(r"""(?:const|let|var)\s+\{([^}]*)\}\s*=\s*require\(\s*['"](?:node:)?(%s)['"]\s*\)""" % _HTTP_MOD_ALT)
431
+ _TEST_FILE = re.compile(r"(^|/)(tests?|__tests__|e2e|spec|specs|fixtures?)/|\.(test|spec)\.[cm]?[jt]sx?$")
432
+
433
+
434
+ def _named(spec: str) -> list[tuple[str, str]]:
435
+ out = []
436
+ for part in spec.split(","):
437
+ bits = [b for b in re.split(r"\s+as\s+|\s+", part.replace("type ", "").strip()) if b]
438
+ if bits:
439
+ out.append((bits[0], bits[-1]))
440
+ return out
441
+
442
+
443
+ def _http_client_calls(src: str) -> int | None:
444
+ """Offset of the first call through an imported HTTP client module in `src`, else None."""
445
+ binds: dict[str, str] = {} # name -> module (the module object itself)
446
+ named: dict[str, str] = {} # local name -> module (an imported client function)
447
+ for m in _IMPORT_DEFAULT.finditer(src):
448
+ binds[m.group(1)] = m.group(3)
449
+ for imp, loc in _named(m.group(2) or ""):
450
+ named[loc] = f"{m.group(3)}:{imp}"
451
+ for m in _IMPORT_NS.finditer(src):
452
+ binds[m.group(1)] = m.group(2)
453
+ for m in _IMPORT_NAMED.finditer(src):
454
+ for imp, loc in _named(m.group(1)):
455
+ named[loc] = f"{m.group(2)}:{imp}"
456
+ for m in _REQ_BIND.finditer(src):
457
+ binds[m.group(1)] = m.group(2)
458
+ for m in _REQ_DESTR.finditer(src):
459
+ for imp, loc in _named(m.group(1).replace(":", " as ")):
460
+ named[loc] = f"{m.group(2)}:{imp}"
461
+ best = None
462
+
463
+ def hit(pos):
464
+ nonlocal best
465
+ if best is None or pos < best:
466
+ best = pos
467
+
468
+ for name, mod in binds.items():
469
+ verbs = _HTTP_ONLY if mod in ("http", "https") else _HTTP_VERBS
470
+ if mod not in ("http", "https"):
471
+ m = re.search(r"(?<![\w.$])%s\s*\(" % re.escape(name), src) # got(url), request(opts), needle(...)
472
+ if m:
473
+ hit(m.start())
474
+ m = re.search(r"(?<![\w.$])%s\s*\.\s*(%s)\s*\(" % (re.escape(name), "|".join(sorted(verbs))), src)
475
+ if m:
476
+ hit(m.start())
477
+ for name, spec in named.items():
478
+ mod, _, imp = spec.partition(":")
479
+ ok = imp in (_HTTP_ONLY if mod in ("http", "https") else _HTTP_VERBS | {"Client", "Pool"})
480
+ if not ok:
481
+ continue
482
+ m = re.search(r"(?<![\w.$])(?:new\s+)?%s\s*\(" % re.escape(name), src)
483
+ if m:
484
+ hit(m.start())
485
+ return best
486
+
487
+
488
+ def _opaque_origin(origin: str | None) -> bool:
489
+ """An origin no host can be read from: a bare placeholder that is not a configured / env value, or a templated host."""
490
+ o = origin or ""
491
+ if not o:
492
+ return False
493
+ if o.startswith("{"):
494
+ return not o.startswith(("{env.", "{runtimeConfig."))
495
+ return "://" in o and "{" in o.split("://", 1)[1].split("/", 1)[0]
496
+
497
+
498
+ def ts_unrecognised_http_client(root: Path, files: list[str], builder) -> dict | None:
499
+ """Files that import an HTTP client module (http, https, undici, got, node-fetch, superagent, needle, phin, request)
500
+ and call it, with no client endpoint made there (a call cg could not follow, or only an origin it could not read)."""
501
+ if builder is None:
502
+ return None
503
+ made: dict[str, bool] = {}
504
+ for e in builder.edges.values():
505
+ if e.kind != "HTTP_CALLS" or not e.file:
506
+ continue
507
+ n = builder.nodes.get(e.dst)
508
+ if n is not None and not _opaque_origin((n.attrs or {}).get("origin")):
509
+ made[e.file] = True
510
+ hits = []
511
+ for f in files:
512
+ if made.get(f) or _TEST_FILE.search(f):
513
+ continue
514
+ src = _read(root, f)
515
+ if not src or not re.search(r"(?:from|require\s*\()\s*['\"](?:node:)?(?:%s)['\"]" % _HTTP_MOD_ALT, src):
516
+ continue
517
+ src = _strip_comments(src)
518
+ pos = _http_client_calls(src)
519
+ if pos is not None:
520
+ hits.append((f, _line(src, pos)))
521
+ return _finding("ts_unrecognised_http_client", hits)
522
+
523
+
418
524
  # ------------------------------------------------------------------------------------------- driver
419
525
 
420
526
  def detect(root: str | Path, files_by_ext: dict[str, list[str]], builder=None, programs: dict | None = None) -> list[dict]:
@@ -442,6 +548,7 @@ def detect(root: str | Path, files_by_ext: dict[str, list[str]], builder=None, p
442
548
  run(express_loop_routes, root, js)
443
549
  run(nuxt_unevaluable_import_dirs, root)
444
550
  run(vue_unresolved_navigation, builder)
551
+ run(ts_unrecognised_http_client, root, js, builder)
445
552
  run(laravel_loop_routes, root, php)
446
553
  py = programs.get("python")
447
554
  if py is not None:
@@ -207,6 +207,23 @@ def main(argv=None):
207
207
  p.add_argument("--source", help="address or credential source: literal, env, env-example, compose, config")
208
208
  p.add_argument("--tls-off", action="store_true", help="only systems known to be reached without TLS")
209
209
  p.add_argument("--max-items", type=int, default=60)
210
+ p = sub.add_parser("surface", help="security views over recorded facts: inbound / outbound attack surface and findings "
211
+ "(hardcoded credentials, plaintext protocols, disabled TLS / SSH verification, unverified "
212
+ "webhooks, unguarded handlers, listeners on all interfaces, exposed IPC); text, JSON or "
213
+ "SARIF; --fail-on for CI gates")
214
+ p.add_argument("--db", required=True)
215
+ p.add_argument("--inbound", action="store_true", help="list the inbound surface (routes, webhooks, sockets, consumers, IPC)")
216
+ p.add_argument("--outbound", action="store_true", help="list the outbound surface (every external system)")
217
+ p.add_argument("--protocol", help="only this protocol (http, ws, grpc, tcp, redis, postgres, saas, ...)")
218
+ from .surface import finding_names
219
+ p.add_argument("--finding", help="only this finding: " + ", ".join(finding_names()))
220
+ p.add_argument("--min-confidence", choices=["heuristic", "resolved", "exact"], default="heuristic",
221
+ help="drop findings and items below this confidence (default: heuristic, keep all)")
222
+ p.add_argument("--format", choices=["text", "json", "sarif"], default="text")
223
+ p.add_argument("--fail-on", help="comma-separated finding types; exit 1 when any remains after ignores")
224
+ p.add_argument("--max-items", type=int, default=200)
225
+ p.add_argument("--strict", action="store_true", help="unguarded: count route guards only (an inline check does not protect)")
226
+ p.add_argument("--show-ignored", action="store_true", help="also list findings accepted by .cg.yaml surface.ignore")
210
227
  p = sub.add_parser("tools", help="LLM tools and MCP tools / resources / prompts: handler, tables it reaches, agents "
211
228
  "offering it, callers, checks; agents, dynamic dispatch, model calls")
212
229
  p.add_argument("pattern", nargs="?", help="tool name, substring or glob")
@@ -569,7 +586,14 @@ def main(argv=None):
569
586
  res = external(st, a.pattern, protocol=a.protocol, source=a.source, tls_off=a.tls_off,
570
587
  max_items=max(a.max_items, 200) if a.json else a.max_items)
571
588
  print(json.dumps(res, indent=1, default=str) if a.json else render_external(res, max_items=a.max_items))
589
+ if not a.json:
590
+ from .coverage import endpoint_note
591
+ n = endpoint_note(st)
592
+ if n:
593
+ print(n)
572
594
  return
595
+ if a.cmd == "surface":
596
+ return surface_cmd(a, st)
573
597
  if a.cmd == "tools":
574
598
  from .aitools import render_tools, tools
575
599
  res = tools(st, a.pattern, framework=a.framework, unmatched=a.unmatched, agent=a.agent,
@@ -721,6 +745,11 @@ def main(argv=None):
721
745
  elif a.cmd == "api-calls":
722
746
  rows = Q.api_calls(st, a.spec)
723
747
  print(json.dumps(rows, indent=1, default=str) if a.json else Q.render_api_calls(rows))
748
+ if not a.json:
749
+ from .coverage import endpoint_note
750
+ n = endpoint_note(st)
751
+ if n:
752
+ print(n)
724
753
  elif a.cmd == "stats":
725
754
  doc = stats_doc(st)
726
755
  print(json.dumps(doc, indent=1, default=str) if a.json else render_stats(st, doc))
@@ -824,6 +853,26 @@ def _note(comp: dict, as_json: bool) -> None:
824
853
  print(n)
825
854
 
826
855
 
856
+ def surface_cmd(a, st) -> int:
857
+ """`cg surface`: exit 0, or 1 when a --fail-on finding remains after ignores; 2 for a bad finding name."""
858
+ from . import surface as S
859
+ fail_on = [x.strip() for x in a.fail_on.split(",") if x.strip()] if a.fail_on else []
860
+ try:
861
+ res = S.surface(st, inbound=a.inbound, outbound=a.outbound, protocol=a.protocol, finding=a.finding,
862
+ min_confidence=a.min_confidence, fail_on=fail_on, max_items=a.max_items, strict=a.strict,
863
+ show_ignored=a.show_ignored)
864
+ except ValueError as ex:
865
+ print(f"cg surface: {ex}", file=sys.stderr)
866
+ return 2
867
+ if a.format == "json":
868
+ print(S.to_json(res))
869
+ elif a.format == "sarif":
870
+ print(json.dumps(S.to_sarif(res), indent=1))
871
+ else:
872
+ print(S.render_surface(res, max_items=a.max_items))
873
+ return 1 if res["failed"] else 0
874
+
875
+
827
876
  def config_cmd(a) -> int:
828
877
  from .config import ConfigError, effective, find, load_file, render_effective, unknown_key_warnings
829
878
  if a.action == "validate":
@@ -26,6 +26,11 @@ Records project-specific knowledge once, for the CLI, the MCP server and the vis
26
26
  protocols:
27
27
  external: ["kafka:audit.*", "http:GET /status"] # <protocol>:<name glob> handled outside the analysed repos
28
28
  # (not reported as no_receiver / no_sender by cg protocols)
29
+ surface:
30
+ ignore: # accepted risks of `cg surface` (reason is required)
31
+ - {finding: unguarded, path: "app/Http/Controllers/HealthController.php", reason: public health probe}
32
+ - {finding: plaintext, id: "external:redis:cache:6379", reason: compose-internal network}
33
+ - {finding: hardcoded, fingerprint: 3f9a1c0b7d2e4a55, reason: rotated test key}
29
34
  gates: config/gates.json # gate scenarios file (cg index --gates)
30
35
  plans:
31
36
  dir: docs/plans # plans directory (--plans-dir)
@@ -53,7 +58,7 @@ SCHEMA: dict[str, set | None] = { # top-level key -> allowed sub-keys (None:
53
58
  "frameworks": {"add", "remove"}, "auth": {"extra_patterns"}, "secret": {"extra_patterns"}, "gates": None,
54
59
  "plans": {"dir", "text_mention_dirs"}, "viz": {"presets"}, "generated": {"paths", "vendored", "keep", "include"},
55
60
  "platforms": {"targets", "paths", "file_suffixes", "path_conventions"}, "include": None, "apps": None,
56
- "rust": {"targets"}, "protocols": {"external"}, "lossy": None,
61
+ "rust": {"targets"}, "protocols": {"external"}, "lossy": None, "surface": {"ignore"},
57
62
  }
58
63
  APP_KEYS = ("name", "root", "role", "links")
59
64
  APP_ROLES = ("backend", "frontend")
@@ -298,6 +303,9 @@ def parse(data: Any, fname: str = ".cg.yaml") -> dict:
298
303
  if bad:
299
304
  raise ConfigError(f"{fname}: protocols.external: {bad[0]!r} is not <protocol>:<name glob> (e.g. kafka:audit.*)")
300
305
  out["protocols"] = {"external": ext}
306
+ sf = _section(data, "surface", fname)
307
+ if sf is not None and sf.get("ignore") is not None:
308
+ out["surface"] = {"ignore": _surface_ignores(sf["ignore"], f"{fname}: surface.ignore")}
301
309
  if data.get("gates") is not None:
302
310
  out["gates"] = norm_root(data["gates"], f"{fname}: gates")
303
311
  pl = _section(data, "plans", fname)
@@ -319,6 +327,38 @@ def parse(data: Any, fname: str = ".cg.yaml") -> dict:
319
327
  return out
320
328
 
321
329
 
330
+ SURFACE_IGNORE_KEYS = ("finding", "path", "id", "fingerprint", "reason")
331
+
332
+
333
+ def _surface_ignores(v: Any, where: str) -> list[dict]:
334
+ """`surface.ignore`: [{finding, path | id | fingerprint, reason}]; the reason is required."""
335
+ from .surface import FINDINGS
336
+ if not isinstance(v, list):
337
+ raise ConfigError(f"{where}: expected a list of {{finding, path | id | fingerprint, reason}} entries")
338
+ out = []
339
+ for i, e in enumerate(v):
340
+ w = f"{where}[{i}]"
341
+ if not isinstance(e, dict):
342
+ raise ConfigError(f"{w}: expected a mapping with finding, path | id | fingerprint and reason")
343
+ bad = sorted(str(k) for k in set(e) - set(SURFACE_IGNORE_KEYS))
344
+ if bad:
345
+ raise ConfigError(f"{w}: unknown key {bad[0]!r} (allowed: {', '.join(SURFACE_IGNORE_KEYS)})")
346
+ f = e.get("finding")
347
+ if f not in FINDINGS:
348
+ raise ConfigError(f"{w}: finding {f!r} is not one of {', '.join(FINDINGS)}")
349
+ reason = e.get("reason")
350
+ if not isinstance(reason, str) or not reason.strip():
351
+ raise ConfigError(f"{w}: reason is required (say why the risk is accepted)")
352
+ sel = {k: e[k] for k in ("path", "id", "fingerprint") if e.get(k) is not None}
353
+ if not sel:
354
+ raise ConfigError(f"{w}: give at least one of path, id or fingerprint")
355
+ for k, x in sel.items():
356
+ if not isinstance(x, str) or not x.strip():
357
+ raise ConfigError(f"{w}: {k} must be a non-empty string")
358
+ out.append({"finding": f, **{k: x.strip() for k, x in sel.items()}, "reason": reason.strip()})
359
+ return out
360
+
361
+
322
362
  def _apps(v: Any, fname: str) -> list[dict]:
323
363
  """apps as a list of {name, root, role, links} (or a mapping name -> {root, role, links}), validated: unique names,
324
364
  role backend / frontend, links naming backend apps. `root` may be relative, `../other-repo` or absolute.
@@ -516,7 +556,7 @@ def effective(root: str | Path, python_roots: list[str] | None = None, gates: st
516
556
  row(f"platforms.{k}", pc.get(k, True), f"{fname} platforms.{k}" if k in pc else f"built-in ({what})")
517
557
  row("auth.token_pattern", PR.values("common", "auth", "token_pattern"), "preset common")
518
558
  for section in ("auth", "secret"):
519
- for key in ("guards", "not_auth") if section == "auth" else ("guards",):
559
+ for key in ("guards", "not_auth", "session_only") if section == "auth" else ("guards",):
520
560
  for p in plan["presets"]:
521
561
  g = PR.values(p, section, key, default=None)
522
562
  if g:
@@ -72,6 +72,7 @@ EDGE_KINDS: dict[str, tuple[bool, str]] = {
72
72
  "READS_PROP": (True, "code reads a stored property (field node: Swift `var x`, `@State`, `@Published`)"),
73
73
  "WRITES_PROP": (True, "code writes a stored property (assignment, compound assignment)"),
74
74
  "USES_VALUE": (True, "code refers to an enum case, a constant, a static/global variable or an object-like macro"),
75
+ "REGISTERS_CALLBACK": (False, "function builds a callback URL for one of the app's own routes and registers it with an external party (attrs.body_key, endpoint)"),
75
76
  "REFERENCES_FN": (True, "code/data takes a function as a value (callback, dispatch table, handler registration)"),
76
77
  "USES_UNSAFE": (True, "code contains an unsafe block or is an unsafe fn (sink node unsafe:<crate>)"),
77
78
  "GATED_BY": (True, "code compiled only under a condition: Cargo feature, cfg predicate or preprocessor macro"),
@@ -59,6 +59,9 @@ class GraphBuilder:
59
59
  # Pigeon API definitions found by the Dart plugin ({Api: {kind: host | flutter, methods, file}}): bridges.py
60
60
  # links their native implementations / native callers
61
61
  self.pigeon_apis: dict = {}
62
+ # keyed-hash salt for the markers that stand in for secret values (cg_code_graph/core/redact.py); the indexer
63
+ # sets it from the graph DB before any plugin runs
64
+ self.redact_salt: str | None = None
62
65
 
63
66
  def add_node(self, kind: str, key: str, name: str | None = None, **kw) -> str:
64
67
  nid = node_id(kind, key)
@@ -0,0 +1,158 @@
1
+ """Secret values never reach the graph database (#47).
2
+
3
+ A literal that belongs to a credential-looking key (password, secret, token, key, api_key, auth, private, credential,
4
+ passphrase, dsn, ...) is stored as `redacted:hmac:<first 8 hex of HMAC-SHA-256(graph salt, value)>`: enough to see that
5
+ a literal exists and to tell two values apart within one graph, not enough to recover one, and not recognisable by
6
+ hashing a common password. The salt is `secrets.token_hex(16)`, created on the first index of a graph DB, kept in its
7
+ `meta` table (`redact_salt`) and carried over by a re-index or refresh of that DB; a new DB gets a new salt, so markers
8
+ of different graphs never compare. Graphs written before part 2d hold `redacted:sha256:<8 hex>` markers, which
9
+ `is_redacted` still accepts. A password inside a URL (`scheme://user:pw@host`) gets the same marker in place of the
10
+ password so the host stays readable. Placeholders (`${X}`, `%(X)s`, `<x>`, `null`) and
11
+ values that are already markers are left alone. Applied at index time by the plugins that keep literal config values
12
+ (Laravel config/*.php `value` and `env()` defaults) and as a last pass over every node / edge attr before the write.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import hashlib
17
+ import hmac
18
+ import re
19
+ import secrets
20
+
21
+ MARK = "redacted:hmac:"
22
+ LEGACY_MARK = "redacted:sha256:"
23
+ SECRET_WORDS = ("PASSWORD", "PASS", "PASSWD", "SECRET", "TOKEN", "SECRET_KEY", "SECRET_ACCESS_KEY", "KEY", "AUTH", "PWD")
24
+ _EXTRA_WORDS = {"PASSWORD", "PASSWD", "PWD", "PASSPHRASE", "SECRET", "SECRETS", "TOKEN", "TOKENS", "AUTH", "AUTHORIZATION",
25
+ "CREDENTIAL", "CREDENTIALS", "PRIVATE", "APIKEY", "DSN", "BEARER", "SALT", "SIGNATURE"}
26
+ _NOT_SECRET_TAIL = ("_KEY_ID", "_PUBLIC_KEY", "_KEY_PATH")
27
+ _URL_PW = re.compile(r"(?P<head>\b[A-Za-z][\w+.-]*://[^/\s:@'\"]*:)(?P<pw>[^@/\s'\"]+)(?P<tail>@)")
28
+ _PLACEHOLDER = ("${", "{{", "%(", "<", "$")
29
+
30
+
31
+ def secret_key(key: str) -> bool:
32
+ """The `cg surface` rule for a config key / env name that holds a credential."""
33
+ k = re.sub(r"[^A-Za-z0-9]+", "_", key).upper().strip("_")
34
+ return any(k.endswith("_" + s) or k == s for s in SECRET_WORDS) and not k.endswith(_NOT_SECRET_TAIL)
35
+
36
+
37
+ def looks_secret(key: str) -> bool:
38
+ """Wider than `secret_key`: any word of the name says credential (private_key_pem, db_dsn, apiKey, auth_header)."""
39
+ if secret_key(key):
40
+ return True
41
+ spaced = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", str(key))
42
+ words = [w for w in re.split(r"[^A-Za-z0-9]+", spaced.upper()) if w]
43
+ if any(w in _EXTRA_WORDS for w in words):
44
+ return True
45
+ return "KEY" in words and not set(words) & {"PUBLIC", "ID", "PATH", "FILE", "NAME", "PREFIX", "TYPE", "SIZE", "LENGTH"}
46
+
47
+
48
+ def is_redacted(v) -> bool:
49
+ return isinstance(v, str) and v.startswith((MARK, LEGACY_MARK))
50
+
51
+
52
+ def new_salt() -> str:
53
+ return secrets.token_hex(16)
54
+
55
+
56
+ def marker(value: str, salt: str) -> str:
57
+ """The stored stand-in for `value` in the graph that owns `salt`. There is no default salt on purpose: an unsalted
58
+ marker would be a plain hash of the value."""
59
+ if not salt:
60
+ raise ValueError("marker() needs the graph's redact salt")
61
+ return MARK + hmac.new(salt.encode(), value.encode("utf-8", "replace"), hashlib.sha256).hexdigest()[:8]
62
+
63
+
64
+ def placeholder(v) -> bool:
65
+ if not isinstance(v, str):
66
+ return True
67
+ t = v.strip()
68
+ return not t or t.startswith(_PLACEHOLDER) or t.lower() in ("null", "none", "false", "true", "changeme?") or is_redacted(t)
69
+
70
+
71
+ def redact_url(v: str, salt: str) -> str:
72
+ if "://" not in v or "@" not in v:
73
+ return v
74
+ return _URL_PW.sub(lambda m: m["head"] + (m["pw"] if placeholder(m["pw"]) else marker(m["pw"], salt)) + m["tail"], v)
75
+
76
+
77
+ def redact(value, key: str | None, salt: str):
78
+ """`value` as it may be stored: a marker for a literal under a credential-looking `key`, a URL password replaced
79
+ by one, anything else unchanged. Non-strings and placeholders pass through."""
80
+ if not isinstance(value, str):
81
+ return value
82
+ if key is not None and looks_secret(key) and not placeholder(value):
83
+ return marker(value, salt)
84
+ return redact_url(value, salt)
85
+
86
+
87
+ _VALUE_KEYS = {"value", "literal", "default", "env_default", "default_value"}
88
+ _NAME_KEYS = ("key", "name", "env", "env_key", "setting")
89
+ # Names that only ever carry a literal credential. `token`, `secret`, `credential(s)` are left out on purpose: plugins
90
+ # use them for identifiers (the Nest dependency-injection token on INJECTS edges, a secret's name, a credential source).
91
+ _DIRECT = {"password", "passwd", "pwd", "passphrase", "api_key", "apikey", "access_key", "secret_key", "client_secret",
92
+ "private_key", "auth_token"}
93
+ _REFERENCE = re.compile(r"^(?:env|config|file|const|literal)[: ]|^\$\{|^[\w./-]+:\d+$")
94
+ STRUCTURAL_EDGES = {"INJECTS", "BOUND_TO", "CREDENTIAL_FROM"}
95
+ # Attr maps keyed by source identifiers: an exported class called `ApiKey` is a name, not a literal under a credential key.
96
+ _IDENTIFIER_MAPS = {"reexports"}
97
+
98
+
99
+ def _holds_secret(d: dict, k: str) -> bool:
100
+ if k.lower() in _DIRECT:
101
+ return not _REFERENCE.match(str(d[k]))
102
+ if k in _VALUE_KEYS:
103
+ return any(isinstance(d.get(nk), str) and looks_secret(d[nk]) for nk in _NAME_KEYS)
104
+ return False
105
+
106
+
107
+ def sweep(builder, salt: str | None = None) -> int:
108
+ """Last pass over node / edge attrs; returns the count of replaced values. Replaces the password of a
109
+ `scheme://user:password@host` string, a literal under a credential-named attr (`password`, `token`, ...), and the
110
+ `value` / `default` of a `{key|name: <credential-looking>, value: ...}` record (TypeScript / Python config readers,
111
+ Laravel config). Booleans such as `credential_literal`, hosts and every other value stay as they are. The salt is
112
+ `salt`, else `builder.redact_salt` (set by the indexer from the graph DB)."""
113
+ salt = salt or getattr(builder, "redact_salt", None)
114
+ if not salt:
115
+ raise ValueError("sweep() needs the graph's redact salt")
116
+ n = 0
117
+
118
+ def walk(x, name=None):
119
+ nonlocal n
120
+ if name in _IDENTIFIER_MAPS:
121
+ return x
122
+ if isinstance(x, str):
123
+ if "@" in x and "://" in x:
124
+ y = redact_url(x, salt)
125
+ if y != x:
126
+ n += 1
127
+ return y
128
+ return x
129
+ if isinstance(x, dict):
130
+ for k, v in x.items():
131
+ if isinstance(v, str) and _holds_secret(x, k) and not placeholder(v):
132
+ x[k] = marker(v, salt)
133
+ n += 1
134
+ continue
135
+ nv = walk(v, k)
136
+ if nv is not v:
137
+ x[k] = nv
138
+ return x
139
+ if isinstance(x, list):
140
+ for i, v in enumerate(x):
141
+ nv = walk(v)
142
+ if nv is not v:
143
+ x[i] = nv
144
+ return x
145
+ return x
146
+ for node in builder.nodes.values():
147
+ doc = getattr(node, "doc", None)
148
+ if isinstance(doc, str) and "@" in doc and "://" in doc:
149
+ y = redact_url(doc, salt)
150
+ if y != doc:
151
+ node.doc = y
152
+ n += 1
153
+ if node.attrs:
154
+ walk(node.attrs)
155
+ for e in builder.edges.values():
156
+ if e.attrs and e.kind not in STRUCTURAL_EDGES:
157
+ walk(e.attrs)
158
+ return n
@@ -343,6 +343,11 @@ def compute(root: str | Path, plugins: dict, scip_imported: bool = False, report
343
343
  at = st.get("realtime_subscriptions_unresolved_receiver_at") or []
344
344
  if at:
345
345
  e["realtime_subscriptions_unresolved_receiver_at"] = at[:8]
346
+ if lang == "typescript" and st and st.get("realtime_subscriptions_unresolved_wrapper"):
347
+ e["realtime_subscriptions_unresolved_wrapper"] = st["realtime_subscriptions_unresolved_wrapper"]
348
+ at = st.get("realtime_subscriptions_unresolved_wrapper_at") or []
349
+ if at:
350
+ e["realtime_subscriptions_unresolved_wrapper_at"] = at[:8]
346
351
  if lang == "python" and st:
347
352
  for k in ("roots_mode", "source_roots", "roots_warnings", "roots_ambiguous", "module_name_collisions"):
348
353
  if st.get(k):
@@ -503,13 +508,22 @@ def _entry_text(e: dict) -> str:
503
508
 
504
509
 
505
510
  def realtime_unresolved_lines(e: dict, indent: str = " ") -> list[str]:
506
- """'.private(...)' on a receiver that is not an Echo client, with a channel literal: where to look."""
507
- n = e.get("realtime_subscriptions_unresolved_receiver") or 0
508
- if not n or e.get("language") != "typescript":
511
+ """'.private(...)' on a receiver that is not an Echo client, with a channel literal, and subscription wrapper calls whose
512
+ channel does not resolve: where to look."""
513
+ if e.get("language") != "typescript":
509
514
  return []
510
- at = e.get("realtime_subscriptions_unresolved_receiver_at") or []
511
- where = f" ({', '.join(at[:5])})" if at else ""
512
- return [f"{indent}typescript: {n} .private(...) call{'s' if n != 1 else ''} on an unknown receiver{where}"]
515
+ out = []
516
+ n = e.get("realtime_subscriptions_unresolved_receiver") or 0
517
+ if n:
518
+ at = e.get("realtime_subscriptions_unresolved_receiver_at") or []
519
+ where = f" ({', '.join(at[:5])})" if at else ""
520
+ out.append(f"{indent}typescript: {n} .private(...) call{'s' if n != 1 else ''} on an unknown receiver{where}")
521
+ w = e.get("realtime_subscriptions_unresolved_wrapper") or 0
522
+ if w:
523
+ at = e.get("realtime_subscriptions_unresolved_wrapper_at") or []
524
+ where = f" ({', '.join(at[:5])})" if at else ""
525
+ out.append(f"{indent}typescript: {w} subscription wrapper call{'s' if w != 1 else ''} whose channel does not resolve{where}")
526
+ return out
513
527
 
514
528
 
515
529
  def syntax_error_lines(e: dict, all_files: bool = False, indent: str = " ") -> list[str]:
@@ -553,9 +567,11 @@ def summary_line(cov: dict | None, repo: str | None = None) -> str:
553
567
 
554
568
  def _bs_count(bs: list[dict]) -> str:
555
569
  r = sum(b["count"] for b in bs if b["category"] == "route")
556
- h = sum(b["count"] for b in bs if b["category"] != "route")
570
+ h = sum(b["count"] for b in bs if b["category"] == "handler")
571
+ x = sum(b["count"] for b in bs if b["category"] == "endpoint")
557
572
  parts = ([f"{r} unmodelled route registration{'s' if r != 1 else ''}"] if r else []) + \
558
- ([f"{h} handler{'s' if h != 1 else ''} registered dynamically"] if h else [])
573
+ ([f"{h} handler{'s' if h != 1 else ''} registered dynamically"] if h else []) + \
574
+ ([f"{x} file{'s' if x != 1 else ''} with an outbound HTTP call that made no endpoint"] if x else [])
559
575
  return ", ".join(parts)
560
576
 
561
577
 
@@ -911,6 +927,21 @@ def completeness(covs: dict[str, dict | None], languages=None, dirs=None, repos=
911
927
  return out
912
928
 
913
929
 
930
+ def endpoint_note(store) -> str:
931
+ """`coverage note:` for outbound HTTP calls cg could not turn into endpoints (the `endpoint` blind spots), shown after
932
+ `cg api-calls` / `cg external`; '' when there are none."""
933
+ try:
934
+ covs = for_graph(store)
935
+ except Exception: # noqa: BLE001
936
+ return ""
937
+ found = [b for cov in covs.values() for b in blind_spots(cov) if b["category"] == "endpoint"]
938
+ if not found:
939
+ return ""
940
+ parts = [f"{b['count']} file{'s' if b['count'] != 1 else ''} call an HTTP client cg made no endpoint for ({', '.join(b['samples'][:3])})"
941
+ for b in found]
942
+ return "coverage note: " + "; ".join(parts) + ". Outbound calls from there may be missing; use your normal search and file reading."
943
+
944
+
914
945
  def completeness_for(store, node_ids=None, categories=("route", "handler"), whole: bool = False, **kw) -> dict:
915
946
  try:
916
947
  covs = for_graph(store)
@@ -931,23 +962,27 @@ def possibly_more(comp: dict) -> str:
931
962
  return ""
932
963
  parts = []
933
964
  r = sum(b["count"] for b in comp.get("blind_spots", []) if b["category"] == "route")
934
- h = sum(b["count"] for b in comp.get("blind_spots", []) if b["category"] != "route")
965
+ h = sum(b["count"] for b in comp.get("blind_spots", []) if b["category"] == "handler")
966
+ x = sum(b["count"] for b in comp.get("blind_spots", []) if b["category"] == "endpoint")
935
967
  if r:
936
968
  parts.append(f"{r} unmodelled route registration{'s' if r != 1 else ''}")
937
969
  if h:
938
970
  parts.append(f"{h} dynamically registered handler{'s' if h != 1 else ''}")
971
+ if x:
972
+ parts.append(f"{x} file{'s' if x != 1 else ''} with an outbound HTTP call that made no endpoint")
939
973
  for k, v in comp.get("languages", {}).items():
940
974
  if v["complete"]:
941
975
  continue
942
- lang = k.rsplit("/", 1)[-1]
976
+ repo, _, lang = k.rpartition("/")
943
977
  label = LANG_LABEL.get(lang, lang)
978
+ of = f" ({repo})" if repo else "" # combined graph: keys are `<repo>/<language>`; two repos must not print identical fragments
944
979
  miss = sum(v.get(b, 0) for b in ("parse_failed", "skipped_oversize", "unmapped"))
945
980
  if v["mode"] in ("exact", "scip") and miss:
946
- parts.append(f"{miss} {label} file{'s' if miss != 1 else ''} not indexed")
981
+ parts.append(f"{miss} {label} file{'s' if miss != 1 else ''} not indexed{of}")
947
982
  elif v["mode"] == "heuristic":
948
- parts.append(f"{label} heuristic only")
983
+ parts.append(f"{label} heuristic only{of}")
949
984
  else:
950
- parts.append(f"{label} {v['mode'].replace('_', ' ')}")
985
+ parts.append(f"{label} {v['mode'].replace('_', ' ')}{of}")
951
986
  se = comp.get("syntax_errors") or []
952
987
  if se:
953
988
  x = se[0]
@@ -968,7 +1003,7 @@ def answer_note(comp: dict) -> str:
968
1003
  return ""
969
1004
  parts = []
970
1005
  for b in comp.get("blind_spots", []):
971
- noun = "route registration" if b["category"] == "route" else "handler registration"
1006
+ noun = {"route": "route registration", "endpoint": "outbound HTTP call"}.get(b["category"], "handler registration")
972
1007
  parts.append(f"{b['count']} {noun}{'s' if b['count'] != 1 else ''} cg does not model ({b['what']}: {b['sample']})")
973
1008
  rest = possibly_more({**comp, "blind_spots": []})
974
1009
  if rest: