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.
- {cg_code_graph-0.19.1/cg_code_graph.egg-info → cg_code_graph-0.20.0}/PKG-INFO +10 -10
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/README.md +9 -9
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/__init__.py +1 -1
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/cli.py +3 -2
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/coverage.py +16 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/mcp_server.py +12 -3
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/payload.py +261 -38
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/express/plugin.py +120 -17
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/laravel/plugin.py +168 -1
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/laravel/values.py +199 -3
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/extractor/extract.php +28 -4
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/gating.py +12 -3
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/http_client.py +20 -4
- cg_code_graph-0.20.0/cg_code_graph/plugins/php/inline_guards.py +758 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/plugin.py +4 -1
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/extractor/extract.mjs +134 -6
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/extractor/fw.mjs +15 -2
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/protocols/view.py +5 -3
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/query.py +25 -19
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/routes.py +40 -8
- cg_code_graph-0.20.0/cg_code_graph/webhooks.py +1426 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0/cg_code_graph.egg-info}/PKG-INFO +10 -10
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph.egg-info/SOURCES.txt +5 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_coverage.py +4 -4
- cg_code_graph-0.20.0/tests/test_echo_ref.py +48 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_elysia.py +13 -0
- cg_code_graph-0.20.0/tests/test_inline_guards.py +186 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_php_http.py +16 -1
- cg_code_graph-0.20.0/tests/test_request_keys.py +229 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_routes_search_explain.py +12 -4
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_ts_nuxt.py +8 -3
- cg_code_graph-0.20.0/tests/test_webhooks_152.py +159 -0
- cg_code_graph-0.19.1/cg_code_graph/webhooks.py +0 -598
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/LICENSE +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/MANIFEST.in +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/__main__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/affected.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/agent_rules.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/agents.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/aitools.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/android_ipc.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/apple_ipc.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/apps.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/blindspots.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/bridges.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/brokers.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/concepts.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/config.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/cache.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/detect.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/env.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/extractors.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/fsutil.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/generated.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/model.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/paths.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/plugin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/store.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/core/syntax_errors.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/dart_isolates.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/doctor.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/explore.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/external.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/graphql.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/hooks.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/indexer.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/jobs.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/link.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/lint_async.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/local_ipc.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/local_sockets.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/mcp_install.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/parity.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/parity_structure.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plans.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/platform_scan.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/platforms.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/astro/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/astro/content.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/astro/plugin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/cfamily/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/cfamily/plugin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/cfamily/syntax.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/bridges.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/extractor/bin/extract.dart +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/extractor/pubspec.lock +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/extractor/pubspec.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/http.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/models.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/plugin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/dart/program.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/django/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/django/extras.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/django/models.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/django/plugin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/django/schemas.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/django/shapes.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/django/urls.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/express/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/flutter/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/flutter/plugin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/java/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/java/exact.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/java/plugin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/jvm/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/jvm/interop.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/jvm/spring.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/kotlin/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/kotlin/exact.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/kotlin/plugin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/kotlin/reparse.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/laravel/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/laravel/broadcast.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/laravel/tests.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/native/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/native/gates.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/native/runner.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/native/scipread.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/native/ts.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/nest/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/nest/plugin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/nextjs/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/nextjs/plugin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/nuxt/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/nuxt/plugin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/external.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/extractor/composer.json +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/extractor/composer.lock +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/php/strings.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/aitools.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/external.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/fields.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/plugin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/refs.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/roots.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/socketio.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/subproc.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/tests.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/python/values.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/pyweb/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/pyweb/plugin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/pyweb/values.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/rust/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/rust/cargo.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/rust/plugin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/rust/syntax.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/scip/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/scip/importer.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/scip/scip.proto +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/scip/scip_pb2.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/stubs/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/stubs/plugins.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/swift/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/swift/baseurl.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/swift/exact.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/swift/indexstore.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/swift/packages.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/swift/plugin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/baseurl.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/extractor/package-lock.json +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/extractor/package.json +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/extractor/rr.mjs +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/nav.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/plugin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/ts/react_router.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/tsweb/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/tsweb/common.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/plugins/tsweb/data.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/c_cpp.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/common.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/dart.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/django-ninja.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/django.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/djangorestframework.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/express.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/java.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/kotlin.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/laravel.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/nest.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/nextjs.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/nuxt.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/php.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/python.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/react-router.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/rust.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/spring.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/swift.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/presets/typescript.yaml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/process_runs.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/protocols/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/protocols/builtin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/protocols/matchers.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/realtime.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/realtime_events.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/realtime_native.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/realtime_ws.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/roundtrip.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/rpc.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/sdk_systems.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/sockets.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/starters.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/tests_index.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/udp_apps.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/__init__.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/graph.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/server.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/app.css +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/app.js +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/index.html +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/layered.js +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/vendor/VERSIONS.txt +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/vendor/cose-base.js +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/vendor/cytoscape-fcose.js +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/vendor/cytoscape.min.js +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/static/vendor/layout-base.js +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/tools/package-lock.json +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/tools/package.json +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/viz/tools/shoot.mjs +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/watch.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph/xcode.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph.egg-info/dependency_links.txt +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph.egg-info/entry_points.txt +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph.egg-info/requires.txt +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/cg_code_graph.egg-info/top_level.txt +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/pyproject.toml +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/setup.cfg +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_affected.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_agents_install.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_agents_md.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_ai_tools.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_android_ipc.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_apple_ipc.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_astro.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_astro_content.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_astro_server.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_bridges.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_bridges_pigeon_monorepo.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_brokers.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_cache_clean.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_cache_invalidation.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_cache_migration.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_channels_tests.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_cli_output.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_completeness.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_config_extras.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_coverage_ts_hint.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_dart_isolates.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_dispatch_interfaces.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_django_flutter.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_docs_languages.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_docs_links.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_enum_constants.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_env.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_explore.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_external.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_gating.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_generated.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_graphql.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_hooks.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_impact_overrides.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_index_isolation.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_inherited_overrides.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_java.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_java_exact.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_jobs.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_js_refs.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_js_runtime.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_kotlin.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_kotlin_binding.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_kotlin_exact.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_kotlin_fields.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_kotlin_mixed.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_kotlin_nav_consts.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_kotlin_props.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_laravel_determinism.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_laravel_resources.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_lint_async.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_local_ipc.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_local_sockets.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_mcp.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_mcp_index.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_mcp_install.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_native_cfamily.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_native_concurrency.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_native_rust.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_nuxt_import_dirs.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_nuxt_rootsrc.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_packaging.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_parity.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_parity_structure.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_php_props.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_plain_js.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_plans.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_platform_gaps.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_platform_type_variants.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_platforms.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_platforms_apple.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_presets_config.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_process_ipc.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_process_runs.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_protocols.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_app_tests_module.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_collection_chains.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_compat.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_entry_refs.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_fields.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_infer_cycles.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_roots.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_subprocess_tests.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_tests.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_web_hosts.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_web_routes.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_python_web_routes_more.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_react_router.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_realtime.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_realtime_native.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_roundtrip.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_route_tables.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_rpc.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_scip_health.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_sdk_external.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_snippet.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_sockets.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_spring.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_starters_budget.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_binding.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_exact.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_facts.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_fields.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_modules.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_more.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_nav_values.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_parse_errors.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_props.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_sdk.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_testing.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_swift_values.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_tests_scope.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_ts_fields.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_ts_fields_js_vue.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_ts_file_specs.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_ts_frameworks.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_ts_src_tsconfig.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_values.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_viz.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_vue_nav.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_watch.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_webhooks.py +0 -0
- {cg_code_graph-0.19.1 → cg_code_graph-0.20.0}/tests/test_websockets.py +0 -0
- {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.
|
|
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
|
-
[](https://pypi.org/project/cg-code-graph/) [](https://pepy.tech/project/cg-code-graph) [](https://pypi.org/project/cg-code-graph/) [](LICENSE) [](https://code-graph.cyberchronos00.workers.dev/)
|
|
36
|
+
[](https://pypi.org/project/cg-code-graph/) [](https://pepy.tech/project/cg-code-graph) [](https://pypi.org/project/cg-code-graph/) [](https://github.com/cyberchronos00/code-graph/blob/main/LICENSE) [](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
|
-
[](docs/media/cg-view-demo.mp4)
|
|
40
|
+
[](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
|
-
[](docs/media/cg-terminal-demo.mp4)
|
|
70
|
+
[](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
|
|  | Django (+ DRF, django-ninja), FastAPI / Starlette, Flask, Celery | exact (stdlib `ast`) | [Python](https://code-graph.cyberchronos00.workers.dev/docs/python) |
|
|
77
77
|
|   | 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
|
-
|  | 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
|
+
|  | 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
|
|  | Flutter | exact (Dart analyzer; needs the Dart SDK) | [Platforms](https://code-graph.cyberchronos00.workers.dev/docs/platforms) |
|
|
80
80
|
|  | Android, Kotlin Multiplatform, Spring | heuristic; exact with scip-java (opt-in) | [Kotlin](https://code-graph.cyberchronos00.workers.dev/docs/kotlin) |
|
|
81
81
|
|  | 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
|
-
[](https://pypi.org/project/cg-code-graph/) [](https://pepy.tech/project/cg-code-graph) [](https://pypi.org/project/cg-code-graph/) [](LICENSE) [](https://code-graph.cyberchronos00.workers.dev/)
|
|
9
|
+
[](https://pypi.org/project/cg-code-graph/) [](https://pepy.tech/project/cg-code-graph) [](https://pypi.org/project/cg-code-graph/) [](https://github.com/cyberchronos00/code-graph/blob/main/LICENSE) [](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
|
-
[](docs/media/cg-view-demo.mp4)
|
|
13
|
+
[](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
|
-
[](docs/media/cg-terminal-demo.mp4)
|
|
43
|
+
[](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
|
|  | Django (+ DRF, django-ninja), FastAPI / Starlette, Flask, Celery | exact (stdlib `ast`) | [Python](https://code-graph.cyberchronos00.workers.dev/docs/python) |
|
|
50
50
|
|   | 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
|
-
|  | 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
|
+
|  | 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
|
|  | Flutter | exact (Dart analyzer; needs the Dart SDK) | [Platforms](https://code-graph.cyberchronos00.workers.dev/docs/platforms) |
|
|
53
53
|
|  | Android, Kotlin Multiplatform, Spring | heuristic; exact with scip-java (opt-in) | [Kotlin](https://code-graph.cyberchronos00.workers.dev/docs/kotlin) |
|
|
54
54
|
|  | 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.
|
|
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).
|
|
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
|
|
5
|
-
|
|
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", "
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
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
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
330
|
+
ak = {b["key"]: b for b in also}
|
|
174
331
|
for k, v in fields.items():
|
|
175
|
-
if k in
|
|
176
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
194
|
-
|
|
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
|
|
198
|
-
|
|
199
|
-
|
|
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):
|