cg-code-graph 0.19.1__tar.gz → 0.20.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 (358) hide show
  1. {cg_code_graph-0.19.1/cg_code_graph.egg-info → cg_code_graph-0.20.0}/PKG-INFO +10 -10
  2. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/README.md +9 -9
  3. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/__init__.py +1 -1
  4. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/cli.py +3 -2
  5. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/coverage.py +16 -0
  6. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/mcp_server.py +12 -3
  7. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/payload.py +261 -38
  8. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/express/plugin.py +120 -17
  9. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/laravel/plugin.py +168 -1
  10. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/laravel/values.py +199 -3
  11. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/extractor/extract.php +28 -4
  12. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/gating.py +12 -3
  13. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/http_client.py +20 -4
  14. cg_code_graph-0.20.0/cg_code_graph/plugins/php/inline_guards.py +758 -0
  15. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/plugin.py +4 -1
  16. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/extractor/extract.mjs +134 -6
  17. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/extractor/fw.mjs +15 -2
  18. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/protocols/view.py +5 -3
  19. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/query.py +25 -19
  20. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/routes.py +40 -8
  21. cg_code_graph-0.20.0/cg_code_graph/webhooks.py +1426 -0
  22. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0/cg_code_graph.egg-info}/PKG-INFO +10 -10
  23. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph.egg-info/SOURCES.txt +5 -0
  24. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_coverage.py +4 -4
  25. cg_code_graph-0.20.0/tests/test_echo_ref.py +48 -0
  26. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_elysia.py +13 -0
  27. cg_code_graph-0.20.0/tests/test_inline_guards.py +186 -0
  28. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_php_http.py +16 -1
  29. cg_code_graph-0.20.0/tests/test_request_keys.py +229 -0
  30. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_routes_search_explain.py +12 -4
  31. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_ts_nuxt.py +8 -3
  32. cg_code_graph-0.20.0/tests/test_webhooks_152.py +159 -0
  33. cg_code_graph-0.19.1/cg_code_graph/webhooks.py +0 -598
  34. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/LICENSE +0 -0
  35. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/MANIFEST.in +0 -0
  36. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/__main__.py +0 -0
  37. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/affected.py +0 -0
  38. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/agent_rules.py +0 -0
  39. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/agents.py +0 -0
  40. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/aitools.py +0 -0
  41. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/android_ipc.py +0 -0
  42. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/apple_ipc.py +0 -0
  43. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/apps.py +0 -0
  44. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/blindspots.py +0 -0
  45. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/bridges.py +0 -0
  46. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/brokers.py +0 -0
  47. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/concepts.py +0 -0
  48. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/config.py +0 -0
  49. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/__init__.py +0 -0
  50. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/cache.py +0 -0
  51. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/detect.py +0 -0
  52. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/env.py +0 -0
  53. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/extractors.py +0 -0
  54. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/fsutil.py +0 -0
  55. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/generated.py +0 -0
  56. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/model.py +0 -0
  57. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/paths.py +0 -0
  58. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/plugin.py +0 -0
  59. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/store.py +0 -0
  60. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/syntax_errors.py +0 -0
  61. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/dart_isolates.py +0 -0
  62. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/doctor.py +0 -0
  63. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/explore.py +0 -0
  64. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/external.py +0 -0
  65. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/graphql.py +0 -0
  66. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/hooks.py +0 -0
  67. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/indexer.py +0 -0
  68. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/jobs.py +0 -0
  69. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/link.py +0 -0
  70. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/lint_async.py +0 -0
  71. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/local_ipc.py +0 -0
  72. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/local_sockets.py +0 -0
  73. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/mcp_install.py +0 -0
  74. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/parity.py +0 -0
  75. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/parity_structure.py +0 -0
  76. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plans.py +0 -0
  77. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/platform_scan.py +0 -0
  78. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/platforms.py +0 -0
  79. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/__init__.py +0 -0
  80. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/astro/__init__.py +0 -0
  81. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/astro/content.py +0 -0
  82. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/astro/plugin.py +0 -0
  83. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/cfamily/__init__.py +0 -0
  84. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/cfamily/plugin.py +0 -0
  85. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/cfamily/syntax.py +0 -0
  86. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/__init__.py +0 -0
  87. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/bridges.py +0 -0
  88. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/extractor/bin/extract.dart +0 -0
  89. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/extractor/pubspec.lock +0 -0
  90. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/extractor/pubspec.yaml +0 -0
  91. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/http.py +0 -0
  92. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/models.py +0 -0
  93. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/plugin.py +0 -0
  94. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/program.py +0 -0
  95. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/django/__init__.py +0 -0
  96. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/django/extras.py +0 -0
  97. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/django/models.py +0 -0
  98. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/django/plugin.py +0 -0
  99. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/django/schemas.py +0 -0
  100. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/django/shapes.py +0 -0
  101. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/django/urls.py +0 -0
  102. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/express/__init__.py +0 -0
  103. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/flutter/__init__.py +0 -0
  104. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/flutter/plugin.py +0 -0
  105. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/java/__init__.py +0 -0
  106. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/java/exact.py +0 -0
  107. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/java/plugin.py +0 -0
  108. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/jvm/__init__.py +0 -0
  109. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/jvm/interop.py +0 -0
  110. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/jvm/spring.py +0 -0
  111. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/kotlin/__init__.py +0 -0
  112. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/kotlin/exact.py +0 -0
  113. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/kotlin/plugin.py +0 -0
  114. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/kotlin/reparse.py +0 -0
  115. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/laravel/__init__.py +0 -0
  116. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/laravel/broadcast.py +0 -0
  117. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/laravel/tests.py +0 -0
  118. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/native/__init__.py +0 -0
  119. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/native/gates.py +0 -0
  120. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/native/runner.py +0 -0
  121. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/native/scipread.py +0 -0
  122. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/native/ts.py +0 -0
  123. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/nest/__init__.py +0 -0
  124. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/nest/plugin.py +0 -0
  125. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/nextjs/__init__.py +0 -0
  126. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/nextjs/plugin.py +0 -0
  127. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/nuxt/__init__.py +0 -0
  128. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/nuxt/plugin.py +0 -0
  129. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/__init__.py +0 -0
  130. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/external.py +0 -0
  131. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/extractor/composer.json +0 -0
  132. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/extractor/composer.lock +0 -0
  133. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/strings.py +0 -0
  134. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/__init__.py +0 -0
  135. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/aitools.py +0 -0
  136. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/external.py +0 -0
  137. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/fields.py +0 -0
  138. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/plugin.py +0 -0
  139. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/refs.py +0 -0
  140. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/roots.py +0 -0
  141. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/socketio.py +0 -0
  142. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/subproc.py +0 -0
  143. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/tests.py +0 -0
  144. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/values.py +0 -0
  145. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/pyweb/__init__.py +0 -0
  146. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/pyweb/plugin.py +0 -0
  147. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/pyweb/values.py +0 -0
  148. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/rust/__init__.py +0 -0
  149. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/rust/cargo.py +0 -0
  150. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/rust/plugin.py +0 -0
  151. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/rust/syntax.py +0 -0
  152. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/scip/__init__.py +0 -0
  153. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/scip/importer.py +0 -0
  154. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/scip/scip.proto +0 -0
  155. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/scip/scip_pb2.py +0 -0
  156. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/stubs/__init__.py +0 -0
  157. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/stubs/plugins.py +0 -0
  158. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/swift/__init__.py +0 -0
  159. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/swift/baseurl.py +0 -0
  160. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/swift/exact.py +0 -0
  161. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/swift/indexstore.py +0 -0
  162. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/swift/packages.py +0 -0
  163. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/swift/plugin.py +0 -0
  164. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/__init__.py +0 -0
  165. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/baseurl.py +0 -0
  166. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/extractor/package-lock.json +0 -0
  167. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/extractor/package.json +0 -0
  168. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/extractor/rr.mjs +0 -0
  169. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/nav.py +0 -0
  170. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/plugin.py +0 -0
  171. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/react_router.py +0 -0
  172. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/tsweb/__init__.py +0 -0
  173. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/tsweb/common.py +0 -0
  174. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/tsweb/data.py +0 -0
  175. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/__init__.py +0 -0
  176. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/c_cpp.yaml +0 -0
  177. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/common.yaml +0 -0
  178. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/dart.yaml +0 -0
  179. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/django-ninja.yaml +0 -0
  180. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/django.yaml +0 -0
  181. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/djangorestframework.yaml +0 -0
  182. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/express.yaml +0 -0
  183. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/java.yaml +0 -0
  184. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/kotlin.yaml +0 -0
  185. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/laravel.yaml +0 -0
  186. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/nest.yaml +0 -0
  187. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/nextjs.yaml +0 -0
  188. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/nuxt.yaml +0 -0
  189. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/php.yaml +0 -0
  190. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/python.yaml +0 -0
  191. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/react-router.yaml +0 -0
  192. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/rust.yaml +0 -0
  193. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/spring.yaml +0 -0
  194. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/swift.yaml +0 -0
  195. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/typescript.yaml +0 -0
  196. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/process_runs.py +0 -0
  197. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/protocols/__init__.py +0 -0
  198. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/protocols/builtin.py +0 -0
  199. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/protocols/matchers.py +0 -0
  200. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/realtime.py +0 -0
  201. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/realtime_events.py +0 -0
  202. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/realtime_native.py +0 -0
  203. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/realtime_ws.py +0 -0
  204. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/roundtrip.py +0 -0
  205. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/rpc.py +0 -0
  206. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/sdk_systems.py +0 -0
  207. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/sockets.py +0 -0
  208. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/starters.py +0 -0
  209. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/tests_index.py +0 -0
  210. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/udp_apps.py +0 -0
  211. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/__init__.py +0 -0
  212. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/graph.py +0 -0
  213. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/server.py +0 -0
  214. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/app.css +0 -0
  215. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/app.js +0 -0
  216. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/index.html +0 -0
  217. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/layered.js +0 -0
  218. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/vendor/VERSIONS.txt +0 -0
  219. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/vendor/cose-base.js +0 -0
  220. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/vendor/cytoscape-fcose.js +0 -0
  221. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/vendor/cytoscape.min.js +0 -0
  222. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/vendor/layout-base.js +0 -0
  223. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/tools/package-lock.json +0 -0
  224. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/tools/package.json +0 -0
  225. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/tools/shoot.mjs +0 -0
  226. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/watch.py +0 -0
  227. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/xcode.py +0 -0
  228. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph.egg-info/dependency_links.txt +0 -0
  229. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph.egg-info/entry_points.txt +0 -0
  230. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph.egg-info/requires.txt +0 -0
  231. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph.egg-info/top_level.txt +0 -0
  232. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/pyproject.toml +0 -0
  233. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/setup.cfg +0 -0
  234. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_affected.py +0 -0
  235. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_agents_install.py +0 -0
  236. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_agents_md.py +0 -0
  237. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_ai_tools.py +0 -0
  238. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_android_ipc.py +0 -0
  239. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_apple_ipc.py +0 -0
  240. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_astro.py +0 -0
  241. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_astro_content.py +0 -0
  242. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_astro_server.py +0 -0
  243. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_bridges.py +0 -0
  244. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_bridges_pigeon_monorepo.py +0 -0
  245. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_brokers.py +0 -0
  246. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_cache_clean.py +0 -0
  247. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_cache_invalidation.py +0 -0
  248. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_cache_migration.py +0 -0
  249. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_channels_tests.py +0 -0
  250. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_cli_output.py +0 -0
  251. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_completeness.py +0 -0
  252. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_config_extras.py +0 -0
  253. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_coverage_ts_hint.py +0 -0
  254. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_dart_isolates.py +0 -0
  255. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_dispatch_interfaces.py +0 -0
  256. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_django_flutter.py +0 -0
  257. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_docs_languages.py +0 -0
  258. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_docs_links.py +0 -0
  259. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_enum_constants.py +0 -0
  260. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_env.py +0 -0
  261. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_explore.py +0 -0
  262. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_external.py +0 -0
  263. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_gating.py +0 -0
  264. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_generated.py +0 -0
  265. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_graphql.py +0 -0
  266. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_hooks.py +0 -0
  267. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_impact_overrides.py +0 -0
  268. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_index_isolation.py +0 -0
  269. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_inherited_overrides.py +0 -0
  270. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_java.py +0 -0
  271. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_java_exact.py +0 -0
  272. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_jobs.py +0 -0
  273. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_js_refs.py +0 -0
  274. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_js_runtime.py +0 -0
  275. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_kotlin.py +0 -0
  276. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_kotlin_binding.py +0 -0
  277. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_kotlin_exact.py +0 -0
  278. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_kotlin_fields.py +0 -0
  279. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_kotlin_mixed.py +0 -0
  280. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_kotlin_nav_consts.py +0 -0
  281. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_kotlin_props.py +0 -0
  282. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_laravel_determinism.py +0 -0
  283. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_laravel_resources.py +0 -0
  284. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_lint_async.py +0 -0
  285. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_local_ipc.py +0 -0
  286. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_local_sockets.py +0 -0
  287. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_mcp.py +0 -0
  288. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_mcp_index.py +0 -0
  289. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_mcp_install.py +0 -0
  290. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_native_cfamily.py +0 -0
  291. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_native_concurrency.py +0 -0
  292. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_native_rust.py +0 -0
  293. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_nuxt_import_dirs.py +0 -0
  294. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_nuxt_rootsrc.py +0 -0
  295. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_packaging.py +0 -0
  296. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_parity.py +0 -0
  297. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_parity_structure.py +0 -0
  298. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_php_props.py +0 -0
  299. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_plain_js.py +0 -0
  300. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_plans.py +0 -0
  301. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_platform_gaps.py +0 -0
  302. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_platform_type_variants.py +0 -0
  303. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_platforms.py +0 -0
  304. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_platforms_apple.py +0 -0
  305. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_presets_config.py +0 -0
  306. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_process_ipc.py +0 -0
  307. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_process_runs.py +0 -0
  308. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_protocols.py +0 -0
  309. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_app_tests_module.py +0 -0
  310. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_collection_chains.py +0 -0
  311. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_compat.py +0 -0
  312. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_entry_refs.py +0 -0
  313. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_fields.py +0 -0
  314. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_infer_cycles.py +0 -0
  315. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_roots.py +0 -0
  316. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_subprocess_tests.py +0 -0
  317. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_tests.py +0 -0
  318. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_web_hosts.py +0 -0
  319. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_web_routes.py +0 -0
  320. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_web_routes_more.py +0 -0
  321. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_react_router.py +0 -0
  322. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_realtime.py +0 -0
  323. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_realtime_native.py +0 -0
  324. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_roundtrip.py +0 -0
  325. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_route_tables.py +0 -0
  326. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_rpc.py +0 -0
  327. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_scip_health.py +0 -0
  328. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_sdk_external.py +0 -0
  329. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_snippet.py +0 -0
  330. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_sockets.py +0 -0
  331. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_spring.py +0 -0
  332. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_starters_budget.py +0 -0
  333. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift.py +0 -0
  334. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_binding.py +0 -0
  335. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_exact.py +0 -0
  336. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_facts.py +0 -0
  337. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_fields.py +0 -0
  338. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_modules.py +0 -0
  339. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_more.py +0 -0
  340. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_nav_values.py +0 -0
  341. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_parse_errors.py +0 -0
  342. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_props.py +0 -0
  343. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_sdk.py +0 -0
  344. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_testing.py +0 -0
  345. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_values.py +0 -0
  346. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_tests_scope.py +0 -0
  347. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_ts_fields.py +0 -0
  348. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_ts_fields_js_vue.py +0 -0
  349. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_ts_file_specs.py +0 -0
  350. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_ts_frameworks.py +0 -0
  351. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_ts_src_tsconfig.py +0 -0
  352. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_values.py +0 -0
  353. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_viz.py +0 -0
  354. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_vue_nav.py +0 -0
  355. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_watch.py +0 -0
  356. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_webhooks.py +0 -0
  357. {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_websockets.py +0 -0
  358. {cg_code_graph-0.19.1 → cg_code_graph-0.20.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.19.1
3
+ Version: 0.20.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
@@ -33,11 +33,11 @@ Dynamic: license-file
33
33
 
34
34
  For you and your AI agent (CLI + MCP). No LLM in the graph.
35
35
 
36
- [![PyPI](https://img.shields.io/pypi/v/cg-code-graph)](https://pypi.org/project/cg-code-graph/) [![Downloads](https://img.shields.io/pepy/dt/cg-code-graph)](https://pepy.tech/project/cg-code-graph) [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-3776AB)](https://pypi.org/project/cg-code-graph/) [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE) [![Docs](https://img.shields.io/badge/docs-code--graph-93b600)](https://code-graph.cyberchronos00.workers.dev/)
36
+ [![PyPI](https://img.shields.io/pypi/v/cg-code-graph)](https://pypi.org/project/cg-code-graph/) [![Downloads](https://img.shields.io/pepy/dt/cg-code-graph)](https://pepy.tech/project/cg-code-graph) [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-3776AB)](https://pypi.org/project/cg-code-graph/) [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/cyberchronos00/code-graph/blob/main/LICENSE) [![Docs](https://img.shields.io/badge/docs-code--graph-93b600)](https://code-graph.cyberchronos00.workers.dev/)
37
37
 
38
- [Docs](https://code-graph.cyberchronos00.workers.dev/) · [Quick start](https://code-graph.cyberchronos00.workers.dev/docs/quickstart) · [CLI](https://code-graph.cyberchronos00.workers.dev/docs/cli) · [MCP](https://code-graph.cyberchronos00.workers.dev/docs/mcp) · [Changelog](CHANGELOG.md)
38
+ [Docs](https://code-graph.cyberchronos00.workers.dev/) · [Quick start](https://code-graph.cyberchronos00.workers.dev/docs/quickstart) · [CLI](https://code-graph.cyberchronos00.workers.dev/docs/cli) · [MCP](https://code-graph.cyberchronos00.workers.dev/docs/mcp) · [Changelog](https://github.com/cyberchronos00/code-graph/blob/main/CHANGELOG.md)
39
39
 
40
- [![cg visual view: the graph around the warehouse connection, a selected node with its evidence paths and source, then the planned-change overlay for the preorders plan](docs/media/cg-view-preview.gif)](docs/media/cg-view-demo.mp4)
40
+ [![cg visual view: the graph around the warehouse connection, a selected node with its evidence paths and source, then the planned-change overlay for the preorders plan](https://raw.githubusercontent.com/cyberchronos00/code-graph/main/docs/media/cg-view-preview.gif)](https://github.com/cyberchronos00/code-graph/blob/main/docs/media/cg-view-demo.mp4)
41
41
 
42
42
  </div>
43
43
 
@@ -67,7 +67,7 @@ dependents: 18 nodes (code: 6, entry points: 9, other: 3)
67
67
  -READS_TABLE[resolved @ catalog/tasks.py:14]-> table:store_books
68
68
  ```
69
69
 
70
- [![Terminal demo: cg indexes the bundled Laravel and Nuxt sample apps, links them, then runs reaches, path, impact and plan check](docs/media/cg-terminal-demo.gif)](docs/media/cg-terminal-demo.mp4)
70
+ [![Terminal demo: cg indexes the bundled Laravel and Nuxt sample apps, links them, then runs reaches, path, impact and plan check](https://raw.githubusercontent.com/cyberchronos00/code-graph/main/docs/media/cg-terminal-demo.gif)](https://github.com/cyberchronos00/code-graph/blob/main/docs/media/cg-terminal-demo.mp4)
71
71
 
72
72
  ## Supported languages
73
73
 
@@ -75,7 +75,7 @@ dependents: 18 nodes (code: 6, entry points: 9, other: 3)
75
75
  |---|---|---|---|
76
76
  | ![Python](https://img.shields.io/badge/Python-3776AB?logo=python&logoColor=white) | Django (+ DRF, django-ninja), FastAPI / Starlette, Flask, Celery | exact (stdlib `ast`) | [Python](https://code-graph.cyberchronos00.workers.dev/docs/python) |
77
77
  | ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white) ![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=black) | Nuxt / Vue, NestJS, Next.js, Astro, Express, Fastify, Koa, Hono, Elysia, React Router / Remix | exact (TypeScript compiler API) | [TS frameworks](https://code-graph.cyberchronos00.workers.dev/docs/ts-frameworks) |
78
- | ![PHP](https://img.shields.io/badge/PHP-777BB4?logo=php&logoColor=white) | Laravel (routes, middleware, queues, broadcasting, outbound HTTP) | exact (nikic/php-parser; needs PHP 8.2+) | [PHP](https://code-graph.cyberchronos00.workers.dev/docs/php) |
78
+ | ![PHP](https://img.shields.io/badge/PHP-777BB4?logo=php&logoColor=white) | Laravel (routes, route attributes, middleware, queues, broadcasting, outbound HTTP) | exact (nikic/php-parser; needs PHP 8.2+) | [PHP](https://code-graph.cyberchronos00.workers.dev/docs/php) |
79
79
  | ![Dart](https://img.shields.io/badge/Dart-0175C2?logo=dart&logoColor=white) | Flutter | exact (Dart analyzer; needs the Dart SDK) | [Platforms](https://code-graph.cyberchronos00.workers.dev/docs/platforms) |
80
80
  | ![Kotlin](https://img.shields.io/badge/Kotlin-7F52FF?logo=kotlin&logoColor=white) | Android, Kotlin Multiplatform, Spring | heuristic; exact with scip-java (opt-in) | [Kotlin](https://code-graph.cyberchronos00.workers.dev/docs/kotlin) |
81
81
  | ![Java](https://img.shields.io/badge/Java-ED8B00?logo=openjdk&logoColor=white) | Spring (routes, guards, beans, tables, clients) | heuristic; exact with scip-java (opt-in) | [Java](https://code-graph.cyberchronos00.workers.dev/docs/java) |
@@ -88,8 +88,8 @@ Go and others: import a SCIP index with `cg index --scip FILE`. Run `cg doctor`
88
88
 
89
89
  - `reaches`, `impact`, `path`: every caller, route, job and page that reaches a table, config key or method.
90
90
  - `cg affected --base main`: the tests and entry points a change reaches (`--quiet` feeds your test runner).
91
- - Across repos: `cg link` matches client HTTP calls (frontends and Laravel / Guzzle backends) to server routes.
92
- - Route guards: `cg routes --writes --unguarded` lists routes that write with no auth guard.
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
+ - 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
93
  - MCP server (`cg-mcp`) so agents query the graph instead of grepping.
94
94
  - Visual view (`cg serve`) and plan checks (`cg plan check`) for planned changes.
95
95
 
@@ -97,6 +97,6 @@ Go and others: import a SCIP index with `cg index --scip FILE`. Run `cg doctor`
97
97
 
98
98
  [Install](https://code-graph.cyberchronos00.workers.dev/docs/install) · [Quick start](https://code-graph.cyberchronos00.workers.dev/docs/quickstart) · [CLI](https://code-graph.cyberchronos00.workers.dev/docs/cli) · [MCP](https://code-graph.cyberchronos00.workers.dev/docs/mcp) · [Configuration](https://code-graph.cyberchronos00.workers.dev/docs/configuration) · [Architecture](https://code-graph.cyberchronos00.workers.dev/docs/architecture) · [Limitations](https://code-graph.cyberchronos00.workers.dev/docs/limitations)
99
99
 
100
- [Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md) · [Security](SECURITY.md) · MIT [License](LICENSE)
100
+ [Changelog](https://github.com/cyberchronos00/code-graph/blob/main/CHANGELOG.md) · [Contributing](https://github.com/cyberchronos00/code-graph/blob/main/CONTRIBUTING.md) · [Security](https://github.com/cyberchronos00/code-graph/blob/main/SECURITY.md) · MIT [License](https://github.com/cyberchronos00/code-graph/blob/main/LICENSE)
101
101
 
102
- More recordings: [docs/media](docs/media).
102
+ More recordings: [docs/media](https://github.com/cyberchronos00/code-graph/tree/main/docs/media).
@@ -6,11 +6,11 @@
6
6
 
7
7
  For you and your AI agent (CLI + MCP). No LLM in the graph.
8
8
 
9
- [![PyPI](https://img.shields.io/pypi/v/cg-code-graph)](https://pypi.org/project/cg-code-graph/) [![Downloads](https://img.shields.io/pepy/dt/cg-code-graph)](https://pepy.tech/project/cg-code-graph) [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-3776AB)](https://pypi.org/project/cg-code-graph/) [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE) [![Docs](https://img.shields.io/badge/docs-code--graph-93b600)](https://code-graph.cyberchronos00.workers.dev/)
9
+ [![PyPI](https://img.shields.io/pypi/v/cg-code-graph)](https://pypi.org/project/cg-code-graph/) [![Downloads](https://img.shields.io/pepy/dt/cg-code-graph)](https://pepy.tech/project/cg-code-graph) [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-3776AB)](https://pypi.org/project/cg-code-graph/) [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](https://github.com/cyberchronos00/code-graph/blob/main/LICENSE) [![Docs](https://img.shields.io/badge/docs-code--graph-93b600)](https://code-graph.cyberchronos00.workers.dev/)
10
10
 
11
- [Docs](https://code-graph.cyberchronos00.workers.dev/) · [Quick start](https://code-graph.cyberchronos00.workers.dev/docs/quickstart) · [CLI](https://code-graph.cyberchronos00.workers.dev/docs/cli) · [MCP](https://code-graph.cyberchronos00.workers.dev/docs/mcp) · [Changelog](CHANGELOG.md)
11
+ [Docs](https://code-graph.cyberchronos00.workers.dev/) · [Quick start](https://code-graph.cyberchronos00.workers.dev/docs/quickstart) · [CLI](https://code-graph.cyberchronos00.workers.dev/docs/cli) · [MCP](https://code-graph.cyberchronos00.workers.dev/docs/mcp) · [Changelog](https://github.com/cyberchronos00/code-graph/blob/main/CHANGELOG.md)
12
12
 
13
- [![cg visual view: the graph around the warehouse connection, a selected node with its evidence paths and source, then the planned-change overlay for the preorders plan](docs/media/cg-view-preview.gif)](docs/media/cg-view-demo.mp4)
13
+ [![cg visual view: the graph around the warehouse connection, a selected node with its evidence paths and source, then the planned-change overlay for the preorders plan](https://raw.githubusercontent.com/cyberchronos00/code-graph/main/docs/media/cg-view-preview.gif)](https://github.com/cyberchronos00/code-graph/blob/main/docs/media/cg-view-demo.mp4)
14
14
 
15
15
  </div>
16
16
 
@@ -40,7 +40,7 @@ dependents: 18 nodes (code: 6, entry points: 9, other: 3)
40
40
  -READS_TABLE[resolved @ catalog/tasks.py:14]-> table:store_books
41
41
  ```
42
42
 
43
- [![Terminal demo: cg indexes the bundled Laravel and Nuxt sample apps, links them, then runs reaches, path, impact and plan check](docs/media/cg-terminal-demo.gif)](docs/media/cg-terminal-demo.mp4)
43
+ [![Terminal demo: cg indexes the bundled Laravel and Nuxt sample apps, links them, then runs reaches, path, impact and plan check](https://raw.githubusercontent.com/cyberchronos00/code-graph/main/docs/media/cg-terminal-demo.gif)](https://github.com/cyberchronos00/code-graph/blob/main/docs/media/cg-terminal-demo.mp4)
44
44
 
45
45
  ## Supported languages
46
46
 
@@ -48,7 +48,7 @@ dependents: 18 nodes (code: 6, entry points: 9, other: 3)
48
48
  |---|---|---|---|
49
49
  | ![Python](https://img.shields.io/badge/Python-3776AB?logo=python&logoColor=white) | Django (+ DRF, django-ninja), FastAPI / Starlette, Flask, Celery | exact (stdlib `ast`) | [Python](https://code-graph.cyberchronos00.workers.dev/docs/python) |
50
50
  | ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white) ![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=black) | Nuxt / Vue, NestJS, Next.js, Astro, Express, Fastify, Koa, Hono, Elysia, React Router / Remix | exact (TypeScript compiler API) | [TS frameworks](https://code-graph.cyberchronos00.workers.dev/docs/ts-frameworks) |
51
- | ![PHP](https://img.shields.io/badge/PHP-777BB4?logo=php&logoColor=white) | Laravel (routes, middleware, queues, broadcasting, outbound HTTP) | exact (nikic/php-parser; needs PHP 8.2+) | [PHP](https://code-graph.cyberchronos00.workers.dev/docs/php) |
51
+ | ![PHP](https://img.shields.io/badge/PHP-777BB4?logo=php&logoColor=white) | Laravel (routes, route attributes, middleware, queues, broadcasting, outbound HTTP) | exact (nikic/php-parser; needs PHP 8.2+) | [PHP](https://code-graph.cyberchronos00.workers.dev/docs/php) |
52
52
  | ![Dart](https://img.shields.io/badge/Dart-0175C2?logo=dart&logoColor=white) | Flutter | exact (Dart analyzer; needs the Dart SDK) | [Platforms](https://code-graph.cyberchronos00.workers.dev/docs/platforms) |
53
53
  | ![Kotlin](https://img.shields.io/badge/Kotlin-7F52FF?logo=kotlin&logoColor=white) | Android, Kotlin Multiplatform, Spring | heuristic; exact with scip-java (opt-in) | [Kotlin](https://code-graph.cyberchronos00.workers.dev/docs/kotlin) |
54
54
  | ![Java](https://img.shields.io/badge/Java-ED8B00?logo=openjdk&logoColor=white) | Spring (routes, guards, beans, tables, clients) | heuristic; exact with scip-java (opt-in) | [Java](https://code-graph.cyberchronos00.workers.dev/docs/java) |
@@ -61,8 +61,8 @@ Go and others: import a SCIP index with `cg index --scip FILE`. Run `cg doctor`
61
61
 
62
62
  - `reaches`, `impact`, `path`: every caller, route, job and page that reaches a table, config key or method.
63
63
  - `cg affected --base main`: the tests and entry points a change reaches (`--quiet` feeds your test runner).
64
- - Across repos: `cg link` matches client HTTP calls (frontends and Laravel / Guzzle backends) to server routes.
65
- - Route guards: `cg routes --writes --unguarded` lists routes that write with no auth guard.
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
+ - 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
66
  - MCP server (`cg-mcp`) so agents query the graph instead of grepping.
67
67
  - Visual view (`cg serve`) and plan checks (`cg plan check`) for planned changes.
68
68
 
@@ -70,6 +70,6 @@ Go and others: import a SCIP index with `cg index --scip FILE`. Run `cg doctor`
70
70
 
71
71
  [Install](https://code-graph.cyberchronos00.workers.dev/docs/install) · [Quick start](https://code-graph.cyberchronos00.workers.dev/docs/quickstart) · [CLI](https://code-graph.cyberchronos00.workers.dev/docs/cli) · [MCP](https://code-graph.cyberchronos00.workers.dev/docs/mcp) · [Configuration](https://code-graph.cyberchronos00.workers.dev/docs/configuration) · [Architecture](https://code-graph.cyberchronos00.workers.dev/docs/architecture) · [Limitations](https://code-graph.cyberchronos00.workers.dev/docs/limitations)
72
72
 
73
- [Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md) · [Security](SECURITY.md) · MIT [License](LICENSE)
73
+ [Changelog](https://github.com/cyberchronos00/code-graph/blob/main/CHANGELOG.md) · [Contributing](https://github.com/cyberchronos00/code-graph/blob/main/CONTRIBUTING.md) · [Security](https://github.com/cyberchronos00/code-graph/blob/main/SECURITY.md) · MIT [License](https://github.com/cyberchronos00/code-graph/blob/main/LICENSE)
74
74
 
75
- More recordings: [docs/media](docs/media).
75
+ More recordings: [docs/media](https://github.com/cyberchronos00/code-graph/tree/main/docs/media).
@@ -1,2 +1,2 @@
1
1
  """code-graph: deterministic code knowledge graph (beta)."""
2
- __version__ = "0.19.1"
2
+ __version__ = "0.20.0"
@@ -171,7 +171,8 @@ def main(argv=None):
171
171
  p.add_argument("--writes", nargs="?", const="*", metavar="TABLE", help="routes reaching a DB write (any table, or TABLE)")
172
172
  p.add_argument("--reaches", nargs="+", metavar="SPEC", help="routes reaching any of these nodes (table, column, connection:, env:, Class.method)")
173
173
  p.add_argument("--missing", metavar="NAME", help="keep routes with no guard whose name contains NAME (e.g. auth:api, ApiKeyGuard)")
174
- p.add_argument("--unguarded", action="store_true", help="keep routes with no auth guard (framework presets, the auth name pattern, .cg.yaml auth.extra_patterns and --auth-pattern)")
174
+ p.add_argument("--unguarded", action="store_true", help="keep routes with no auth guard (framework presets, the auth name pattern, .cg.yaml auth.extra_patterns and --auth-pattern). An unconditional inline check (Laravel authorize / permission / shared secret) counts; --strict ignores it")
175
+ p.add_argument("--strict", action="store_true", help="with --unguarded, count only route-level guards (inline checks are still listed)")
175
176
  p.add_argument("--auth-pattern", help="extra regex for guard names that count as auth")
176
177
  p.add_argument("--min-confidence", default="heuristic", choices=["heuristic", "resolved", "exact"])
177
178
  p.add_argument("--max-items", type=int, default=200); p.add_argument("--no-paths", action="store_true"); p.add_argument("--json", action="store_true")
@@ -548,7 +549,7 @@ def main(argv=None):
548
549
  if a.cmd == "routes":
549
550
  from . import routes as R
550
551
  res = R.routes_report(st, writes=a.writes, reaches=a.reaches, missing=a.missing, unguarded=a.unguarded,
551
- auth_pattern=a.auth_pattern, min_conf=a.min_confidence, platform=a.platform)
552
+ strict=a.strict, auth_pattern=a.auth_pattern, min_conf=a.min_confidence, platform=a.platform)
552
553
  if a.json:
553
554
  res["completeness"] = R.route_completeness(st)
554
555
  print(json.dumps(res, indent=1, default=str) if a.json else R.render_routes(res, st, max_items=a.max_items, paths=not a.no_paths))
@@ -338,6 +338,11 @@ def compute(root: str | Path, plugins: dict, scip_imported: bool = False, report
338
338
  e["syntax_error_files"] = len(se)
339
339
  e["parsed_with_errors"] = sum(1 for x in se if not x.get("parse_failed"))
340
340
  e["decls_lost"] = sum(x.get("decls_lost", 0) for x in se)
341
+ if lang == "typescript" and st and st.get("realtime_subscriptions_unresolved_receiver"):
342
+ e["realtime_subscriptions_unresolved_receiver"] = st["realtime_subscriptions_unresolved_receiver"]
343
+ at = st.get("realtime_subscriptions_unresolved_receiver_at") or []
344
+ if at:
345
+ e["realtime_subscriptions_unresolved_receiver_at"] = at[:8]
341
346
  if lang == "python" and st:
342
347
  for k in ("roots_mode", "source_roots", "roots_warnings", "roots_ambiguous", "module_name_collisions"):
343
348
  if st.get(k):
@@ -497,6 +502,16 @@ def _entry_text(e: dict) -> str:
497
502
  return f"{e['language']} {e['files']} {e['status'].replace('_', ' ')}{errs}"
498
503
 
499
504
 
505
+ 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":
509
+ 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}"]
513
+
514
+
500
515
  def syntax_error_lines(e: dict, all_files: bool = False, indent: str = " ") -> list[str]:
501
516
  """'swift: syntax errors in 8 files, 12 declarations lost (most first):' and one line per file with its error
502
517
  line spans and the declarations lost there (5 files unless all_files), #73."""
@@ -702,6 +717,7 @@ def render(covs: dict[str, dict | None], all_files: bool = False) -> str:
702
717
  out.append(f" fix: {e['hint']}")
703
718
  for e in (cov or {}).get("languages", []):
704
719
  out += syntax_error_lines(e, all_files)
720
+ out += realtime_unresolved_lines(e)
705
721
  for e in (cov or {}).get("languages", []): # which mode an exact-capable language ran in, and why
706
722
  if e["language"] in ("kotlin", "swift", "java") and not _is_gap(e) and e.get("reason"):
707
723
  out.append(f" {e['language']}: {e['files']} files {e['status']}: {e['reason']}")
@@ -904,13 +904,16 @@ def explore(query: str, budget_tokens: int = 3000, max_symbols: int = 4) -> str:
904
904
 
905
905
  @tool
906
906
  def routes(writes: str | None = None, reaches: list[str] | None = None, missing: str | None = None,
907
- unguarded: bool = False, auth_pattern: str | None = None, max_items: int = 40, paths: bool = True,
907
+ unguarded: bool = False, strict: bool = False, auth_pattern: str | None = None, max_items: int = 40, paths: bool = True,
908
908
  min_confidence: str = "heuristic", platform: str | None = None) -> str:
909
909
  """Routes with their middleware / guards / auth, in one call. Optional scope: writes="*" (routes that reach any
910
910
  DB write) or writes="<table>", reaches=[specs] (routes that reach any of these nodes: table, column, connection,
911
911
  method, env key...). Optional filters: missing="<name>" keeps routes with no guard whose name contains it (e.g.
912
912
  "auth:api", "ApiKeyGuard"), unguarded=true keeps routes with no auth guard (the framework presets' auth guards and
913
- the auth name pattern; extend with auth_pattern, a regex, or .cg.yaml auth.extra_patterns). Each route: guards, what it reaches with one evidence chain, and the frontend callers on a
913
+ the auth name pattern; extend with auth_pattern, a regex, or .cg.yaml auth.extra_patterns). An unconditional
914
+ inline check on a Laravel action (authorize, Gate, a permission check, FormRequest::authorize, controller
915
+ middleware, a shared-secret compare) is listed under guards as `inline:` and counts for unguarded. strict=true
916
+ with unguarded counts only route-level guards. Each route: guards, what it reaches with one evidence chain, and the frontend callers on a
914
917
  combined graph. Guards come from Laravel middleware, Nest guards/interceptors, Express/Koa/Fastify/Hono/Elysia
915
918
  middleware, Next.js middleware.ts / handler wrappers, django-ninja auth= and Django/DRF view access checks.
916
919
  min_confidence: keep the default (heuristic) for reviews: every edge still shows its own label, and a stricter
@@ -923,7 +926,7 @@ def routes(writes: str | None = None, reaches: list[str] | None = None, missing:
923
926
  st = _st()
924
927
  pf, _ = _platform(st, platform)
925
928
  _scope(whole=True, categories=("route",), unsupported=False)
926
- res = R.routes_report(st, writes=writes, reaches=reaches, missing=missing, unguarded=unguarded,
929
+ res = R.routes_report(st, writes=writes, reaches=reaches, missing=missing, unguarded=unguarded, strict=strict,
927
930
  auth_pattern=auth_pattern, min_conf=min_confidence, platform=pf)
928
931
  return R.render_routes(res, st, max_items=max_items, paths=paths, compact=True)
929
932
 
@@ -1149,6 +1152,12 @@ def api_calls(filter: str = "all", max_items: int = 60) -> str:
1149
1152
  for c in r["calls"][:3]:
1150
1153
  h = f" via {short(c['via_helper']['fn'])}" if c.get("via_helper") else ""
1151
1154
  out.append(f" ← {short(c['caller'])} @ {at(c['at'])}{h}")
1155
+ bk = Q._key_names(c.get("body_keys"))
1156
+ qk = Q._key_names(c.get("query_keys"))
1157
+ if bk:
1158
+ out.append(f" body keys: {', '.join(bk)}")
1159
+ if qk:
1160
+ out.append(f" query keys: {', '.join(qk)}")
1152
1161
  for g in gaps.get(r["endpoint"], [])[:3]:
1153
1162
  out.append(f" ! {short(g['caller'])} @ {at(g['call_at'])} passes {', '.join(g['dropped'])}: sent but not forwarded "
1154
1163
  f"(request sends {', '.join(g['request_keys'])})")
@@ -1,8 +1,10 @@
1
1
  """Payload / field contract check between a client endpoint and the backend route it matched.
2
2
 
3
3
  Inputs (all from the graphs, no execution):
4
- client HTTP_CALLS edge attrs: body_keys [{key,type,line,file,model?}], response_models [{model,path,line}],
5
- response_keys [{key,path,line,cast,coalesce}], status_checks [{op,v,line}], trailing_slash;
4
+ client HTTP_CALLS edge attrs: body_keys and query_keys, either the Dart / Kotlin / Swift list
5
+ [{key,type,line,file,model?}] or the TypeScript / PHP dict {keys, conditional, opaque},
6
+ response_models [{model,path,line}], response_keys [{key,path,line,cast,coalesce}],
7
+ status_checks [{op,v,line}], trailing_slash;
6
8
  model class attrs json_from / json_to (key, field, type, cast, nullable, required, ref, many), enum_values.
7
9
  server route attrs: request {schemas:[{class,location}], keys:[...]}, response {schemas:[{class,status}],
8
10
  shapes:[{status, keys: shape}]} (django/shapes.py), trailing_slash, framework, auth;
@@ -19,16 +21,49 @@ import re
19
21
 
20
22
  DART_KIND = {"String": "str", "int": "int", "double": "float", "num": "number", "bool": "bool", "List": "list", "Iterable": "list",
21
23
  "Set": "list", "Map": "dict", "DateTime": "datetime", "dynamic": "any", "Object": "any", "Uri": "str", "Duration": "any"}
22
- SERVER_KIND = {"str": "str", "int": "int", "float": "float", "number": "number", "decimal": "decimal", "bool": "bool", "uuid": "str",
23
- "datetime": "str", "date": "str", "time": "str", "duration": "any", "list": "list", "dict": "dict", "json": "any",
24
- "file": "str", "model": "any", "any": "any", "null": "null", "email": "str", "url": "str", "fk": "int",
25
- "m2m": "list", "bytes": "str", "enum": "str"}
24
+ SERVER_KIND = {"str": "str", "string": "str", "int": "int", "integer": "int", "float": "float", "number": "number",
25
+ "numeric": "number", "decimal": "decimal", "bool": "bool", "boolean": "bool", "uuid": "str",
26
+ "datetime": "str", "date": "str", "time": "str", "duration": "any", "list": "list", "array": "list",
27
+ "dict": "dict", "json": "any", "file": "str", "model": "any", "any": "any", "null": "null", "email": "str",
28
+ "url": "str", "fk": "int", "m2m": "list", "bytes": "str", "enum": "str"}
26
29
 
27
30
 
28
31
  def norm_key(k: str) -> str:
29
32
  return re.sub(r"[_\-]", "", k).lower()
30
33
 
31
34
 
35
+ def _canon_parts(key: str) -> list[str]:
36
+ """Case-folded path. Numeric indexes and `*` are the same wildcard segment."""
37
+ out = []
38
+ for part in str(key).split("."):
39
+ if part == "*" or part.isdigit():
40
+ out.append("*")
41
+ else:
42
+ out.append(norm_key(part))
43
+ return out
44
+
45
+
46
+ def _struct_parts(key: str) -> list[str]:
47
+ """Path with indexes folded to `*`, spelling kept, so `items.0.id` matches `items.*.id`."""
48
+ return ["*" if p == "*" or p.isdigit() else p for p in str(key).split(".")]
49
+
50
+
51
+ def _canon_eq(a: str, b: str) -> bool:
52
+ """Same key under case-folding. `items.book_id` matches `items.*.bookId`."""
53
+ pa, pb = _canon_parts(a), _canon_parts(b)
54
+ if pa == pb:
55
+ return True
56
+ sa, sb = [p for p in pa if p != "*"], [p for p in pb if p != "*"]
57
+ return sa == sb and ("*" in pa or "*" in pb)
58
+
59
+
60
+ def _route_params(uri: str | None) -> set[str]:
61
+ return {m.rstrip("?") for m in re.findall(r"\{([^}/]+)\}", uri or "")}
62
+
63
+
64
+ _CASE_MW = re.compile(r"convert[_-]?case|camel[_-]?case|snake[_-]?case", re.I)
65
+
66
+
32
67
  def dart_kind(t: str | None, enums: set[str] | None = None) -> tuple[str | None, bool]:
33
68
  if not t:
34
69
  return None, False
@@ -149,31 +184,155 @@ class Checker:
149
184
  f"server success responses use {sorted(ok)}", client_check=self.at("fe", sc.get("file"), sc.get("line")))
150
185
  return issues
151
186
 
152
- def check_request(self, ra, call, add):
153
- req = ra.get("request") or {}
154
- fields = {}
155
- for sc in req.get("schemas") or []:
156
- if sc.get("location") in ("body", "form", None):
157
- for k, v in self.schema_shape(sc["class"]).items():
158
- if not v.get("read_only"):
159
- fields[k] = dict(v, partial=sc.get("partial"))
160
- for k in req.get("keys") or []:
161
- if k.get("location") in ("body", "form"):
162
- fields.setdefault(k["name"], {"type": None, "required": not k.get("optional"), "line": k.get("line"), "file": k.get("file")})
163
- body = call.get("body_keys") or []
187
+ def _call_pos(self, call) -> tuple:
188
+ """file, line from a link call (`repo/file:line`) or a single-graph call (`file:line`)."""
189
+ at = call.get("at") or ""
190
+ file, _, line = at.rpartition(":")
191
+ if self.fe_name and file.startswith(self.fe_name + "/"):
192
+ file = file[len(self.fe_name) + 1:]
193
+ return (file or None), (int(line) if line.isdigit() else None)
194
+
195
+ def normalize_keys(self, raw, call) -> tuple[list[dict], bool]:
196
+ """One list shape for body or query keys: [{key, type, line, file, conditional}].
197
+
198
+ Dart, Kotlin and Swift store a list. TypeScript and PHP store `{keys, conditional, opaque}`.
199
+ Returns (keys, opaque). An opaque body (a spread, FormData, an unknown object) suppresses
200
+ missing-key reports.
201
+ """
202
+ file, line = self._call_pos(call)
203
+ if not raw:
204
+ return [], False
205
+ if isinstance(raw, list):
206
+ out = []
207
+ for b in raw:
208
+ if isinstance(b, str):
209
+ out.append({"key": b, "file": file, "line": line})
210
+ elif isinstance(b, dict) and b.get("key"):
211
+ item = dict(b)
212
+ item.setdefault("file", file)
213
+ item.setdefault("line", line)
214
+ out.append(item)
215
+ return out, False
216
+ if isinstance(raw, dict):
217
+ opaque = bool(raw.get("opaque"))
218
+ src_file = raw.get("file") or file
219
+ src_line = raw.get("line") if raw.get("line") is not None else line
220
+ out = []
221
+ for k in raw.get("keys") or []:
222
+ if isinstance(k, str):
223
+ out.append({"key": k, "conditional": False, "file": src_file, "line": src_line})
224
+ elif isinstance(k, dict) and k.get("key"):
225
+ item = dict(k)
226
+ item.setdefault("file", src_file)
227
+ item.setdefault("line", src_line)
228
+ out.append(item)
229
+ for k in raw.get("conditional") or []:
230
+ if isinstance(k, str):
231
+ out.append({"key": k, "conditional": True, "file": src_file, "line": src_line})
232
+ elif isinstance(k, dict) and k.get("key"):
233
+ item = dict(k, conditional=True)
234
+ item.setdefault("file", src_file)
235
+ item.setdefault("line", src_line)
236
+ out.append(item)
237
+ return out, opaque
238
+ return [], False
239
+
240
+ def _field_from_key(self, k: dict) -> dict:
241
+ if "required" in k:
242
+ required = bool(k["required"])
243
+ else:
244
+ required = not k.get("optional")
245
+ return {"type": k.get("type"), "required": required, "nullable": k.get("nullable"),
246
+ "sometimes": bool(k.get("sometimes")), "conditional": bool(k.get("conditional")),
247
+ "line": k.get("line"), "file": k.get("file"), "enum": k.get("enum")}
248
+
249
+ def _diff_fields(self, fields: dict, body: list[dict], opaque: bool, add, *, empty_kind: str | None,
250
+ also: list[dict] | None = None, other_fields: dict | None = None,
251
+ route_params: set[str] | None = None, fold_case: bool = False):
164
252
  if not fields:
165
- return []
166
- if not body:
167
- req_fields = [k for k, v in fields.items() if v.get("required") and not v.get("partial")]
168
- if req_fields and not call.get("body_opaque"):
169
- add("request_body_missing", "high", f"client sends no JSON body; server requires {req_fields}",
170
- server_field=self.at("be", fields[req_fields[0]].get("file"), fields[req_fields[0]].get("line")))
171
- return []
253
+ return
254
+ route_params = route_params or set()
255
+ other_fields = other_fields or {}
256
+ also = also or []
257
+
258
+ def where(v):
259
+ bits = []
260
+ if v.get("file"):
261
+ bits.append(v["file"].rsplit("/", 1)[-1])
262
+ if v.get("type"):
263
+ bits.append(str(v["type"]))
264
+ return f" ({', '.join(bits)})" if bits else ""
265
+
266
+ def hard(v) -> bool:
267
+ return bool(v.get("required") and not v.get("sometimes") and not v.get("conditional")
268
+ and not v.get("nullable") and not v.get("partial") and not v.get("default"))
269
+
270
+ def exact(server: str, keys: dict) -> dict | None:
271
+ if server in keys:
272
+ return keys[server]
273
+ ss = _struct_parts(server)
274
+ for c, b in keys.items():
275
+ if _struct_parts(c) == ss:
276
+ return b
277
+ return None
278
+
279
+ def wildcard(server: str, keys: dict) -> dict | None:
280
+ """`items.book_id` covers `items.*.book_id` when the spelling of each segment matches."""
281
+ for c, b in keys.items():
282
+ if c == server:
283
+ continue
284
+ pa, pb = _struct_parts(server), _struct_parts(c)
285
+ if pa == pb:
286
+ return b
287
+ sa = [p for p in pa if p != "*"]
288
+ sb = [p for p in pb if p != "*"]
289
+ if sa == sb and ("*" in pa or "*" in pb):
290
+ return b
291
+ return None
292
+
293
+ def prefix(server: str, keys: dict) -> dict | None:
294
+ """A bare `items` covers `items.*` / `items.*.book_id` when the client did not expand that prefix."""
295
+ sc = _canon_parts(server)
296
+ best = None
297
+ for c, b in keys.items():
298
+ cc = _canon_parts(c)
299
+ if len(cc) >= len(sc) or sc[:len(cc)] != cc:
300
+ continue
301
+ deeper = any(len(_canon_parts(o)) > len(cc) and _canon_parts(o)[:len(cc)] == cc for o in keys)
302
+ if not deeper and (best is None or len(cc) > len(_canon_parts(best[0]))):
303
+ best = (c, b)
304
+ return best[1] if best else None
305
+
306
+ def case_variant(server: str, keys: dict) -> str | None:
307
+ """Folded spelling matches exactly one server field, and that exact field is absent."""
308
+ if fold_case or exact(server, keys) is not None:
309
+ return None
310
+ group = [name for name in fields if _canon_eq(name, server)]
311
+ if len(group) != 1:
312
+ return None
313
+ hits = [c for c in keys if _canon_eq(c, server) and not wildcard(server, {c: keys[c]})]
314
+ return hits[0] if len(hits) == 1 else None
315
+
316
+ if not body and not also:
317
+ req_fields = [k for k, v in fields.items() if hard(v) and k not in route_params]
318
+ if req_fields and not opaque:
319
+ if empty_kind:
320
+ add(empty_kind, "high", f"client sends no JSON body; server requires {req_fields}",
321
+ server_field=self.at("be", fields[req_fields[0]].get("file"), fields[req_fields[0]].get("line")))
322
+ else:
323
+ for k in req_fields:
324
+ v = fields[k]
325
+ add("request_missing_required", "high",
326
+ f"server requires `{k}`{where(v)}; client sends none", key=k,
327
+ server_field=self.at("be", v.get("file"), v.get("line")))
328
+ return
172
329
  ck = {b["key"]: b for b in body}
173
- out_n = 0
330
+ ak = {b["key"]: b for b in also}
174
331
  for k, v in fields.items():
175
- if k in ck:
176
- b = ck[k]
332
+ if k in route_params:
333
+ continue
334
+ b = exact(k, ck)
335
+ if b is not None:
177
336
  kind, nullable = dart_kind(b.get("type"), self.enums)
178
337
  sk = SERVER_KIND.get(v.get("type") or "", None)
179
338
  if not compatible(kind, sk):
@@ -185,18 +344,82 @@ class Checker:
185
344
  + ("" if b.get("conditional") else " -> a null value fails validation (422)"),
186
345
  key=k, client_field=self.at("fe", b.get("file"), b.get("line")), server_field=self.at("be", v.get("file"), v.get("line")))
187
346
  continue
188
- close = [c for c in ck if norm_key(c) == norm_key(k)]
189
- if close:
190
- add("request_case_mismatch", "high", f"client sends `{close[0]}`, server field is `{k}`", key=k,
191
- client_field=self.at("fe", ck[close[0]].get("file"), ck[close[0]].get("line")),
347
+ if wildcard(k, ck) is not None or prefix(k, ck) is not None:
348
+ continue
349
+ if exact(k, ak) is not None or wildcard(k, ak) is not None or prefix(k, ak) is not None or case_variant(k, ak):
350
+ continue # the key was sent on the other location (query vs body)
351
+ if fold_case and any(_canon_eq(c, k) for c in ck):
352
+ continue
353
+ if any(_canon_eq(c, k) for c in ck) and len([name for name in fields if _canon_eq(name, k)]) != 1:
354
+ continue # both spellings are declared; the client sent one of them
355
+ alt = case_variant(k, ck)
356
+ if alt:
357
+ add("request_case_mismatch", "medium", f"client sends `{alt}`, server field is `{k}`{where(v)}", key=k,
358
+ client_field=self.at("fe", ck[alt].get("file"), ck[alt].get("line")),
359
+ server_field=self.at("be", v.get("file"), v.get("line")))
360
+ elif hard(v) and not opaque:
361
+ add("request_missing_required", "high", f"server requires `{k}`{where(v)}; client body has {sorted(ck)}", key=k,
362
+ client_field=self.at("fe", body[0].get("file"), body[0].get("line")) if body else None,
192
363
  server_field=self.at("be", v.get("file"), v.get("line")))
193
- elif v.get("required") and not v.get("partial") and not v.get("default"):
194
- add("request_missing_required", "high", f"server requires `{k}` ({v.get('type')}); client body has {sorted(ck)}", key=k,
195
- client_field=self.at("fe", body[0].get("file"), body[0].get("line")), server_field=self.at("be", v.get("file"), v.get("line")))
364
+ if opaque:
365
+ return
196
366
  for c, b in ck.items():
197
- if c not in fields and not any(norm_key(c) == norm_key(k) for k in fields):
198
- add("request_unknown_field", "medium", f"client sends `{c}`, which the server schema does not declare (ignored/dropped)",
199
- key=c, client_field=self.at("fe", b.get("file"), b.get("line")))
367
+ if c in route_params or c in fields or exact(c, {n: {} for n in fields}) is not None:
368
+ continue
369
+ if any(_canon_eq(c, k) or _canon_parts(k)[:len(_canon_parts(c))] == _canon_parts(c) for k in fields):
370
+ continue
371
+ if c in other_fields or any(_canon_eq(c, k) for k in other_fields):
372
+ continue
373
+ add("request_unknown_field", "medium", f"client sends `{c}`, which the server schema does not declare (ignored/dropped)",
374
+ key=c, client_field=self.at("fe", b.get("file"), b.get("line")))
375
+
376
+ def _folds_case(self, ra: dict) -> bool:
377
+ names = []
378
+ for m in ra.get("middleware") or []:
379
+ if isinstance(m, str):
380
+ names.append(m)
381
+ elif isinstance(m, dict):
382
+ names.append(str(m.get("name") or m.get("class") or ""))
383
+ return any(_CASE_MW.search(n) for n in names)
384
+
385
+ def check_request(self, ra, call, add):
386
+ req = ra.get("request") or {}
387
+ if req.get("closed") is False:
388
+ return []
389
+ unknown = set(req.get("unknown") or [])
390
+ body_fields, query_fields = {}, {}
391
+ for sc in req.get("schemas") or []:
392
+ if sc.get("location") in ("body", "form", None):
393
+ for k, v in self.schema_shape(sc["class"]).items():
394
+ if not v.get("read_only"):
395
+ body_fields[k] = dict(v, partial=sc.get("partial"))
396
+ for k in req.get("keys") or []:
397
+ loc = k.get("location") or "body"
398
+ if loc == "form":
399
+ loc = "body"
400
+ bucket = query_fields if loc == "query" else body_fields if loc == "body" else None
401
+ if bucket is not None and k.get("name"):
402
+ bucket.setdefault(k["name"], self._field_from_key(k))
403
+ if "body" in unknown:
404
+ body_fields = {}
405
+ if "query" in unknown:
406
+ query_fields = {}
407
+ if not body_fields and not query_fields:
408
+ return []
409
+ params = _route_params(ra.get("uri"))
410
+ fold = self._folds_case(ra)
411
+ body, body_opaque = self.normalize_keys(call.get("body_keys"), call)
412
+ if call.get("body_opaque"):
413
+ body_opaque = True
414
+ query, query_opaque = self.normalize_keys(call.get("query_keys"), call)
415
+ if call.get("query_opaque"):
416
+ query_opaque = True
417
+ if "body" not in unknown:
418
+ self._diff_fields(body_fields, body, body_opaque, add, empty_kind="request_body_missing",
419
+ also=query, other_fields=query_fields, route_params=params, fold_case=fold)
420
+ if "query" not in unknown:
421
+ self._diff_fields(query_fields, query, query_opaque, add, empty_kind=None,
422
+ also=body, other_fields=body_fields, route_params=params, fold_case=fold)
200
423
  return []
201
424
 
202
425
  def check_response(self, ra, call, add):